免费图库集成 - Pexels API实战
04 - 免费图库集成 — Pexels API 实战
本章目标
- 注册 Pexels API 并获取免费 Key
- 用 Go 的
net/http调用 REST API - JSON 反序列化处理 API 响应
- 下载图片并限制文件大小
- 将 PexelsService 注册为 Wails Service
4.1 Pexels API 简介
Pexels 是全球最大的免费图库之一,提供高质量的照片和视频。它的 API 对开发者非常友好:
- 免费 — 无需信用卡,注册即用
- 速率限制 — 每月 200 次请求(免费版),开发测试完全够用
- 高质量 — 照片均由专业摄影师上传
- 无需署名 — 虽然建议署名,但不强制
注册获取 API Key
- 访问 pexels.com/api
- 点击「Join Pexels」注册账号
- 登录后到 pexels.com/api/new 创建应用
- 复制生成的 API Key(格式类似:
AbCDeFgHiJkLmNoPqRsTuVwXyZ123456)
4.2 API 接口分析
搜索图片
GET https://api.pexels.com/v1/search?query=dogs&per_page=5
Headers:
Authorization: YOUR_API_KEY
Response:
{
"total_results": 10000,
"page": 1,
"per_page": 5,
"photos": [
{
"id": 1234567,
"width": 4000,
"height": 6000,
"url": "https://www.pexels.com/photo/...",
"photographer": "John Doe",
"src": {
"original": "https://images.pexels.com/photos/...",
"large": "https://images.pexels.com/photos/...?w=1920",
"medium": "https://images.pexels.com/photos/...?w=800",
"small": "https://images.pexels.com/photos/...?w=400",
"portrait": "https://images.pexels.com/photos/...?w=600",
"landscape": "https://images.pexels.com/photos/...?w=1200",
"tiny": "https://images.pexels.com/photos/...?w=200"
},
"alt": "A cute dog playing in the park"
}
]
}
💡 知识点: Pexels 对同一张图片提供了 7 种尺寸。我们选择
medium(800px 宽)用于 Word 文档嵌入,既能保证清晰度,又不会让文档体积过大。
4.3 Go 结构体设计
创建 backend/pexels.go:
package backend
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// PexelsPhoto Pexels 图片信息
type PexelsPhoto struct {
ID int `json:"id"`
Width int `json:"width"`
Height int `json:"height"`
URL string `json:"url"`
Photographer string `json:"photographer"`
PhotographerURL string `json:"photographer_url"`
Src struct {
Original string `json:"original"`
Large string `json:"large"`
Medium string `json:"medium"`
Small string `json:"small"`
Portrait string `json:"portrait"`
Landscape string `json:"landscape"`
Tiny string `json:"tiny"`
} `json:"src"`
Alt string `json:"alt"`
}
// PexelsResponse Pexels API 响应
type PexelsResponse struct {
TotalResults int `json:"total_results"`
Page int `json:"page"`
PerPage int `json:"per_page"`
Photos []PexelsPhoto `json:"photos"`
NextPage string `json:"next_page"`
}
💡 知识点:
Src字段使用了内嵌匿名 struct(Src struct {...} json:"src")。当 JSON 嵌套层级较深且不想创建独立类型时,这种写法很方便。
4.4 创建 PexelsService
// PexelsService Pexels 配图服务
type PexelsService struct {
configService *ConfigService
httpClient *http.Client
}
// NewPexelsService 创建 Pexels 服务
func NewPexelsService(cs *ConfigService) *PexelsService {
return &PexelsService{
configService: cs,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
⚠️ 注意: 为
http.Client设置超时很重要。如果不设超时,网络异常时 goroutine 会永久阻塞。
4.5 实现图片搜索
// SearchImages 搜索图片(前端调用)
// query: 搜索关键词
// perPage: 返回数量(最多 80)
func (ps *PexelsService) SearchImages(query string, perPage int) ([]PexelsPhoto, error) {
cfg, err := ps.configService.Load()
if err != nil {
return nil, fmt.Errorf("加载配置失败: %w", err)
}
if cfg.PexelsAPIKey == "" {
return nil, fmt.Errorf("请先配置 Pexels API Key")
}
if perPage <= 0 {
perPage = 5
}
if perPage > 80 {
perPage = 80
}
// 构建请求 URL
apiURL := fmt.Sprintf("https://api.pexels.com/v1/search?query=%s&per_page=%d",
url.QueryEscape(query), perPage)
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
req.Header.Set("Authorization", cfg.PexelsAPIKey)
resp, err := ps.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("Pexels API 请求失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, fmt.Errorf("Pexels API 返回错误 (状态码 %d): %s",
resp.StatusCode, string(body))
}
var result PexelsResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("解析 Pexels 响应失败: %w", err)
}
return result.Photos, nil
}
逐行解析
| 代码 | 说明 |
|---|---|
url.QueryEscape(query) |
URL 编码,将"金色 幼犬"转为 %E9%87%91%E8%89%B2+%E5%B9%BC%E7%8A%AC |
req.Header.Set("Authorization", ...) |
Pexels 使用 Header 认证,不是 query param |
defer resp.Body.Close() |
确保响应体一定被关闭,防止连接泄漏 |
io.LimitReader(resp.Body, 1024) |
限制错误响应的读取量,防止内存被大量错误信息撑爆 |
json.NewDecoder(resp.Body).Decode(...) |
流式解码,避免将整个响应读入内存 |
4.6 实现图片下载
// DownloadImage 下载图片(内部使用)
func (ps *PexelsService) DownloadImage(imageURL string) ([]byte, error) {
resp, err := ps.httpClient.Get(imageURL)
if err != nil {
return nil, fmt.Errorf("下载图片失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("下载图片返回状态码 %d", resp.StatusCode)
}
// 限制最大 10MB,防止恶意或异常大文件
data, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
if err != nil {
return nil, fmt.Errorf("读取图片数据失败: %w", err)
}
return data, nil
}
⚠️ 安全提示: 永远对从互联网下载的数据设置大小限制。不加限制的话,攻击者或错误的 API 可能返回数 GB 的数据,直接撑爆内存。
4.7 注册到 main.go
func main() {
configService := backend.NewConfigService()
agentService := backend.NewAgentService(configService)
pexelsService := backend.NewPexelsService(configService)
app := application.New(application.Options{
Services: []application.Service{
application.NewService(configService),
application.NewService(agentService),
application.NewService(pexelsService),
},
// ...
})
}
4.8 前端调用示例
import { PexelsService } from "../bindings/pet-content-creator/backend";
// 搜索图片
const photos = await PexelsService.SearchImages("golden retriever puppy", 6);
photos.forEach(p => {
console.log(p.src.medium, p.photographer);
});
因为 PexelsService 注册到了 Wails,前端绑定会自动生成,不需要写任何 IPC 代码。
4.9 错误处理最佳实践
网络层面的容错
图片搜索可能因为以下原因失败:
- 无网络连接
- Pexels API 超时
- API Key 过期
- 达到速率限制
在后续的编排章节中,我们会看到如何优雅地处理这些错误:
// 搜索失败时不阻塞整体流程
photos, err := ps.SearchImages(tag, count)
if err != nil {
log.Printf("搜索图片失败 (tag=%s): %v", tag, err)
continue // 尝试下一个关键词
}
本章总结
| 你已经学会 | 对应能力 |
|---|---|
net/http 构造 GET 请求 |
REST API 调用 |
req.Header.Set() |
自定义请求头 |
json.NewDecoder(resp.Body) |
流式 JSON 解码 |
url.QueryEscape() |
URL 参数编码 |
io.LimitReader() |
限制下载/读取大小 |
http.Client + Timeout |
HTTP 客户端超时控制 |
| 错误隔离 | 局部失败不影响整体 |
🔧 动手练习
- 实现按颜色搜索(Pexels 支持
color=参数,如red、blue) - 实现按方向搜索(
orientation=landscape/portrait/square) - 增加图片缓存:同一 URL 不重复下载,先检查本地缓存目录
- 为
SearchImages增加page参数,支持翻页
👉 下一章:纯 Go 生成 Word 文档 — OOXML 探秘