Go语言进阶之Go语言高性能Web框架Iris项目实战-项目结构优化EP05
liuian 2025-05-09 20:03 19 浏览
前文再续,上一回我们完成了用户管理模块的CURD(增删改查)功能,功能层面,无甚大观,但有一个结构性的缺陷显而易见,那就是项目结构过度耦合,项目的耦合性(Coupling),也叫耦合度,进而言之,模块之间的关系,是对项目结构中各模块间相互联系紧密程度的一种量化。耦合的强弱取决于模块间调用的复杂性、调用模块之间的方式以及通过函数或者方法传送数据对象的多少。模块间的耦合度是指模块之间的依赖关系,包括包含关系、控制关系、调用关系、数据传递关系以及依赖关系。项目模块的相互依赖越多,其耦合性越强,同时表明其独立性越差,愈加难以维护。
项目结构优化
目前IrisBlog项目的问题就是独立性太差,截至目前为止,项目结构如下:
.
├── README.md
├── assets
│ ├── css
│ │ └── style.css
│ └── js
│ ├── axios.js
│ └── vue.js
├── favicon.ico
├── go.mod
├── go.sum
├── main.go
├── model
│ └── model.go
├── mytool
│ └── mytool.go
├── tmp
│ └── runner-build
└── views
├── admin
│ └── user.html
├── index.html
└── test.html
一望而知,前端页面(views)以及静态文件(assets)的工程化尚可,不再需要进行分层操作,但是在后端,虽然模型层(model.go)和工具层(mytool.go)已经分离出主模块,但主要业务代码还是集中在入口文件main.go中:
package main
import (
"IrisBlog/model"
"IrisBlog/mytool"
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/kataras/iris/v12"
)
func main() {
db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err)
panic("无法连接数据库")
}
fmt.Println("连接数据库成功")
//单数模式
db.SingularTable(true)
// 创建默认表
db.AutoMigrate(&model.User{})
// 逻辑结束后关闭数据库
defer func() {
_ = db.Close()
}()
app := newApp(db)
app.HandleDir("/assets", iris.Dir("./assets"))
app.Favicon("./favicon.ico")
app.Listen(":5000")
}
func newApp(db *gorm.DB) *iris.Application {
app := iris.New()
tmpl := iris.HTML("./views", ".html")
// Set custom delimeters.
tmpl.Delims("${", "}")
// Enable re-build on local template files changes.
tmpl.Reload(true)
app.RegisterView(tmpl)
app.Delete("/admin/user_action/", func(ctx iris.Context) {
ID := ctx.URLParamIntDefault("id", 0)
db.Delete(&model.User{}, ID)
ret := map[string]string{
"errcode": "0",
"msg": "删除用户成功",
}
ctx.JSON(ret)
})
app.Put("/admin/user_action/", func(ctx iris.Context) {
ID := ctx.PostValue("id")
Password := ctx.PostValue("password")
user := &model.User{}
db.First(&user, ID)
user.Password = mytool.Make_password(Password)
db.Save(&user)
ret := map[string]string{
"errcode": "0",
"msg": "更新密码成功",
}
ctx.JSON(ret)
})
app.Post("/admin/user_action/", func(ctx iris.Context) {
username := ctx.PostValue("username")
password := ctx.PostValue("password")
fmt.Println(username, password)
md5str := mytool.Make_password(password)
user := &model.User{Username: username, Password: md5str}
res := db.Create(user)
if res.Error != nil {
fmt.Println(res.Error)
ret := map[string]string{
"errcode": "1",
"msg": "用户名不能重复",
}
ctx.JSON(ret)
return
}
ret := map[string]string{
"errcode": "0",
"msg": "ok",
}
ctx.JSON(ret)
})
return app
}
入口文件main.go承载了太多业务,既需要负责数据库结构体的创建,又得操心模板的渲染和接口逻辑的编写,说白了:泥沙俱下,沉渣泛起。
事实上,像这样把所有代码都堆到一个文件中,还会带来协作问题,比如,当你花了一整天的时间,好不容易完成了一段业务逻辑,也通过了本地测试,准备第二天提交线上测试,但是第二天上班时却发现这个逻辑莫名其妙地开始报错了,这通常是因为有同事在你走后修改了你编写或者依赖的那个模块,归根结底,并不完全是协作的问题,项目结构也是因素之一。
多个研发同时修改了同一个源代码文件。虽然在规模相对较小、人员较少的项目中,这种问题或许并不严重,但是随着项目的增长,研发人员的增加,这种每天早上刚上班时都要经历一遍的痛苦就会越来越多,甚至会严重到让有的团队在长达数周的时间内都不能发布一个稳定的项目版本,因为每个人都在不停地修改自己的代码,以适应其他人所提交的变更,周而复始,恶性循环。
所以我们必须把业务单独抽离出来,比如用户管理其实是后台模块功能,只有特定的管理员才可能在其页面进行操作,所以我们可以单独创建一个控制层:
mkdir handler
cd hanler
随后编写后台控制逻辑admin.go:
package handler
import (
"github.com/kataras/iris/v12"
)
//用户管理页面模板
func Admin_user_page(ctx iris.Context) {
ctx.View("/admin/user.html")
}
这里把用户管理页面的解析函数单独抽离在handler包中,注意函数的首字母要进行大写处理,因为首字母小写函数是私有函数,只能在包内使用,无法被别的包调用。
随后改造入口文件main.go逻辑:
app.Get("/admin/user/", handler.Admin_user_page)
路由匹配时,只需要引入handler包中的Admin_user_page函数就可以了。
随后,对路由进行分组优化,同属一个业务的模块绑定在同一个分组中:
adminhandler := app.Party("/admin")
{
adminhandler.Use(iris.Compression)
adminhandler.Get("/user/", handler.Admin_user_page)
adminhandler.Get("/userlist/", handler.Admin_userlist)
adminhandler.Delete("/user_action/", handler.Admin_userdel)
adminhandler.Put("/user_action/", handler.Admin_userupdate)
adminhandler.Post("/user_action/", handler.Admin_useradd)
}
如此,业务和路由解析就彻底分开了,结构体创建函数也清爽了不少:
func newApp(db *gorm.DB) *iris.Application {
app := iris.New()
tmpl := iris.HTML("./views", ".html")
tmpl.Delims("${", "}")
tmpl.Reload(true)
app.RegisterView(tmpl)
adminhandler := app.Party("/admin")
{
adminhandler.Use(iris.Compression)
adminhandler.Get("/user/", handler.Admin_user_page)
adminhandler.Get("/userlist/", handler.Admin_userlist)
adminhandler.Delete("/user_action/", handler.Admin_userdel)
adminhandler.Put("/user_action/", handler.Admin_userupdate)
adminhandler.Post("/user_action/", handler.Admin_useradd)
}
return app
}
数据层结构优化
业务层进行了拆分,但是数据层还集成在入口文件中main.go:
package main
import (
"IrisBlog/handler"
"IrisBlog/model"
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/kataras/iris/v12"
)
func main() {
db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err)
panic("无法连接数据库")
}
fmt.Println("连接数据库成功")
//单数模式
db.SingularTable(true)
// 创建默认表
db.AutoMigrate(&model.User{})
// 逻辑结束后关闭数据库
defer func() {
_ = db.Close()
}()
app := newApp(db)
app.HandleDir("/assets", iris.Dir("./assets"))
app.Favicon("./favicon.ico")
app.Listen(":5000")
}
这里的含义是,一旦进入入口逻辑,就立刻初始化数据库,随后执行业务代码,当业务执行完毕后,利用延迟函数defer关闭数据库链接。
这种逻辑的弊端是,一旦数据库服务挂掉,整个项目服务也会受影响,再者,很多纯静态化页面并不需要数据库链接,每一次都链接数据库,显然是画蛇添足。
所以单独建立数据包:
mkdir database
cd database
建立数据层逻辑database.go:
package database
import (
"IrisBlog/model"
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
func Db() *gorm.DB {
db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err)
panic("无法连接数据库")
}
fmt.Println("连接数据库成功")
//单数模式
db.SingularTable(true)
// 创建默认表
db.AutoMigrate(&model.User{})
return db
}
这里我们构建函数Db(),它返回一个数据库操作的结构体指针,专门用来执行数据库操作,需要注意的是,删除函数内之前的延后defer关闭链接函数,否则链接在函数体内就关闭了,调用方就无法使用数据库了。
调用上,直接调用database包中的Db(),就可以直接使用数据库指针了:
//用户列表接口
func Admin_userlist(ctx iris.Context) {
db := database.Db()
var users []model.User
res := db.Find(&users)
// 逻辑结束后关闭数据库
defer func() {
_ = db.Close()
}()
ctx.JSON(res)
}
随后,继续优化入口文件:
package main
import (
"IrisBlog/handler"
"github.com/kataras/iris/v12"
)
func main() {
app := newApp()
app.HandleDir("/assets", iris.Dir("./assets"))
app.Favicon("./favicon.ico")
app.Listen(":5000")
}
func newApp() *iris.Application {
app := iris.New()
tmpl := iris.HTML("./views", ".html")
tmpl.Delims("${", "}")
tmpl.Reload(true)
app.RegisterView(tmpl)
adminhandler := app.Party("/admin")
{
adminhandler.Use(iris.Compression)
adminhandler.Get("/user/", handler.Admin_user_page)
adminhandler.Get("/userlist/", handler.Admin_userlist)
adminhandler.Delete("/user_action/", handler.Admin_userdel)
adminhandler.Put("/user_action/", handler.Admin_userupdate)
adminhandler.Post("/user_action/", handler.Admin_useradd)
}
}
这里优化了main函数,使其逻辑更加简明和清晰。
最后,优化数据层逻辑database.go:
package database
import (
"IrisBlog/model"
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
const db_type int = 1
func sqlite3() *gorm.DB {
db, err := gorm.Open("sqlite3", "/tmp/IrisBlog.db")
if err != nil {
fmt.Println(err)
panic("无法连接数据库")
}
fmt.Println("连接sqlite3数据库成功")
return db
}
func mysql() *gorm.DB {
db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err)
panic("无法连接数据库")
}
fmt.Println("连接mysql数据库成功")
return db
}
func Db() *gorm.DB {
switch db_type {
case 0:
db := mysql()
//单数模式
db.SingularTable(true)
// 创建默认表
db.AutoMigrate(&model.User{})
return db
case 1:
db := sqlite3()
//单数模式
db.SingularTable(true)
// 创建默认表
db.AutoMigrate(&model.User{})
return db
default:
panic("未知的数据库")
}
}
这里我们分别封装mysql和sqlite3数据库指针函数,然后通过switch语句来根据不同的开发环境而进行切换和控制。
至此,项目结构的首次结构性优化就完成了,优化后的结构如下:
├── README.md
├── assets
│ ├── css
│ │ └── style.css
│ └── js
│ ├── axios.js
│ └── vue.js
├── database
│ └── database.go
├── favicon.ico
├── go.mod
├── go.sum
├── handler
│ └── admin.go
├── main.go
├── model
│ └── model.go
├── mytool
│ └── mytool.go
├── tmp
│ └── runner-build
└── views
├── admin
│ └── user.html
├── index.html
└── test.html
结语
为什么我们一开始不直接采用低耦合高内聚的项目架构?因为别人的经验并不是我们的经验,只有真正经历过才是真实的开发经验,项目开发没有标准答案,只有选择,然后承担后果,只有尝试过苦涩的果实之后,下一次才会做出正确的选择。该项目已开源在Github:
https://github.com/zcxey2911/IrisBlog ,与君共觞,和君共勉。
相关推荐
- Optional是个好东西,如果用错了就太可惜了
-
原文出处:https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be我们都知道,在Java8新增了一个类-Optional,主要是用来解决程...
- IDEA建议:不要在字段上使用@Autowire了!
-
在使用IDEA写Spring相关的项目的时候,在字段上使用@Autowired注解时,总是会有一个波浪线提示:Fieldinjectionisnotrecommended.纳尼?我天天用,咋...
- Spring源码|Spring实例Bean的方法
-
Spring实例Bean的方法,在AbstractAutowireCapableBeanFactory中的protectedBeanWrappercreateBeanInstance(String...
- Spring技巧:深入研究Java 14和SpringBoot
-
在本期文章中,我们将介绍Java14中的新特性及其在构建基于SpringBoot的应用程序中的应用。开始,我们需要使用Java的最新版本,也是最棒的版本,Java14,它现在还没有发布。预计将于2...
- Java开发200+个学习知识路线-史上最全(框架篇)
-
1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...
- 年末将至,Java 开发者必须了解的 15 个Java 顶级开源项目
-
专注于Java领域优质技术,欢迎关注作者:SnailClimbStar的数量统计于2019-12-29。1.JavaGuideGuide哥大三开始维护的,目前算是纯Java类型项目中Sta...
- 字节跨平台框架 Lynx 开源:一个 Web 开发者的原生体验
-
最近各大厂都在开源自己的跨平台框架,前脚腾讯刚宣布计划四月开源基于Kotlin的跨平台框架「Kuikly」,后脚字节跳动旧开源了他们的跨平台框架「Lynx」,如果说Kuikly是一个面向...
- 我要狠狠的反驳“公司禁止使用Lombok”的观点
-
经常在其它各个地方在说公司禁止使用Lombok,我一直不明白为什么不让用,今天看到一篇文章列举了一下“缺点”,这里我只想狠狠地反驳,看到列举的理由我竟无言以对。原文如下:下面,结合我自己使用Lomb...
- SpringBoot Lombok使用详解:从入门到精通(注解最全)
-
一、Lombok概述与基础使用1.1Lombok是什么Lombok是一个Java库,它通过注解的方式自动生成Java代码(如getter、setter、toString等),从而减少样板代码的编写,...
- Java 8之后的那些新特性(六):记录类 Record Class
-
Java是一门面向对象的语言,而对于面向对象的语言中,一个众所周知的概念就是,对象是包含属性与行为的。比如HR系统中都会有雇员的概念,那雇员会有姓名,ID身份,性别等,这些我们称之为属性;而雇员同时肯...
- 为什么大厂要求安卓开发者掌握Kotlin和Jetpack?优雅草卓伊凡
-
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡一、Kotlin:Android开发的现代语言选择1.1Kotlin是什么?Kotlin是由...
- Kotlin这5招太绝了!码农秒变优雅艺术家!
-
Kotlin因其简洁性、空安全性和与Java的无缝互操作性而备受喜爱。虽然许多开发者熟悉协程、扩展函数和数据类等特性,但还有一些鲜为人知的特性可以让你的代码从仅仅能用变得真正优雅且异常简洁。让我们来看...
- 自行部署一款免费高颜值的IT资产管理系统-咖啡壶chemex
-
在运维时,ICT资产太多怎么办,还是用excel表格来管理?效率太低,也不好多人使用。在几个IT资产管理系统中选择比较中,最终在Snipe-IT和chemex间选择了chemex咖啡壶。Snip...
- PHP对接百度语音识别技术(php对接百度语音识别技术实验报告)
-
引言在目前的各种应用场景中,语音识别技术已经越来越常用,并且其应用场景正在不断扩大。百度提供的语音识别服务允许用户通过简单的接口调用,将语音内容转换为文本。本文将通过PHP语言集成百度的语音识别服务,...
- 知识付费系统功能全解析(知识付费项目怎么样)
-
开发知识付费系统需包含核心功能模块,确保内容变现、用户体验及运营管理需求。以下是完整功能架构:一、用户端功能注册登录:手机号/邮箱注册,第三方登录(微信、QQ)内容浏览:分类展示课程、文章、音频等付费...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
python使用fitz模块提取pdf中的图片
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)