📋 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).List | pkg/repository | GET /packages/list.json | /sdk/packagist/methods/list-packages |
📝 说明:
Repository.List与PackagistClient.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.Name建map[string]*Package,后续查找从 O(n) 降为 O(1),适合需要反复检索的场景。 - 🧵 批量补全详情:拿到包名列表后用工作池并发调用
PackagistClient.GetPackage拉取每个包的元数据,注意加令牌桶限速以尊重 Packagist 速率限制。 - 💾 缓存列表:把
packages序列化落盘(参考download_index的DownloadIndexToFile),下次启动先加载缓存再按需增量刷新,降低对远程 API 的依赖。