百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Go语言进阶之Go语言高性能Web框架Iris项目实战-项目结构优化EP05

ccwgpt 2025-06-09 20:31 1 浏览 0 评论

前文再续,上一回我们完成了用户管理模块的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 ,与君共觞,和君共勉。

相关推荐

阿里大数据技术架构师整理分享java面试核心知识点框架篇文档

前言本文是对Java程序员面试中常见的微服务、网络编程、分布式存储和分布式计算等必备知识点的总结,包括Spring原理及应用、SpringCloud原理及应用、Netty网络编程原理及应用、Zoo...

初探分布式Agent系统架构,及全新AutoGen框架下分布式Agent 体验

AIAgent(智能体)系统发展迅猛,且关注点已经不再局限在Agent的规划推理等基本能力,智能体系统在扩展性、互操作、安全性等工程化方面的挑战也越来越引起重视,比如最近的MCP和A2A。上一篇我们...

微软分布式云计算框架Orleans(2):容灾与集群(1)

在上一篇:微软分布式云计算框架Orleans(1):HelloWorld,我们大概了解了Orleans如何运用,当然上一篇的例子可以说是简单且无效的,因为用了Orleans不可能只写一个Hello...

分布式光伏发电项目合作框架协议模板

分布式光伏发电项目合作框架协议模板复制链接-微信或浏览器打开-领取电子档:https://mp.weixin.qq.com/s/0QU_rZEDG0cuS1jxSlaOeA...

晶科科技:签署户用分布式光伏项目合作框架协议

晶科科技公告,公司近日与湖南新华水利电力有限公司(简称“湖南新华”)签署《关于户用分布式光伏项目合作框架协议》,公司拟与湖南新华在2024年—2026年内累计合作开展不低于6GW户用分布式光伏项目。每...

国人之光-分布式存储框架FastDFS入门篇

在这里插入图片描述一、分布式文件存储1.分布式文件存储的由来在我们的项目中有很多需要存储的内容出现,比如图片,视频,文件等等,在早期的时候用户量不大,产生的文件也不是很多,这时我们可以把文件和服务...

分布式计算框架——Hadoop(hadoop分布式计算框架是)

Hadoop是一个开源的分布式计算框架,旨在解决大规模数据集的存储和处理问题。它基于Google的MapReduce论文和Google文件系统(GFS),提供了一种可靠、可扩展的方式来处理海量数据。以...

坐读与行读 精读与泛览(读写坐立行)

【我是这样做学问的】作者:荣新江(北京大学历史学系教授)在大学里教书,如果有初入史学之门的学生问我这个问题,我会把做学问的一般方法讲给他们来听,这里面既有我自己的经验之谈,也有很多其他成功学者的治学之...

文言文实词“顾”的解析(高中文言文实词120个精编汇总)

文言文实词“顾”的解析A笔记栏(KeyNotes)核心知识点具体内容/例句/解析实词“顾”的本义与引申义-本义:回头看(形旁“页”与“头”相关,强调头部动作)例:“临行反顾”(临走时回头看)...

快期中考试了,串讲人教版七下道法,我的知识库清晰了好多…

自从道法上了主科赛道,已经不再是背书那么简单的了。开卷,更是加大了难度。不知道考的是啥,要翻到哪里抄。下周期中考试,出于各种原因,我给儿子班上的一些同学串讲了一下第一单元内容。从备考任务、梳理框架、单...

杜甫《佳人》的图像解读及其意义(杜甫《佳人》的图像解读及其意义概括)

杜甫《佳人》“天寒翠袖薄,日暮倚修竹”一句,凄丽动人,宋人据此作《天寒翠袖图》(现藏于北京故宫博物院)与《竹林仕女图》(现藏于美国费城艺术博物馆)。二图布局极为相似,当为同源画本。杜甫《佳人》诗因诗意...

窠字不读guǒ,不念cháo,窠怎么读,什么意思?窠臼是什么意思?

中午的学校食堂,热闹非凡。一位阿姨在窗口忙着打饭,勺子与餐盘的碰撞声“叮叮当当”。一位同学一边排队一边和身边的朋友讨论着下午的课程,话语声此起彼伏。打饭的声音和讨论声交织在充满期待的午休时光里。言归正...

字象字母B,又象眼镜镜框,这是个什么字?

字象字母B,又象眼镜镜框,这是个什么字?在《殷周金文集成》8498号金文中,有如图这样一个古文字,字形近似一个大写的英文字母B,也象一副框架眼镜的镜框,这是个什么古文字?容庚《金文编》、严志斌《商金文...

文言文实词“当”的解析(文言文实词"当"的解析研究)

文言文实词“当”的解析笔记栏(KeyContent)1.核心义项与语境解析c义项1:掌管、主持(dāng)例:“李斯为秦相,当政”(担任宰相,掌管政权)。搭配:当政、当权(强调权力或职位的执...

语文班主任:三年级下册单元知识点归纳,孩子期末复习的好帮手!

家人们,孩子步入三年级,语文学习难度直线上升,马上又到期末,复习压力是不是扑面而来别慌!作为班主任,今天给大家带来一份超实用的三年级下册单元知识点归纳,堪称孩子期末复习的王炸好帮手!三年级语文的知识...

取消回复欢迎 发表评论: