🔧 chore: 初始化番茄小说 API 项目结构
新增项目基础框架,包含核心 API 实现、算法模块、HTTP 处理器等完整功能模块
This commit is contained in:
commit
6981148727
107 files changed
+12463
No files matched your search
@@ -0,0 +1,113 @@
|
||||
package fqapi
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AudioCacheItem 音频缓存项
|
||||
type AudioCacheItem struct {
|
||||
OriginalURL string `json:"original_url"` // 原始CDN URL
|
||||
DeviceID string `json:"device_id"` // 关联的设备ID
|
||||
Headers map[string]string `json:"headers"` // 请求头信息
|
||||
CachedAt time.Time `json:"cached_at"` // 缓存时间
|
||||
}
|
||||
|
||||
// GenerateAudioProxyURL 生成音频代理URL
|
||||
func GenerateAudioProxyURL(originalURL, deviceID string, headers map[string]string) string {
|
||||
// 生成缓存key
|
||||
key := generateRandomKey(16)
|
||||
|
||||
// 将headers序列化为JSON字符串
|
||||
headersJSON, _ := json.Marshal(headers)
|
||||
|
||||
// 存储到数据库
|
||||
if cacheDB != nil {
|
||||
cacheMutex.Lock()
|
||||
defer cacheMutex.Unlock()
|
||||
|
||||
_, err := cacheDB.Exec(`
|
||||
INSERT INTO audio_cache (key, original_url, device_id, headers, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, key, originalURL, deviceID, string(headersJSON), time.Now().Unix())
|
||||
if err != nil {
|
||||
audioLogger.Error("保存音频缓存失败: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 返回代理URL
|
||||
return fmt.Sprintf("/audio/proxy/%s", key)
|
||||
}
|
||||
|
||||
// HandleAudioProxy 处理音频代理请求
|
||||
func HandleAudioProxy(key string) ([]byte, string, error) {
|
||||
if cacheDB == nil {
|
||||
return nil, "", fmt.Errorf("数据库未初始化")
|
||||
}
|
||||
|
||||
// 从数据库获取音频信息
|
||||
var originalURL, deviceID, headersJSON string
|
||||
var createdAt int64
|
||||
|
||||
cacheMutex.Lock()
|
||||
err := cacheDB.QueryRow(`
|
||||
SELECT original_url, device_id, headers, created_at
|
||||
FROM audio_cache WHERE key = ?
|
||||
`, key).Scan(&originalURL, &deviceID, &headersJSON, &createdAt)
|
||||
cacheMutex.Unlock()
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, "", fmt.Errorf("音频信息不存在,请先调用 /audio 接口")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("查询音频缓存失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查缓存是否过期(2小时,匹配CDN链接有效期)
|
||||
if time.Now().Unix()-createdAt > 7200 { // 2小时 = 7200秒
|
||||
// 清理过期缓存
|
||||
cacheMutex.Lock()
|
||||
cacheDB.Exec("DELETE FROM audio_cache WHERE key = ?", key)
|
||||
cacheMutex.Unlock()
|
||||
return nil, "", fmt.Errorf("音频信息已过期,请重新调用 /audio 接口")
|
||||
}
|
||||
|
||||
// 解析headers
|
||||
var headers map[string]string
|
||||
json.Unmarshal([]byte(headersJSON), &headers)
|
||||
if headers == nil {
|
||||
headers = make(map[string]string)
|
||||
}
|
||||
|
||||
// 下载音频内容,使用关联的设备信息
|
||||
data, contentType, err := downloadMediaWithRetry(originalURL, deviceID, headers, 3)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("下载音频失败: %w", err)
|
||||
}
|
||||
|
||||
return data, contentType, nil
|
||||
}
|
||||
|
||||
// CleanExpiredAudioCache 清理过期的音频缓存
|
||||
func CleanExpiredAudioCache() {
|
||||
if cacheDB == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cacheMutex.Lock()
|
||||
defer cacheMutex.Unlock()
|
||||
|
||||
// 删除2小时前的缓存
|
||||
result, err := cacheDB.Exec("DELETE FROM audio_cache WHERE created_at < ?", time.Now().Unix()-7200)
|
||||
if err != nil {
|
||||
audioLogger.Error("清理过期音频缓存失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rowsAffected, _ := result.RowsAffected()
|
||||
if rowsAffected > 0 {
|
||||
audioLogger.Info(fmt.Sprintf("已清理 %d 条过期音频缓存", rowsAffected))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user