完善搜索
This commit is contained in:
@@ -511,6 +511,17 @@
|
|||||||
|
|
||||||
prevButton.disabled = currentPage <= 1;
|
prevButton.disabled = currentPage <= 1;
|
||||||
nextButton.disabled = currentPage >= totalPages;
|
nextButton.disabled = currentPage >= totalPages;
|
||||||
|
|
||||||
|
// 添加页码显示
|
||||||
|
const paginationDiv = document.querySelector('.pagination');
|
||||||
|
const pageInfo = document.getElementById('pageInfo');
|
||||||
|
if (!pageInfo) {
|
||||||
|
const infoSpan = document.createElement('span');
|
||||||
|
infoSpan.id = 'pageInfo';
|
||||||
|
infoSpan.style.margin = '0 10px';
|
||||||
|
paginationDiv.insertBefore(infoSpan, nextButton);
|
||||||
|
}
|
||||||
|
document.getElementById('pageInfo').textContent = `第 ${currentPage} / ${totalPages} 页`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSearchResults() {
|
function showSearchResults() {
|
||||||
@@ -539,7 +550,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('执行搜索:', query);
|
console.log('执行搜索:', query);
|
||||||
const response = await fetch(`/search?q=${encodeURIComponent(query)}`);
|
const response = await fetch(`/search?q=${encodeURIComponent(query)}&page=${currentPage}&page_size=25`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -547,6 +558,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('搜索响应:', data);
|
console.log('搜索响应:', data);
|
||||||
|
|
||||||
|
// 更新总页数和分页状态
|
||||||
|
if (typeof data.count === 'number') {
|
||||||
|
totalPages = Math.ceil(data.count / 25);
|
||||||
|
} else {
|
||||||
|
totalPages = data.results ? Math.ceil(data.results.length / 25) : 1;
|
||||||
|
}
|
||||||
|
updatePagination();
|
||||||
|
|
||||||
displayResults(data.results);
|
displayResults(data.results);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('搜索错误:', error);
|
console.error('搜索错误:', error);
|
||||||
@@ -572,57 +592,55 @@
|
|||||||
function formatTimeAgo(dateString) {
|
function formatTimeAgo(dateString) {
|
||||||
if (!dateString) return '未知时间';
|
if (!dateString) return '未知时间';
|
||||||
|
|
||||||
const date = new Date(dateString);
|
try {
|
||||||
if (isNaN(date.getTime())) {
|
let date;
|
||||||
// 尝试解析其他日期格式
|
// 尝试标准格式解析
|
||||||
const formats = [
|
date = new Date(dateString);
|
||||||
'YYYY-MM-DD',
|
|
||||||
'YYYY/MM/DD',
|
|
||||||
'MM/DD/YYYY',
|
|
||||||
'DD/MM/YYYY'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const format of formats) {
|
// 如果解析失败,尝试其他常见格式
|
||||||
const d = tryParseDate(dateString, format);
|
if (isNaN(date.getTime())) {
|
||||||
if (d && !isNaN(d.getTime())) {
|
const formats = [
|
||||||
date = d;
|
{ regex: /^(\d{4})-(\d{2})-(\d{2})$/, handler: (m) => new Date(m[1], m[2] - 1, m[3]) },
|
||||||
break;
|
{ regex: /^(\d{4})\/(\d{2})\/(\d{2})$/, handler: (m) => new Date(m[1], m[2] - 1, m[3]) },
|
||||||
|
{ regex: /^(\d{2})\/(\d{2})\/(\d{4})$/, handler: (m) => new Date(m[3], m[1] - 1, m[2]) }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const format of formats) {
|
||||||
|
const match = dateString.match(format.regex);
|
||||||
|
if (match) {
|
||||||
|
date = format.handler(match);
|
||||||
|
if (!isNaN(date.getTime())) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
console.warn('无法解析日期:', dateString);
|
||||||
|
return '未知时间';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(date.getTime())) return '未知时间';
|
const now = new Date();
|
||||||
|
const diffTime = Math.abs(now - date);
|
||||||
|
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
||||||
|
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
||||||
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
const diffMonths = Math.floor(diffDays / 30);
|
||||||
|
const diffYears = Math.floor(diffDays / 365);
|
||||||
|
|
||||||
|
// 更精确的时间显示
|
||||||
|
if (diffMinutes < 1) return '刚刚';
|
||||||
|
if (diffMinutes < 60) return `${diffMinutes}分钟前`;
|
||||||
|
if (diffHours < 24) return `${diffHours}小时前`;
|
||||||
|
if (diffDays < 7) return `${diffDays}天前`;
|
||||||
|
if (diffDays < 30) return `${Math.floor(diffDays / 7)}周前`;
|
||||||
|
if (diffMonths < 12) return `${diffMonths}个月前`;
|
||||||
|
if (diffYears < 1) return '近1年';
|
||||||
|
return `${diffYears}年前`;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('日期处理错误:', error);
|
||||||
|
return '未知时间';
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const diffTime = Math.abs(now - date);
|
|
||||||
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
|
||||||
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
|
||||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
|
||||||
const diffMonths = Math.floor(diffDays / 30);
|
|
||||||
const diffYears = Math.floor(diffDays / 365);
|
|
||||||
|
|
||||||
if (diffMinutes < 5) return '几秒前';
|
|
||||||
if (diffMinutes < 60) return `${diffMinutes}分钟前`;
|
|
||||||
if (diffHours < 24) return `${diffHours}小时前`;
|
|
||||||
if (diffDays < 7) return `${diffDays}天前`;
|
|
||||||
if (diffDays < 30) return `${Math.floor(diffDays / 7)}周前`;
|
|
||||||
if (diffMonths < 12) return `${diffMonths}个月前`;
|
|
||||||
return `${diffYears}年前`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryParseDate(dateString, format) {
|
|
||||||
const parts = dateString.split(/[-/]/);
|
|
||||||
const formatParts = format.split(/[-/]/);
|
|
||||||
const dateObj = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < formatParts.length; i++) {
|
|
||||||
const part = formatParts[i].toUpperCase();
|
|
||||||
if (part === 'YYYY') dateObj.year = parseInt(parts[i]);
|
|
||||||
else if (part === 'MM') dateObj.month = parseInt(parts[i]) - 1;
|
|
||||||
else if (part === 'DD') dateObj.day = parseInt(parts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(dateObj.year, dateObj.month, dateObj.day);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayResults(results) {
|
function displayResults(results) {
|
||||||
@@ -738,8 +756,10 @@
|
|||||||
function displayTags(tags) {
|
function displayTags(tags) {
|
||||||
const tagList = document.getElementById('tagList');
|
const tagList = document.getElementById('tagList');
|
||||||
const namespace = currentRepo.namespace || (currentRepo.is_official ? 'library' : '');
|
const namespace = currentRepo.namespace || (currentRepo.is_official ? 'library' : '');
|
||||||
const repoName = currentRepo.name || currentRepo.repo_name;
|
const name = currentRepo.name || currentRepo.repo_name || '';
|
||||||
const fullRepoName = namespace ? `${namespace}/${repoName}` : repoName;
|
// 移除可能重复的 library/ 前缀
|
||||||
|
const cleanName = name.replace(/^library\//, '');
|
||||||
|
const fullRepoName = currentRepo.is_official ? cleanName : `${namespace}/${cleanName}`;
|
||||||
|
|
||||||
let header = `
|
let header = `
|
||||||
<div class="tag-header">
|
<div class="tag-header">
|
||||||
@@ -748,12 +768,11 @@
|
|||||||
${fullRepoName}
|
${fullRepoName}
|
||||||
${currentRepo.is_official ? '<span class="badge badge-official">官方</span>' : ''}
|
${currentRepo.is_official ? '<span class="badge badge-official">官方</span>' : ''}
|
||||||
${currentRepo.affiliation ? `<span class="badge badge-organization">By ${currentRepo.affiliation}</span>` : ''}
|
${currentRepo.affiliation ? `<span class="badge badge-organization">By ${currentRepo.affiliation}</span>` : ''}
|
||||||
${currentRepo.is_automated ? '<span class="badge badge-automated">自动构建</span>' : ''}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-description">${currentRepo.short_description || '暂无描述'}</div>
|
<div class="tag-description">${currentRepo.short_description || '暂无描述'}</div>
|
||||||
<div class="tag-meta">
|
<div class="tag-meta">
|
||||||
${currentRepo.star_count > 0 ? `<span class="meta-item">⭐ ${formatNumber(currentRepo.star_count)}</span>` : ''}
|
${currentRepo.star_count > 0 ? `<span class="meta-item">⭐ ${formatNumber(currentRepo.star_count)}</span>` : ''}
|
||||||
${currentRepo.pull_count > 0 ? `<span class="meta-item">⬇️ ${formatNumber(currentRepo.pull_count)}</span>` : ''}
|
${currentRepo.pull_count > 0 ? `<span class="meta-item">⬇️ ${formatNumber(currentRepo.pull_count)}+</span>` : ''}
|
||||||
${currentRepo.last_updated ? `<span class="meta-item">更新于 ${formatTimeAgo(currentRepo.last_updated)}</span>` : ''}
|
${currentRepo.last_updated ? `<span class="meta-item">更新于 ${formatTimeAgo(currentRepo.last_updated)}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-pull-command">
|
<div class="tag-pull-command">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -70,41 +71,206 @@ type cacheEntry struct {
|
|||||||
timestamp time.Time
|
timestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
const (
|
||||||
cache = make(map[string]cacheEntry)
|
maxCacheSize = 1000 // 最大缓存条目数
|
||||||
cacheLock sync.RWMutex
|
cacheTTL = 30 * time.Minute
|
||||||
cacheTTL = 30 * time.Minute
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCachedResult(key string) (interface{}, bool) {
|
type Cache struct {
|
||||||
cacheLock.RLock()
|
data map[string]cacheEntry
|
||||||
defer cacheLock.RUnlock()
|
mu sync.RWMutex
|
||||||
entry, exists := cache[key]
|
maxSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
searchCache = &Cache{
|
||||||
|
data: make(map[string]cacheEntry),
|
||||||
|
maxSize: maxCacheSize,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cache) Get(key string) (interface{}, bool) {
|
||||||
|
c.mu.RLock()
|
||||||
|
entry, exists := c.data[key]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Since(entry.timestamp) > cacheTTL {
|
if time.Since(entry.timestamp) > cacheTTL {
|
||||||
|
c.mu.Lock()
|
||||||
|
delete(c.data, key)
|
||||||
|
c.mu.Unlock()
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.data, true
|
return entry.data, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCacheResult(key string, data interface{}) {
|
func (c *Cache) Set(key string, data interface{}) {
|
||||||
cacheLock.Lock()
|
c.mu.Lock()
|
||||||
defer cacheLock.Unlock()
|
defer c.mu.Unlock()
|
||||||
cache[key] = cacheEntry{
|
|
||||||
|
// 如果缓存已满,删除最旧的条目
|
||||||
|
if len(c.data) >= c.maxSize {
|
||||||
|
oldest := time.Now()
|
||||||
|
var oldestKey string
|
||||||
|
for k, v := range c.data {
|
||||||
|
if v.timestamp.Before(oldest) {
|
||||||
|
oldest = v.timestamp
|
||||||
|
oldestKey = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(c.data, oldestKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.data[key] = cacheEntry{
|
||||||
data: data,
|
data: data,
|
||||||
timestamp: time.Now(),
|
timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Cleanup() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for key, entry := range c.data {
|
||||||
|
if now.Sub(entry.timestamp) > cacheTTL {
|
||||||
|
delete(c.data, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定期清理过期缓存
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
|
for range ticker.C {
|
||||||
|
searchCache.Cleanup()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改进的搜索结果过滤函数
|
||||||
|
func filterSearchResults(results []Repository, query string) []Repository {
|
||||||
|
searchTerm := strings.ToLower(strings.TrimPrefix(query, "library/"))
|
||||||
|
filtered := make([]Repository, 0)
|
||||||
|
|
||||||
|
for _, repo := range results {
|
||||||
|
// 标准化仓库名称
|
||||||
|
repoName := strings.ToLower(repo.Name)
|
||||||
|
repoDesc := strings.ToLower(repo.Description)
|
||||||
|
|
||||||
|
// 计算相关性得分
|
||||||
|
score := 0
|
||||||
|
|
||||||
|
// 完全匹配
|
||||||
|
if repoName == searchTerm {
|
||||||
|
score += 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前缀匹配
|
||||||
|
if strings.HasPrefix(repoName, searchTerm) {
|
||||||
|
score += 50
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含匹配
|
||||||
|
if strings.Contains(repoName, searchTerm) {
|
||||||
|
score += 30
|
||||||
|
}
|
||||||
|
|
||||||
|
// 描述匹配
|
||||||
|
if strings.Contains(repoDesc, searchTerm) {
|
||||||
|
score += 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// 官方镜像加分
|
||||||
|
if repo.IsOfficial {
|
||||||
|
score += 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分数达到阈值的结果才保留
|
||||||
|
if score > 0 {
|
||||||
|
filtered = append(filtered, repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按相关性排序
|
||||||
|
sort.Slice(filtered, func(i, j int) bool {
|
||||||
|
// 优先考虑官方镜像
|
||||||
|
if filtered[i].IsOfficial != filtered[j].IsOfficial {
|
||||||
|
return filtered[i].IsOfficial
|
||||||
|
}
|
||||||
|
// 其次考虑拉取次数
|
||||||
|
return filtered[i].PullCount > filtered[j].PullCount
|
||||||
|
})
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
// searchDockerHub 搜索镜像
|
// searchDockerHub 搜索镜像
|
||||||
func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*SearchResult, error) {
|
func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*SearchResult, error) {
|
||||||
cacheKey := fmt.Sprintf("search:%s:%d:%d", query, page, pageSize)
|
cacheKey := fmt.Sprintf("search:%s:%d:%d", query, page, pageSize)
|
||||||
if cached, ok := getCachedResult(cacheKey); ok {
|
|
||||||
|
// 尝试从缓存获取
|
||||||
|
if cached, ok := searchCache.Get(cacheKey); ok {
|
||||||
return cached.(*SearchResult), nil
|
return cached.(*SearchResult), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重试逻辑
|
||||||
|
var result *SearchResult
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
for retries := 3; retries > 0; retries-- {
|
||||||
|
result, lastErr = trySearchDockerHub(ctx, query, page, pageSize)
|
||||||
|
if lastErr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否需要重试
|
||||||
|
if !isRetryableError(lastErr) {
|
||||||
|
return nil, fmt.Errorf("搜索失败: %v", lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待后重试
|
||||||
|
time.Sleep(time.Second * time.Duration(4-retries))
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastErr != nil {
|
||||||
|
return nil, fmt.Errorf("搜索失败,已重试3次: %v", lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤和处理搜索结果
|
||||||
|
result.Results = filterSearchResults(result.Results, query)
|
||||||
|
result.Count = len(result.Results)
|
||||||
|
|
||||||
|
// 缓存结果
|
||||||
|
searchCache.Set(cacheKey, result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断错误是否可重试
|
||||||
|
func isRetryableError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络错误、超时等可以重试
|
||||||
|
if strings.Contains(err.Error(), "timeout") ||
|
||||||
|
strings.Contains(err.Error(), "connection refused") ||
|
||||||
|
strings.Contains(err.Error(), "no such host") ||
|
||||||
|
strings.Contains(err.Error(), "too many requests") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// trySearchDockerHub 执行实际的Docker Hub API请求
|
||||||
|
func trySearchDockerHub(ctx context.Context, query string, page, pageSize int) (*SearchResult, error) {
|
||||||
// 构建Docker Hub API请求
|
// 构建Docker Hub API请求
|
||||||
baseURL := "https://registry.hub.docker.com/v2/search/repositories/"
|
baseURL := "https://registry.hub.docker.com/v2/search/repositories/"
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
@@ -125,7 +291,17 @@ func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*Se
|
|||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
DisableCompression: true,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
MaxIdleConnsPerHost: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("发送请求失败: %v", err)
|
return nil, fmt.Errorf("发送请求失败: %v", err)
|
||||||
@@ -140,25 +316,25 @@ func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*Se
|
|||||||
|
|
||||||
// 检查响应状态码
|
// 检查响应状态码
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("请求失败: 状态码=%d, 响应=%s", resp.StatusCode, string(body))
|
// 特殊处理常见错误
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
|
return nil, fmt.Errorf("请求过于频繁,请稍后重试")
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return nil, fmt.Errorf("未找到相关镜像")
|
||||||
|
case http.StatusBadGateway, http.StatusServiceUnavailable:
|
||||||
|
return nil, fmt.Errorf("Docker Hub服务暂时不可用,请稍后重试")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("请求失败: 状态码=%d, 响应=%s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印响应内容以便调试
|
|
||||||
fmt.Printf("搜索响应: %s\n", string(body))
|
|
||||||
|
|
||||||
// 解析响应
|
// 解析响应
|
||||||
var result SearchResult
|
var result SearchResult
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印解析后的结果
|
|
||||||
fmt.Printf("搜索结果: 总数=%d, 结果数=%d\n", result.Count, len(result.Results))
|
|
||||||
for i, repo := range result.Results {
|
|
||||||
fmt.Printf("仓库[%d]: 名称=%s, 所有者=%s, 描述=%s, 是否官方=%v\n",
|
|
||||||
i, repo.Name, repo.RepoOwner, repo.Description, repo.IsOfficial)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理搜索结果
|
// 处理搜索结果
|
||||||
for i := range result.Results {
|
for i := range result.Results {
|
||||||
if result.Results[i].IsOfficial {
|
if result.Results[i].IsOfficial {
|
||||||
@@ -180,7 +356,6 @@ func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*Se
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheResult(cacheKey, &result)
|
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +366,7 @@ func getRepositoryTags(ctx context.Context, namespace, name string) ([]TagInfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
cacheKey := fmt.Sprintf("tags:%s:%s", namespace, name)
|
cacheKey := fmt.Sprintf("tags:%s:%s", namespace, name)
|
||||||
if cached, ok := getCachedResult(cacheKey); ok {
|
if cached, ok := searchCache.Get(cacheKey); ok {
|
||||||
return cached.([]TagInfo), nil
|
return cached.([]TagInfo), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +428,8 @@ func getRepositoryTags(ctx context.Context, namespace, name string) ([]TagInfo,
|
|||||||
i, tag.Name, tag.FullSize, tag.LastUpdated)
|
i, tag.Name, tag.FullSize, tag.LastUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheResult(cacheKey, result.Results)
|
// 缓存结果
|
||||||
|
searchCache.Set(cacheKey, result.Results)
|
||||||
return result.Results, nil
|
return result.Results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user