重构MVC
1 路由和控制器
本节要将业务代码从main.go中移走,先从首页、关于和404静态页面开始。
1.1 新建路由文件
routes/web.go
// Package routes 存放应用路由
package routes
import (
"net/http"
"github.com/gorilla/mux"
)
// RegisterWebRoutes 注册网页相关路由
func RegisterWebRoutes(r *mux.Router) {
// 静态页面
r.HandleFunc("/", homeHandler).Methods("GET").Name("home")
r.HandleFunc("/about", aboutHandler).Methods("GET").Name("about")
r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
}
暂时会报错,routes包中没有定义homeHandler等函数,后面再进行调用修改
1.2 注册路由
更新pkg/route/router.go文件内容
.
.
.
// Initialize 初始化路由
func Initialize() {
Router = mux.NewRouter()
routes.RegisterWebRoutes(Router)
}
.
.
.
1.3 添加控制器 pages_controller
新建app/http/controllers/pages_controller.go
// Package controllers 应用控制层
package controllers
import (
"fmt"
"net/http"
)
// PagesController 处理静态页面
type PagesController struct {
}
// Home 首页
func (*PagesController) Home(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<h1>Hello, 欢迎来到 goblog!</h1>")
}
// About 关于我们页面
func (*PagesController) About(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "此博客是用以记录编程笔记,如您有反馈或建议,请联系 "+
"<a href=\"mailto:summer@example.com\">summer@example.com</a>")
}
// NotFound 404 页面
func (*PagesController) NotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "<h1>请求页面未找到 :(</h1><p>如有疑惑,请联系我们。</p>")
}
1.4 完善路由文件
完善routes/web.go
.
.
.
// RegisterWebRoutes 注册网页相关路由
func RegisterWebRoutes(r *mux.Router) {
// 静态页面
pc := new(controllers.PagesController)
r.NotFoundHandler = http.HandlerFunc(pc.NotFound)
r.HandleFunc("/", pc.Home).Methods("GET").Name("home")
r.HandleFunc("/about", pc.About).Methods("GET").Name("about")
}
1.5 删除路由
main.go ,将以下几个多余的路由删除:
router.HandleFunc("/", homeHandler).Methods("GET").Name("home")
router.HandleFunc("/about", aboutHandler).Methods("GET").Name("about")
// 自定义 404 页面
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
2 循环引用
articles.show ,将 main.go 里的控制器逻辑移到专属的文章控制器中
2.1 routes/web.go中注册路由
.
.
.
func RegisterWebRoutes(r *mux.Router) {
.
.
.
// 文章相关页面
ac := new(controllers.ArticlesController)
r.HandleFunc("/articles/{id:[0-9]+}", ac.Show).Methods("GET").Name("articles.show")
}
main.go 文件中,将不再需要的代码删除:
router.HandleFunc("/articles/{id:[0-9]+}", articlesShowHandler).Methods("GET").Name("articles.show")
2.2 ArticlesController 控制器
添加app/http/controllers/articles_controller.go
package controllers
import (
"net/http"
)
// ArticlesController 文章相关页面
type ArticlesController struct {
}
// Show 文章详情页面
func (*ArticlesController) Show(w http.ResponseWriter, r *http.Request) {
}
2.3 完善app/http/controllers/articles_controller.go
articlesShowHandler 将内容复制到刚刚创建的 Show 方法中:
app/http/controllers/articles_controller.go
.
.
.
func (*ArticlesController) Show(w http.ResponseWriter, r *http.Request) {
// 1. 获取 URL 参数
id := route.GetRouteVariable("id", r)
// 2. 读取对应的文章数据
article, err := getArticleByID(id)
// 3. 如果出现错误
if err != nil {
if err == sql.ErrNoRows {
// 3.1 数据未找到
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "404 文章未找到")
} else {
// 3.2 数据库错误
logger.LogError(err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "500 服务器内部错误")
}
} else {
// 4. 读取成功,显示文章
tmpl, err := template.New("show.gohtml").
Funcs(template.FuncMap{
"RouteName2URL": route.Name2URL,
"Int64ToString": types.Int64ToString,
}).
ParseFiles("resources/views/articles/show.gohtml")
logger.LogError(err)
err = tmpl.Execute(w, article)
logger.LogError(err)
}
}
2.4 import cycle not allowed 循环导入问题#
Go 编译器不允许这样做是出于编译性能考虑。允许循环引用会使编译时间激增,一旦某个包更新,整个依赖循环都需要重新编译。
2.5 新建 bootstrap 包
这个包将用来存放程序初始化相关逻辑,例如路由初始化、数据库初始化、配置信息初始化等。
新建文件:bootstrap/route.go
// Package bootstrap 负责应用初始化相关工作,比如初始化路由。
package bootstrap
import (
"goblog/routes"
"github.com/gorilla/mux"
)
// SetupRoute 路由初始化
func SetupRoute() *mux.Router {
router := mux.NewRouter()
routes.RegisterWebRoutes(router)
return router
}
修改 pkg 里的 route 包,将初始化的代码删除,只保留两个工具函数:
pkg/route/router.go
// Package route 路由相关
package route
import (
"net/http"
"github.com/gorilla/mux"
)
// Name2URL 通过路由名称来获取 URL
func Name2URL(routeName string, pairs ...string) string {
var route *mux.Router
url, err := route.Get(routeName).URL(pairs...)
if err != nil {
// checkError(err)
return ""
}
return url.String()
}
// GetRouteVariable 获取 URI 路由参数
func GetRouteVariable(parameterName string, r *http.Request) string {
vars := mux.Vars(r)
return vars[parameterName]
}
将 GetRouteVariable() 方法重新移回 main.go 中,并将这个本地的方法替换掉 route.GetRouteVariable() 的调用。
main.go 里新增 getRouteVariable() 方法,因为不需要导出,函数名首字母小写:
.
.
.
func getRouteVariable(parameterName string, r *http.Request) string {
vars := mux.Vars(r)
return vars[parameterName]
}
func main() {
.
.
.
}
报新的错,但循环引用问题已解决
3 安装GORM
go get -u gorm.io/gorm
安装 GORM 的 MySQL 的数据库驱动
go get -u gorm.io/driver/mysql
3.1 创建基础模型
pkg/model/model.go
// Package model 应用模型数据层
package model
import (
"goblog/pkg/logger"
"gorm.io/gorm"
// GORM 的 MySQL 数据库驱动导入
"gorm.io/driver/mysql"
)
// DB gorm.DB 对象
var DB *gorm.DB
// ConnectDB 初始化模型
func ConnectDB() *gorm.DB {
var err error
config := mysql.New(mysql.Config{
DSN: "root:secret@tcp(127.0.0.1:3306)/goblog?charset=utf8&parseTime=True&loc=Local",
})
// 准备数据库连接池
DB, err = gorm.Open(config, &gorm.Config{})
logger.LogError(err)
return DB
}
DB 变量是一个 *gorm.DB 对象,我们会在模型文件中大量使用到。ConnectDB() 方法,将会在数据库初始化中使用。
3.2 数据库初始化
把数据库初始化的逻辑放到 bootstrap 包里:
package bootstrap
import (
"goblog/pkg/model"
"time"
)
// SetupDB 初始化数据库和 ORM
func SetupDB() {
// 建立数据库连接池
db := model.ConnectDB()
// 命令行打印数据库请求的信息
sqlDB, _ := db.DB()
// 设置最大连接数
sqlDB.SetMaxOpenConns(100)
// 设置最大空闲连接数
sqlDB.SetMaxIdleConns(25)
// 设置每个链接的过期时间
sqlDB.SetConnMaxLifetime(5 * time.Minute)
}