基于Netlify的Go云函数开发

Go基础云函数开发示例

目标:创建一个名为 hello 的 API 接口,它能够:

  1. 被通过 .../.netlify/functions/hello 访问。
  2. 可以接收一个可选的 name 查询参数,例如 ?name=Go
  3. 返回一个 JSON 格式的问候语。

第一阶段:创建一个最简单的 “Hello, World” 风格的 Go 云函数并部署到 Netlify。熟悉从本地开发、测试到线上部署的完整工作流。


开发环境准备

在开始之前,请确保您的电脑上已经安装了以下工具:

  1. Go 语言环境:这里使用的版本是 v0.147.3
  2. Netlify 账号Signup
  3. Netlify CLI:用于本地测试。安装命令:pnpm install netlify-cli -g
  4. Git 和 GitHub 账号:用于代码版本控制和部署。

初始化项目

首先,我们在本地创建一个项目目录并初始化 Go 模块。

  1. 打开您的终端或命令行工具。

  2. 创建项目目录并进入:

    bash
    mkdir my-netlify-go-app
    cd my-netlify-go-app
  3. 初始化 Go 模块。这会创建一个 go.mod 文件,用于管理依赖。

    bash
    go mod init my-netlify-go-app
  4. 创建 Netlify Functions 所需的目录结构:

    bash
    mkdir -p netlify/functions/hello
    • netlify/functions/ 是 Netlify 默认存放所有云函数的目录。
    • hello/ 是我们第一个函数的名字。

项目结构现在是:

text
/my-netlify-go-app
|-- go.mod
|-- netlify/
    |-- functions/
        |-- hello/

编写Go云函数代码

现在,我们来编写函数的具体逻辑。

  1. netlify/functions/hello/ 目录下创建一个 main.go 文件。

  2. 将以下代码复制并粘贴到 main.go 文件中:

    go
    // netlify/functions/hello/main.go
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"github.com/aws/aws-lambda-go/events"
    	"github.com/aws/aws-lambda-go/lambda"
    )
    
    // Response is a custom struct for our JSON response
    type Response struct {
    	Message string `json:"message"`
    }
    
    // handler is the main function logic
    func handler(request events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
    	// 1. 从查询参数中获取 'name'
    	name := request.QueryStringParameters["name"]
    
    	// 2. 如果 'name' 为空,则提供一个默认值
    	if name == "" {
    		name = "World"
    	}
    
    	// 3. 构建我们的响应消息
    	message := fmt.Sprintf("Hello, %s!", name)
    
    	// 4. 将消息封装到我们的自定义 Response 结构体中
    	responseObject := Response{
    		Message: message,
    	}
    
    	// 5. 将 Response 结构体序列化为 JSON 字符串
    	responseBody, err := json.Marshal(responseObject)
    	if err != nil {
    		return nil, err
    	}
    
    	// 6. 返回一个标准的 API Gateway 响应
    	return &events.APIGatewayProxyResponse{
    		StatusCode: 200, // OK
    		Headers: map[string]string{
    			"Content-Type": "application/json",
    		},
    		Body: string(responseBody),
    	}, nil
    }
    
    func main() {
    	// 将我们的 handler 函数包装成一个 Lambda 服务
    	lambda.Start(handler)
    }
  3. 安装依赖。aws-lambda-go 是让我们的 Go 代码能与 AWS Lambda (Netlify Functions 的底层) 兼容的关键库。

    bash
    go get github.com/aws/aws-lambda-go/events
    go get github.com/aws/aws-lambda-go/lambda

    运行后,go.modgo.sum 文件会自动更新。


Netlify构建配置

我们需要告诉 Netlify 如何编译我们的 Go 代码。

  1. 在项目根目录(与 go.mod 同级)创建一个 netlify.toml 文件。

  2. 将以下配置粘贴到 netlify.toml 文件中:

    toml
    # netlify.toml
    
    [build]
      # 构建命令:编译我们的 Go 函数
      # -o 指定输出文件路径和名称。Netlify 会在这个目录里寻找可执行文件。
      command = "go build -o netlify/functions/hello/main netlify/functions/hello/main.go"
    
      # 函数目录
      functions = "netlify/functions/"
    
    [functions]
      # 明确指定 Go 函数的运行时
      # 这有助于 Netlify 优化构建过程
      included_files = ["**/*.go", "go.mod", "go.sum"]

本地测试(Netlify CLI)

在部署到线上之前,我们先用 Netlify CLI 在本地进行测试。

  1. 在项目根目录运行:

    bash
    netlify dev
  2. Netlify CLI 会启动一个本地开发服务器。你会看到类似下面的输出:

    text
    ◈ Server listening on http://localhost:8888
    ...
    ◈ Functions server is listening on 53011
  3. 现在,打开你的浏览器或 Postman,访问以下 URL:

如果本地测试成功,说明我们的代码和配置都是正确的!

确认这个基本流程成功跑通后,就可以进入下一步:在 Go 函数中集成 Supabase 或Neon。


上线—部署到Netlify

最后一步,我们将它部署到互联网上。

  1. 初始化 Git 并推送到 GitHub

    bash
    git init
    git add .
    git commit -m "feat: initial commit with hello function"
    • 在 GitHub 上创建一个新的空仓库(例如 my-netlify-go-app)。
    • 按照 GitHub 提供的指令,将你的本地仓库与远程仓库关联并推送:
      bash
      git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git
      git branch -M main
      git push -u origin main
  2. 在 Netlify 上创建站点

    • 登录 Netlify,点击 “Add new site” -> “Import an existing project”。
    • 选择 GitHub,并授权 Netlify 访问你的仓库。
    • 从列表中选择你刚刚创建的 my-netlify-go-app 仓库。
    • Netlify 会自动读取你的 netlify.toml 文件,所以构建设置通常无需修改。直接点击 “Deploy site”。
  3. 等待部署完成: Netlify 会开始拉取代码、执行 go build 命令并部署。你可以在 “Deploys” 页面看到部署日志。

  4. 验证线上接口

    • 部署成功后,Netlify 会给你一个独一无二的网址,例如 https://some-random-name.netlify.app
    • 你的 Go 云函数 API 就可以通过 https://your-site-name.netlify.app/.netlify/functions/hello 访问了。
    • 像本地测试一样,在浏览器中访问它,看看是否能得到正确的结果。

Go云函数与Neon数据库集成

目标: 沿用 my-netlify-go-app 项目,在其中添加一个名为 get-products 的新 API 接口。这个接口将连接到您的 Neon 数据库,查询一个产品列表,并以 JSON 格式返回。

创建一个新的 API 端点:.../.netlify/functions/get-products。当访问它时,它会:

  1. 安全地从环境变量中读取 Neon 数据库的连接信息。
  2. 连接到 Neon 数据库。
  3. 从一个 products 表中查询所有数据。
  4. 将查询结果以 JSON 数组的形式返回。

准备Neon数据库

根据Netlify中云函数部署的 区域(一般都是us-east-2 (Ohio)),在Neon中创建一个处于同一区域的数据库。(不在同一个区域会导致云函数执行时间变长)

查看Netlify项目云函数部署区域的地址:https://app.netlify.com/projects/my-netlify-app/configuration/deploys#functions-region ,注意将 my-netlify-app 替换为自己项目的名字


接下来还需要在数据库中创建一些示例数据,以便我们的函数有内容可以查询。

  1. 登录到您的 Neon 控制台

  2. 选择您的项目,然后导航到 SQL Editor (SQL 编辑器)。

  3. 复制以下 SQL 代码,粘贴到编辑器中,然后点击 “Run” 执行。

    sql
    -- 创建一个名为 'products' 的表
    CREATE TABLE products (
        id SERIAL PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        price NUMERIC(10, 2) NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
    );
    
    -- 插入一些示例数据
    INSERT INTO products (name, price) VALUES
    ('Go Gopher Plush', 19.99),
    ('Netlify Sticker Pack', 4.99),
    ('Neon Serverless Mug', 12.50);

    执行成功后,您的数据库中就有了一个包含 3 条记录的 products 表。


在 Netlify 中设置环境变量

最关键的安全步骤: 绝不能将数据库连接密码硬编码在代码里,而是通过Netlify的环境变量来管理

  1. 在您的 Neon 项目控制台的 Dashboard 页面,找到 Connection Details (连接详情) 部分。
  2. 从连接信息中,复制 Postgres connection string (它看起来像 postgres://user:password@host/dbname)。
  3. 打开您的 Netlify 站点后台 (app.netlify.com)。
  4. 导航到 Projects > 指定的项目(如:my-netlify-go-app) > Project configuration > Environment variables
  5. 点击 “Add a variable”,选择 “Add a single variable”。
  6. 键 (Key) 输入:NEON_DATABASE_URL
  7. 值 (Value) 粘贴:您刚刚从 Neon 复制的完整连接字符串。
  8. 点击 “Create variable” 保存。

现在 Go 云函数在部署后就可以通过 os.Getenv("NEON_DATABASE_URL") 来安全地访问这个值了。


更新 Go 项目

  1. 安装 Go Postgres 驱动: 我们需要 pgx 库,它是目前最高性能、功能最丰富的 Go Postgres 驱动。在您的项目根目录 my-netlify-go-app/ 中运行:

    bash
    go get github.com/jackc/pgx/v5
  2. 创建新函数的目录

    bash
    mkdir -p netlify/functions/get-products
  3. 编写 Go 函数代码 (get-products/main.go): 在 netlify/functions/get-products/ 目录下创建 main.go 文件,并粘贴以下代码:

    go
    // netlify/functions/get-products/main.go
    package main
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"os"
    
    	"github.com/aws/aws-lambda-go/events"
    	"github.com/aws/aws-lambda-go/lambda"
    	"github.com/jackc/pgx/v5"
    )
    
    // Product 结构体,用于映射数据库中的 products 表
    type Product struct {
    	ID    int     `json:"id"`
    	Name  string  `json:"name"`
    	Price float64 `json:"price"`
    }
    
    func handler(ctx context.Context) (*events.APIGatewayProxyResponse, error) {
    	// 1. 从环境变量中安全地读取数据库连接字符串
    	connString := os.Getenv("NEON_DATABASE_URL")
    	if connString == "" {
    		return &events.APIGatewayProxyResponse{StatusCode: 500, Body: "NEON_DATABASE_URL not set"}, nil
    	}
    
    	// 2. 连接到 Neon 数据库
    	conn, err := pgx.Connect(ctx, connString)
    	if err != nil {
    		return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Unable to connect to database: %v", err)}, nil
    	}
    	defer conn.Close(ctx) // 确保函数结束时关闭连接
    
    	// 3. 执行 SQL 查询
    	rows, err := conn.Query(ctx, "SELECT id, name, price FROM products ORDER BY id;")
    	if err != nil {
    		return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Query failed: %v", err)}, nil
    	}
    	defer rows.Close() // 确保处理完结果集后关闭它
    
    	// 4. 处理查询结果
    	var products []Product
    	for rows.Next() {
    		var p Product
    		err := rows.Scan(&p.ID, &p.Name, &p.Price)
    		if err != nil {
    			return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Failed to scan row: %v", err)}, nil
    		}
    		products = append(products, p)
    	}
    
    	// 5. 将结果序列化为 JSON
    	responseBody, err := json.Marshal(products)
    	if err != nil {
    		return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Failed to marshal response: %v", err)}, nil
    	}
    
    	// 6. 返回成功的响应
    	return &events.APIGatewayProxyResponse{
    		StatusCode: 200,
    		Headers:    map[string]string{"Content-Type": "application/json"},
    		Body:       string(responseBody),
    	}, nil
    }
    
    func main() {
    	lambda.Start(handler)
    }

更新netlify配置

之前的 netlify.toml 文件只构建了一个 hello 函数。现在我们需要一个能构建所有函数的通用命令。

打开 netlify.toml 文件,将其中的 command 修改为以下内容:

toml
# netlify.toml

[build]
  # 在项目根目录执行,为每个函数目录构建一个 main 可执行文件。
  command = "for dir in netlify/functions/*; do function_name=$(basename $dir); go build -o \"netlify/functions/${function_name}/main\" \"./netlify/functions/${function_name}/\"; done"

  functions = "netlify/functions/"

# ... 其他配置 ...

这个命令会自动找到 netlify/functions 下的所有子目录(helloget-products),并为它们分别执行 go build。这是一个可扩展的、一劳永逸的配置。


再次本地测试

为了在本地测试,netlify dev 命令需要能访问到数据库连接字符串。

  1. 在项目根目录 my-netlify-go-app/ 创建一个名为 .env 的文件。
  2. .env 文件中添加以下内容,将值替换为您自己的连接字符串
    text
    NEON_DATABASE_URL="postgres://user:password@host/dbname"
  3. 重要:将 .env 文件添加到 .gitignore 中,防止将密码提交到 Git 仓库!
    text
    # .gitignore
    .env
  4. 现在,运行本地开发服务器:
    bash
    netlify dev
  5. 访问新接口的本地 URL:http://localhost:8888/.netlify/functions/get-products
  6. 您应该能看到浏览器返回了您在数据库中创建的三个产品的 JSON 数据。

本地测试成功后,就可以部署上线了。


部署到线上

  1. 将所有更改提交到 Git:
    bash
    git add .
    git commit -m "feat: add get-products function connecting to Neon"
    git push
  2. Netlify 会自动检测到您的代码推送,并使用 netlify.toml 中新的构建命令来部署您的两个函数。
  3. 部署成功后,访问您的线上 URL https://your-site-name.netlify.app/.netlify/functions/get-products 进行最终验证。

保活Neon的定时函数

目标:创建一个名为 keep-alive 的 Netlify 云函数,它没有公开的 API 接口,而是由 Netlify 的服务器根据预设的时间表自动在后台触发。它的唯一目的就是定期向 Neon 数据库发送一个极轻量的查询,防止数据库因闲置而进入休眠状态。


创建新函数的目录

my-netlify-go-app 项目中,为新函数创建一个目录。

  1. 打开终端,进入项目根目录。
  2. 执行命令:
    bash
    mkdir -p netlify/functions/keep-alive

项目结构现在看起来像这样:

text
/my-netlify-go-app
|-- ... (go.mod, netlify.toml, etc.)
|-- netlify/
    |-- functions/
        |-- hello/
        |-- get-products/
        |-- keep-alive/  <-- 新创建的目录

编写保活函数代码

这个函数的代码与 get-products 非常相似,但更简单,因为它不需要处理 HTTP 请求和响应。

  1. netlify/functions/keep-alive/ 目录下创建一个 main.go 文件。

  2. 将以下代码复制并粘贴到 main.go 文件中:

    go
    // netlify/functions/keep-alive/main.go
    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"os"
    
    	"github.com/aws/aws-lambda-go/lambda"
    	"github.com/jackc/pgx/v5"
    )
    
    // handleRequest是函数的核心逻辑。
    // 注意:它的签名比API函数更简单,因为它不处理HTTP事件。
    func handleRequest(ctx context.Context) error {
    	log.Println("Running keep-alive function to ping Neon database...")
    
    	// 从环境变量安全地读取数据库连接字符串
    	connString := os.Getenv("NEON_DATABASE_URL")
    	if connString == "" {
    		log.Println("Error: NEON_DATABASE_URL environment variable not set.")
    		return fmt.Errorf("NEON_DATABASE_URL not set")
    	}
    
    	// 连接数据库
    	conn, err := pgx.Connect(ctx, connString)
    	if err != nil {
    		log.Printf("Error: Unable to connect to database: %v\n", err)
    		return err
    	}
    	defer conn.Close(ctx)
    
    	// 执行一个最轻量的查询,目的仅仅是“触摸”一下数据库
    	var result int
    	err = conn.QueryRow(ctx, "SELECT 1;").Scan(&result)
    	if err != nil {
    		log.Printf("Error: Keep-alive query failed: %v\n", err)
    		return err
    	}
    
    	// 成功的日志对于验证至关重要
    	log.Printf("Keep-alive ping to Neon successful. Query result: %d", result)
    	return nil
    }
    
    func main() {
    	// 启动 Lambda 处理程序
    	lambda.Start(handleRequest)
    }

代码要点

  • 日志 (Logging):我们使用了 log 包来打印信息。因为这个函数没有 API 响应,查看日志是验证它是否成功运行的唯一方式
  • 轻量查询SELECT 1; 是最理想的保活查询。它几乎不消耗任何数据库资源,但足以让数据库认为自己是“活跃”的。
  • 简单的 Handler:函数签名是 func(context.Context) error,因为它由调度器触发,而不是 HTTP 请求。

配置定时调度

这是将普通函数变为“定时函数”的关键一步。

  1. 打开项目根目录下的 netlify.toml 文件。

  2. 不需要修改 [build] 部分的 command,之前设置的 for 循环命令会自动构建这个新的 keep-alive 函数。

  3. 在文件的末尾,添加以下新的配置块:

    toml
    # netlify.toml
    
    # 现有的 [build] 配置 ...
    
    [functions."keep-alive"]
    # 调度计划:
    # - Cron 表达式: "*/5 1-15 * * *"
    # - UTC 时间: 每天的 01:00 到 15:59 之间,每5分钟执行一次。
    # - 对应东八区时间 (UTC+8): 每天的 09:00 到 23:59 之间,每5分钟执行一次。
    schedule = "*/5 1-15 * * *"

配置解释

  • [functions."keep-alive"]: 这个特殊的 TOML 语法表示我们正在为名为 keep-alive 的函数进行特定配置。
  • Netlify 的定时函数调度器(以及几乎所有的国际通用 Cron 系统)都使用 UTC (协调世界时) 来解析 Cron 表达式
    • 需要的时间 (东八区, UTC+8): 每天的 09:00 到 23:59 转换到 UTC 时间:
      • 开始时间:东八区 09:00 = 09:00 - 8小时 = UTC 01:00
      • 结束时间:东八区 23:59 = 23:59 - 8小时 = UTC 15:59
    • 得到的 Cron 表达式是: */5 1-15 * * *

部署与验证

  1. 部署: 将所有更改提交到您的 Git 仓库:

    bash
    git add .
    git commit -m "feat: add keep-alive scheduled function for Neon"
    git push

    Netlify 将自动开始部署。

  2. 验证 (最重要的一步): 由于这个函数没有 URL 可以访问,我们需要通过查看日志来验证它是否在按计划运行。

    • 部署成功后,等待几分钟(最多等 4-5 分钟,让第一个调度周期命中)。
    • 登录到您的 Netlify 站点后台。
    • 点击顶部的 Functions 标签页。
    • 在函数列表中,点击您新创建的 keep-alive 函数。
    • 在函数详情页面,查看下方的 Function log (函数日志) 部分。

    您应该能看到类似下面的日志条目,并且每隔大约 4 分钟就会出现一条新的

    text
    ... INFO: Running keep-alive function to ping Neon database...
    ... INFO: Keep-alive ping to Neon successful. Query result: 1

一旦这个 keep-alive 定时函数开始稳定运行,就可以避免 Neon 数据库5分钟不活动后进入休眠状态


QtuDR6BwjYtLksTJ


与Supabase数据库集成

目标: 在 my-netlify-go-app 项目中,添加一个名为 get-users 的新 API 接口。我们将使用 Supabase 作为后端数据库,并通过原生的 pgx 驱动执行 SQL 查询,而不是使用 Supabase 封装的客户端库。

这个新的 API 端点:.../.netlify/functions/get-users 将实现以下功能:

  1. 安全地从环境变量中读取 Supabase 数据库的连接信息。
  2. 连接到 Supabase 的 Postgres 数据库。
  3. 从一个 users 表中查询所有数据。
  4. 将查询结果以 JSON 数组的形式返回。

方法说明: 我们将直接使用 Go 的原生 pgx 驱动来执行 SQL 查询,而不是使用 Supabase 官方的 Go 客户端库。这种方法更底层,通用性更强,与前面 Neon 的示例完全一致,能让您更好地理解底层原理。


准备 Supabase 数据库

首先,我们需要在 Supabase 中创建一个项目,并准备好我们的数据表和示例数据。

  1. 登录到您的 Supabase Dashboard 并创建一个新项目。由于是与Netlify交互,所以 区域还是选择 us-east-2 (Ohio)

  2. 项目创建后,从左侧导航栏进入 SQL Editor

  3. 点击 + New query,然后将以下 SQL 代码粘贴到编辑器中并点击 RUN

    sql
    -- 创建一个名为 'users' 的表
    -- 注意: Supabase 默认开启了 Row Level Security (RLS),
    -- 为了让我们的云函数能直接访问,我们暂时禁用它。
    -- 在生产环境中,您应该创建更精细的 RLS 策略。
    CREATE TABLE users (
        id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
        username TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        created_at TIMESTAMPTZ DEFAULT NOW()
    );
    
    -- 禁用新表的 Row Level Security,以便云函数可以访问
    ALTER TABLE users DISABLE ROW LEVEL SECURITY;
    
    -- 插入一些示例数据
    INSERT INTO users (username, email) VALUES
    ('gopher', '[email protected]'),
    ('netlify-user', '[email protected]'),
    ('supabase-fan', '[email protected]');

    执行成功后,您的 Supabase 数据库中就有了一个包含 3 条记录的 users 表。


再次设置环境变量

与 Neon 类似,我们需要将 Supabase 的数据库连接字符串安全地存储在 Netlify 的环境变量中。

  1. 在 Supabase 项目仪表盘中,进入对应的项目, 点击顶部的 connect
  2. Connection string 部分,找到并复制 Transaction pooler 处对应的 的连接字符串。它看起来像 postgresql://postgres.jdgfpjgejgwnpexcogts:[YOUR-PASSWORD]@aws-0-us-east-2.pooler.supabase.com:6543/postgres
  3. 打开您的 Netlify 站点后台 (app.netlify.com)。
  4. 导航到 Projects > 指定的项目(如:my-netlify-go-app) > Project configuration > Environment variables
  5. 点击 “Add a variable”,选择 “Add a single variable”。
  6. 键 (Key) 输入:SUPABASE_DATABASE_URL
  7. 值 (Value) 粘贴:您刚刚从 Supabase 复制的完整连接字符串。
  • [YOUR-PASSWORD]替换为自己的密码
  • 并在连接末尾添加 ?default_query_exec_mode=simple_protocol
  1. 点击 “Create variable” 保存。

现在 Go 云函数就可以在运行时通过 os.Getenv("SUPABASE_DATABASE_URL") 来安全地访问这个值了。


编写Go云函数

接下来,我们为新的 get-users 接口创建代码。

  1. 检查 Go Postgres 驱动: 由于我们前面的 Neon 示例已经安装了 pgx/v5 库,这里无需重复安装。如果您是直接跳到此步骤,请确保在项目根目录运行 go get github.com/jackc/pgx/v5

  2. 创建新函数的目录

    bash
    mkdir -p netlify/functions/get-users
  3. 编写 Go 函数代码 (get-users/main.go): 在 netlify/functions/get-users/ 目录下创建 main.go 文件,并粘贴以下代码:

    go
    // netlify/functions/get-users/main.go
    package main
    
    import (
        "context"
        "encoding/json"
        "fmt"
        "github.com/aws/aws-lambda-go/events"
        "github.com/aws/aws-lambda-go/lambda"
        "github.com/jackc/pgx/v5"
        "os"
        "time" // 引入 time 包
    )
    
    // User 结构体保持不变
    type User struct {
        ID       int64  `json:"id"`
        Username string `json:"username"`
        Email    string `json:"email"`
    }
    
    // 1. 更新 API 响应结构体,以包含两个独立的计时
    type APIResponse struct {
        Data           []User `json:"data"`
        ConnectionTime string `json:"connection_time_ms"`
        QueryTime      string `json:"query_time_ms"`
    }
    
    func handler(ctx context.Context) (*events.APIGatewayProxyResponse, error) {
        // 从环境变量中安全地读取数据库连接字符串
        connString := os.Getenv("SUPABASE_DATABASE_URL")
        if connString == "" {
            return &events.APIGatewayProxyResponse{StatusCode: 500, Body: "SUPABASE_DATABASE_URL not set"}, nil
        }
    
        // 2. 测量数据库连接耗时
        connStartTime := time.Now()
        conn, err := pgx.Connect(ctx, connString)
        connDuration := time.Since(connStartTime) // 计算连接耗时
    
        if err != nil {
            // 即使连接失败,也可以记录耗时(如果需要的话)
            body := fmt.Sprintf("Unable to connect to database (took %s): %v", connDuration, err)
            return &events.APIGatewayProxyResponse{StatusCode: 500, Body: body}, nil
        }
        defer conn.Close(ctx)
    
        // 3. 测量 SQL 查询执行耗时
        queryStartTime := time.Now()
        rows, err := conn.Query(ctx, "SELECT id, username, email FROM users ORDER BY id;")
        queryDuration := time.Since(queryStartTime) // 计算查询耗时
    
        if err != nil {
            body := fmt.Sprintf("Query failed (took %s): %v", queryDuration, err)
            return &events.APIGatewayProxyResponse{StatusCode: 500, Body: body}, nil
        }
        defer rows.Close()
    
        // 处理查询结果
        var users []User
        for rows.Next() {
            var u User
            if err := rows.Scan(&u.ID, &u.Username, &u.Email); err != nil {
                return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Failed to scan row: %v", err)}, nil
            }
            users = append(users, u)
        }
        if err := rows.Err(); err != nil {
            return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Error during rows iteration: %v", err)}, nil
        }
    
        // 4. 构建包含所有信息的最终响应对象
        responseObject := APIResponse{
            Data:           users,
            ConnectionTime: fmt.Sprintf("%.2fms", float64(connDuration.Microseconds())/1000.0),
            QueryTime:      fmt.Sprintf("%.2fms", float64(queryDuration.Microseconds())/1000.0),
        }
    
        // 将最终对象序列化为 JSON
        responseBody, err := json.Marshal(responseObject)
        if err != nil {
            return &events.APIGatewayProxyResponse{StatusCode: 500, Body: fmt.Sprintf("Failed to marshal response: %v", err)}, nil
        }
    
        // 返回成功的响应
        return &events.APIGatewayProxyResponse{
            StatusCode: 200,
            Headers:    map[string]string{"Content-Type": "application/json"},
            Body:       string(responseBody),
        }, nil
    }
    
    func main() {
        lambda.Start(handler)
    }

再次更新构建配置

由于我们之前在 netlify.toml 中设置了一个通用的构建命令,它会自动发现并构建所有在 netlify/functions/ 目录下的函数。

因此,不再需要对 netlify.toml 文件做任何修改


再次本地测试

为了在本地测试连接 Supabase,我们需要将它的连接字符串添加到本地的 .env 文件中。

  1. 打开项目根目录下的 .env 文件。
  2. 在文件中添加新的一行,将值替换为您自己的 Supabase 连接字符串
    text
    # .env
    NEON_DATABASE_URL="postgres://..." # 已有的
    SUPABASE_DATABASE_URL="postgresql://postgres.jdgfpjgejgwnpexcogts:[YOUR-PASSWORD]@aws-0-us-east-2.pooler.supabase.com:6543/postgres"
  3. 如果之前没有创建 .gitignore 文件,请确保创建一个并将 .env 添加进去,以防泄露密码。
  4. 运行本地开发服务器:
    bash
    netlify dev
  5. 在浏览器或 Postman 中访问新接口的本地 URL:http://localhost:8888/.netlify/functions/get-users

重新部署上线

最后一步,将我们的新功能部署到 Netlify。

  1. 将所有更改提交到 Git:
    bash
    git add .
    git commit -m "feat: add get-users function connecting to Supabase"
    git push
  2. Netlify 会自动检测到代码推送,并开始新的部署。
  3. 部署成功后,访问线上 URL https://your-site-name.netlify.app/.netlify/functions/get-users 进行最终验证