This commit is contained in:
mangomqy
2025-11-13 02:54:06 +00:00
commit c5e51ed069
254 changed files with 54901 additions and 0 deletions

View File

@ -0,0 +1,262 @@
# Artifact MediaType Filter 功能实现
## 概述
实现了 artifact registries 的 mediaType 过滤功能,支持后端返回不同类型的制品,前端在部署时只获取 chart 类型的制品。
## 功能特性
### 后端功能
1. **支持的 MediaType 过滤器**
- `all` - 返回所有类型的 artifacts默认
- `image` - 只返回容器镜像Docker/OCI
- `chart` - 只返回 Helm Charts
- `other` - 返回未识别类型的 artifacts
2. **模糊匹配机制**
- 使用智能模糊匹配来识别 artifact 类型
- 兼容不同版本的 media type 规范
- 支持未来的新 media type 格式
3. **类型识别规则**
- **Helm Chart**:包含 `helm`, `cncf.helm`, `helm.chart`, `chart+json` 等关键词
- **Docker Image**:包含 `docker`, `vnd.docker`, `docker.distribution` 等关键词
- **OCI Image**:包含 `vnd.oci`, `oci.image`, `opencontainers`, `container.image` 等关键词
### 前端功能
1. **API 接口更新**
- `getTags()` 函数现在接受可选的 `mediaType` 参数
- 默认获取所有类型(`"all"`)以支持客户端过滤切换
2. **部署场景**
- 前端在部署场景中只会使用 chart 类型
- LaunchModal 组件专门用于部署 Helm Charts
3. **性能优化**
- 客户端缓存所有类型的 tags
- 支持无需重新请求即可切换过滤器
- 减少不必要的网络请求
## 技术实现
### 后端实现
#### 1. OpenAPI 规范更新
```yaml
# backend/docs/openapi.yaml
parameters:
- name: media_type
in: query
description: Filter artifacts by media type (all, image, chart, other)
schema:
type: string
enum: [all, image, chart, other]
default: all
```
#### 2. REST Handler 更新
```go
// backend/internal/adapter/input/http/rest/artifact_handler.go
func (h *ArtifactHandler) ListArtifacts(w http.ResponseWriter, r *http.Request) {
// 获取 mediaType 过滤参数
mediaTypeFilter := r.URL.Query().Get("media_type")
if mediaTypeFilter == "" {
mediaTypeFilter = "all"
}
artifacts, err := h.artifactService.ListArtifacts(
r.Context(),
registryID,
repositoryName,
mediaTypeFilter,
)
// ...
}
```
#### 3. Domain Service 更新
```go
// backend/internal/domain/service/artifact_service.go
func (s *ArtifactService) ListArtifacts(
ctx context.Context,
registryID,
repository,
mediaTypeFilter string,
) ([]*entity.Artifact, error) {
// ...
return s.ociClient.ListArtifacts(ctx, registry, repository, mediaTypeFilter)
}
```
#### 4. OCI Client 实现
```go
// backend/internal/adapter/output/oci/real/oci_client.go
func (c *OCIClient) shouldIncludeArtifact(artifact *entity.Artifact, filter string) bool {
if filter == "" || filter == "all" {
return true
}
switch filter {
case "chart":
return artifact.Type == entity.ArtifactTypeHelm
case "image":
return artifact.Type == entity.ArtifactTypeDocker ||
artifact.Type == entity.ArtifactTypeOCI
case "other":
return artifact.Type == entity.ArtifactTypeUnknown
default:
return true
}
}
```
### 前端实现
#### 1. API 调用更新
```typescript
// frontend/src/core/api/artifact.api.ts
export async function getTags(
registryId: string,
repository: string,
mediaType: string = "all"
): Promise<Tag[]> {
// REST mode
const url = `/v1/registries/${registryId}/repositories/${encodeURIComponent(repository)}/artifacts?media_type=${mediaType}`;
const response = await apiRequest<Tag[]>(url);
return response;
}
```
#### 2. 组件更新
所有调用 `getTags` 的组件都已更新为默认获取 `"all"` 类型:
- `RegistryTreeExplorer.tsx` - 主浏览器组件
- `RepositoryItem.tsx` - 仓库项组件
- `RegistryCard.tsx` - 注册表卡片组件
## API 使用示例
### 获取所有类型的 artifacts默认
```bash
GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts
# 或
GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=all
```
### 只获取 Helm Charts
```bash
GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart
```
### 只获取容器镜像
```bash
GET /api/v1/registries/harbor-prod/repositories/library%2Falpine/artifacts?media_type=image
```
### 只获取未识别类型
```bash
GET /api/v1/registries/harbor-prod/repositories/misc%2Fdata/artifacts?media_type=other
```
## 测试场景
### 场景 1混合仓库过滤
**仓库内容**
- `charts/vllm-serve:0.1.0` (Helm Chart)
- `charts/vllm-serve:0.2.0` (Helm Chart)
- `library/alpine:3.18` (Docker Image)
- `library/alpine:latest` (Docker Image)
**测试**
```bash
# 获取所有
curl "http://localhost:8080/api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=all"
# 返回2 个 charts
# 只获取 charts
curl "http://localhost:8080/api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart"
# 返回2 个 charts
# 只获取 images
curl "http://localhost:8080/api/v1/registries/harbor-prod/repositories/library%2Falpine/artifacts?media_type=image"
# 返回2 个 images
```
### 场景 2前端部署流程
1. 用户浏览 artifact registries
2. 前端默认显示 chart 过滤器(但获取所有类型以支持切换)
3. 用户点击 "Launch" 按钮部署 Helm Chart
4. LaunchModal 只处理 chart 类型的 artifacts
## 兼容性
### 向后兼容
- 不带 `media_type` 参数的请求默认返回所有类型
- 前端组件可以正常工作,无需升级
- 现有的 API 客户端不受影响
### 未来扩展
该实现支持未来添加新的 artifact 类型:
1. 在后端 `entity.Artifact.SetType()` 中添加新的识别规则
2.`shouldIncludeArtifact()` 中添加新的过滤条件
3. 前端自动支持新类型(通过 `type` 字段)
## 性能考虑
1. **后端过滤**
- 在 OCI client 层面进行过滤,减少内存使用
- 避免传输不需要的数据
2. **前端缓存**
- 获取所有类型并缓存
- 客户端快速切换过滤器
- 减少重复的网络请求
3. **并发控制**
- 批量加载时使用并发限制3个并发
- 避免过多同时请求
## 相关文件
### 后端文件
- `backend/docs/openapi.yaml` - API 规范定义
- `backend/internal/adapter/input/http/rest/artifact_handler.go` - HTTP handler
- `backend/internal/domain/service/artifact_service.go` - 领域服务
- `backend/internal/domain/repository/oci_client.go` - OCI 客户端接口
- `backend/internal/adapter/output/oci/real/oci_client.go` - 真实 OCI 客户端实现
- `backend/internal/adapter/output/oci/mock/oci_client_mock.go` - Mock OCI 客户端实现
- `backend/internal/domain/entity/artifact.go` - Artifact 实体(类型识别逻辑)
### 前端文件
- `frontend/src/core/api/artifact.api.ts` - API 客户端
- `frontend/src/features/artifact/registries/components/RegistryTreeExplorer.tsx` - 主浏览器
- `frontend/src/features/artifact/registries/components/RepositoryItem.tsx` - 仓库项
- `frontend/src/features/artifact/registries/components/RegistryCard.tsx` - 注册表卡片
- `frontend/src/features/artifact/registries/components/LaunchModal.tsx` - 部署模态框
- `frontend/src/features/artifact/registries/utils/artifactType.ts` - 类型工具
## 总结
✅ 后端支持返回所有 artifact 制品
✅ 支持通过 mediaType 参数过滤image、chart、other、all
✅ 采用模糊匹配机制,兼容未来版本
✅ 前端在部署时专注于 chart 类型
✅ 性能优化:客户端缓存 + 服务端过滤
✅ 完全向后兼容
✅ 易于扩展新的 artifact 类型

View File

@ -0,0 +1,225 @@
# MediaType Filter 功能测试指南
## 快速测试
### 1. 启动服务
```bash
# 启动后端Mock 模式)
cd backend
make run-mock
# 启动前端(新终端)
cd frontend
npm run dev
```
### 2. API 测试
#### 测试默认行为(返回所有类型)
```bash
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts"
```
**预期结果**:返回所有 artifactsMock 数据中有 2 个 Helm Charts
#### 测试 Chart 过滤
```bash
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart"
```
**预期结果**:只返回 Helm Charts
#### 测试 Image 过滤
```bash
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/library%2Falpine/artifacts?media_type=image"
```
**预期结果**:只返回 Docker/OCI 镜像Mock 数据中有 2 个 Alpine 镜像)
#### 测试混合过滤
```bash
# 测试获取所有类型
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=all"
# 测试只获取 image应该返回空因为这个 repo 只有 charts
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=image"
# 测试只获取 chart应该返回数据
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart"
```
### 3. 前端功能测试
#### 浏览器测试流程
1. **打开浏览器**http://localhost:5173
2. **登录系统**
- 用户名:`admin`
- 密码:`admin123`
3. **导航到 Artifact Registries**
- 点击左侧菜单的 "Artifact Registries"
4. **测试类型过滤**
- 观察默认过滤器设置为 "Chart"
- 点击不同的过滤器按钮Chart, Image, Other, All
- 验证列表根据类型正确过滤
5. **测试部署功能**
- 展开一个 registry
- 选择一个 chart repository`charts/vllm-serve`
- 点击某个 tag 旁边的 "Launch" 按钮
- 验证只有 chart 类型显示 Launch 按钮
#### 浏览器控制台验证
打开浏览器开发者工具F12在 Console 中查看:
```
[OCI API] Fetching tags for harbor-bwgdi-prod/charts/vllm-serve (mediaType: all)
[RESTful OCI] Got 2 tags for charts/vllm-serve (mediaType: all)
```
### 4. Mock 数据说明
Mock 实现包含以下测试数据:
#### Helm Charts
- `charts/vllm-serve:0.1.0` - ArtifactTypeHelm
- `charts/vllm-serve:0.2.0` - ArtifactTypeHelm
- `charts/nginx:1.0.0` - ArtifactTypeHelm
- `charts/redis:6.2.0` - ArtifactTypeHelm
#### Docker Images
- `library/alpine:3.18` - ArtifactTypeDocker
- `library/alpine:latest` - ArtifactTypeDocker
### 5. 验证检查点
#### ✅ 后端验证
- [ ] API 接受 `media_type` 查询参数
- [ ] 默认行为(无参数)返回所有类型
- [ ] `media_type=chart` 只返回 Helm Charts
- [ ] `media_type=image` 只返回容器镜像
- [ ] `media_type=other` 只返回未知类型
- [ ] `media_type=all` 返回所有类型
#### ✅ 前端验证
- [ ] 前端调用 API 时传递 `media_type` 参数
- [ ] 默认获取所有类型(支持客户端过滤切换)
- [ ] 浏览器界面显示正确的过滤结果
- [ ] Launch Modal 只处理 chart 类型
- [ ] 无需重新请求即可切换过滤器
### 6. 性能测试
#### 测试缓存机制
1. 打开浏览器开发者工具的 Network 标签
2. 展开一个 registry观察网络请求
3. 切换过滤器Chart → Image → All
4. **验证**:切换过滤器时不应该有新的网络请求(使用缓存)
#### 测试并发加载
1. 清除缓存并刷新页面
2. 展开一个 registry
3. **观察**repositories 的 tags 按批次加载(每批 3 个并发)
4. **验证**Console 显示加载进度
### 7. 预期行为
#### 场景 1查看 Helm Chart Repository
```
Request: GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart
Response: [
{
"repositoryName": "charts/vllm-serve",
"tag": "0.1.0",
"type": "helm",
"size": 12345678
},
{
"repositoryName": "charts/vllm-serve",
"tag": "0.2.0",
"type": "helm",
"size": 13456789
}
]
```
#### 场景 2查看 Docker Image Repository
```
Request: GET /api/v1/registries/harbor-prod/repositories/library%2Falpine/artifacts?media_type=image
Response: [
{
"repositoryName": "library/alpine",
"tag": "3.18",
"type": "docker",
"size": 2345678
},
{
"repositoryName": "library/alpine",
"tag": "latest",
"type": "docker",
"size": 2456789
}
]
```
#### 场景 3错误的过滤器应该过滤掉所有
```
Request: GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=image
Response: [] # 空数组,因为这个 repo 只有 charts
```
## 故障排查
### 问题 1后端返回所有类型而不是过滤后的
**检查**
```bash
# 验证参数是否正确传递
curl -v "http://localhost:8080/api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart" | jq
```
**解决方案**:检查 `artifact_handler.go` 中的参数解析
### 问题 2前端过滤器不工作
**检查**
- 打开浏览器控制台查看错误
- 检查网络请求是否包含 `media_type` 参数
- 验证 `getTags()` 函数调用是否正确
### 问题 3类型识别错误
**检查**
- 查看 `entity/artifact.go` 中的 `SetType()` 方法
- 验证 mediaType 值是否匹配识别规则
- 添加日志输出 artifact 的 MediaType 值
## 完成标志
当以下所有测试通过时,功能实现完成:
- ✅ 后端 API 接受并正确处理 `media_type` 参数
- ✅ 不同类型的 artifacts 被正确过滤
- ✅ 前端可以成功调用带参数的 API
- ✅ 前端界面正确显示过滤结果
- ✅ 部署功能只处理 chart 类型
- ✅ 缓存机制正常工作
- ✅ 无 linter 错误
- ✅ 向后兼容(不带参数的请求正常工作)