Go入门: gin 框架极速搭建图书管理系统
Go入门: gin 框架极速搭建图书管理系统
前言
本项目适合 Golang 初学者,通过简单的项目实践来加深对 Golang 的基本语法和 Web 开发的理解。
项目结构
D:.
├─ go.mod
├─ go.sum
│
├─ cmd
│ └─ main
│ └─ main.go
│
└─ pkg
├─ config
│ └─ app.go
│
├─ controllers
│ └─ book-controller.go
│
├─ models
│ └─ book.go
│
└─ routes
└─ bookstore-routes.go
项目流程图
- 技术栈
- 项目结构
- 项目路由
项目初始化
- 初始化项目文件夹
md go-bookstore
- 初始化
mod
文件
cd go-bookstore
go mod init github.com/your_username/go-bookstore
注意,此处的
your_username
请替换为你的 GitHub 用户名
- 安装依赖包
# GORM 是一个用于 Go 的 ORM(对象关系映射)库,可以简化与数据库的交互。
go get "github.com/jinzhu/gorm"
# 这个包提供了与 MySQL 数据库的连接和操作功能,使得可以使用 GORM 进行 MySQL 数据库的 CRUD 操作。
go get "github.com/jinzhu/gorm/dialects/mysql"
# gin 是一个 Go 语言的 Web 框架,它可以快速搭建 Web 服务。
go get "github.com/go-gonic/gin"
# 打开 VSCode 编辑器
code .
- 创建
go-bookstore
数据库
打开
Mysql
客户端,输入以下命令创建go-bookstore
数据库:
CREATE DATABASE `database_name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
其中,
database_name
请替换为你自己喜欢的数据库名称。
- 初始化项目结构
一行代码在项目根目录下创建目录和空文件
# windows系统
mkdir cmd\main pkg\config pkg\controllers pkg\models pkg\routes & type nul > cmd\main\main.go & type nul > pkg\config\app.go & type nul > pkg\controllers\book-controller.go & type nul > pkg\models\book.go & type nul > pkg\routes\bookstore-routes.go
# Linux/mac系统
mkdir -p cmd/main pkg/config pkg/controllers pkg/models pkg/routes && touch touch cmd/main/main.go pkg/config/app.go pkg/controllers/book-controller.go pkg/models/book.go pkg/routes/bookstore-routes.go
项目编写
- 编写
pkg/routes/bookstore-routes.go
文件
为什么要最先编写路由?
优先选择编写路由文件的原因在于路由决定了用户访问的 URL 所对应的页面和内容。也就是说,路由是用户请求的起点。因为所有操作都从请求接口开始,定义好路由可以帮助我们明确应用的整体结构。
在路由确定之后,我们可以进一步编写控制器和模型,这样可以确保应用的各个部分都能协调工作。
虽然每个人的开发习惯和业务逻辑可能不同,但从路由入手通常是一个推荐的方法,它能帮助你更清晰地组织代码, 并且让你曾经觉得难以完成的独立开发一个项目变得轻松可行。
package routes
import (
// 引入控制器
"github.com/your_username/go-bookstore/pkg/controllers"
// 引入 Gin 框架
"github.com/gin-gonic/gin"
)
// 定义路由组及其路由
func Router() *gin.Engine {
r := gin.Default()
book := r.Group("/book")
{
book.GET("/", controllers.GetBookTest)
book.GET("/:bookId", controllers.GetBookByIdTest)
book.POST("/", controllers.CreateBookTest)
book.PUT("/:bookId", controllers.UpdateBookTest)
book.DELETE("/:bookId", controllers.DeleteBookTest)
}
return r
}
注意,此处的
your_username
请替换为你的 GitHub 用户名。
- 引入控制器和
Gin
框架:引入了自定义的控制器包controllers
和Gin
框架的包github.com/gin-gonic/gin
gin.Default()
:使用gin.Default()
创建一个默认的Gin
引擎实例,该实例已包含了默认的中间件,如日志记录和恢复中间件。- 定义路由组:
r.Group("/book")
创建了一个路由组,所有以/book
开头的路径都将由这个组处理。- 路由配置:
GET /
:映射到controllers.GetBookTest
,用于获取所有书籍的测试数据。GET /:bookId
:映射到controllers.GetBookByIdTest
,根据书籍 ID 获取指定书籍的信息。POST /
:映射到controllers.CreateBookTest
,用于创建新书籍。PUT /:bookId
:映射到controllers.UpdateBookTest
,根据书籍 ID 更新书籍信息。DELETE /:bookId
:映射到controllers.DeleteBookTest
,根据书籍 ID 删除书籍。- 返回路由:在函数
Router()
的最后,返回了创建的*gin.Engine
实例r
; 其中*gin.Engine
实例是整个应用的核心路由处理对象,包含了所有定义的路由和中间件; 返回这个实例是为了在应用的其他部分(例如main
函数)可以使用它来启动HTTP
服务器或进行其他操作。
- 编写
pkg/config/app.go
文件
package config
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
var (
db *gorm.DB
)
func Connect() {
// 其中root对应你的数据库用户名,yourpassword对应你的数据库密码,database_name对应你的数据库名称。
d, err := gorm.Open("mysql", "root:your_password@tcp(127.0.0.1:3306)/go-bookstore03?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
panic(err)
}
fmt.Println("Connect to database successfully")
db = d
}
func GetDB() *gorm.DB {
return db
}
注意,此处的
your_password
请替换为你的数据库密码。
- 引入包:使用
gorm
和 MySQL 驱动。- 全局变量:
db
存储数据库连接实例。Connect
函数:连接到 MySQL 数据库,连接成功后保存连接实例到db
。如果连接失败,程序会终止。GetDB
函数:返回db
,让其他代码可以使用数据库连接。- 连接字符串参数:
charset=utf8mb4
:支持更多字符。parseTime=True
:正确解析时间字段。loc=Local
:使用本地时区。
- 编写
pkg/models/models.go
文件
package models
import (
"github.com/jinzhu/gorm"
"github.com/Done-0/go-bookstore/pkg/config"
)
var db *gorm.DB
type Book struct {
// 嵌套 gorm.Model 结构体,它包含了默认的字段:ID(主键)、CreatedAt、UpdatedAt 和 DeletedAt(软删除)。
gorm.Model
Name string `json:"name"`
Author string `json:"author"`
Publication string `json:"publication"`
}
// init 函数是 Go 语言中的特殊函数,它会在包初始化时自动执行。通常用来做一些初始化操作,如设置数据库连接、初始化配置等。
func init() {
config.Connect()
db = config.GetDB()
// 使用 GORM 的 AutoMigrate 方法自动迁移 Book 结构体。
// 自动迁移会创建或更新数据库表,使其与 Book 结构体匹配。如果表不存在,则创建表;如果表已存在,则更新表结构以匹配 Book 结构体的定义。
db.AutoMigrate(&Book{})
}
// CreateBook 方法用于创建 Book 结构体的实例并插入到数据库中。
func (b *Book) CreateBook() *Book{
db.NewRecord(b)
db.Create(&b)
return b
}
// GetAllBooks 方法用于从数据库中获取所有 Book 结构体的实例。
func GetAllBooks() []Book {
var Books []Book
db.Find(&Books)
return Books
}
// GetBookById 方法用于从数据库中获取指定 ID 的 Book 结构体的实例。
func GetBookById(Id int64) (*Book, *gorm.DB) {
var getBook Book
db:=db.Where("ID=?", Id).Find(&getBook)
return &getBook, db
}
// DeleteBook 方法用于从数据库中删除指定 ID 的 Book 结构体的实例。
func DeleteBook(ID int64) Book {
var book Book
// 软删除,将 DeletedAt 字段设置为当前时间。
db.Where("ID=?", ID).Delete(&book)
return book
}
- 依赖包:使用
gorm
和config
包。- 全局数据库变量:
db
用于存储数据库连接实例。Book
结构体:定义了书籍模型,包括ID
、CreatedAt
、UpdatedAt
、DeletedAt
(GORM 提供的默认字段)以及Name
、Author
、Publication
。init
函数:连接数据库,并自动迁移Book
表。CreateBook
方法:创建并插入新的书籍记录。GetAllBooks
方法:获取所有书籍记录。GetBookById
方法:根据 ID 获取特定书籍记录。DeleteBook
方法:根据 ID 删除特定书籍记录。
- 编写
cmd/main/main.go
文件
package main
import (
"github.com/your_name/go-bookstore/pkg/routes"
)
func main() {
r := routes.Router()
r.Run(":9010")
}
注意,此处的
your_name
请替换为你的 GitHub 用户名。
可以注意到,引入gin
框架 -> 调用Router()
函 -> 启动HTTP
服务,这一条逻辑线明显比使用纯原生的net/http
包更加简洁。
- 编写
pkg/controllers/book-controller.go
文件
package controllers
import (
"fmt"
"net/http"
"strconv"
// 请替换 your_name 为你的 GitHub 用户名
"github.com/your_name/go-bookstore/pkg/models"
"github.com/gin-gonic/gin"
)
// NewBook 是一个用来创建新书的结构体
var NewBook models.Book
// GetBookTest 函数返回所有的书籍
func GetBookTest(c *gin.Context) {
newBooks := models.GetAllBooks()
c.JSON(http.StatusOK, newBooks)
}
// GetBookByIdTest 函数返回指定ID的书籍
func GetBookByIdTest(c *gin.Context) {
bookId := c.Param("bookId")
ID, _ := strconv.ParseInt(bookId,0,0)
bookDetails, _ := models.GetBookById(ID)
// 以JSON格式返回书籍详情
c.JSON(http.StatusOK, bookDetails)
}
// CreateBookTest 函数创建一个新的书籍并返回详细信息
func CreateBookTest(c *gin.Context) {
// 初始化一个新书籍结构体
var CreateBook = &models.Book{}
// 从请求体中解析书籍信息
c.ShouldBindJSON(CreateBook)
// 创建书籍并保存到数据库
b := CreateBook.CreateBook()
// 以JSON格式返回书籍详情
c.JSON(http.StatusOK, b)
}
func DeleteBookTest(c *gin.Context) {
bookId := c.Param("bookId")
ID, err := strconv.ParseInt(bookId,0,0)
if err != nil {
fmt.Printf("解析错误!")
}
// 删除之前获取书籍详情, 并删除书籍
book, _ := models.GetBookById(ID)
models.DeleteBook(ID)
c.JSON(http.StatusOK, book)
}
func UpdateBookTest(c *gin.Context) {
var updateBook = &models.Book{}
c.ShouldBindJSON(updateBook)
bookId := c.Param("bookId")
ID, err := strconv.ParseInt(bookId,0,0)
if err != nil {
fmt.Println("解析错误!")
}
bookDetails, db := models.GetBookById(ID)
// 如果Name、Author、Publication字段有更新,则更新数据库对应数据
if updateBook.Name != "" {
bookDetails.Name = updateBook.Name
}
if updateBook.Author != "" {
bookDetails.Author = updateBook.Author
}
if updateBook.Publication != "" {
bookDetails.Publication = updateBook.Publication
}
// 保存更新后的书籍信息到数据库
db.Save(&bookDetails)
c.JSON(http.StatusOK, bookDetails)
}
注意,此处的
your_name
请替换为你的 GitHub 用户名。
为什么在开头创建一个空的结构体变量?
1. 清晰的数据管理
结构体用途:
models.Book
:用来表示书籍的完整数据模型,包括所有字段,如Name
、Author
、Publication
等。NewBook
:作为全局变量,实际用处不大,可能是用于创建新的书籍实例,但它的实际用法可能会产>生混淆。2. 灵活的请求处理
具体代码应用:
CreateBookTest
函数:
- 目的:处理创建新书籍的请求。
- 结构体用法:创建了一个新的
models.Book
实例来接收客户端提交的书籍数据。- 为什么这样做:通过解析请求体中的 JSON 数据填充这个结构体,然后用它创建新书籍。这确保了处理请求时,数据结构与数据库模型一致。
func CreateBookTest(c *gin.Context) { var CreateBook = &models.Book{} c.ShouldBindJSON(CreateBook) b := CreateBook.CreateBook() c.JSON(http.StatusOK, b) }
UpdateBookTest
函数:
- 目的:处理更新书籍信息的请求。
- 结构体用法:创建一个
models.Book
实例来接收更新的数据。- 为什么这样做:结构体的字段用于确定哪些书籍属性需要更新。这使得更新逻辑更加清晰,并且只更新客户端提供的字段。
func UpdateBookTest(c *gin.Context) { var updateBook = &models.Book{} c.ShouldBindJSON(updateBook) bookId := c.Param("bookId") ID, err := strconv.ParseInt(bookId,0,0) if err != nil { fmt.Println("解析错误!") } bookDetails, db := models.GetBookById(ID) if updateBook.Name != "" { bookDetails.Name = updateBook.Name } if updateBook.Author != "" { bookDetails.Author = updateBook.Author } if updateBook.Publication != "" { bookDetails.Publication = updateBook.Publication } db.Save(&bookDetails) c.JSON(http.StatusOK, bookDetails) }
3. 数据验证和清理
在处理创建和更新请求时,使用不同的结构体可以方便地进行数据验证。例如,可以在
CreateBookTest
中要求所有字段都必须提供,而在UpdateBookTest
中只更新那些提供了新值的字段。这种做法使得每个请求的处理更加灵活。4. 代码可维护性
结构体的清晰定义:
models.Book
:定义了书籍的完整数据结构。CreateBook
和updateBook
:专门用于请求处理,便于在实际操作中进行数据绑定和验证。这种做法使得代码的每个部分都专注于自己需要处理的任务,从而提高了代码的可读性和可维护性。即使在未来需要对请求格式或数据模型进行修改,也能在对应的位置进行调整,而不影响其他部分的代码。
运行项目
- 编译项目
go run cmd/main/main.go
postman
测试
进入Postman测试接口,验证项目功能是否正常:
GET ALL
接口地址:http://localhost:9010/book/
GET BY ID
接口地址:http://localhost:9010/book/1
CREATE
接口地址:http://localhost:9010/book/
UPDATE
接口地址:http://localhost:9010/book/1
DELETE
接口地址:http://localhost:9010/book/1
书籍创建之
JSON
模板:
{ "ID": 1, "createdAt": "2024-08-29T13:26:00Z", "updatedAt": "2024-08-29T13:26:00Z", "DeleteAt": null, "name": "Go 语言", "author": "老子", "publication": "人民邮电出版社", }
- 感谢你赐予我前进的力量