Skip to content

📦 包信息

获取 Packagist 上某个包的元数据、版本、下载统计与变更跟踪。这组方法覆盖单个包的全部「读」维度,是依赖治理、镜像同步、监控告警的基础。

何时使用

  • 📦 依赖看板:拉取项目所有直接依赖的包信息(描述、维护者、star、下载量)做可视化。
  • 🛠️ 镜像同步:用 GetPackageChanges 增量同步自建镜像,避免每次全量拉取。
  • 📊 下载监控:定期记录 GetPackageStats 的每日/每月下载,绘制趋势。
  • 🧩 版本探测:用 GetPackageWithV2Metadata / GetPackageDevVersions 拿到 Composer V2 解析器所需的完整版本树(含 dev 分支)。

数据模型

PackageInfo

pkg/domain/package.go 中定义,对应 https://packagist.org/packages/{name}.json 响应里 package 字段的内容。

go
type PackageInfo struct {
    Name              string                `json:"name"`
    Description       string                `json:"description"`
    Time              time.Time             `json:"time"`
    Maintainers       []*Maintainer         `json:"maintainers"`
    Versions          map[string]*Version   `json:"versions"`
    Type              string                `json:"type"`
    Repository        string                `json:"repository"`
    GithubStars       int                   `json:"github_stars"`
    GithubWatchers    int                   `json:"github_watchers"`
    GithubForks       int                   `json:"github_forks"`
    GithubOpenIssues  int                   `json:"github_open_issues"`
    Language          string                `json:"language"`
    Dependents        int                   `json:"dependents"`
    Suggesters        int                   `json:"suggesters"`
    Downloads         PackageDownloads      `json:"downloads"`
    Favers            int                   `json:"favers"`
}
字段类型说明
Namestring包名(vendor/package)
Descriptionstring包描述
Timetime.Time包信息最近更新时间
Maintainers[]*Maintainer维护者列表(NameAvatarURL
Versionsmap[string]*Version版本号 → 版本详情,键如 "v6.4.0"
Typestring包类型,如 librarycomposer-plugin
Repositorystring源代码仓库 URL
GithubStarsint仓库的 star / watcher / fork / open issue 计数
Languagestring仓库主语言
Dependentsint被多少个其它包依赖
Suggestersint建议安装数
DownloadsPackageDownloads下载统计(见下)
Faversint收藏数

PackageDownloads

go
type PackageDownloads struct {
    Total   int `json:"total"`
    Monthly int `json:"monthly"`
    Daily   int `json:"daily"`
}
字段类型说明
Totalint历史总下载
Monthlyint本月下载
Dailyint今日下载

ComposerPackageInfo

GetPackage 返回的顶层结构,在 PackageInfo 外再包了一层,加上包名、时间戳等本地化字段:

go
type ComposerPackageInfo struct {
    PackageName          string     `json:"package_name"`
    PackageNameLowercase string     `json:"package_name_lowercase"`
    Package              PackageInfo `json:"package"`
    PackageInfoMd5       string     `json:"package_info_md5"`
    CreateTime           *time.Time `json:"create_time"`
    UpdateTime           *time.Time `json:"update_time"`
    ChangeTime           *time.Time `json:"change_time"`
}
字段类型说明
PackageNamestring包名(与请求时传入一致)
PackageNameLowercasestring小写包名,用于不区分大小写的查询
PackagePackageInfo真正的包信息
PackageInfoMd5string信息 MD5,用于识别是否较之前发生变化(SDK 当前不自动填充)
CreateTime / UpdateTime / ChangeTime*time.Time创建 / 更新 / 变更时间戳(GetPackage 会把当前时间填入前两者)

PackageStatsResponse

pkg/domain/package_stats.go,对应 /packages/{name}/stats.json

go
type PackageStatsResponse struct {
    Downloads PackageDownloads `json:"downloads"`
    Versions  []string         `json:"versions"`
    Date      string           `json:"date"`
}
字段类型说明
DownloadsPackageDownloads下载统计(total/monthly/daily)
Versions[]string可用版本列表
Datestring统计起始日期

ChangeTrackingResponse / ChangeAction

pkg/domain/package_stats.go,对应 /metadata/changes.json

go
type ChangeTrackingResponse struct {
    Error     string          `json:"error,omitempty"`
    Timestamp int64           `json:"timestamp"`
    Actions   []ChangeAction  `json:"actions,omitempty"`
}

type ChangeAction struct {
    Type    string `json:"type"`
    Package string `json:"package"`
    Time    int64  `json:"time"`
}
字段类型说明
Errorstringsince 参数缺失或无效时返回的错误信息
Timestampint64当前时间戳,作为下次增量同步的游标
Actions[]ChangeAction变更操作列表
Actions[].Typestring操作类型:updatedelete
Actions[].Packagestring发生变更的包名
Actions[].Timeint64操作发生时间的 Unix 时间戳

GetPackage

📦 获取指定包的完整信息(含版本、维护者、下载统计、GitHub 指标)。对应 GET https://packagist.org/packages/{name}.json

签名

go
func (c *ComposerClient) GetPackage(packageName string) (*domain.ComposerPackageInfo, error)

参数

参数类型说明
packageNamestring包名,格式 vendor/package,如 symfony/console

返回值

类型说明
结果*domain.ComposerPackageInfo包信息,Package 字段为详细元数据
错误errorHTTP 失败、非 200、JSON 解析失败时返回

示例

go
package main

import (
    "fmt"
    "log"
    "time"

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

func main() {
    c := client.NewComposerClient(30 * time.Second)

    info, err := c.GetPackage("symfony/console")
    if err != nil {
        log.Fatalf("获取包信息失败: %v", err)
    }
    fmt.Printf("包名: %s\n", info.PackageName)
    fmt.Printf("描述: %s\n", info.Package.Description)
    fmt.Printf("类型: %s\n", info.Package.Type)
    fmt.Printf("GitHub stars: %d\n", info.Package.GithubStars)
    fmt.Printf("总下载: %d (本月 %d, 今日 %d)\n",
        info.Package.Downloads.Total,
        info.Package.Downloads.Monthly,
        info.Package.Downloads.Daily,
    )
    fmt.Printf("版本数: %d\n", len(info.Package.Versions))
    for v := range info.Package.Versions {
        fmt.Println("  -", v)
    }
}

响应解析

/packages/{name}.json 返回 {"package": {...}} 外层包装。GetPackage 内部先解到 wrapper 结构取出 PackageInfo,再封装为 ComposerPackageInfo,并把 CreateTime / UpdateTime 填为当前时间,方便直接入库。


GetPackageWithV2Metadata

📦 获取 Composer V2 元数据格式的包信息(原始 JSON 字节)。对应 GET https://repo.packagist.org/p2/{name}.json

签名

go
func (c *ComposerClient) GetPackageWithV2Metadata(packageName string) ([]byte, error)

参数

参数类型说明
packageNamestring包名,如 symfony/console

返回值

类型说明
数据[]byteV2 元数据原始 JSON 字节
错误errorHTTP 失败、非 200 时返回

示例

go
data, err := c.GetPackageWithV2Metadata("symfony/console")
if err != nil {
    log.Fatal(err)
}
// 自行按 V2 schema 解析,或直接落盘
fmt.Println(string(data))

为什么返回 []byte?

V2 元数据结构较复杂(含 packages.{name}.{version} 多层嵌套与 minified 字段),不同使用方关心的字段不同,因此 SDK 不强行建模,把原始 JSON 交给调用方按需解析。


GetPackageDevVersions

📦 获取包的开发版本(分支版本)信息(原始 JSON 字节)。对应 GET https://repo.packagist.org/p2/{name}~dev.json

签名

go
func (c *ComposerClient) GetPackageDevVersions(packageName string) ([]byte, error)

参数

参数类型说明
packageNamestring包名,如 symfony/console

返回值

类型说明
数据[]bytedev 版本元数据原始 JSON 字节
错误errorHTTP 失败、非 200 时返回

示例

go
data, err := c.GetPackageDevVersions("symfony/console")
if err != nil {
    log.Fatal(err)
}
// 包含 dev-master / dev-main 等分支版本
fmt.Println(string(data))

URL 约定

Packagist 用包名后追加 ~dev 表示请求开发分支版本,SDK 自动拼接为 {repoURL}/p2/{name}~dev.json


GetPackageStats

📦 获取包的下载统计与可用版本列表。对应 GET https://packagist.org/packages/{name}/stats.json

签名

go
func (c *ComposerClient) GetPackageStats(packageName string) (*domain.PackageStatsResponse, error)

参数

参数类型说明
packageNamestring包名,如 symfony/console

返回值

类型说明
结果*domain.PackageStatsResponse含下载统计、版本列表、统计日期
错误errorHTTP 失败、非 200、JSON 解析失败时返回

示例

go
stats, err := c.GetPackageStats("symfony/console")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("总下载: %d\n", stats.Downloads.Total)
fmt.Printf("本月下载: %d\n", stats.Downloads.Monthly)
fmt.Printf("今日下载: %d\n", stats.Downloads.Daily)
fmt.Printf("可用版本数: %d\n", len(stats.Versions))
fmt.Printf("统计起始日期: %s\n", stats.Date)

GetPackageChanges

📦 获取包元数据的增量变更记录。对应 GET https://packagist.org/metadata/changes.json?since={timestamp}

签名

go
func (c *ComposerClient) GetPackageChanges(ctx context.Context, since int64) (*domain.ChangeTrackingResponse, error)

参数

参数类型说明
ctxcontext.Context上下文,用于超时与取消
sinceint64增量游标(Unix 时间戳);传 0 则返回当前时间戳但 Actions 为空,用于首次获取游标

返回值

类型说明
结果*domain.ChangeTrackingResponseTimestamp(下次游标)与 Actions 变更列表
错误errorHTTP 失败、非 200、JSON 解析失败时返回

示例

go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// 首次同步:拿一个起始游标
resp, err := c.GetPackageChanges(ctx, 0)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("起始游标: %d\n", resp.Timestamp)

// 下次用这个游标做增量
resp, err = c.GetPackageChanges(ctx, resp.Timestamp)
if err != nil {
    log.Fatal(err)
}
for _, a := range resp.Actions {
    fmt.Printf("[%s] %s @ %d\n", a.Type, a.Package, a.Time)
}
fmt.Printf("下次游标: %d\n", resp.Timestamp)

增量同步模式

since=0 时只返回当前 Timestamp 而不返回历史变更。正确做法是:① 先用 since=0 拿到起始 Timestamp 并持久化;② 此后每次用上次的 Timestamp 作为 since,处理返回的 Actions,再保存新的 Timestamp。若 since 无效,响应 Error 字段会有错误说明。

进阶

镜像同步典型流程

结合 GetPackageChanges + GetPackage 可以构建一个轻量自建镜像:

  1. 首次:GetPackageChanges(ctx, 0) 拿起始游标 T0
  2. 周期性:GetPackageChanges(ctx, T0) 拿到 Actions,对每个 Type=update 的包调 GetPackage 刷新本地缓存,对 Type=delete 的包从本地删除。
  3. 把响应的 Timestamp 存为新的 T0,进入下一轮。

🔗 相关

  • 📋 批量取包名见 包列表
  • 🛠️ 写操作(创建/编辑/更新包)见 包管理

基于 MIT 许可证发布