本文档建立在 Vol.1、Vol.2 与 Vol.3 之上,还未完成前序内容的请先完成Vol.1、Vol.2与Vol.3 前序教程。通过之前的实践,我们已经完成了使用 Gin 框架并结合 Nginx 代理搭建网站的基础架构。
在真实开发场景中,后端开发需要编写很多个API,但人力终究是有限的,不可能所有的 API 都在后端手写完成;特别是在调用 LLM(大型语言模型)服务时,我们也不可能在本地手搓一个大模型。所以,我们需要让后端自己去调用外部的 API 。本文档以调用 Gemini API 为例,实战搭建一个 AI 聊天网站,后端语言为 Go,前端为 HTML。本文会有概念与实操,按文档进行以获得最佳效果。
模块一:核心概念
1. 什么是调用外部 API?
之前我们写的 API,是用户(浏览器)向我们的服务器发请求,由我们的服务器接收请求并返回结果 。而“调用外部 API”,则是将我们的服务器(Go 后端)成为另一个客户端,从而去向第三方的服务器发送请求 。
在本例中,为什么不让前端直接去调用 AI 大模型的API?
- 安全风险:调用外部 AI 通常需要一个极其机密的密码(API Key) 。如果让前端去调用,这个密码就会明文暴露在前端代码里,任何人只要按 F12 打开 DevTools 就能偷走密钥,去倒刷你的额度,直到彻底耗尽 。
- 无视跨域:同源策略是浏览器的单方面限制。服务器和服务器之间互相发请求,是没有任何跨域限制的 !所以我们的 Go 后端去请求外部服务,不需要再去配置复杂的跨域中间件。
2. 什么是 OpenAI 格式?
现在市面上有无数的大模型(如 ChatGPT、DeepSeek 等)。为了不让程序员每接入一个新模型就要重写一遍代码,业界逐渐形成了一个默认的“行业标准”——OpenAI API 格式 。
它的核心交互格式非常直白,本质上就是一段 JSON,包含模型名字(model)和聊天记录(messages):
{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "你好,请自我介绍"}
]
}
- role(角色):
user代表你(用户),assistant代表 AI 。 - content(内容):具体说了什么话 。
但是值得注意的是,谷歌提供的API格式是与OpenAI API不同的,最近才开放兼容OpenAI API。故本例依旧使用OpenAI API标准格式。
3. Cloudflare:全球网络的中转枢纽
因为网络防火墙和跨国调用的延迟问题,国内服务器直接请求国外的 AI 接口经常会遇到 i/o timeout 。为了能顺利完成本教程,我已经配置好了基于 Cloudflare 的代理,你可以通过直连 https://gobackapi.xyz 转发请求 。
Cloudflare 是什么?
它是全球最大的网络安全和 CDN(内容分发网络)服务商,并免费提供 DNS 服务。你可以把它理解为一个遍布全球的高速中转站。
- DNS:将域名(如
imaginary.com)转化为 IP 地址(如111.11.111.1)的解析系统。 - CDN 的作用:客户端与服务器的物理距离越远,连接越慢甚至超时,导致极差的用户体验 。部署在全球数据中心的 CDN 节点会缓存静态资源,让用户直接从网络距离最近的节点读取数据,大幅加快加载速度并减少服务器压力。不过需要注意的是,大模型对话属于动态处理,无法通过 CDN 缓存,最终仍需经过 Cloudflare 路由到模型原生服务器 。
模块二:实战模拟
我们将 Vol.1 中的前后端交互实例 2进行升级,把后端服务从原生逻辑改进为接入 Gemini 模型 。
当前的系统架构流转应当是:
前端(浏览器) --> Nginx --> Go 服务器 --> Cloudflare 代理 --> 谷歌服务器
Step 1: 建立目录结构
go-demo1/
├── main.go
└── index.html
Step 2: 编写后端代码main.go
将 Go 服务器转化为请求第三方接口的客户端。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/gin-gonic/gin"
)
// FrontendRequest 定义前端浏览器发给我们的格式
// 比如前端传过来 {"message": "你好"}
type FrontendRequest struct {
Message string `json:"message"`
}
// OpenAIMessage 定义单条对话的格式
type OpenAIMessage struct {
Role string `json:"role"` // 角色:你是 "user",AI 是 "assistant"
Content string `json:"content"` // 具体的文字内容
}
// OpenAIRequest 定义我们要发给外部 AI (Gemini) 的整体格式
// 必须严格遵守 OpenAI 的标准规范
type OpenAIRequest struct {
Model string `json:"model"` // 告诉 AI 我们要用哪个模型
Messages []OpenAIMessage `json:"messages"` // 把聊天记录打包传过去
}
// OpenAIResponse 定义外部 AI 返回给我们的复杂格式
// 我们只需要从这个复杂的结构中找到我们要的回复内容
type OpenAIResponse struct {
Choices []struct {
Message OpenAIMessage `json:"message"`
} `json:"choices"`
}
func main() {
// 创建一个默认的 Gin 路由引擎 (相当于启动我们的 Web 服务器)
r := gin.Default()
// 开放一个 POST 接口,前端浏览器请求 /api/chat 时,就会执行下面大括号里的代码
r.POST("/api/chat", func(c *gin.Context) {
// 1. 解析前端发送的数据
var req FrontendRequest
// 尝试把前端发来的 JSON 数据解析到我们的结构体里
// 如果前端发来的格式不对,就报错并返回 400 状态码
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "参数解析失败"})
return
}
fmt.Println("收到前端提问:", req.Message)
// 2. 整理我们要请求第三方API的内容
aiReqData := OpenAIRequest{
// 使用最新、免费且速度极快的 flash 模型
Model: "gemini-3-flash-preview",
Messages: []OpenAIMessage{
// 把前端刚才说的话,以 user (用户) 的身份塞进去
{Role: "user", Content: req.Message},
},
}
// 把我们组装好的数据打包为 JSON
requestBody, _ := json.Marshal(aiReqData)
// 3. 向第三方API发送请求
// 必须使用 Google 官方的 OpenAI 兼容路径:/v1beta/openai/chat/completions
apiURL := "https://gobackapi.xyz/v1beta/openai/chat/completions"
// 准备一封信 (POST 请求),贴上地址 (apiURL),装入信件内容 (requestBody)
outReq, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(requestBody))
// 4. 在信封上写明注意事项 (设置 HTTP 请求头)
outReq.Header.Set("Content-Type", "application/json") // 告诉对方我们寄的是 JSON 数据
// 填入 Gemini API Key,注意保留 "Bearer " 前缀和空格!
// 这一步就是服务器代替前端,向外部服务器出示通行密码
outReq.Header.Set("Authorization", "Bearer YOUR_API_KEY_HERE")
// 5. 正式发送请求
client := &http.Client{}
resp, err := client.Do(outReq) // Do 方法会一直等,直到 AI 把话说完传回来
// 如果网络断开或者服务器死机,给前端报个 500 错误
if err != nil {
c.JSON(500, gin.H{"error": "连接 AI 接口失败"})
return
}
// 无论发生什么,在这个功能结束前,必须挂断和外部 API 的连线,防止内存泄漏
defer resp.Body.Close()
// 6. 解析第三方 API (AI) 返回的内容
bodyBytes, _ := io.ReadAll(resp.Body) // 读取全部原始数据
// 打印API完整内容,用于调试
fmt.Println("API 原始返回内容:", string(bodyBytes))
// 创建一个空盒子,准备装解析后的数据
var aiResp OpenAIResponse
// 把 JSON 格式的响应解压到我们的盒子 (OpenAIResponse 结构体) 里
json.Unmarshal(bodyBytes, &aiResp)
// 7. 提取出最核心的 AI 回复文本
replyText := "AI没有返回内容"
// 为了防止程序崩溃 (如果 AI 报错,Choices 可能是空的,直接去取就会报错),先检查一下长度
if len(aiResp.Choices) > 0 {
// 一层一层剥开结构体,拿到具体的文本
replyText = aiResp.Choices[0].Message.Content
}
fmt.Println("AI 返回结果:", replyText)
// 8. 把提取出来的文本转交给前端浏览器
c.JSON(200, gin.H{
"reply": replyText,
})
})
// 启动服务器,让它保持运行并监听 8080 端口
fmt.Println(">>> Gin 后端已启动!监听端口: 8080")
r.Run(":8080")
}
Step 3: 获取 API Key
这里对特定读者提供我自己的 API Key,请保护好Key,任何时候不要明文写在前端页面,也不要泄漏。点击下方按钮进行密码验证,即可提取专属 API Key 。
访问控制区
此 Key 已被加密,请输入邀请码解锁。
也可访问https://aistudio.google.com/api-keys获取属于你的API Key。
Step 4: 编写前端代码 index.html
这里的前端只负责向我们同源的 Nginx 发请求,完全不知道外部 API 的存在。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>第三方API交互示例</title>
<style>
/* 简单美化:让界面看起来清爽 */
body { font-family: 'PingFang SC', sans-serif; padding: 40px; background: #f5f5f5; line-height: 1.6; }
.container { max-width: 600px; margin: 0 auto; background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
input { padding: 10px; width: 70%; border: 1px solid #ddd; border-radius: 4px; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.result-box { margin-top: 20px; padding: 15px; border-radius: 6px; font-size: 13px; background: #f8f9fa; border-left: 5px solid #343a40;}
.success-text { color: #333; }
.error-text { color: #cf1322; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h2>调用 AI 示例</h2>
<div style="margin-top: 20px;">
<input type="text" id="chatInput" placeholder="今天在想些什么?">
<button onclick="sendToAI()">发送</button>
</div>
<div class="result-box">
<p id="chatResult" class="success-text">你好,今天心情怎么样?</p>
</div>
</div>
<script>
// 云服务器公网 IP
const SERVER_IP = "47.101.141.52";
async function sendToAI() {
const msg = document.getElementById('chatInput').value;
if (!msg.trim()) {
alert("请输入内容后再发送!");
return;
}
const resultBox = document.getElementById('chatResult');
resultBox.innerText = " 正在思考中...";
resultBox.className = "success-text"; // 重置为正常颜色
// 请求我们同源的 Nginx 代理地址 (888 端口)
const url = `http://${SERVER_IP}:888/api/chat`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg }),
});
const data = await response.json();
// 处理后端的 400/500 等 HTTP 错误
if (!response.ok) {
resultBox.innerText = `请求报错:${data.error || '未知错误'}`;
resultBox.className = "error-text";
return;
}
// 处理截图中的“AI没有返回内容”逻辑
if (data.reply === "AI没有返回内容" || data.reply === "") {
resultBox.innerText = "出了点问题,没有返回任何内容 (可能是 API Key 错误或额度耗尽)。";
resultBox.className = "error-text";
} else {
// 正常返回 AI 的回答
resultBox.innerText = data.reply;
resultBox.className = "success-text";
}
} catch (error) {
// 处理网络断开或 Nginx 没开等极狐错误
resultBox.innerText = "前端网络请求失败,请检查 Nginx 或网络连接!";
resultBox.className = "error-text";
console.error(error);
}
}
</script>
</body>
</html>
Step 5: 部署与访问
启动 Go 服务并确保 Nginx 正常运行,访问 http://47.101.141.52:888/。至此,使用 Nginx 代理部署到服务器上、属于你的第一个调用第三方 API 的网站就正式上线了!
END