我觉得调用别人的接口来查看效果是一个很有趣的开始,在写自己的接口之前我想首先来探索和这些组件的连接配合方法,毕竟绝大多数程序员都是单线程的API调用工程师(doge)。还有一点则是因为我已经有了Python基础,写了不少接口。只要Go也能够如Python般让我操作这些组件,我能更快地将我的想法落地,或者进行更多的尝试。
[一、 API接口](#一、 API接口)
[1. 保存读取数据](#1. 保存读取数据)
[2. 调用LLM得到回复](#2. 调用LLM得到回复)
[3. 轮询获得AI绘图的图片](#3. 轮询获得AI绘图的图片)
[二、 与Redis](#二、 与Redis)
[三、 与DB(各种各样的DB,这里以pgsql和mongodb为例)](#三、 与DB(各种各样的DB,这里以pgsql和mongodb为例))
[1. pgsql](#1. pgsql)
[2. mongodb](#2. mongodb)
一、 API接口
让我们先通过各种各样的LLM接口来尝试它的使用,获得LLM回复、保存读取数据、轮询一个接口保存绘制的图片......
1. 保存读取数据
很不幸地一点是,Go中保存读取数据并不像Python般方便,尤其是在读取中文的时候会出现意想不到的乱码,这是因utf-8编码产生的影响。在开始接下来的任何一个任务之前,我们首先应该解决这个问题。如果大家试过将文本保存到txt文件就会发现Go对于中文的读取有极大的不便,为了一劳永逸地解决再读取过程的不确定性,通过二进制文件保存是一个非常不错的选择。
Go
package main
import (
"encoding/gob"
"encoding/json"
"fmt"
"log"
"os"
)
type Resume struct {
Name string
Age int
Address string
Language string
}
func main() {
// 创建一些数据
resume := Resume{
Name: "张三",
Age: 30,
Address: "唐山市",
Language: "中文",
}
// 创建一个文件来保存数据(.gob 格式)
fileGob, err := os.Create("resume_data.gob")
if err != nil {
log.Fatal("Error creating .gob file:", err)
}
defer fileGob.Close()
// 使用 gob 编码器将数据序列化为二进制并写入文件
encoder := gob.NewEncoder(fileGob)
err = encoder.Encode(resume)
if err != nil {
log.Fatal("Error encoding .gob data:", err)
}
fmt.Println("数据已保存为二进制格式 (.gob)")
// 创建一个文件来保存数据(.json 格式)
fileJson, err := os.Create("resume_data.json")
if err != nil {
log.Fatal("Error creating .json file:", err)
}
defer fileJson.Close()
// 使用 json 编码器将数据序列化为 JSON 格式并写入文件
encoderJson := json.NewEncoder(fileJson)
err = encoderJson.Encode(resume)
if err != nil {
log.Fatal("Error encoding .json data:", err)
}
fmt.Println("数据已保存为 JSON 格式 (.json)")
}
我们这里将一个结构体保存到二进制文件中,包含特殊字符。之后我们再读取出来。那如果我们想让数据可视化怎么办呢,总不能一直看二进制数据吧?这个问题很好解决,我们保存的时候一式两份就可以了(json格式似乎同样稳定,但是txt、docx、excel等格式都可能出现中文或者特殊字符加载问题),二进制文件用于后续程序的读取保证数据传递的稳定性,这样我们就即完成了数据的稳定传输和可视化。
Go
package main
import (
"encoding/gob"
"encoding/json"
"fmt"
"log"
"os"
)
type Resume struct {
Name string
Age int
Address string
Language string
}
func main() {
// 打开已保存的二进制文件
fileGob, err := os.Open("resume_data.gob")
if err != nil {
log.Fatal("Error opening .gob file:", err)
}
defer fileGob.Close()
// 创建解码器从二进制文件读取数据
var resumeGob Resume
decoderGob := gob.NewDecoder(fileGob)
err = decoderGob.Decode(&resumeGob)
if err != nil {
log.Fatal("Error decoding .gob data:", err)
}
// 打印读取的二进制数据
fmt.Println("从二进制文件读取的数据:")
fmt.Printf("Gob 数据: %+v\n", resumeGob)
// 打开已保存的 JSON 文件
fileJson, err := os.Open("resume_data.json")
if err != nil {
log.Fatal("Error opening .json file:", err)
}
defer fileJson.Close()
// 创建解码器从 JSON 文件读取数据
var resumeJson Resume
decoderJson := json.NewDecoder(fileJson)
err = decoderJson.Decode(&resumeJson)
if err != nil {
log.Fatal("Error decoding .json data:", err)
}
// 打印读取的 JSON 数据
fmt.Println("从 JSON 文件读取的数据:")
fmt.Printf("JSON 数据: %+v\n", resumeJson)
// 比较两者的输出
if resumeGob == resumeJson {
fmt.Println("\n从 .gob 文件和 .json 文件读取的数据相同")
} else {
fmt.Println("\n从 .gob 文件和 .json 文件读取的数据不同")
}
}

2. 调用LLM得到回复
我们依然使用IPPO网站:https://ppio.com/user/register?invited_by=HBCTMT 中的模型来测试这两个方面的调用。Go中实现流式调用有着意想不到的问题,我们不能简单地通过json解码来获取响应的数据,因为流式传输的不仅内容是被拆开的,json格式也是同样被拆开的。在Python中我们可以调用from fastapi.responses import StreamingResponse来包裹流式响应,让它呈现比较好的结构。但是在Go中就需要我们首先通过line将结果读出,自行实现算法。在这里我们就简单展示,将来再探索这部分的内容。
(1)流式调用
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
// 请求结构体
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Stream bool `json:"stream"`
}
func main() {
baseURL := "https://api.ppinfra.com/openai"
apiKey := "" // 替换为你的 API Key
model := "deepseek/deepseek-r1-0528-qwen3-8b"
// 设置请求体
chatRequest := ChatRequest{
Model: model,
Messages: []Message{
{
Role: "user",
Content: "Hi there!",
},
},
Stream: true, // 设置为 true 以启用流式输出
}
// 将请求结构体编码成 JSON
reqBody, err := json.Marshal(chatRequest)
if err != nil {
log.Fatalf("Error marshalling request body: %v", err)
}
// 创建 HTTP 请求
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequest("POST", baseURL+"/v1/chat/completions", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error creating request: %v", err)
}
// 设置请求头
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
// 发送请求
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error sending request: %v", err)
}
defer resp.Body.Close()
// 逐步读取流数据
decoder := json.NewDecoder(resp.Body)
// 临时存储部分内容
var partialContent string
for {
// 解析一个数据块
var chunk interface{} // 不指定类型,接收任意类型的内容
// 读取一个完整的数据块,检查流中的数据
if err := decoder.Decode(&chunk); err != nil {
if err.Error() == "EOF" {
// 如果到达流的末尾(EOF),则退出
time.Sleep(1)
break
}
log.Fatalf("Error decoding response chunk: %v", err)
}
// 打印每次接收到的 chunk 内容
fmt.Printf("Received chunk: %v\n", chunk)
// 如果接收到 [DONE],表示流结束
if chunk == "[DONE]" {
log.Println("Stream finished with [DONE] message.")
break
}
// 直接将接收到的 chunk 转换为字符串并打印
// 如果是 string 类型的数据块,则直接输出内容
switch v := chunk.(type) {
case string:
fmt.Print(v) // 输出接收到的文本内容
partialContent += v
default:
fmt.Printf("Received non-text chunk: %v\n", v)
}
}
// 打印流结束后的内容
fmt.Println("\nStream finished. Full response:", partialContent)
// 将内容保存到指定的 JSON 文件
err = saveToJSONFile("output.json", partialContent)
if err != nil {
log.Fatalf("Error saving to file: %v", err)
}
}
// 将内容保存到 JSON 文件
func saveToJSONFile(filename, content string) error {
// 创建文件
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error creating file: %v", err)
}
defer file.Close()
// 将内容作为 JSON 保存
data := map[string]string{"response": content}
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ") // 设置格式化输出
err = encoder.Encode(data)
if err != nil {
return fmt.Errorf("error encoding JSON to file: %v", err)
}
fmt.Println("Response saved to", filename)
return nil
}
一般调用的话只需要将stream改为false即可。或者使用更加直白的方法:
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
// 请求结构体
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Stream bool `json:"stream"`
}
type ChatResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []struct {
Index int `json:"index"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
}
func main() {
baseURL := "https://api.ppinfra.com/openai"
apiKey := "" // 替换为你的 API Key
model := "deepseek/deepseek-r1-0528-qwen3-8b"
// 设置请求体
chatRequest := ChatRequest{
Model: model,
Messages: []Message{
{
Role: "user",
Content: "Hi there!",
},
},
Stream: false, // 设置为 true 以启用流式输出
}
// 将请求结构体编码成 JSON
reqBody, err := json.Marshal(chatRequest)
if err != nil {
log.Fatalf("Error marshalling request body: %v", err)
}
// 创建 HTTP 请求
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequest("POST", baseURL+"/v1/chat/completions", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error creating request: %v", err)
}
// 设置请求头
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
// 发送请求
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error sending request: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response body: %v", err)
}
fmt.Println(string(body))
// 解析成结构体
var chatResp ChatResponse
if err := json.Unmarshal(body, &chatResp); err != nil {
log.Fatalf("Error unmarshalling JSON: %v", err)
}
// 打印解析后的字段
fmt.Println("Model:", chatResp.Model)
fmt.Println("Content:", chatResp.Choices[0].Message.Content)
saveToJSONFile("response.txt", chatResp.Choices[0].Message.Content)
}
// 将内容保存到 JSON 文件
func saveToJSONFile(filename, content string) error {
// 创建文件
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("error creating file: %v", err)
}
defer file.Close()
// 将内容作为 JSON 保存
data := map[string]string{"response": content}
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ") // 设置格式化输出
err = encoder.Encode(data)
if err != nil {
return fmt.Errorf("error encoding JSON to file: %v", err)
}
fmt.Println("Response saved to", filename)
return nil
}
3. 轮询获得AI绘图的图片
同样使用的是PPIO为例子:
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
type GraphRequest struct {
Prompt string `json:"prompt"`
Size string `json:"size"`
Watermark bool `json:"watermark"`
SequentialImageGeneration string `json:"sequential_image_generation"`
MaxImages int `json:"max_images"`
}
type Response struct {
Images []string `json:"images"`
}
func main() {
baseURL := "https://api.ppinfra.com/v3/seedream-4.0"
apiKey := "" // 替换为你的 API Key
// 设置请求体
graphRequest := GraphRequest{
Prompt: `动漫风格,高清8K画质,细腻光影渲染,电影感构图。场景设定于极具设计感的现代办公楼长廊内,采用对称式走廊构图增强空间纵深感。
【人物塑造】
主角少女:
形象特征:茶色波波头短发搭配复古圆框眼镜,镜片反射微妙高光
服装设计:简约白色衬衫配灰色百褶裙,外套材质选用低调的羊绒质感
标志道具:手持靛青色传统油纸伞,伞面绘有浮世绘风格波纹图案
动态表现:撑伞转身的瞬间,裙摆扬起优雅弧线,展现灵动气质
叙述者视角:
职业装扮:标准深色西装套装,手持电子记事本
互动姿态:微微倾身聆听,表情带着好奇与思索
【环境构建】
建筑空间:
廊道设计:极简主义风格的宽阔走廊,抛光大理石地面呈现完美倒影
窗外景致:左侧整面落地窗外是精心打理的和风庭院,包含枯山水、惊鹿与红枫
光线设计:午后斜阳透过格栅形成几何光斑,与伞影构成明暗对比
【细节刻画】
伞骨末端悬挂迷你推理小说挂坠
衬衫领口别着隐藏家纹的胸针
地面倒影中隐约显现非常规的景物变形
窗外庭院暗藏数字投影的仙鹤影像
【色彩体系】
主色调:高级灰与米白为基底
强调色:伞面靛青与枫叶朱红形成视觉焦点
光效处理:柔光滤镜配合局部补光,营造神秘氛围
整体画面平衡现代建筑与传统元素,通过油纸伞与办公环境的戏剧性碰撞,营造出"日常中的非凡"的独特质感,完美呈现角色神秘气质与场景叙事张力。`,
Size: "2048x2048",
Watermark: false,
SequentialImageGeneration: "disabled", // 如果需要序列化生成
MaxImages: 1, // 生成最大图像数
}
// 将请求结构体编码成 JSON
reqBody, err := json.Marshal(graphRequest)
if err != nil {
log.Fatalf("Error marshalling request body: %v", err)
}
fmt.Println(string(reqBody))
// 创建 HTTP 请求
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequest("POST", baseURL, bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error creating request: %v", err)
}
// 设置请求头
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
// 发送请求
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error sending request: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(resp)
fmt.Println(string(body))
// 创建结构体用于解析 JSON 数据
var result Response
// 解析 JSON 数据
err = json.Unmarshal(body, &result)
if err != nil {
fmt.Println("解析 JSON 错误:", err)
return
}
// 获取图片 URL
imageURL := result.Images[0] // 假设返回只有一张图片
// 下载图片
respImage, err := http.Get(imageURL)
if err != nil {
fmt.Println("下载图片失败:", err)
return
}
defer respImage.Body.Close()
// 获取当前时间戳,并生成文件名
currentTime := time.Now().Format("20060102150405") // 格式化为:年年年年月月日日时分秒
fileName := fmt.Sprintf("image_%s.jpg", currentTime)
// 创建文件用于保存图片
outFile, err := os.Create(fileName)
if err != nil {
fmt.Println("创建文件失败:", err)
return
}
defer outFile.Close()
// 将图片写入文件
_, err = io.Copy(outFile, respImage.Body)
if err != nil {
fmt.Println("保存图片失败:", err)
return
}
fmt.Println("图片已成功保存为 image.jpg")
}
二、 与Redis
Redis相比DB来说要简单得多,数据结构并不复杂,操作花样也不多,我们就先从软柿子开始捏。首先我们使用Go连通Redis:
Go
// 首先运行这段代码将Go需要的redis包安装好
go get github.com/go-redis/redis/v8
//然后就可以使用了
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-redis/redis/v8"
)
func main() {
// 创建 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 服务器地址
Password: "", // 没有密码,默认空
DB: 0, // 默认数据库 0
})
// 使用 context
ctx := context.Background()
// 🔹 1. 推送一个新的键值对
err := rdb.Set(ctx, "username", "GoRedisUser", 10*time.Minute).Err()
if err != nil {
log.Fatalf("设置键值对失败: %v", err)
}
fmt.Println("已成功推送键值对 -> username: GoRedisUser")
// 🔹 2. 获取所有的键
keys, err := rdb.Keys(ctx, "*").Result()
if err != nil {
log.Fatalf("获取键失败: %v", err)
}
// 🔹 3. 打印所有键值对
fmt.Println("当前 Redis 中的所有键值对:")
for _, key := range keys {
val, err := rdb.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
continue
}
log.Printf("获取值失败:%v", err)
continue
}
fmt.Printf("键: %s, 值: %s\n", key, val)
}
}
测试连接的同时我们也看到了如何向其中推送和查找kv对数据。那其他的比如列表、数值、字符串、队列、栈等的数据结构也可以通过我们的rdb客户端实现。我们可以使用下面的代码去分别测试各种各样的数据结构的表现:
Go
package main
import (
"context"
"fmt"
"log"
"github.com/go-redis/redis/v8"
)
func main() {
// 创建 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 服务器地址
Password: "", // 没有密码,默认空
DB: 0, // 默认数据库 0
})
// 使用 context
ctx := context.Background()
// --------- 1. 推送字符串(String)数据 ---------
err := rdb.Set(ctx, "username", "GoRedisUser", 0).Err()
if err != nil {
log.Fatalf("设置键值对失败: %v", err)
}
fmt.Println("推送字符串数据 -> username: GoRedisUser")
// --------- 2. 推送列表(List)数据 ---------
rdb.RPush(ctx, "myList", "item1", "item2", "item3")
fmt.Println("推送列表数据 -> myList: item1, item2, item3")
// --------- 3. 推送集合(Set)数据 ---------
rdb.SAdd(ctx, "mySet", "item1", "item2", "item3")
fmt.Println("推送集合数据 -> mySet: item1, item2, item3")
// --------- 4. 推送有序集合(Sorted Set)数据 ---------
rdb.ZAdd(ctx, "mySortedSet",
&redis.Z{Score: 1, Member: "item1"},
&redis.Z{Score: 2, Member: "item2"},
)
fmt.Println("推送有序集合数据 -> mySortedSet: item1(1), item2(2)")
// --------- 5. 推送哈希(Hash)数据 ---------
rdb.HSet(ctx, "myHash", "field1", "value1", "field2", "value2")
fmt.Println("推送哈希数据 -> myHash: field1=value1, field2=value2")
// --------- 6. 推送队列(Queue)数据 ---------
rdb.LPush(ctx, "myQueue", "task1", "task2", "task3")
fmt.Println("推送队列数据 -> myQueue: task1, task2, task3")
// --------- 7. 推送栈(Stack)数据 ---------
rdb.LPush(ctx, "myStack", "top1", "top2", "top3")
fmt.Println("推送栈数据 -> myStack: top1, top2, top3")
// 获取并打印 Redis 中的数据:
// --------- 1. 获取字符串(String)数据 ---------
username, err := rdb.Get(ctx, "username").Result()
if err != nil {
log.Fatalf("获取字符串数据失败: %v", err)
}
fmt.Printf("获取字符串数据 -> username: %s\n", username)
// --------- 2. 获取列表(List)数据 ---------
list, err := rdb.LRange(ctx, "myList", 0, -1).Result()
if err != nil {
log.Fatalf("获取列表数据失败: %v", err)
}
fmt.Printf("获取列表数据 -> myList: %v\n", list)
// --------- 3. 获取集合(Set)数据 ---------
set, err := rdb.SMembers(ctx, "mySet").Result()
if err != nil {
log.Fatalf("获取集合数据失败: %v", err)
}
fmt.Printf("获取集合数据 -> mySet: %v\n", set)
// --------- 4. 获取有序集合(Sorted Set)数据 ---------
sortedSet, err := rdb.ZRange(ctx, "mySortedSet", 0, -1).Result()
if err != nil {
log.Fatalf("获取有序集合数据失败: %v", err)
}
fmt.Printf("获取有序集合数据 -> mySortedSet: %v\n", sortedSet)
// --------- 5. 获取哈希(Hash)数据 ---------
hash, err := rdb.HGetAll(ctx, "myHash").Result()
if err != nil {
log.Fatalf("获取哈希数据失败: %v", err)
}
fmt.Printf("获取哈希数据 -> myHash: %v\n", hash)
// --------- 6. 获取队列(Queue)数据 ---------
queue, err := rdb.LRange(ctx, "myQueue", 0, -1).Result()
if err != nil {
log.Fatalf("获取队列数据失败: %v", err)
}
fmt.Printf("获取队列数据 -> myQueue: %v\n", queue)
// --------- 7. 获取栈(Stack)数据 ---------
stack, err := rdb.LRange(ctx, "myStack", 0, -1).Result()
if err != nil {
log.Fatalf("获取栈数据失败: %v", err)
}
fmt.Printf("获取栈数据 -> myStack: %v\n", stack)
}
三、 与DB(各种各样的DB,这里以pgsql和mongodb为例)
这两个数据库比较有代表性,一个是关系型数据库,一个是非关系型数据库,囊括了主流的两个大方向的数据库操作。
1. pgsql
为什么不选择mysql呢?其实我是有一个比较单纯的想法。目前我接触到的凡是使用mysql的场景都是使用"上古时代"的版本,其中某些聚合函数无法使用,并且听说存在着性能瓶颈。其中如果需要存储json类型的数据或者二进制数据在一开始的版本中支持的并不是特别优秀,往往还是回到表单形式来存储。而pgsql比起mysql就更加灵活一点儿,正好使用一个比较新的版本来学习也能跟得上未来的时代。其对于json和二进制数据的支持就稍微好一些,性能上限也高一些,我还听说直接用pgsql存储知识图谱结构和向量数据的做法,想必它一定有它的道理(笑)。
Go
// 同样在一开始的时候需要导入pgsql包
go get github.com/jackc/pgx/v4
// 接下来就可以是使用了
package main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v4"
)
func main() {
// PostgreSQL 连接字符串
connStr := "postgres://myuser:mypassword@localhost:5432/mydb"
// 连接到 PostgreSQL 数据库
conn, err := pgx.Connect(context.Background(), connStr)
if err != nil {
log.Fatalf("无法连接到数据库: %v", err)
}
defer conn.Close(context.Background())
// 创建表
createTableSQL := `
CREATE TABLE IF NOT EXISTS test_table (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
`
_, err = conn.Exec(context.Background(), createTableSQL)
if err != nil {
log.Fatalf("创建表失败: %v", err)
}
fmt.Println("表 test_table 创建成功")
// 查询所有的表名
queryTablesSQL := `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';`
rows, err := conn.Query(context.Background(), queryTablesSQL)
if err != nil {
log.Fatalf("查询表名失败: %v", err)
}
defer rows.Close()
fmt.Println("当前数据库中的表名:")
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err != nil {
log.Fatalf("扫描表名失败: %v", err)
}
fmt.Println(tableName)
}
// 删除表
dropTableSQL := `DROP TABLE IF EXISTS test_table;`
_, err = conn.Exec(context.Background(), dropTableSQL)
if err != nil {
log.Fatalf("删除表失败: %v", err)
}
fmt.Println("表 test_table 删除成功")
}
2. mongodb
这是非常典型的非关系型数据库,相信各位AI应用开发者都感受到结构化数据在AI领域中的不方便,如果想要设计一个健壮、智能的AI处理工作流,对于一些字段的开放性是非常必要的,比如某些字段我需要是灵活的字符串、数值、列表等数据,我会灵活地对传递的状态字典,各种各样的字典在不同地方进行不同的处理、不同的取舍。这一切对于结构化数据库都是非常折磨的开发,一旦接触了mongodb存储的json结构或者二进制数据后,就再也回不去了(笑)。
Go
// 这里是需要安装的依赖
go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/mongo/options
//然后就可以使用了
package main
import (
"context"
"fmt"
"log"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
// 连接 MongoDB 本地数据库
clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") // 默认本地 MongoDB 连接
client, err := mongo.Connect(context.Background(), clientOptions)
if err != nil {
log.Fatalf("无法连接到 MongoDB: %v", err)
}
defer client.Disconnect(context.Background())
// 测试连接
err = client.Ping(context.Background(), nil)
if err != nil {
log.Fatalf("连接测试失败: %v", err)
}
fmt.Println("成功连接到 MongoDB!")
// 选择数据库和集合
collection := client.Database("testdb").Collection("testcollection")
// 向集合中插入一条文档
doc := bson.D{
{Key: "name", Value: "John Doe"},
{Key: "age", Value: 30},
{Key: "city", Value: "New York"},
}
insertResult, err := collection.InsertOne(context.Background(), doc)
if err != nil {
log.Fatalf("插入文档失败: %v", err)
}
fmt.Printf("成功插入文档,ID: %v\n", insertResult.InsertedID)
// 查询插入的文档
var result bson.M
err = collection.FindOne(context.Background(), bson.D{{Key: "name", Value: "John Doe"}}).Decode(&result)
if err != nil {
log.Fatalf("查询文档失败: %v", err)
}
fmt.Printf("查询到文档: %v\n", result)
// 删除该文档
deleteResult, err := collection.DeleteOne(context.Background(), bson.D{{Key: "name", Value: "John Doe"}})
if err != nil {
log.Fatalf("删除文档失败: %v", err)
}
fmt.Printf("删除了 %v 条文档\n", deleteResult.DeletedCount)
}
四、与消息队列(这里仅以kafka为例)
常用的消息队列工具有kafka和rabbitmq。这里选择kafka的原因在于我了解到rabbitmq实际上更加简单一些,它更专注于确保消息的一定传递送达,而不是kafka这样需要面对极端并发的场景,也不会像kafka这样有极大的自定义空间来确定它的表现。于是乎如果我学会了kafka,那么rabbitmq上手也会很快。退一步来说,消息队列在我们的开发当中更多只是充当一个"功能"来使用,不需要太过在意它的性能,最基础的已经足够应对绝大多数的情况了。所以不如就从kafka学起吧~
Go
//这里是安装的依赖
go get github.com/confluentinc/confluent-kafka-go/kafka
//然后就可以开始了
package main
import (
"fmt"
"log"
"github.com/confluentinc/confluent-kafka-go/kafka"
)
func main() {
// Kafka 配置
broker := "localhost:9092" // Kafka broker 地址,假设本地运行
topic := "test-topic" // 主题
groupID := "test-group" // 消费者组
// 生产者配置
producerConfig := &kafka.ConfigMap{
"bootstrap.servers": broker,
}
// 创建生产者
producer, err := kafka.NewProducer(producerConfig)
if err != nil {
log.Fatalf("Failed to create producer: %v", err)
}
defer producer.Close()
// 消费者配置
consumerConfig := &kafka.ConfigMap{
"bootstrap.servers": broker,
"group.id": groupID,
"auto.offset.reset": "earliest", // 从最早的消息开始消费
}
// 创建消费者
consumer, err := kafka.NewConsumer(consumerConfig)
if err != nil {
log.Fatalf("Failed to create consumer: %v", err)
}
defer consumer.Close()
// 订阅主题
err = consumer.Subscribe(topic, nil)
if err != nil {
log.Fatalf("Failed to subscribe to topic: %v", err)
}
// 生产消息
message := "Hello Kafka!"
err = producer.Produce(&kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny},
Value: []byte(message),
}, nil)
if err != nil {
log.Fatalf("Failed to produce message: %v", err)
}
fmt.Printf("Produced message: %s\n", message)
// 消费消息
msg, err := consumer.ReadMessage(-1) // 等待消息
if err != nil {
log.Fatalf("Failed to read message: %v", err)
}
fmt.Printf("Consumed message: %s\n", string(msg.Value))
}
到现在为止,我们已经将API接口、内存管理Redis、关系型和非关系型数据库、消息队列都成功跑通。创建一个Web应用的所有代码组件我们都掌握了,如果不关心接口文档的可视化和一些方便调试的工具的话,我们已经能够完成一个完整Web应用的后端服务开发了。这无疑是振奋人心的!