🏷️ Domain 领域模型概览
pkg/domain 是 Composer Skills 的数据结构层。它不包含任何业务逻辑,只定义与 Packagist REST API 以及 Composer 元数据对应的 Go 结构体,供 pkg/client(API 客户端)和 pkg/repository(仓库访问层)在请求/响应反序列化时使用。
一句话定位
domain 包是「网络字节 → Go 结构体」的契约层:API 返回什么 JSON,这里就有对应什么 struct。
📦 包路径
- 导入路径:
github.com/scagogogo/composer-skills/pkg/domain - 包名:
domain - 依赖:仅依赖 Go 标准库(
time),零外部依赖,可被任何模块安全引用
🧩 为什么需要独立的 domain 层
| 关注点 | 说明 |
|---|---|
| 🔄 解耦 | API 客户端(pkg/client)只负责 HTTP,不感知字段含义;业务代码只消费 struct,不感知 JSON |
| 🧪 可测试 | 测试时可直接构造 domain 结构体做断言,无需 mock 网络 |
| 📊 可持久化 | 多数字段同时标注了 json 与 bson tag,可直接落库 MongoDB |
| 🎯 类型安全 | 把松散的 JSON 映射成强类型 Go 字段,编译期捕获字段拼写错误 |
📋 类型清单
按用途分为六大类,每类对应一个子文档:
📦 包信息模型 — package.md
| 类型 | 用途 | 来源文件 |
|---|---|---|
PackageInfo | 包的基本信息(名称、描述、GitHub 统计、下载量等) | package.go |
ComposerPackageInfo | 包的完整信息(含小写包名、md5、三个时间戳) | package.go |
PackageDownloads | 下载统计(总/月/日) | package.go |
Maintainer | 维护者(姓名、头像) | maintainer.go |
Version | 单个版本的完整元数据(依赖、源码、dist、license 等) | version.go |
🔒 安全公告模型 — advisory.md
| 类型 | 用途 | 来源文件 |
|---|---|---|
AdvisoriesResponse | 安全公告顶层响应(按包名分组) | advisory.go |
Advisory | 单条安全公告(CVE、受影响版本、来源) | advisory.go |
Source | 公告来源(GitHub、NVD 等) | advisory.go |
🔍 搜索结果模型 — search.md
| 类型 | 用途 | 来源文件 |
|---|---|---|
SearchResponse | 搜索响应顶层(结果列表 + 分页) | search.go |
SearchResult | 单条搜索结果 | search.go |
📊 统计模型 — statistics.md
| 类型 | 用途 | 来源文件 |
|---|---|---|
StatisticsResponse | 仓库统计顶层 | statistics.go |
Totals | 仓库总量(下载/包/版本) | statistics.go |
PackageStatsResponse | 单包下载统计 | package_stats.go |
ChangeTrackingResponse | 元数据变更跟踪响应 | package_stats.go |
ChangeAction | 单条变更操作(update/delete) | package_stats.go |
🛠️ 创建/编辑/列表模型 — create-package.md
| 类型 | 用途 | 来源文件 |
|---|---|---|
PackageCreateRequest / PackageCreateResponse | 创建包 | create_package.go |
PackageEditRequest / PackageEditResponse | 编辑包 | create_package.go |
PackageUpdateRequest / PackageUpdateResponse | 更新包 | create_package.go |
PackageListResponse | 包名列表 | package_list.go |
PackageListWithDataResponse | 带附加数据的包列表 | package_list.go |
PackageData | 包附加数据(类型/仓库/废弃状态) | package_list.go |
PopularPackagesResponse / PopularPackage | 流行包列表 | popular_packages.go |
🔗 类型与 API 方法的对应关系
下表列出 pkg/client.ComposerClient 的方法与返回/入参 domain 类型的对应:
| ComposerClient 方法 | 入参 domain 类型 | 返回 domain 类型 |
|---|---|---|
GetPackage | — | *ComposerPackageInfo |
GetStatistics | — | *StatisticsResponse |
GetSecurityAdvisories | — | *AdvisoriesResponse |
GetSecurityAdvisoriesForPackages | — | *AdvisoriesResponse |
GetSecurityAdvisoriesSince | — | *AdvisoriesResponse |
ListPackages / ListPackagesByVendor / ListPackagesByType | — | *PackageListResponse |
ListPackagesWithData | — | *PackageListWithDataResponse |
ListPopularPackages | — | *PopularPackagesResponse |
SearchPackages / SearchPackagesByTags / SearchPackagesByType | — | *SearchResponse |
GetPackageStats | — | *PackageStatsResponse |
GetPackageChanges | — | *ChangeTrackingResponse |
CreatePackage | *PackageCreateRequest | *PackageCreateResponse |
EditPackage | *PackageEditRequest | *PackageEditResponse |
UpdatePackage | *PackageUpdateRequest | *PackageUpdateResponse |
同样适用于 Repository 层
pkg/repository.Repository 的方法(如 Statistics、ListSecurityAdvisories、ListAdvisories)也复用同一套 domain 类型,保证两层行为一致。
🚀 快速示例
从 Packagist 拿到一个包的信息后,domain 结构体长这样:
go
package main
import (
"fmt"
"log"
"time"
"github.com/scagogogo/composer-skills/pkg/client"
"github.com/scagogogo/composer-skills/pkg/domain"
)
func main() {
c := client.NewComposerClient(30*time.Second)
info, err := c.GetPackage("symfony/console")
if err != nil {
log.Fatalf("获取包信息失败: %v", err)
}
// info 是 *domain.ComposerPackageInfo
fmt.Printf("包名: %s\n", info.PackageName)
fmt.Printf("描述: %s\n", info.Package.Description)
fmt.Printf("总下载量: %d\n", info.Package.Downloads.Total)
fmt.Printf("GitHub Stars: %d\n", info.Package.GithubStars)
for name := range info.Package.Versions {
fmt.Printf("版本: %s\n", name)
}
}📚 下一步
- 📦 想了解包详情字段 → package.md
- 🔒 关心安全审计 → advisory.md
- 🔍 做包搜索 → search.md
- 📊 看仓库/包统计 → statistics.md
- 🛠️ 要创建/编辑包 → create-package.md