Skip to content

📥 download_index — 下载包索引

本示例演示如何一次性下载完整的 Composer 包名索引(Packagist 全量包清单),并把索引数据落到本地文件。

🎯 示例定位

download_index 属于 Packagist API 远程操作 系列的第 2 个示例,承接 basic_setup 之后。它聚焦于一个最朴素但极具实用价值的场景:拿到仓库里所有包的名字

  • 📚 你将学到:如何调用 pkg/repository 包级别的便捷函数,把 https://packagist.org/packages/list.json 这个轻量索引端点的原始字节拉到内存,再落盘成 JSON 文件。
  • 🔗 对应 SDK 方法:repository.DownloadIndexrepository.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 方法

方法名所属包端点文档链接
DownloadIndexpkg/repositoryGET /packages/list.json/sdk/packagist/methods/list-packages
DownloadIndexToFilepkg/repositoryGET /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 并与上次结果对比,判断索引是否有更新,避免重复全量拉取。

基于 MIT 许可证发布