重构MVC
- 1 Article 模型
- 1.1 首先创建 Article 模型文件
- 1.2 接下来创建获取文章的方法
- 1.3 新增 types.StringToUint64()函数
- 1.4 修改控制器的调用
- 1.5 重构 route 包
- 1.6 通过 SetRoute 来传参对象变量
- 1.7 新增方法:
- 1.8 控制器将 Int64ToString 改为 Uint64ToString
- 1.9 模板里修改 Int64ToString 为 Uint64ToString:
- 1.10 修改logger:
- 1.11 修改articles_controller.go的判断:
- 2 重构文章列表
- 2.1 移动articles.index路由
- 2.2 添加Index控制器方法
- 2.3 获取文章列表 GetAll
- 2.4 调试程序
- 2.5 代码版本标记
1 Article 模型
解决undefined: getArticleByID 的报错
config := mysql.New(mysql.Config{
DSN: "root:secret@tcp(127.0.0.1:3306)/goblog?charset=utf8&parseTime=True&loc=Local",
})
在提供的代码中,用户名是"root",密码是"secret"。这段代码中的DSN(Data Source Name)参数指定了数据库连接的信息,其中"root"是用户名,“secret"是密码。所以,根据这段代码,用户是"root”,密码是"secret"。
1.1 首先创建 Article 模型文件
app/models/article/article.go
// Package article 应用的文章模型
package article
// Article 文章模型
type Article struct {
ID uint64
Title string
Body string
}
1.2 接下来创建获取文章的方法
app/models/article/crud.go
package article
import (
"goblog/pkg/model"
"goblog/pkg/types"
)
// Get 通过 ID 获取文章
func Get(idstr string) (Article, error) {
var article Article
id := types.StringToUint64(idstr)
if err := model.DB.First(&article, id).Error; err != nil {
return article, err
}
return article, nil
}
First() 是 gorm.DB 提供的用以从结果集中获取第一条数据的查询方法,.Error 是 GORM 的错误处理机制。与常见的 Go 代码不同,因 GORM 提供的是链式 API,如果遇到任何错误,GORM 会设置 *gorm.DB 的 Error 字段。
1.3 新增 types.StringToUint64()函数
pkg/types/converter.go
.
.
.
// StringToUint64 将字符串转换为 uint64
func StringToUint64(str string) uint64 {
i, err := strconv.ParseUint(str, 10, 64)
if err != nil {
logger.LogError(err)
}
return i
}
1.4 修改控制器的调用
app/http/controllers/articles_controller.go
.
.
.
// Show 文章详情页面
func (*ArticlesController) Show(w http.ResponseWriter, r *http.Request) {
.
.
.
// 2. 读取对应的文章数据
article, err := article.Get(id)
.
.
.
}
1.5 重构 route 包
pkg/route/router.go
// Package route 路由相关
package route
import (
"goblog/pkg/logger"
"net/http"
"github.com/gorilla/mux"
)
var route *mux.Router
// SetRoute 设置路由实例,以供 Name2URL 等函数使用
func SetRoute(r *mux.Router) {
route = r
}
// Name2URL 通过路由名称来获取 URL
func Name2URL(routeName string, pairs ...string) string {
url, err := route.Get(routeName).URL(pairs...)
if err != nil {
logger.LogError(err)
return ""
}
return url.String()
}
.
.
.
1.6 通过 SetRoute 来传参对象变量
bootstrap/route.go
.
.
.
// SetupRoute 路由初始化
func SetupRoute() *mux.Router {
router := mux.NewRouter()
routes.RegisterWebRoutes(router)
route.SetRoute(router)
return router
}
1.7 新增方法:
pkg/types/converter.go
.
.
.
// Uint64ToString 将 uint64 转换为 string
func Uint64ToString(num uint64) string {
return strconv.FormatUint(num, 10)
}
1.8 控制器将 Int64ToString 改为 Uint64ToString
app/http/controllers/articles_controller.go
.
.
.
// Show 文章详情页面
func (*ArticlesController) Show(w http.ResponseWriter, r *http.Request) {
.
.
.
} else {
// 4. 读取成功,显示文章
tmpl, err := template.New("show.gohtml").
Funcs(template.FuncMap{
"RouteName2URL": route.Name2URL,
"Uint64ToString": types.Uint64ToString,
}).
1.9 模板里修改 Int64ToString 为 Uint64ToString:
resources/views/articles/show.gohtml
{{/* 构建删除按钮 */}}
{{ $idString := Uint64ToString .ID }}
<form action="{{ RouteName2URL "articles.delete" "id" $idString }}" method="post">
<button type="submit" onclick="return confirm('删除动作不可逆,请确定是否继续')">删除</button>
</form>
浏览 localhost:3000/articles/3 ,显示:
访问localhost:3000/articles/1000,显示拒绝访问
1.10 修改logger:
pkg/logger/logger.go
// Package logger 日志相关
package logger
import "log"
// LogError 当存在错误时记录日志
func LogError(err error) {
if err != nil {
log.Println(err)
}
}
1.11 修改articles_controller.go的判断:
GORM 有单独的错误类型 —— gorm.ErrRecordNotFound
app/http/controllers/articles_controller.go
.
.
.
// Show 文章详情页面
func (*ArticlesController) Show(w http.ResponseWriter, r *http.Request) {
.
.
.
// 3. 如果出现错误
if err != nil {
if err == gorm.ErrRecordNotFound { // <---- 这一行
// 3.1 数据未找到
.
.
.
} else {
// 3.2 数据库错误
.
.
.
}
} else {
// 4. 读取成功,显示文章
.
.
.
}
}
再次访问localhost:3000/articles/521
2 重构文章列表
2.1 移动articles.index路由
把 main.go 里的articles.index路由剪切到 web.go 中,并简单修改
routes/web.go
.
.
.
func RegisterWebRoutes(r *mux.Router) {
.
.
.
r.HandleFunc("/articles", ac.Index).Methods("GET").Name("articles.index")
}
2.2 添加Index控制器方法
将 main.go 中 articlesIndexHandler 函数剪切并修改名称到控制器中:
app/http/controllers/articles_controller.go
// Index 文章列表页
func (*ArticlesController) Index(w http.ResponseWriter, r *http.Request) {
// 1. 获取结果集
articles, err := article.GetAll()
if err != nil {
// 数据库错误
logger.LogError(err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "500 服务器内部错误")
} else {
// 2. 加载模板
tmpl, err := template.ParseFiles("resources/views/articles/index.gohtml")
logger.LogError(err)
// 3. 渲染模板,将所有文章的数据传输进去
err = tmpl.Execute(w, articles)
logger.LogError(err)
}
}
2.3 获取文章列表 GetAll
获取文章列表封装到模型的 GetAll 方法里:
app/models/article/crud.go
// GetAll 获取全部文章
func GetAll() ([]Article, error) {
var articles []Article
if err := model.DB.Find(&articles).Error; err != nil {
return articles, err
}
return articles, nil
}
上面两个步骤的代码的功能与下面代码一样:
// 1. 执行查询语句,返回一个结果集
rows, err := db.Query("SELECT * from articles")
logger.LogError(err)
defer rows.Close()
var articles []Article
//2. 循环读取结果
for rows.Next() {
var article Article
// 2.1 扫码每一行的结果并赋值到一个 article 对象中
err := rows.Scan(&article.ID, &article.Title, &article.Body)
logger.LogError(err)
// 2.2 将 article 追加到 articles 的这个数组中
articles = append(articles, article)
}
// 2.3 检测循环时是否发生错误
err = rows.Err()
logger.LogError(err)
GORM 的优势之一:不需要时刻记住关闭连接
访问 localhost:3000/articles :
没有显示数据
2.4 调试程序
GORM 提供了一个调试功能,允许在命令行里查看请求的 SQL 信息,config里面设置:
pkg/model/model.go
// Package model 应用模型数据层
package model
import (
"goblog/pkg/logger"
"gorm.io/gorm"
gormlogger "gorm.io/gorm/logger"
// GORM 的 MSYQL 数据库驱动导入
"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: gormlogger.Default.LogMode(gormlogger.Info),
})
logger.LogError(err)
return DB
}
顶部的 import 语句,导入 gorm/logger 时,因 goblog/pkg/logger 名称冲突,故为其指定名称:
gormlogger “gorm.io/gorm/logger”
刷新 localhost:3000/articles 页面
[rows:3] 意味着从数据库了成功取出了三条数据。
试着在控制器里打印一下 articles 变量:
app/http/controllers/articles_controller.go
.
.
.
// Index 文章列表页
func (*ArticlesController) Index(w http.ResponseWriter, r *http.Request) {
// 1. 获取结果集
articles, err := article.GetAll()
fmt.Println("文章数据", articles)
.
.
.
}
刷新 localhost:3000/articles 页面,观察命令行:
经调试,数据没问题,模板index.gohtml的问题
resources/views/articles/index.gohtml查看模板
<!DOCTYPE html>
<html lang="en">
<head>
<title>所有文章 —— 我的技术博客</title>
<style type="text/css">.error {color: red;}</style>
</head>
<body>
<h1>所有文章</h1>
<ul>
{{ range $key, $article := . }}
<li><a href="{{ $article.Link }}"><strong>{{ $article.ID }}</strong>: {{ $article.Title }}</a></li>
{{ end }}
</ul>
</body>
</html>
$article.Link 这是一个对象方法,还未创建,将 main 里的对应方法移动到模型中,并简单修改:
app/models/article/article.go
.
.
.
// Link 方法用来生成文章链接
func (article Article) Link() string {
return route.Name2URL("articles.show", "id", strconv.FormatUint(article.ID, 10))
}
刷新 localhost:3000/articles 页面
问题解决,清理刚才调试的内容:
- 删除 fmt.Println(“文章数据”, articles)
- 设置日志级别为Warn即可,pkg/model/model.go 中设置为Logger: gormlogger.Default.LogMode(gormlogger.Warn)
- 删除main.go中的 articlesIndexHandler 和 Link 这两个函数
2.5 代码版本标记
git add .
git commit -m "重构文章列表页面"
git push //注释,push到远程github上