構建 Go RESTful API 的終極指南:Fiber、GORM 和 PostgreSQL
本文將帶你一步步構建一個基於 Go、Fiber 和 GORM 的 RESTful API,並使用 PostgreSQL 作爲數據庫。我們將涵蓋從項目搭建、數據庫配置到 API 路由、認證、授權、錯誤處理等關鍵環節,並附有完整代碼示例和詳細解釋,幫助你快速上手 Go RESTful API 開發。
項目搭建
首先,我們需要創建一個新的 Go 項目並安裝必要的依賴包。
# 初始化 Go 模塊
go mod init your-project-name
# 安裝依賴
go get github.com/gofiber/fiber/v2
go get github.com/jinzhu/gorm
go get github.com/joho/godotenv
go get github.com/spf13/viper
go get github.com/swaggo/swag
go get github.com/stretchr/testify
go get github.com/go-playground/validator/v10
go get github.com/sirupsen/logrus
數據庫配置
-
創建 PostgreSQL 數據庫: 使用你喜歡的數據庫管理工具(例如 pgAdmin)創建名爲
fiberdb的數據庫,並創建一個名爲postgres的用戶,並賦予其對fiberdb數據庫的讀寫權限。 -
配置環境變量: 創建一個
.env文件用於存儲數據庫配置信息。
# .env
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=fiberdb
- 連接數據庫: 在你的
main.go文件中,使用gorm庫連接到 PostgreSQL 數據庫。
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres" // 導入 PostgreSQL 驅動
)
func main() {
// 加載環境變量
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
// 連接數據庫
db, err := gorm.Open("postgres", fmt.Sprintf("host=%s port=%s user=%s password=%s db,
os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME")))
if err != nil {
log.Fatal("Error connecting to database:", err)
}
defer db.Close()
// ... 你的其他代碼 ...
}
模型定義
定義你的數據庫模型。例如,創建一個 User 模型:
package model
import (
"time"
"github.com/jinzhu/gorm"
)
type User struct {
gorm.Model
Name string `json:"name" gorm:"not null"`
Email string `json:"email" gorm:"not null;unique"`
Password string `json:"password" gorm:"not null"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
遷移數據庫
使用 gorm 的遷移功能來創建數據庫表。
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"your-project-name/model"
)
func main() {
// ... (數據庫連接代碼) ...
// 自動遷移
err := db.AutoMigrate(&model.User{}).Error
if err != nil {
log.Fatal("Error migrating database:", err)
}
// ... (你的其他代碼) ...
}
使用 Fiber 創建 API 路由
- 創建路由處理器: 創建一個
handler包,用於處理 API 請求。
package handler
import (
"github.com/gofiber/fiber/v2"
"your-project-name/model"
)
func CreateUser(c *fiber.Ctx) error {
// ... (處理創建用戶邏輯) ...
}
func GetUser(c *fiber.Ctx) error {
// ... (處理獲取用戶邏輯) ...
}
- 定義 API 路由: 在
main.go文件中使用fiber創建路由。
package main
import (
"log"
"os"
"github.com/gofiber/fiber/v2"
"github.com/joho/godotenv"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"your-project-name/handler"
"your-project-name/model"
)
func main() {
// ... (數據庫連接代碼) ...
// 創建 Fiber 應用
app := fiber.New()
// 定義路由
app.Post("/users", handler.CreateUser)
app.Get("/users/:id", handler.GetUser)
// 啓動服務器
log.Fatal(app.Listen(fmt.Sprintf(":%s", os.Getenv("APP_PORT"))))
}
認證和授權
- JWT 認證: 使用
JWT作爲認證機制,並創建token包來生成和驗證 JWT。
package token
import (
"fmt"
"os"
"time"
"github.com/dgrijalva/jwt-go"
)
// 定義 JWT 聲明結構
type Claims struct {
UserID uint `json:"user_id"`
Role string `json:"role"`
jwt.StandardClaims
}
// 生成 JWT
func GenerateToken(userID uint, role string) (string, error) {
// 創建 JWT 聲明
claims := &Claims{
UserID: userID,
Role: role,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), // 設置過期時間
Issuer: "your-project-name",
},
}
// 使用 JWT 簽名密鑰生成 JWT
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(os.Getenv("JWT_SECRET")))
}
// 驗證 JWT
func ValidateToken(tokenString string) (*Claims, error) {
// 解析 JWT
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil {
return nil, err
}
// 驗證 JWT 聲明
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
} else {
return nil, fmt.Errorf("invalid token")
}
}
- 創建認證中間件: 創建一個
middleware包,用於處理認證邏輯。
package middleware
import (
"fmt"
"strings"
"github.com/gofiber/fiber/v2"
"your-project-name/token"
)
func Auth(c *fiber.Ctx) error {
// 獲取 Authorization 頭信息
authHeader := c.Get("Authorization")
if authHeader == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Missing Authorization header"})
}
// 驗證 JWT
tokenString := strings.Split(authHeader, " ")[1]
claims, err := token.ValidateToken(tokenString)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid token"})
}
// 將用戶 ID 和角色信息添加到上下文
c.Locals("user_id", claims.UserID)
c.Locals("role", claims.Role)
return c.Next()
}
- 使用認證中間件: 在需要認證的路由上使用
Auth中間件。
app.Post("/users", middleware.Auth, handler.CreateUser)
- 授權: 使用
roles包定義角色和權限。
package roles
const (
AdminRole = "admin"
UserRole = "user"
ManageUsers = "manage_users"
)
// 定義角色權限映射
var RolePermissions = map[string][]string{
AdminRole: {ManageUsers},
UserRole: {},
}
- 創建授權中間件: 創建一個
middleware包,用於處理授權邏輯。
package middleware
import (
"fmt"
"github.com/gofiber/fiber/v2"
"your-project-name/roles"
)
func Authorize(c *fiber.Ctx, permission string) error {
// 獲取角色
role := c.Locals("role").(string)
// 檢查權限
if !roles.HasPermission(role, permission) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"message": "Unauthorized"})
}
return c.Next()
}
// 檢查角色是否擁有權限
func HasPermission(role string, permission string) bool {
permissions, ok := roles.RolePermissions[role]
if !ok {
return false
}
for _, p := range permissions {
if p == permission {
return true
}
}
return false
}
- 使用授權中間件: 在需要授權的路由上使用
Authorize中間件。
app.Post("/users", middleware.Auth, middleware.Authorize("manage_users"), handler.CreateUser)
錯誤處理
創建一個 error 包,用於處理 API 錯誤。
package error
import (
"github.com/gofiber/fiber/v2"
)
// 自定義錯誤結構體
type AppError struct {
Code int `json:"code"`
Status string `json:"status"`
Message string `json:"message"`
}
// 創建新的錯誤對象
func New(code int, message string) *AppError {
return &AppError{
Code: code,
Status: "error",
Message: message,
}
}
// 處理錯誤中間件
func ErrorHandler(c *fiber.Ctx, err error) error {
// 處理自定義錯誤
if appErr, ok := err.(*AppError); ok {
return c.Status(appErr.Code).JSON(appErr)
}
// 處理其他錯誤
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"status": "error", "message": "Internal server error"})
}
API 文檔
使用 swagger 生成 API 文檔。
- 安裝
swag庫:
go get github.com/swaggo/swag/cmd/swag
- 生成
swagger文件: 在項目根目錄運行swag init命令。
swag init
- 添加
swagger註解: 在你的handler文件中添加swagger註解來描述 API。
// @Summary 創建用戶
// @Description 創建一個新的用戶
// @Tags users
// @Accept json
// @Produce json
// @Param user body model.User true "用戶信息"
// @Success 201 {object} model.User
// @Failure 400 {object} error.AppError
// @Router /users [post]
func CreateUser(c *fiber.Ctx) error {
// ... (創建用戶邏輯) ...
}
- 啓動
swagger服務: 在main.go文件中添加swagger路由。
package main
import (
"fmt"
"log"
"os"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
"github.com/joho/godotenv"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"your-project-name/handler"
"your-project-name/model"
"your-project-name/error"
"your-project-name/middleware"
)
func main() {
// ... (數據庫連接代碼) ...
// 創建 Fiber 應用
app := fiber.New()
// ... (路由定義) ...
// 註冊錯誤處理中間件
app.Use(error.ErrorHandler)
// 註冊 `swagger` 路由
app.Get("/docs", swagger.HandlerDefault)
// 啓動服務器
log.Fatal(app.Listen(fmt.Sprintf(":%s", os.Getenv("APP_PORT"))))
}
測試
- 創建測試文件: 創建一個
test包用於編寫測試用例。
package test
import (
"testing"
"github.com/stretchr/testify/assert"
"your-project-name/handler"
"your-project-name/model"
)
func TestCreateUser(t *testing.T) {
// ... (測試用例代碼) ...
}
- 編寫測試用例: 在測試文件中編寫測試用例來測試你的 API 功能。
func TestCreateUser(t *testing.T) {
// 創建測試用戶
user := &model.User{
Name: "Test User",
Email: "test@example.com",
Password: "password",
}
// 模擬 API 請求
c := fiber.New(fiber.Config{
DisableStartupMessage: true,
}).Acquire(fiber.AcquireConfig{
Method: "POST",
Path: "/users",
Body: user,
})
// 調用 API 路由處理器
err := handler.CreateUser(c)
// 斷言返回值
assert.NoError(t, err)
assert.Equal(t, fiber.StatusCreated, c.Response.StatusCode)
// 驗證數據庫記錄
// ... (驗證數據庫記錄) ...
}
- 運行測試: 在項目根目錄運行
go test命令。
go test
其他功能
-
日誌記錄: 使用
logrus庫記錄 API 日誌。 -
數據庫遷移: 使用
gorm的遷移功能來管理數據庫表結構。 -
數據驗證: 使用
validator庫驗證 API 請求數據。 -
CORS: 添加
CORS中間件來允許跨域請求。 -
壓縮: 添加壓縮中間件來壓縮 API 響應。
總結
本文提供了一個完整的 Go RESTful API 開發指南,從項目搭建、數據庫配置到 API 路由、認證、授權、錯誤處理、測試等各個方面進行了詳細講解。希望本文能幫助你快速上手 Go RESTful API 開發,並構建出安全、高效、易維護的 API。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/w1ULznUqQseWivxTf5JA4g