跳到主要内容

Gin

Gin 是一个用 Go 语言编写的 HTTP Web 框架。它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。Gin 使用了自定义版本的 HttpRouter,因此它不仅提供了极快的路由,还提供了中间件支持。

本指南介绍如何在 CloudBase HTTP 云函数上部署 Gin 应用程序。

示例源码请参考: cloudrun-gin

前置条件

在开始之前,请确保您已经:

  • 安装了 Go 1.23 或更高版本
  • 拥有腾讯云账号并开通了 CloudBase 服务
  • 了解基本的 Go 语言和 Gin 框架开发知识

第一步:创建 Gin 应用

💡 提示:如果您已经有一个 Gin 应用,可以跳过此步骤。

创建项目目录

mkdir cloudrun-gin
cd cloudrun-gin

初始化 Go 模块

go mod init cloudrun-gin
go get -u github.com/gin-gonic/gin

创建主应用文件

cloudrun-gin 目录下创建 main.go 文件:

package main

import (
"net/http"
"os"

"github.com/gin-gonic/gin"
)

func main() {
// 设置 Gin 模式
if os.Getenv("GIN_MODE") == "" {
gin.SetMode(gin.ReleaseMode)
}

router := gin.Default()

// 基础路由
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "欢迎使用 Gin CloudBase 应用!",
"status": "running",
})
})

// 健康检查
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"framework": "Gin",
"go_version": "1.19+",
"gin_version": gin.Version,
})
})

// 获取端口,支持环境变量
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

// 启动服务器
router.Run(":" + port)
}

本地测试应用

启动应用:

go run main.go

打开浏览器访问 http://localhost:8080,您应该能看到 JSON 响应。

第二步:添加 API 路由

让我们创建一个 RESTful API 来演示 Gin 的功能。

创建用户模型

在项目根目录创建 models 目录,并创建 user.go 文件:

package models

type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}

type ApiResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}

创建用户控制器

在项目根目录创建 controllers 目录,并创建 user.go 文件:

package controllers

import (
"net/http"
"strconv"
"sync"

"cloudrun-gin/models"

"github.com/gin-gonic/gin"
)

var (
users []models.User
usersMu sync.RWMutex
nextID = 1
)

func init() {
// 初始化测试数据
users = []models.User{
{ID: 1, Name: "张三", Email: "zhangsan@example.com"},
{ID: 2, Name: "李四", Email: "lisi@example.com"},
{ID: 3, Name: "王五", Email: "wangwu@example.com"},
}
nextID = 4
}

// GetUsers 获取用户列表
func GetUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))

usersMu.RLock()
defer usersMu.RUnlock()

startIndex := (page - 1) * limit
endIndex := startIndex + limit

if startIndex >= len(users) {
c.JSON(http.StatusOK, models.ApiResponse{
Success: true,
Message: "获取成功",
Data: []models.User{},
})
return
}

if endIndex > len(users) {
endIndex = len(users)
}

paginatedUsers := users[startIndex:endIndex]

c.JSON(http.StatusOK, models.ApiResponse{
Success: true,
Message: "获取成功",
Data: gin.H{
"total": len(users),
"page": page,
"limit": limit,
"items": paginatedUsers,
},
})
}

// GetUser 根据ID获取用户
func GetUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, models.ApiResponse{
Success: false,
Message: "无效的用户ID",
})
return
}

usersMu.RLock()
defer usersMu.RUnlock()

for _, user := range users {
if user.ID == id {
c.JSON(http.StatusOK, models.ApiResponse{
Success: true,
Message: "获取成功",
Data: user,
})
return
}
}

c.JSON(http.StatusNotFound, models.ApiResponse{
Success: false,
Message: "用户不存在",
})
}

// CreateUser 创建用户
func CreateUser(c *gin.Context) {
var newUser models.User
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, models.ApiResponse{
Success: false,
Message: "请求参数错误: " + err.Error(),
})
return
}

if newUser.Name == "" || newUser.Email == "" {
c.JSON(http.StatusBadRequest, models.ApiResponse{
Success: false,
Message: "姓名和邮箱不能为空",
})
return
}

usersMu.Lock()
newUser.ID = nextID
nextID++
users = append(users, newUser)
usersMu.Unlock()

c.JSON(http.StatusCreated, models.ApiResponse{
Success: true,
Message: "创建成功",
Data: newUser,
})
}

更新主应用文件

更新 main.go 文件,添加路由和中间件:

package main

import (
"fmt"
"net/http"
"os"
"time"

"cloudrun-gin/controllers"

"github.com/gin-gonic/gin"
)

func main() {
// 设置 Gin 模式
if os.Getenv("GIN_MODE") == "" {
gin.SetMode(gin.ReleaseMode)
}

router := gin.Default()

// 添加 CORS 中间件
router.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")

if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}

c.Next()
})

// 添加日志中间件
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))

// 基础路由
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "欢迎使用 Gin CloudBase 应用!",
"status": "running",
})
})

// 健康检查
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"timestamp": time.Now().Format(time.RFC3339),
"framework": "Gin",
"go_version": "1.19+",
"gin_version": gin.Version,
})
})

// API 路由组
api := router.Group("/api")
{
users := api.Group("/users")
{
users.GET("", controllers.GetUsers)
users.GET("/:id", controllers.GetUser)
users.POST("", controllers.CreateUser)
}
}

// 获取端口,支持环境变量
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

// 启动服务器
router.Run(":" + port)
}

第三步:本地测试

更新依赖

go mod tidy

启动应用

go run main.go

测试 API 接口

# 测试健康检查
curl http://localhost:8080/health

# 测试首页
curl http://localhost:8080/

# 测试用户列表
curl http://localhost:8080/api/users

# 测试分页
curl "http://localhost:8080/api/users?page=1&limit=2"

# 测试获取单个用户
curl http://localhost:8080/api/users/1

# 测试创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"新用户","email":"newuser@example.com"}'

第四步:准备部署文件

1. 创建启动脚本

创建 scf_bootstrap 文件(无扩展名):

#!/bin/bash
export PORT=9000
export GIN_MODE=release
./main

为启动脚本添加执行权限:

chmod +x scf_bootstrap

2. 编译应用

编译 Go 应用为 Linux 二进制文件:

# 交叉编译为 Linux 64位
GOOS=linux GOARCH=amd64 go build -o main .

3. 项目结构

cloudrun-gin/
├── controllers/
│ └── user.go
├── models/
│ └── user.go
├── main.go
├── go.mod
├── go.sum
├── main # 编译后的二进制文件
└── scf_bootstrap # 🔑 云函数启动脚本

💡 说明

  • scf_bootstrap 是 CloudBase 云函数的启动脚本
  • 需要将 Go 应用编译为 Linux 二进制文件
  • 设置 PORT=9000 环境变量确保应用监听正确端口
  • 设置 GIN_MODE=release 优化性能

第五步: 部署到 CloudBase

通过控制台部署

  1. 登录 CloudBase 控制台
  2. 选择您的环境,进入「云函数」页面
  3. 点击「新建云函数」
  4. 选择「HTTP 云函数」
  5. 填写函数名称(如:gin-app
  6. 选择运行时:Go 1.x(或其他支持的版本)
  7. 提交方法选择:本地上传代码包
  8. 上传编译后的二进制文件和 scf_bootstrap 文件
  9. 自动安装依赖:关闭(Go 应用无需此选项)
  10. 点击「创建」按钮等待部署完成

打包部署

如果需要手动打包:

# 编译应用
GOOS=linux GOARCH=amd64 go build -o main .

# 创建部署包
zip -j gin-app.zip main scf_bootstrap

第六步: 访问您的应用

部署成功后,您可以参考通过 HTTP 访问云函数设置自定义域名访问HTTP 云函数

示例请求

# 健康检查
curl https://your-app-url/health

# 获取用户列表
curl https://your-app-url/api/users

# 分页查询
curl "https://your-app-url/api/users?page=1&limit=2"

# 创建新用户
curl -X POST https://your-app-url/api/users \
-H "Content-Type: application/json" \
-d '{"name":"测试用户","email":"test@example.com"}'

常见问题

Q: 为什么 HTTP 云函数必须使用 9000 端口?

A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。应用通过环境变量 PORT=9000 来控制端口,本地开发时默认使用 8080 端口。

Q: Go 应用如何进行交叉编译?

A: 使用以下命令进行交叉编译:

GOOS=linux GOARCH=amd64 go build -o main .

Q: 如何优化 Go 应用的冷启动时间?

A:

  • 减少依赖包数量
  • 使用 gin.ReleaseMode 模式
  • 避免在启动时进行重复的初始化操作
  • 合理设置内存配置

Q: scf_bootstrap 文件有什么作用?

A: scf_bootstrap 是云函数的启动脚本,用于设置环境变量和启动编译后的二进制文件。

下一步