📥 download_index — 下载包索引
本示例演示如何一次性下载完整的 Composer 包名索引(Packagist 全量包清单),并把索引数据落到本地文件。
🎯 示例定位
download_index 属于 Packagist API 远程操作 系列的第 2 个示例,承接 basic_setup 之后。它聚焦于一个最朴素但极具实用价值的场景:拿到仓库里所有包的名字。
- 📚 你将学到:如何调用
pkg/repository包级别的便捷函数,把https://packagist.org/packages/list.json这个轻量索引端点的原始字节拉到内存,再落盘成 JSON 文件。 - 🔗 对应 SDK 方法:
repository.DownloadIndex与repository.DownloadIndexToFile。 - 💡 与
list_packages的区别:list_packages走的是PackagistClient的类型化方法(返回结构化PackageListResponse);而本例直接下载原始字节,适合做镜像初始化、离线缓存或自定义解析流水线。
💻 完整代码
go
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/scagogogo/composer-skills/pkg/repository"
)
func main() {
// 示例 2: 下载 Composer 包索引
// 索引文件包含仓库中所有可用包的列表。
// 步骤 1: 创建上下文,可用于控制请求超时等
ctx := context.Background()
// 步骤 2: 直接下载索引到内存
fmt.Println("正在下载包索引...")
indexBytes, err := repository.DownloadIndex(ctx)
if err != nil {
fmt.Printf("下载索引失败: %v\n", err)
return
}
fmt.Printf("索引下载成功: %d 字节\n", len(indexBytes))
// 索引数据是 JSON 字符串,例如:
// {"packageNames":["vendor1/package1","vendor2/package2",...]}
// 可通过 JSON 解析获取所有包名
// 步骤 3: 将索引保存到文件
tempDir, err := os.MkdirTemp("", "composer-index")
if err != nil {
fmt.Printf("创建临时目录失败: %v\n", err)
return
}
defer os.RemoveAll(tempDir) // 示例结束时清理
indexPath := filepath.Join(tempDir, "composer-index.json")
// 使用便捷方法直接下载并保存到文件
fmt.Printf("将索引保存到文件: %s\n", indexPath)
err = repository.DownloadIndexToFile(ctx, indexPath)
if err != nil {
fmt.Printf("保存索引文件失败: %v\n", err)
return
}
// 获取文件信息以验证保存成功
fileInfo, err := os.Stat(indexPath)
if err != nil {
fmt.Printf("获取文件信息失败: %v\n", err)
return
}
fmt.Printf("索引文件保存成功,文件大小: %d 字节\n", fileInfo.Size())
// 输出示例:
// 正在下载包索引...
// 索引下载成功: 1234567 字节
// 将索引保存到文件: /tmp/composer-index-123456/composer-index.json
// 索引文件保存成功,文件大小: 1234567 字节
}🧩 代码讲解
- 🧱 创建上下文:
ctx := context.Background()作为 API 调用的根 context,可在此基础上派生带超时的 context(context.WithTimeout)避免大文件下载时长期阻塞。 - 🌐 下载原始索引:
repository.DownloadIndex(ctx)命中list.json端点,返回[]byte原始 JSON。它不解析结构,因此内存占用和 CPU 开销都最低,适合先落盘、再由下游消费者按需解析。 - 📦 JSON 结构提示:返回的字节流形如
{"packageNames":[...]},需要包名时用encoding/json反序列化即可(本例为突出「下载」语义未展开解析)。 - 🗂️ 创建临时目录:
os.MkdirTemp("", "composer-index")在系统临时目录下建一个专属子目录,defer os.RemoveAll(tempDir)保证示例结束后清理,避免泄露临时文件。 - 💾 一步落盘:
repository.DownloadIndexToFile(ctx, indexPath)是「下载 + 写文件」的便捷封装,内部复用DownloadIndex后用os.WriteFile写入,权限为os.ModePerm。 - ✅ 验证结果:
os.Stat(indexPath)取回文件信息,比对fileInfo.Size()与内存字节数,确认落盘完整无误。 - 🛡️ 错误处理:每一步都检查
err并在失败时return,避免错误向下传播;生产环境可替换为日志/告警。
▶️ 运行方式
在示例目录下直接运行:
bash
cd /home/cc11001100/github/scagogogo/composer-skills/examples/download_index
go run main.go⚠️ 本示例会发起真实的 Packagist API 调用,请避免高频运行以免给目标服务器造成负担。索引文件较大(数 MB 量级),请确保网络与内存充足。
📚 涉及的 SDK 方法
| 方法名 | 所属包 | 端点 | 文档链接 |
|---|---|---|---|
DownloadIndex | pkg/repository | GET /packages/list.json | /sdk/packagist/methods/list-packages |
DownloadIndexToFile | pkg/repository | GET /packages/list.json + 本地写文件 | /sdk/packagist/methods/list-packages |
📝 说明:
repository包以包级函数形式提供这两个便捷方法,背后请求的端点与PackagistClient.ListPackages完全一致(均为list.json)。上表链接指向该端点的类型化方法文档,便于对照结构化返回值PackageListResponse的字段定义。
🚀 进阶
- ⏱️ 加超时控制:用
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)替换裸context.Background(),并在defer cancel()后再调用DownloadIndex,防止网络卡死。 - 🔍 结构化解析:把
indexBytes反序列化为domain.PackageListResponse,再结合repository.GetPackage批量拉取每个包的元数据,构建本地搜索索引。 - 🪞 镜像初始化:将
DownloadIndexToFile的产物作为自建 Satis / 私有镜像的「全量清单」起点,配合get-package-changes做增量更新。 - 🧵 并发下载:拿到包名列表后,用工作池(
errgroup或带缓冲 channel)并发拉取各包详情,注意加time.Sleep或令牌桶限速以尊重 Packagist 的速率限制。 - 🔐 校验完整性:落盘后计算文件 SHA256 并与上次结果对比,判断索引是否有更新,避免重复全量拉取。