mirror of
https://github.com/woodchen-ink/random-api-go.git
synced 2025-07-18 05:42:01 +08:00
225 lines
6.2 KiB
Go
225 lines
6.2 KiB
Go
package service
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"random-api-go/model"
|
||
"time"
|
||
)
|
||
|
||
// LankongFetcher 兰空图床获取器
|
||
type LankongFetcher struct {
|
||
client *http.Client
|
||
retryConfig *RetryConfig
|
||
}
|
||
|
||
// RetryConfig 重试配置
|
||
type RetryConfig struct {
|
||
MaxRetries int // 最大重试次数
|
||
BaseDelay time.Duration // 基础延迟
|
||
}
|
||
|
||
// NewLankongFetcher 创建兰空图床获取器
|
||
func NewLankongFetcher() *LankongFetcher {
|
||
return &LankongFetcher{
|
||
client: &http.Client{
|
||
Timeout: 60 * time.Second, // 增加超时时间
|
||
},
|
||
retryConfig: &RetryConfig{
|
||
MaxRetries: 7, // 最多重试7次 (0秒/15秒/15秒/30秒/30秒/60秒/60秒/180秒)
|
||
BaseDelay: 1 * time.Second, // 基础延迟(实际不使用,使用固定延迟序列)
|
||
},
|
||
}
|
||
}
|
||
|
||
// NewLankongFetcherWithConfig 创建带自定义配置的兰空图床获取器
|
||
func NewLankongFetcherWithConfig(maxRetries int) *LankongFetcher {
|
||
return &LankongFetcher{
|
||
client: &http.Client{
|
||
Timeout: 60 * time.Second,
|
||
},
|
||
retryConfig: &RetryConfig{
|
||
MaxRetries: maxRetries,
|
||
BaseDelay: 1 * time.Second,
|
||
},
|
||
}
|
||
}
|
||
|
||
// LankongResponse 兰空图床API响应
|
||
type LankongResponse struct {
|
||
Status bool `json:"status"`
|
||
Message string `json:"message"`
|
||
Data struct {
|
||
CurrentPage int `json:"current_page"`
|
||
LastPage int `json:"last_page"`
|
||
Data []struct {
|
||
Links struct {
|
||
URL string `json:"url"`
|
||
} `json:"links"`
|
||
} `json:"data"`
|
||
} `json:"data"`
|
||
}
|
||
|
||
// FetchURLs 从兰空图床获取URL列表
|
||
func (lf *LankongFetcher) FetchURLs(config *model.LankongConfig) ([]string, error) {
|
||
var allURLs []string
|
||
baseURL := config.BaseURL
|
||
if baseURL == "" {
|
||
baseURL = "https://img.czl.net/api/v1/images"
|
||
}
|
||
|
||
for _, albumID := range config.AlbumIDs {
|
||
log.Printf("开始获取相册 %s 的图片", albumID)
|
||
|
||
// 获取第一页以确定总页数
|
||
firstPageURL := fmt.Sprintf("%s?album_id=%s&page=1", baseURL, albumID)
|
||
response, err := lf.fetchPageWithRetry(firstPageURL, config.APIToken)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch first page for album %s: %v", albumID, err)
|
||
continue
|
||
}
|
||
|
||
totalPages := response.Data.LastPage
|
||
log.Printf("相册 %s 共有 %d 页", albumID, totalPages)
|
||
|
||
// 处理所有页面
|
||
albumURLs := []string{}
|
||
for page := 1; page <= totalPages; page++ {
|
||
reqURL := fmt.Sprintf("%s?album_id=%s&page=%d", baseURL, albumID, page)
|
||
pageResponse, err := lf.fetchPageWithRetry(reqURL, config.APIToken)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch page %d for album %s: %v", page, albumID, err)
|
||
continue
|
||
}
|
||
|
||
for _, item := range pageResponse.Data.Data {
|
||
if item.Links.URL != "" {
|
||
albumURLs = append(albumURLs, item.Links.URL)
|
||
}
|
||
}
|
||
|
||
// 进度日志
|
||
if page%10 == 0 || page == totalPages {
|
||
log.Printf("相册 %s: 已处理 %d/%d 页,收集到 %d 个URL", albumID, page, totalPages, len(albumURLs))
|
||
}
|
||
}
|
||
|
||
allURLs = append(allURLs, albumURLs...)
|
||
log.Printf("完成相册 %s: 收集到 %d 个URL", albumID, len(albumURLs))
|
||
}
|
||
|
||
return allURLs, nil
|
||
}
|
||
|
||
// fetchPageWithRetry 带重试的页面获取
|
||
func (lf *LankongFetcher) fetchPageWithRetry(url string, apiToken string) (*LankongResponse, error) {
|
||
var lastErr error
|
||
|
||
for attempt := 0; attempt <= lf.retryConfig.MaxRetries; attempt++ {
|
||
response, err := lf.fetchPage(url, apiToken)
|
||
if err == nil {
|
||
return response, nil
|
||
}
|
||
|
||
lastErr = err
|
||
|
||
// 如果是最后一次尝试,不再重试
|
||
if attempt == lf.retryConfig.MaxRetries {
|
||
break
|
||
}
|
||
|
||
// 计算延迟时间
|
||
var delay time.Duration
|
||
if isRateLimitError(err) {
|
||
// 对于429错误,使用固定的延迟序列
|
||
delay = getRateLimitDelay(attempt)
|
||
log.Printf("遇到频率限制 (尝试 %d/%d): %v,等待 %v 后重试", attempt+1, lf.retryConfig.MaxRetries+1, err, delay)
|
||
} else {
|
||
// 其他错误使用较短的延迟
|
||
delay = time.Duration(attempt+1) * time.Second
|
||
log.Printf("请求失败 (尝试 %d/%d): %v,%v 后重试", attempt+1, lf.retryConfig.MaxRetries+1, err, delay)
|
||
}
|
||
|
||
time.Sleep(delay)
|
||
}
|
||
|
||
return nil, fmt.Errorf("重试 %d 次后仍然失败: %v", lf.retryConfig.MaxRetries, lastErr)
|
||
}
|
||
|
||
// getRateLimitDelay 获取频率限制的延迟时间
|
||
// 延迟序列:0秒 / 15秒 / 15秒 / 30秒 / 30秒 / 60秒 / 60秒 / 180秒
|
||
func getRateLimitDelay(attempt int) time.Duration {
|
||
delaySequence := []time.Duration{
|
||
0 * time.Second, // 第1次重试:立即
|
||
15 * time.Second, // 第2次重试:15秒后
|
||
15 * time.Second, // 第3次重试:15秒后
|
||
30 * time.Second, // 第4次重试:30秒后
|
||
30 * time.Second, // 第5次重试:30秒后
|
||
60 * time.Second, // 第6次重试:60秒后
|
||
60 * time.Second, // 第7次重试:60秒后
|
||
180 * time.Second, // 第8次重试:180秒后
|
||
}
|
||
|
||
if attempt < len(delaySequence) {
|
||
return delaySequence[attempt]
|
||
}
|
||
|
||
// 如果超出序列长度,使用最后一个值
|
||
return delaySequence[len(delaySequence)-1]
|
||
}
|
||
|
||
// isRateLimitError 检查是否是频率限制错误
|
||
func isRateLimitError(err error) bool {
|
||
if err == nil {
|
||
return false
|
||
}
|
||
errStr := err.Error()
|
||
return fmt.Sprintf("%v", err) == "rate limit exceeded (429), need to slow down requests" ||
|
||
fmt.Sprintf("%v", errStr) == "API returned status code: 429"
|
||
}
|
||
|
||
// fetchPage 获取兰空图床单页数据
|
||
func (lf *LankongFetcher) fetchPage(url string, apiToken string) (*LankongResponse, error) {
|
||
req, err := http.NewRequest("GET", url, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+apiToken)
|
||
req.Header.Set("Accept", "application/json")
|
||
|
||
resp, err := lf.client.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 特殊处理429错误
|
||
if resp.StatusCode == 429 {
|
||
return nil, fmt.Errorf("rate limit exceeded (429), need to slow down requests")
|
||
}
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("API returned status code: %d", resp.StatusCode)
|
||
}
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var lankongResp LankongResponse
|
||
if err := json.Unmarshal(body, &lankongResp); err != nil {
|
||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||
}
|
||
|
||
if !lankongResp.Status {
|
||
return nil, fmt.Errorf("API error: %s", lankongResp.Message)
|
||
}
|
||
|
||
return &lankongResp, nil
|
||
}
|