1.Gin框架介紹
Gin 是最受歡迎的 Go HTTP Web 框架。 Gin 因其高效能(使用 httprouter)、直覺的 API 和豐富的中間件生態系統而脫穎而出。
1.1.比較 Gin 與其他框架
| 標準 | 琴酒 | 迴音 | 纖維 | 志 |
|---|---|---|---|---|
| 效能 | 非常高 | 非常高 | 最高(fasthttp) | 高 |
| 人氣 | ⭐ 80k+ | ⭐ 30k+ | ⭐ 34k+ | ⭐ 18k+ |
| API風格 | 類似快遞 | 類似快遞 | 類似快遞 | 相容 net/http |
| 中介軟體 | 很多 | 很多 | 很多 | 氣相容 |
| 網路/http 相容 | 部分 | 滿 | ❌(快速http) | 滿 |
| 驗證 | 內建 | 內建 | 內建 | 手冊 |
| 昂首闊步 | 斯瓦戈 | 斯瓦戈 | 斯瓦戈 | 斯瓦戈 |
什麼時候選擇杜松子酒? 最受歡迎,資源豐富,功能和效能之間取得了良好的平衡。適合大多數項目。
1.2.設置
mkdir gin-api && cd gin-api
go mod init github.com/yourname/gin-api
go get github.com/gin-gonic/gin
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// gin.Default() = Logger + Recovery middleware
r := gin.Default()
// gin.New() = no middleware (production)
// r := gin.New()
// r.Use(gin.Logger(), gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run(":8080") // Default :8080
}
2. 路由
func main() {
r := gin.Default()
// Basic routes
r.GET("/users", listUsers)
r.POST("/users", createUser)
r.GET("/users/:id", getUser) // Path parameter
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)
// Route groups
api := r.Group("/api/v1")
{
users := api.Group("/users")
{
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
posts := api.Group("/posts")
posts.Use(authMiddleware()) // Middleware cho group
{
posts.GET("", listPosts)
posts.POST("", createPost)
}
}
// Wildcard route
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.String(http.StatusOK, "File: %s", filepath)
})
r.Run(":8080")
}
3. 請求處理
3.1.參數
func getUser(c *gin.Context) {
// Path parameter
id := c.Param("id")
// Query parameters
page := c.DefaultQuery("page", "1")
limit := c.DefaultQuery("limit", "10")
search := c.Query("search") // "" if not present
// Header
token := c.GetHeader("Authorization")
c.JSON(http.StatusOK, gin.H{
"id": id,
"page": page,
"limit": limit,
"search": search,
"token": token,
})
}
3.2.請求綁定和驗證
// Gin sử dụng go-playground/validator cho validation
type CreateUserInput struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8,max=72"`
Age int `json:"age" binding:"required,gte=18,lte=120"`
Role string `json:"role" binding:"required,oneof=admin user moderator"`
}
type UpdateUserInput struct {
Name *string `json:"name" binding:"omitempty,min=2,max=50"`
Email *string `json:"email" binding:"omitempty,email"`
Age *int `json:"age" binding:"omitempty,gte=18,lte=120"`
}
// Query parameters binding
type ListUsersQuery struct {
Page int `form:"page" binding:"omitempty,min=1"`
Limit int `form:"limit" binding:"omitempty,min=1,max=100"`
Search string `form:"search" binding:"omitempty,max=200"`
SortBy string `form:"sort_by" binding:"omitempty,oneof=name email created_at"`
}
func createUser(c *gin.Context) {
var input CreateUserInput
// ShouldBindJSON - returns error (không abort)
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Validation failed",
"details": formatValidationErrors(err),
})
return
}
// ... create user
c.JSON(http.StatusCreated, gin.H{
"message": "User created",
"data": input,
})
}
func listUsers(c *gin.Context) {
var query ListUsersQuery
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Set defaults
if query.Page == 0 { query.Page = 1 }
if query.Limit == 0 { query.Limit = 20 }
// ... fetch users
}
// Format validation errors
func formatValidationErrors(err error) map[string]string {
errors := make(map[string]string)
var ve validator.ValidationErrors
if stderrors.As(err, &ve) {
for _, fe := range ve {
field := fe.Field()
switch fe.Tag() {
case "required":
errors[field] = field + " is required"
case "email":
errors[field] = "Invalid email format"
case "min":
errors[field] = field + " must be at least " + fe.Param()
case "max":
errors[field] = field + " must be at most " + fe.Param()
default:
errors[field] = "Invalid " + field
}
}
}
return errors
}
3.3.自訂驗證器
import "github.com/go-playground/validator/v10"
// Custom validator
func setupValidators(r *gin.Engine) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// Custom validation tag
v.RegisterValidation("strong_password", func(fl validator.FieldLevel) bool {
password := fl.Field().String()
hasUpper := false
hasLower := false
hasDigit := false
for _, ch := range password {
switch {
case unicode.IsUpper(ch):
hasUpper = true
case unicode.IsLower(ch):
hasLower = true
case unicode.IsDigit(ch):
hasDigit = true
}
}
return hasUpper && hasLower && hasDigit
})
// Vietnamese phone number
v.RegisterValidation("vn_phone", func(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^(0|\+84)(3|5|7|8|9)\d{8}$`, phone)
return matched
})
}
}
type RegisterInput struct {
Password string `json:"password" binding:"required,min=8,strong_password"`
Phone string `json:"phone" binding:"required,vn_phone"`
}
4. 中介軟體
// Auth middleware
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Authorization header required",
})
return
}
// Remove "Bearer " prefix
token = strings.TrimPrefix(token, "Bearer ")
claims, err := validateJWT(token)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Invalid token",
})
return
}
// Set user info in context
c.Set("user_id", claims.UserID)
c.Set("user_role", claims.Role)
c.Next() // Continue to next handler
}
}
// Rate limiting middleware
func rateLimitMiddleware(rps int) gin.HandlerFunc {
limiter := rate.NewLimiter(rate.Limit(rps), rps)
return func(c *gin.Context) {
if !limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "Rate limit exceeded",
})
return
}
c.Next()
}
}
// Request ID middleware
func requestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
c.Set("request_id", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// Usage
func main() {
r := gin.New()
// Global middleware
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(requestIDMiddleware())
r.Use(corsMiddleware())
// Public routes
r.POST("/auth/login", loginHandler)
r.POST("/auth/register", registerHandler)
// Protected routes
protected := r.Group("/api")
protected.Use(authMiddleware())
protected.Use(rateLimitMiddleware(100))
{
protected.GET("/profile", getProfile)
protected.PUT("/profile", updateProfile)
}
r.Run(":8080")
}
5. 響應處理
// Standardized API Response
type APIResponse struct {
Success bool `json:"success"`
Data any `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
Pagination *Pagination `json:"pagination,omitempty"`
}
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
Details map[string]string `json:"details,omitempty"`
}
type Pagination struct {
Page int `json:"page"`
Limit int `json:"limit"`
Total int64 `json:"total"`
TotalPages int `json:"total_pages"`
}
// Helper functions
func respondSuccess(c *gin.Context, data any) {
c.JSON(http.StatusOK, APIResponse{
Success: true,
Data: data,
})
}
func respondCreated(c *gin.Context, data any) {
c.JSON(http.StatusCreated, APIResponse{
Success: true,
Data: data,
})
}
func respondError(c *gin.Context, status int, code, message string) {
c.JSON(status, APIResponse{
Success: false,
Error: &APIError{
Code: code,
Message: message,
},
})
}
func respondPaginated(c *gin.Context, data any, page, limit int, total int64) {
totalPages := int(total) / limit
if int(total)%limit > 0 {
totalPages++
}
c.JSON(http.StatusOK, APIResponse{
Success: true,
Data: data,
Pagination: &Pagination{
Page: page,
Limit: limit,
Total: total,
TotalPages: totalPages,
},
})
}
// Usage
func listUsersHandler(c *gin.Context) {
users, total, err := userService.List(c.Request.Context(), page, limit)
if err != nil {
respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", "Failed to fetch users")
return
}
respondPaginated(c, users, page, limit, total)
}
6. 集中錯誤處理
// Custom error types
type AppError struct {
Code int `json:"-"`
ErrCode string `json:"code"`
Message string `json:"message"`
}
func (e *AppError) Error() string {
return e.Message
}
var (
ErrNotFound = &AppError{Code: 404, ErrCode: "NOT_FOUND", Message: "Resource not found"}
ErrUnauthorized = &AppError{Code: 401, ErrCode: "UNAUTHORIZED", Message: "Unauthorized"}
ErrForbidden = &AppError{Code: 403, ErrCode: "FORBIDDEN", Message: "Forbidden"}
ErrBadRequest = &AppError{Code: 400, ErrCode: "BAD_REQUEST", Message: "Bad request"}
)
// Error handling middleware
func errorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next() // Process request
// Check for errors after handler
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
var appErr *AppError
if errors.As(err, &appErr) {
c.JSON(appErr.Code, APIResponse{
Success: false,
Error: &APIError{Code: appErr.ErrCode, Message: appErr.Message},
})
return
}
// Unknown error
c.JSON(http.StatusInternalServerError, APIResponse{
Success: false,
Error: &APIError{Code: "INTERNAL_ERROR", Message: "Internal server error"},
})
}
}
}
// Usage in handler
func getUserHandler(c *gin.Context) {
id := c.Param("id")
user, err := userService.GetByID(c.Request.Context(), id)
if err != nil {
c.Error(err) // Add error to context
return
}
respondSuccess(c, user)
}
7. Gin API 的專案結構
gin-api/
├── cmd/
│ └── api/
│ └── main.go # Entry point
├── internal/
│ ├── config/
│ │ └── config.go # App configuration
│ ├── handler/
│ │ ├── user_handler.go # HTTP handlers
│ │ ├── auth_handler.go
│ │ └── router.go # Route definitions
│ ├── middleware/
│ │ ├── auth.go
│ │ ├── cors.go
│ │ ├── logger.go
│ │ └── ratelimit.go
│ ├── model/
│ │ ├── user.go # Domain models
│ │ └── post.go
│ ├── repository/
│ │ ├── user_repository.go # Data access
│ │ └── post_repository.go
│ ├── service/
│ │ ├── user_service.go # Business logic
│ │ └── auth_service.go
│ └── dto/
│ ├── request.go # Request DTOs
│ └── response.go # Response DTOs
├── pkg/
│ ├── database/
│ │ └── postgres.go
│ └── validator/
│ └── custom.go
├── migrations/
├── docs/ # Swagger generated
├── go.mod
├── go.sum
├── Makefile
└── Dockerfile
八、總結
- 琴酒:最受歡迎的框架,高性能,豐富的中間件
- 路由:群組、路徑參數、查詢參數、通配符
- 裝訂:ShouldBindJSON,帶有驗證器標籤的ShouldBindQuery
- 中介軟體:身份驗證、速率限制、CORS、請求 ID、錯誤處理
- 回應:標準化API回應格式
- 專案結構:處理程序→服務→儲存庫層
下一部分: GORM 和資料庫集成 — 連接Go到PostgreSQL並處理資料層。