优化代码格式
This commit is contained in:
@@ -78,7 +78,7 @@ func DefaultConfig() *AppConfig {
|
|||||||
Host: "0.0.0.0",
|
Host: "0.0.0.0",
|
||||||
Port: 5000,
|
Port: 5000,
|
||||||
FileSize: 2 * 1024 * 1024 * 1024, // 2GB
|
FileSize: 2 * 1024 * 1024 * 1024, // 2GB
|
||||||
EnableH2C: false, // 默认关闭H2C
|
EnableH2C: false, // 默认关闭H2C
|
||||||
},
|
},
|
||||||
RateLimit: struct {
|
RateLimit: struct {
|
||||||
RequestLimit int `toml:"requestLimit"`
|
RequestLimit int `toml:"requestLimit"`
|
||||||
@@ -268,4 +268,4 @@ func CreateDefaultConfigFile() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile("config.toml", data, 0644)
|
return os.WriteFile("config.toml", data, 0644)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ require (
|
|||||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
|
||||||
golang.org/x/sync v0.14.0 // indirect
|
golang.org/x/sync v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
|||||||
@@ -605,4 +605,4 @@ func createUpstreamOptions(mapping config.RegistryMapping) []remote.Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func GitHubProxyHandler(c *gin.Context) {
|
|||||||
for strings.HasPrefix(rawPath, "/") {
|
for strings.HasPrefix(rawPath, "/") {
|
||||||
rawPath = strings.TrimPrefix(rawPath, "/")
|
rawPath = strings.TrimPrefix(rawPath, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动补全协议头
|
// 自动补全协议头
|
||||||
if !strings.HasPrefix(rawPath, "https://") {
|
if !strings.HasPrefix(rawPath, "https://") {
|
||||||
if strings.HasPrefix(rawPath, "http:/") || strings.HasPrefix(rawPath, "https:/") {
|
if strings.HasPrefix(rawPath, "http:/") || strings.HasPrefix(rawPath, "https:/") {
|
||||||
@@ -47,7 +47,7 @@ func GitHubProxyHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
rawPath = "https://" + rawPath
|
rawPath = "https://" + rawPath
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := CheckGitHubURL(rawPath)
|
matches := CheckGitHubURL(rawPath)
|
||||||
if matches != nil {
|
if matches != nil {
|
||||||
if allowed, reason := utils.GlobalAccessController.CheckGitHubAccess(matches); !allowed {
|
if allowed, reason := utils.GlobalAccessController.CheckGitHubAccess(matches); !allowed {
|
||||||
@@ -96,7 +96,7 @@ func proxyGitHubWithRedirect(c *gin.Context, u string, redirectCount int) {
|
|||||||
c.String(http.StatusLoopDetected, "重定向次数过多,可能存在循环重定向")
|
c.String(http.StatusLoopDetected, "重定向次数过多,可能存在循环重定向")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(c.Request.Method, u, c.Request.Body)
|
req, err := http.NewRequest(c.Request.Method, u, c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", err))
|
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", err))
|
||||||
@@ -210,4 +210,4 @@ func proxyGitHubWithRedirect(c *gin.Context, u string, redirectCount int) {
|
|||||||
// 直接流式转发
|
// 直接流式转发
|
||||||
io.Copy(c.Writer, resp.Body)
|
io.Copy(c.Writer, resp.Body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -855,4 +855,4 @@ func (is *ImageStreamer) StreamMultipleImages(ctx context.Context, imageRefs []s
|
|||||||
|
|
||||||
log.Printf("批量下载完成,共处理 %d 个镜像", len(imageRefs))
|
log.Printf("批量下载完成,共处理 %d 个镜像", len(imageRefs))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,27 +26,27 @@ type SearchResult struct {
|
|||||||
|
|
||||||
// Repository 仓库信息
|
// Repository 仓库信息
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
Name string `json:"repo_name"`
|
Name string `json:"repo_name"`
|
||||||
Description string `json:"short_description"`
|
Description string `json:"short_description"`
|
||||||
IsOfficial bool `json:"is_official"`
|
IsOfficial bool `json:"is_official"`
|
||||||
IsAutomated bool `json:"is_automated"`
|
IsAutomated bool `json:"is_automated"`
|
||||||
StarCount int `json:"star_count"`
|
StarCount int `json:"star_count"`
|
||||||
PullCount int `json:"pull_count"`
|
PullCount int `json:"pull_count"`
|
||||||
RepoOwner string `json:"repo_owner"`
|
RepoOwner string `json:"repo_owner"`
|
||||||
LastUpdated string `json:"last_updated"`
|
LastUpdated string `json:"last_updated"`
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
Organization string `json:"affiliation"`
|
Organization string `json:"affiliation"`
|
||||||
PullsLastWeek int `json:"pulls_last_week"`
|
PullsLastWeek int `json:"pulls_last_week"`
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagInfo 标签信息
|
// TagInfo 标签信息
|
||||||
type TagInfo struct {
|
type TagInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FullSize int64 `json:"full_size"`
|
FullSize int64 `json:"full_size"`
|
||||||
LastUpdated time.Time `json:"last_updated"`
|
LastUpdated time.Time `json:"last_updated"`
|
||||||
LastPusher string `json:"last_pusher"`
|
LastPusher string `json:"last_pusher"`
|
||||||
Images []Image `json:"images"`
|
Images []Image `json:"images"`
|
||||||
Vulnerabilities struct {
|
Vulnerabilities struct {
|
||||||
Critical int `json:"critical"`
|
Critical int `json:"critical"`
|
||||||
High int `json:"high"`
|
High int `json:"high"`
|
||||||
@@ -79,15 +79,15 @@ type cacheEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxCacheSize = 1000
|
maxCacheSize = 1000
|
||||||
maxPaginationCache = 200
|
maxPaginationCache = 200
|
||||||
cacheTTL = 30 * time.Minute
|
cacheTTL = 30 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
data map[string]cacheEntry
|
data map[string]cacheEntry
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
maxSize int
|
maxSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -101,18 +101,18 @@ func (c *Cache) Get(key string) (interface{}, bool) {
|
|||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
entry, exists := c.data[key]
|
entry, exists := c.data[key]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(entry.expiresAt) {
|
if time.Now().After(entry.expiresAt) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
delete(c.data, key)
|
delete(c.data, key)
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.data, true
|
return entry.data, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +123,11 @@ func (c *Cache) Set(key string, data interface{}) {
|
|||||||
func (c *Cache) SetWithTTL(key string, data interface{}, ttl time.Duration) {
|
func (c *Cache) SetWithTTL(key string, data interface{}, ttl time.Duration) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if len(c.data) >= c.maxSize {
|
if len(c.data) >= c.maxSize {
|
||||||
c.cleanupExpiredLocked()
|
c.cleanupExpiredLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
c.data[key] = cacheEntry{
|
c.data[key] = cacheEntry{
|
||||||
data: data,
|
data: data,
|
||||||
expiresAt: time.Now().Add(ttl),
|
expiresAt: time.Now().Add(ttl),
|
||||||
@@ -153,7 +153,7 @@ func init() {
|
|||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(5 * time.Minute)
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
searchCache.Cleanup()
|
searchCache.Cleanup()
|
||||||
}
|
}
|
||||||
@@ -163,45 +163,45 @@ func init() {
|
|||||||
func filterSearchResults(results []Repository, query string) []Repository {
|
func filterSearchResults(results []Repository, query string) []Repository {
|
||||||
searchTerm := strings.ToLower(strings.TrimPrefix(query, "library/"))
|
searchTerm := strings.ToLower(strings.TrimPrefix(query, "library/"))
|
||||||
filtered := make([]Repository, 0)
|
filtered := make([]Repository, 0)
|
||||||
|
|
||||||
for _, repo := range results {
|
for _, repo := range results {
|
||||||
repoName := strings.ToLower(repo.Name)
|
repoName := strings.ToLower(repo.Name)
|
||||||
repoDesc := strings.ToLower(repo.Description)
|
repoDesc := strings.ToLower(repo.Description)
|
||||||
|
|
||||||
score := 0
|
score := 0
|
||||||
|
|
||||||
if repoName == searchTerm {
|
if repoName == searchTerm {
|
||||||
score += 100
|
score += 100
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(repoName, searchTerm) {
|
if strings.HasPrefix(repoName, searchTerm) {
|
||||||
score += 50
|
score += 50
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(repoName, searchTerm) {
|
if strings.Contains(repoName, searchTerm) {
|
||||||
score += 30
|
score += 30
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(repoDesc, searchTerm) {
|
if strings.Contains(repoDesc, searchTerm) {
|
||||||
score += 10
|
score += 10
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.IsOfficial {
|
if repo.IsOfficial {
|
||||||
score += 20
|
score += 20
|
||||||
}
|
}
|
||||||
|
|
||||||
if score > 0 {
|
if score > 0 {
|
||||||
filtered = append(filtered, repo)
|
filtered = append(filtered, repo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(filtered, func(i, j int) bool {
|
sort.Slice(filtered, func(i, j int) bool {
|
||||||
if filtered[i].IsOfficial != filtered[j].IsOfficial {
|
if filtered[i].IsOfficial != filtered[j].IsOfficial {
|
||||||
return filtered[i].IsOfficial
|
return filtered[i].IsOfficial
|
||||||
}
|
}
|
||||||
return filtered[i].PullCount > filtered[j].PullCount
|
return filtered[i].PullCount > filtered[j].PullCount
|
||||||
})
|
})
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ func normalizeRepository(repo *Repository) {
|
|||||||
if repo.Namespace == "" && repo.RepoOwner != "" {
|
if repo.Namespace == "" && repo.RepoOwner != "" {
|
||||||
repo.Namespace = repo.RepoOwner
|
repo.Namespace = repo.RepoOwner
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(repo.Name, "/") {
|
if strings.Contains(repo.Name, "/") {
|
||||||
parts := strings.Split(repo.Name, "/")
|
parts := strings.Split(repo.Name, "/")
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
@@ -239,14 +239,14 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
return nil, fmt.Errorf("搜索请求过于复杂,请尝试更具体的关键词")
|
return nil, fmt.Errorf("搜索请求过于复杂,请尝试更具体的关键词")
|
||||||
}
|
}
|
||||||
cacheKey := fmt.Sprintf("search:%s:%d:%d", query, page, pageSize)
|
cacheKey := fmt.Sprintf("search:%s:%d:%d", query, page, pageSize)
|
||||||
|
|
||||||
if cached, ok := searchCache.Get(cacheKey); ok {
|
if cached, ok := searchCache.Get(cacheKey); ok {
|
||||||
return cached.(*SearchResult), nil
|
return cached.(*SearchResult), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserRepo := strings.Contains(query, "/")
|
isUserRepo := strings.Contains(query, "/")
|
||||||
var namespace, repoName string
|
var namespace, repoName string
|
||||||
|
|
||||||
if isUserRepo {
|
if isUserRepo {
|
||||||
parts := strings.Split(query, "/")
|
parts := strings.Split(query, "/")
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
@@ -254,11 +254,11 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
repoName = parts[1]
|
repoName = parts[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := "https://registry.hub.docker.com/v2"
|
baseURL := "https://registry.hub.docker.com/v2"
|
||||||
var fullURL string
|
var fullURL string
|
||||||
var params url.Values
|
var params url.Values
|
||||||
|
|
||||||
if isUserRepo && namespace != "" {
|
if isUserRepo && namespace != "" {
|
||||||
fullURL = fmt.Sprintf("%s/repositories/%s/", baseURL, namespace)
|
fullURL = fmt.Sprintf("%s/repositories/%s/", baseURL, namespace)
|
||||||
params = url.Values{
|
params = url.Values{
|
||||||
@@ -273,20 +273,20 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
"page_size": {fmt.Sprintf("%d", pageSize)},
|
"page_size": {fmt.Sprintf("%d", pageSize)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fullURL = fullURL + "?" + params.Encode()
|
fullURL = fullURL + "?" + params.Encode()
|
||||||
|
|
||||||
resp, err := utils.GetSearchHTTPClient().Get(fullURL)
|
resp, err := utils.GetSearchHTTPClient().Get(fullURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("请求Docker Hub API失败: %v", err)
|
return nil, fmt.Errorf("请求Docker Hub API失败: %v", err)
|
||||||
}
|
}
|
||||||
defer safeCloseResponseBody(resp.Body, "搜索响应体")
|
defer safeCloseResponseBody(resp.Body, "搜索响应体")
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
case http.StatusTooManyRequests:
|
case http.StatusTooManyRequests:
|
||||||
@@ -302,7 +302,7 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
return nil, fmt.Errorf("请求失败: 状态码=%d, 响应=%s", resp.StatusCode, string(body))
|
return nil, fmt.Errorf("请求失败: 状态码=%d, 响应=%s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result *SearchResult
|
var result *SearchResult
|
||||||
if isUserRepo && namespace != "" {
|
if isUserRepo && namespace != "" {
|
||||||
var userRepos struct {
|
var userRepos struct {
|
||||||
@@ -314,14 +314,14 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
if err := json.Unmarshal(body, &userRepos); err != nil {
|
if err := json.Unmarshal(body, &userRepos); err != nil {
|
||||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result = &SearchResult{
|
result = &SearchResult{
|
||||||
Count: userRepos.Count,
|
Count: userRepos.Count,
|
||||||
Next: userRepos.Next,
|
Next: userRepos.Next,
|
||||||
Previous: userRepos.Previous,
|
Previous: userRepos.Previous,
|
||||||
Results: make([]Repository, 0),
|
Results: make([]Repository, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repo := range userRepos.Results {
|
for _, repo := range userRepos.Results {
|
||||||
if repoName == "" || strings.Contains(strings.ToLower(repo.Name), strings.ToLower(repoName)) {
|
if repoName == "" || strings.Contains(strings.ToLower(repo.Name), strings.ToLower(repoName)) {
|
||||||
repo.Namespace = namespace
|
repo.Namespace = namespace
|
||||||
@@ -329,22 +329,22 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
result.Results = append(result.Results, repo)
|
result.Results = append(result.Results, repo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Results) == 0 {
|
if len(result.Results) == 0 {
|
||||||
return searchDockerHubWithDepth(ctx, repoName, page, pageSize, depth+1)
|
return searchDockerHubWithDepth(ctx, repoName, page, pageSize, depth+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Count = len(result.Results)
|
result.Count = len(result.Results)
|
||||||
} else {
|
} else {
|
||||||
result = &SearchResult{}
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range result.Results {
|
for i := range result.Results {
|
||||||
normalizeRepository(&result.Results[i])
|
normalizeRepository(&result.Results[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
if isUserRepo && namespace != "" {
|
if isUserRepo && namespace != "" {
|
||||||
filteredResults := make([]Repository, 0)
|
filteredResults := make([]Repository, 0)
|
||||||
for _, repo := range result.Results {
|
for _, repo := range result.Results {
|
||||||
@@ -356,7 +356,7 @@ func searchDockerHubWithDepth(ctx context.Context, query string, page, pageSize
|
|||||||
result.Count = len(filteredResults)
|
result.Count = len(filteredResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchCache.Set(cacheKey, result)
|
searchCache.Set(cacheKey, result)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -365,14 +365,14 @@ func isRetryableError(err error) bool {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "timeout") ||
|
if strings.Contains(err.Error(), "timeout") ||
|
||||||
strings.Contains(err.Error(), "connection refused") ||
|
strings.Contains(err.Error(), "connection refused") ||
|
||||||
strings.Contains(err.Error(), "no such host") ||
|
strings.Contains(err.Error(), "no such host") ||
|
||||||
strings.Contains(err.Error(), "too many requests") {
|
strings.Contains(err.Error(), "too many requests") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ func fetchTagPage(ctx context.Context, url string, maxRetries int) (*struct {
|
|||||||
Results []TagInfo `json:"results"`
|
Results []TagInfo `json:"results"`
|
||||||
}, error) {
|
}, error) {
|
||||||
var lastErr error
|
var lastErr error
|
||||||
|
|
||||||
for retry := 0; retry < maxRetries; retry++ {
|
for retry := 0; retry < maxRetries; retry++ {
|
||||||
if retry > 0 {
|
if retry > 0 {
|
||||||
time.Sleep(time.Duration(retry) * 500 * time.Millisecond)
|
time.Sleep(time.Duration(retry) * 500 * time.Millisecond)
|
||||||
@@ -442,7 +442,7 @@ func fetchTagPage(ctx context.Context, url string, maxRetries int) (*struct {
|
|||||||
defer safeCloseResponseBody(resp.Body, "标签响应体")
|
defer safeCloseResponseBody(resp.Body, "标签响应体")
|
||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
if retry < maxRetries-1 {
|
if retry < maxRetries-1 {
|
||||||
@@ -478,21 +478,21 @@ func fetchTagPage(ctx context.Context, url string, maxRetries int) (*struct {
|
|||||||
|
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, lastErr
|
return nil, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePaginationParams(c *gin.Context, defaultPageSize int) (page, pageSize int) {
|
func parsePaginationParams(c *gin.Context, defaultPageSize int) (page, pageSize int) {
|
||||||
page = 1
|
page = 1
|
||||||
pageSize = defaultPageSize
|
pageSize = defaultPageSize
|
||||||
|
|
||||||
if p := c.Query("page"); p != "" {
|
if p := c.Query("page"); p != "" {
|
||||||
fmt.Sscanf(p, "%d", &page)
|
fmt.Sscanf(p, "%d", &page)
|
||||||
}
|
}
|
||||||
if ps := c.Query("page_size"); ps != "" {
|
if ps := c.Query("page_size"); ps != "" {
|
||||||
fmt.Sscanf(ps, "%d", &pageSize)
|
fmt.Sscanf(ps, "%d", &pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return page, pageSize
|
return page, pageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,4 +556,4 @@ func RegisterSearchRoute(r *gin.Engine) {
|
|||||||
c.JSON(http.StatusOK, tags)
|
c.JSON(http.StatusOK, tags)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/main.go
42
src/main.go
@@ -119,12 +119,12 @@ func main() {
|
|||||||
fmt.Printf("🚀 HubProxy 启动成功\n")
|
fmt.Printf("🚀 HubProxy 启动成功\n")
|
||||||
fmt.Printf("📡 监听地址: %s:%d\n", cfg.Server.Host, cfg.Server.Port)
|
fmt.Printf("📡 监听地址: %s:%d\n", cfg.Server.Host, cfg.Server.Port)
|
||||||
fmt.Printf("⚡ 限流配置: %d请求/%g小时\n", cfg.RateLimit.RequestLimit, cfg.RateLimit.PeriodHours)
|
fmt.Printf("⚡ 限流配置: %d请求/%g小时\n", cfg.RateLimit.RequestLimit, cfg.RateLimit.PeriodHours)
|
||||||
|
|
||||||
// 显示HTTP/2支持状态
|
// 显示HTTP/2支持状态
|
||||||
if cfg.Server.EnableH2C {
|
if cfg.Server.EnableH2C {
|
||||||
fmt.Printf("H2c: 已启用\n")
|
fmt.Printf("H2c: 已启用\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("🔗 项目地址: https://github.com/sky22333/hubproxy\n")
|
fmt.Printf("🔗 项目地址: https://github.com/sky22333/hubproxy\n")
|
||||||
|
|
||||||
// 创建HTTP2服务器
|
// 创建HTTP2服务器
|
||||||
@@ -155,17 +155,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 简单的健康检查
|
// 简单的健康检查
|
||||||
func formatBeijingTime(t time.Time) string {
|
|
||||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
|
||||||
if err != nil {
|
|
||||||
loc = time.FixedZone("CST", 8*3600)
|
|
||||||
}
|
|
||||||
return t.In(loc).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDuration(d time.Duration) string {
|
func formatDuration(d time.Duration) string {
|
||||||
if d < time.Minute {
|
if d < time.Minute {
|
||||||
return fmt.Sprintf("%d秒", int(d.Seconds()))
|
return fmt.Sprintf("%d秒", int(d.Seconds()))
|
||||||
@@ -180,26 +170,20 @@ func formatDuration(d time.Duration) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initHealthRoutes(router *gin.Engine) {
|
func getUptimeInfo() (time.Duration, float64, string) {
|
||||||
router.GET("/health", func(c *gin.Context) {
|
uptime := time.Since(serviceStartTime)
|
||||||
uptime := time.Since(serviceStartTime)
|
return uptime, uptime.Seconds(), formatDuration(uptime)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
}
|
||||||
"status": "healthy",
|
|
||||||
"timestamp_unix": serviceStartTime.Unix(),
|
|
||||||
"uptime_sec": uptime.Seconds(),
|
|
||||||
"service": "hubproxy",
|
|
||||||
"start_time_bj": formatBeijingTime(serviceStartTime),
|
|
||||||
"uptime_human": formatDuration(uptime),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
func initHealthRoutes(router *gin.Engine) {
|
||||||
router.GET("/ready", func(c *gin.Context) {
|
router.GET("/ready", func(c *gin.Context) {
|
||||||
uptime := time.Since(serviceStartTime)
|
_, uptimeSec, uptimeHuman := getUptimeInfo()
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"ready": true,
|
"ready": true,
|
||||||
"timestamp_unix": time.Now().Unix(),
|
"service": "hubproxy",
|
||||||
"uptime_sec": uptime.Seconds(),
|
"start_time_unix": serviceStartTime.Unix(),
|
||||||
"uptime_human": formatDuration(uptime),
|
"uptime_sec": uptimeSec,
|
||||||
|
"uptime_human": uptimeHuman,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,4 +202,4 @@ func (ac *AccessController) checkList(matches, list []string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,4 +161,4 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,4 +64,4 @@ func GetGlobalHTTPClient() *http.Client {
|
|||||||
// GetSearchHTTPClient 获取搜索HTTP客户端
|
// GetSearchHTTPClient 获取搜索HTTP客户端
|
||||||
func GetSearchHTTPClient() *http.Client {
|
func GetSearchHTTPClient() *http.Client {
|
||||||
return searchHTTPClient
|
return searchHTTPClient
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,4 +91,4 @@ func transformURL(url, host string) string {
|
|||||||
cleanHost = strings.TrimSuffix(cleanHost, "/")
|
cleanHost = strings.TrimSuffix(cleanHost, "/")
|
||||||
|
|
||||||
return cleanHost + "/" + url
|
return cleanHost + "/" + url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,4 +269,4 @@ func RateLimitMiddleware(limiter *IPRateLimiter) gin.HandlerFunc {
|
|||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user