Skip to content

📋 list_packages — 列出包

本示例演示如何获取 Composer 仓库(Packagist)中所有可用包的列表,并对返回的结构化包数据做基础的遍历、抽样打印与精确查找。

🎯 示例定位

list_packages 属于 Packagist API 远程操作 系列的第 3 个示例,承接 download_index 之后。它聚焦于「拿到包列表之后能做什么」:不再停留在原始字节的下载,而是走类型化方法拿到结构化的 Package 切片,进而在内存里做展示与查找。

  • 📚 你将学到:如何初始化 repository.Repository、调用其 List 方法获取 []*Package,并对其中的 Name 字段做遍历输出与字符串精确匹配。
  • 🔗 对应 SDK 方法:pkg/repository 包的 (*Repository).List
  • 💡 与 download_index 的区别:download_index 用包级函数 repository.DownloadIndex 拉回原始 JSON 字节,适合落盘/镜像初始化;而本例走 Repository 实例方法,返回的是已解析好的 Package 结构体切片,适合直接在程序里消费(展示、过滤、检索)。

💻 完整代码

go
package main

import (
	"context"
	"fmt"

	"github.com/scagogogo/composer-skills/pkg/repository"
)

func main() {
	// 示例 3: 列出 Composer 仓库中的包
	// 展示如何获取所有可用包的列表,以及如何处理这些包信息。

	// 步骤 1: 初始化仓库客户端
	options := &repository.Options{
		ServerUrl: "https://packagist.org", // 使用官方仓库
	}
	fmt.Printf("使用服务器 URL: %s\n", options.ServerUrl)

	// 创建仓库客户端实例
	repo := &repository.Repository{}
	_ = options // 实际项目中通过构造函数注入 options

	// 步骤 2: 列出所有包
	fmt.Println("正在获取包列表...")
	ctx := context.Background()

	packages, err := repo.List(ctx)
	if err != nil {
		fmt.Printf("获取包列表失败: %v\n", err)
		return
	}

	// 步骤 3: 处理包列表
	fmt.Printf("成功获取 %d 个包\n", len(packages))

	// 打印前 10 个包的名称
	fmt.Println("\n前 10 个包:")
	maxPrint := 10
	if len(packages) < maxPrint {
		maxPrint = len(packages)
	}
	for i := 0; i < maxPrint; i++ {
		fmt.Printf("  %d. %s\n", i+1, packages[i].Name)
	}

	// 步骤 4: 按名称搜索包(精确匹配示例)
	searchTerm := "symfony/console"
	fmt.Printf("\n搜索包含 '%s' 的包:\n", searchTerm)

	found := 0
	for _, pkg := range packages {
		if found >= 5 {
			break // 只显示前 5 个匹配的结果
		}
		if pkg.Name == searchTerm {
			fmt.Printf("  找到完全匹配: %s\n", pkg.Name)
			found++
		}
	}
	if found == 0 {
		fmt.Printf("  未找到完全匹配 '%s' 的包\n", searchTerm)
	}

	// 输出示例:
	// 使用服务器 URL: https://packagist.org
	// 正在获取包列表...
	// 成功获取 25000 个包
	//
	// 前 10 个包:
	//   1. symfony/polyfill
	//   2. symfony/console
	//   ...
	//
	// 搜索包含 'symfony/console' 的包:
	//   找到完全匹配: symfony/console
}

🧩 代码讲解

  • 🧱 构造仓库选项repository.Options{ServerUrl: ...} 指定目标仓库地址。示例里用字面量构造,实际项目应通过 repository.NewRepository(options) 之类的构造函数把 options 真正注入到 repo 中(示例为简化省略了注入细节)。
  • 🏗️ 创建客户端实例repo := &repository.Repository{} 得到一个仓库客户端,List 等方法挂在 *Repository 上,因此必须取指针。
  • 🌐 发起列表请求repo.List(ctx) 在内部请求 list.json 端点并把 JSON 反序列化为 []*Package,调用方拿到的已是结构化数据,无需自己处理 encoding/json
  • 🔢 抽样打印:先取 len(packages) 看总量,再用 maxPrint 上限保护循环,避免列表过长时刷屏;这是处理大批量 API 返回值的常用模式。
  • 🔍 精确匹配查找:遍历 packages 比较 pkg.Name == searchTerm,配合 found >= 5 提前 break,演示「在已加载列表里做内存检索」的最朴素写法。
  • 🛡️ 错误处理List 返回 error 时直接打印并 return,避免对 nil 切片解引用;生产环境可换成结构化日志或重试逻辑。

▶️ 运行方式

在示例目录下直接运行:

bash
cd /home/cc11001100/github/scagogogo/composer-skills/examples/list_packages
go run main.go

⚠️ 本示例会发起真实的 Packagist API 调用,返回的包列表较大(数万条),请避免高频运行,并确保网络与内存充足。

📚 涉及的 SDK 方法

方法名所属包端点文档链接
(*Repository).Listpkg/repositoryGET /packages/list.json/sdk/packagist/methods/list-packages

📝 说明:Repository.ListPackagistClient.ListPackages 背后请求的是同一个 list.json 端点,区别在于 Repository 走的是仓库抽象层,返回 []*repository.Package;上表链接指向该端点的类型化方法文档,可对照 Package 结构体字段定义。

🚀 进阶

  • ⏱️ 加超时控制:用 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) 替换裸 context.Background(),并 defer cancel(),防止网络卡死时整个进程被挂住。
  • 🏷️ 按 vendor/类型过滤:把精确匹配升级为 strings.HasPrefix(pkg.Name, vendor+"/") 实现按厂商过滤;若需按类型筛选,改用 PackagistClient.ListPackagesByType 直接走服务端过滤,减少全量传输。
  • 🔎 构建内存索引:遍历一次 packages,按 pkg.Namemap[string]*Package,后续查找从 O(n) 降为 O(1),适合需要反复检索的场景。
  • 🧵 批量补全详情:拿到包名列表后用工作池并发调用 PackagistClient.GetPackage 拉取每个包的元数据,注意加令牌桶限速以尊重 Packagist 速率限制。
  • 💾 缓存列表:把 packages 序列化落盘(参考 download_indexDownloadIndexToFile),下次启动先加载缓存再按需增量刷新,降低对远程 API 的依赖。

基于 MIT 许可证发布