mirror of
https://github.com/woodchen-ink/random-api-go.git
synced 2025-07-18 05:42:01 +08:00
392 lines
12 KiB
Go
392 lines
12 KiB
Go
package service
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"math/rand"
|
||
"random-api-go/database"
|
||
"random-api-go/model"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// EndpointService API端点服务
|
||
type EndpointService struct {
|
||
cacheManager *CacheManager
|
||
dataSourceFetcher *DataSourceFetcher
|
||
preloader *Preloader
|
||
}
|
||
|
||
var endpointService *EndpointService
|
||
var once sync.Once
|
||
|
||
// GetEndpointService 获取端点服务单例
|
||
func GetEndpointService() *EndpointService {
|
||
once.Do(func() {
|
||
// 创建组件
|
||
cacheManager := NewCacheManager()
|
||
dataSourceFetcher := NewDataSourceFetcher(cacheManager)
|
||
preloader := NewPreloader(dataSourceFetcher, cacheManager)
|
||
|
||
endpointService = &EndpointService{
|
||
cacheManager: cacheManager,
|
||
dataSourceFetcher: dataSourceFetcher,
|
||
preloader: preloader,
|
||
}
|
||
|
||
// 启动预加载器
|
||
preloader.Start()
|
||
})
|
||
return endpointService
|
||
}
|
||
|
||
// CreateEndpoint 创建API端点
|
||
func (s *EndpointService) CreateEndpoint(endpoint *model.APIEndpoint) error {
|
||
if err := database.DB.Create(endpoint).Error; err != nil {
|
||
return fmt.Errorf("failed to create endpoint: %w", err)
|
||
}
|
||
|
||
// 清理缓存
|
||
s.cacheManager.InvalidateMemoryCache(endpoint.URL)
|
||
|
||
// 预加载数据源
|
||
s.preloader.PreloadEndpointOnSave(endpoint)
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetEndpoint 获取API端点
|
||
func (s *EndpointService) GetEndpoint(id uint) (*model.APIEndpoint, error) {
|
||
var endpoint model.APIEndpoint
|
||
if err := database.DB.Preload("DataSources").Preload("URLReplaceRules").First(&endpoint, id).Error; err != nil {
|
||
return nil, fmt.Errorf("failed to get endpoint: %w", err)
|
||
}
|
||
return &endpoint, nil
|
||
}
|
||
|
||
// GetEndpointByURL 根据URL获取端点
|
||
func (s *EndpointService) GetEndpointByURL(url string) (*model.APIEndpoint, error) {
|
||
var endpoint model.APIEndpoint
|
||
if err := database.DB.Preload("DataSources").Preload("URLReplaceRules").
|
||
Where("url = ? AND is_active = ?", url, true).First(&endpoint).Error; err != nil {
|
||
return nil, fmt.Errorf("failed to get endpoint by URL: %w", err)
|
||
}
|
||
return &endpoint, nil
|
||
}
|
||
|
||
// ListEndpoints 列出所有端点
|
||
func (s *EndpointService) ListEndpoints() ([]*model.APIEndpoint, error) {
|
||
var endpoints []*model.APIEndpoint
|
||
if err := database.DB.Preload("DataSources").Preload("URLReplaceRules").
|
||
Order("sort_order ASC, created_at DESC").Find(&endpoints).Error; err != nil {
|
||
return nil, fmt.Errorf("failed to list endpoints: %w", err)
|
||
}
|
||
return endpoints, nil
|
||
}
|
||
|
||
// UpdateEndpoint 更新API端点
|
||
func (s *EndpointService) UpdateEndpoint(endpoint *model.APIEndpoint) error {
|
||
// 只更新指定字段,避免覆盖 created_at 和 sort_order 等字段
|
||
updates := map[string]interface{}{
|
||
"name": endpoint.Name,
|
||
"url": endpoint.URL,
|
||
"description": endpoint.Description,
|
||
"is_active": endpoint.IsActive,
|
||
"show_on_homepage": endpoint.ShowOnHomepage,
|
||
"updated_at": time.Now(),
|
||
}
|
||
|
||
// 如果 sort_order 不为 0,则更新它(0 表示未设置)
|
||
if endpoint.SortOrder != 0 {
|
||
updates["sort_order"] = endpoint.SortOrder
|
||
}
|
||
|
||
if err := database.DB.Model(&model.APIEndpoint{}).Where("id = ?", endpoint.ID).Updates(updates).Error; err != nil {
|
||
return fmt.Errorf("failed to update endpoint: %w", err)
|
||
}
|
||
|
||
// 清理缓存
|
||
s.cacheManager.InvalidateMemoryCache(endpoint.URL)
|
||
|
||
// 预加载数据源
|
||
s.preloader.PreloadEndpointOnSave(endpoint)
|
||
|
||
return nil
|
||
}
|
||
|
||
// DeleteEndpoint 删除API端点
|
||
func (s *EndpointService) DeleteEndpoint(id uint) error {
|
||
// 先获取URL用于清理缓存
|
||
endpoint, err := s.GetEndpoint(id)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get endpoint for deletion: %w", err)
|
||
}
|
||
|
||
// 删除相关的数据源和URL替换规则
|
||
if err := database.DB.Select("DataSources", "URLReplaceRules").Delete(&model.APIEndpoint{}, id).Error; err != nil {
|
||
return fmt.Errorf("failed to delete endpoint: %w", err)
|
||
}
|
||
|
||
// 清理缓存
|
||
s.cacheManager.InvalidateMemoryCache(endpoint.URL)
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetRandomURL 获取随机URL
|
||
func (s *EndpointService) GetRandomURL(url string) (string, error) {
|
||
// 获取端点信息
|
||
endpoint, err := s.GetEndpointByURL(url)
|
||
if err != nil {
|
||
return "", fmt.Errorf("endpoint not found: %w", err)
|
||
}
|
||
|
||
// 检查是否包含API类型或端点类型的数据源
|
||
hasRealtimeDataSource := false
|
||
for _, dataSource := range endpoint.DataSources {
|
||
if dataSource.IsActive && (dataSource.Type == "api_get" || dataSource.Type == "api_post" || dataSource.Type == "endpoint") {
|
||
hasRealtimeDataSource = true
|
||
break
|
||
}
|
||
}
|
||
|
||
// 如果包含实时数据源,不使用内存缓存,直接实时获取
|
||
if hasRealtimeDataSource {
|
||
log.Printf("端点包含实时数据源,使用实时请求模式: %s", url)
|
||
return s.getRandomURLRealtime(endpoint)
|
||
}
|
||
|
||
// 非实时数据源,使用缓存模式但也先选择数据源
|
||
return s.getRandomURLWithCache(endpoint)
|
||
}
|
||
|
||
// getRandomURLRealtime 实时获取随机URL(用于包含API数据源的端点)
|
||
func (s *EndpointService) getRandomURLRealtime(endpoint *model.APIEndpoint) (string, error) {
|
||
// 收集所有激活的数据源
|
||
var activeDataSources []model.DataSource
|
||
for _, dataSource := range endpoint.DataSources {
|
||
if dataSource.IsActive {
|
||
activeDataSources = append(activeDataSources, dataSource)
|
||
}
|
||
}
|
||
|
||
if len(activeDataSources) == 0 {
|
||
return "", fmt.Errorf("no active data sources for endpoint: %s", endpoint.URL)
|
||
}
|
||
|
||
// 先随机选择一个数据源
|
||
selectedDataSource := activeDataSources[rand.Intn(len(activeDataSources))]
|
||
log.Printf("随机选择数据源: %s (ID: %d)", selectedDataSource.Type, selectedDataSource.ID)
|
||
|
||
// 只从选中的数据源获取URL
|
||
urls, err := s.dataSourceFetcher.FetchURLs(&selectedDataSource)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to get URLs from selected data source %d: %w", selectedDataSource.ID, err)
|
||
}
|
||
|
||
if len(urls) == 0 {
|
||
return "", fmt.Errorf("no URLs available from selected data source %d", selectedDataSource.ID)
|
||
}
|
||
|
||
// 从选中数据源的URL中随机选择一个
|
||
randomURL := urls[rand.Intn(len(urls))]
|
||
|
||
// 如果是端点类型的URL,需要递归调用
|
||
if strings.HasPrefix(randomURL, "endpoint://") {
|
||
endpointIDStr := strings.TrimPrefix(randomURL, "endpoint://")
|
||
endpointID, err := strconv.ParseUint(endpointIDStr, 10, 32)
|
||
if err != nil {
|
||
return "", fmt.Errorf("invalid endpoint ID in URL: %s", randomURL)
|
||
}
|
||
|
||
// 获取目标端点信息
|
||
targetEndpoint, err := s.GetEndpoint(uint(endpointID))
|
||
if err != nil {
|
||
return "", fmt.Errorf("target endpoint not found: %w", err)
|
||
}
|
||
|
||
// 递归调用获取目标端点的随机URL
|
||
return s.GetRandomURL(targetEndpoint.URL)
|
||
}
|
||
|
||
return s.applyURLReplaceRules(randomURL, endpoint.URL), nil
|
||
}
|
||
|
||
// getRandomURLWithCache 使用缓存模式获取随机URL(先选择数据源)
|
||
func (s *EndpointService) getRandomURLWithCache(endpoint *model.APIEndpoint) (string, error) {
|
||
// 收集所有激活的数据源
|
||
var activeDataSources []model.DataSource
|
||
for _, dataSource := range endpoint.DataSources {
|
||
if dataSource.IsActive {
|
||
activeDataSources = append(activeDataSources, dataSource)
|
||
}
|
||
}
|
||
|
||
if len(activeDataSources) == 0 {
|
||
return "", fmt.Errorf("no active data sources for endpoint: %s", endpoint.URL)
|
||
}
|
||
|
||
// 先随机选择一个数据源
|
||
selectedDataSource := activeDataSources[rand.Intn(len(activeDataSources))]
|
||
log.Printf("随机选择数据源: %s (ID: %d)", selectedDataSource.Type, selectedDataSource.ID)
|
||
|
||
// 从选中的数据源获取URL(会使用缓存)
|
||
urls, err := s.dataSourceFetcher.FetchURLs(&selectedDataSource)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to get URLs from selected data source %d: %w", selectedDataSource.ID, err)
|
||
}
|
||
|
||
if len(urls) == 0 {
|
||
return "", fmt.Errorf("no URLs available from selected data source %d", selectedDataSource.ID)
|
||
}
|
||
|
||
// 从选中数据源的URL中随机选择一个
|
||
randomURL := urls[rand.Intn(len(urls))]
|
||
|
||
// 如果是端点类型的URL,需要递归调用
|
||
if strings.HasPrefix(randomURL, "endpoint://") {
|
||
endpointIDStr := strings.TrimPrefix(randomURL, "endpoint://")
|
||
endpointID, err := strconv.ParseUint(endpointIDStr, 10, 32)
|
||
if err != nil {
|
||
return "", fmt.Errorf("invalid endpoint ID in URL: %s", randomURL)
|
||
}
|
||
|
||
// 获取目标端点信息
|
||
targetEndpoint, err := s.GetEndpoint(uint(endpointID))
|
||
if err != nil {
|
||
return "", fmt.Errorf("target endpoint not found: %w", err)
|
||
}
|
||
|
||
// 递归调用获取目标端点的随机URL
|
||
return s.GetRandomURL(targetEndpoint.URL)
|
||
}
|
||
|
||
return s.applyURLReplaceRules(randomURL, endpoint.URL), nil
|
||
}
|
||
|
||
// applyURLReplaceRules 应用URL替换规则
|
||
func (s *EndpointService) applyURLReplaceRules(url, endpointURL string) string {
|
||
// 获取端点的替换规则
|
||
endpoint, err := s.GetEndpointByURL(endpointURL)
|
||
if err != nil {
|
||
log.Printf("Failed to get endpoint for URL replacement: %v", err)
|
||
return url
|
||
}
|
||
|
||
result := url
|
||
for _, rule := range endpoint.URLReplaceRules {
|
||
if rule.IsActive {
|
||
result = strings.ReplaceAll(result, rule.FromURL, rule.ToURL)
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// CreateDataSource 创建数据源
|
||
func (s *EndpointService) CreateDataSource(dataSource *model.DataSource) error {
|
||
if err := database.DB.Create(dataSource).Error; err != nil {
|
||
return fmt.Errorf("failed to create data source: %w", err)
|
||
}
|
||
|
||
// 获取关联的端点URL用于清理缓存
|
||
if endpoint, err := s.GetEndpoint(dataSource.EndpointID); err == nil {
|
||
s.cacheManager.InvalidateMemoryCache(endpoint.URL)
|
||
}
|
||
|
||
// 预加载数据源
|
||
s.preloader.PreloadDataSourceOnSave(dataSource)
|
||
|
||
return nil
|
||
}
|
||
|
||
// UpdateDataSource 更新数据源
|
||
func (s *EndpointService) UpdateDataSource(dataSource *model.DataSource) error {
|
||
if err := database.DB.Save(dataSource).Error; err != nil {
|
||
return fmt.Errorf("failed to update data source: %w", err)
|
||
}
|
||
|
||
// 获取关联的端点URL用于清理缓存
|
||
if endpoint, err := s.GetEndpoint(dataSource.EndpointID); err == nil {
|
||
s.cacheManager.InvalidateMemoryCache(endpoint.URL)
|
||
}
|
||
|
||
// 预加载数据源
|
||
s.preloader.PreloadDataSourceOnSave(dataSource)
|
||
|
||
return nil
|
||
}
|
||
|
||
// DeleteDataSource 删除数据源
|
||
func (s *EndpointService) DeleteDataSource(id uint) error {
|
||
// 先获取数据源信息
|
||
var dataSource model.DataSource
|
||
if err := database.DB.First(&dataSource, id).Error; err != nil {
|
||
return fmt.Errorf("failed to get data source: %w", err)
|
||
}
|
||
|
||
// 删除数据源
|
||
if err := database.DB.Delete(&dataSource).Error; err != nil {
|
||
return fmt.Errorf("failed to delete data source: %w", err)
|
||
}
|
||
|
||
// 获取关联的端点URL用于清理缓存
|
||
if endpoint, err := s.GetEndpoint(dataSource.EndpointID); err == nil {
|
||
s.cacheManager.InvalidateMemoryCache(endpoint.URL)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// RefreshDataSource 手动刷新数据源
|
||
func (s *EndpointService) RefreshDataSource(dataSourceID uint) error {
|
||
return s.preloader.RefreshDataSource(dataSourceID)
|
||
}
|
||
|
||
// RefreshEndpoint 手动刷新端点
|
||
func (s *EndpointService) RefreshEndpoint(endpointID uint) error {
|
||
return s.preloader.RefreshEndpoint(endpointID)
|
||
}
|
||
|
||
// GetPreloader 获取预加载器(用于外部控制)
|
||
func (s *EndpointService) GetPreloader() *Preloader {
|
||
return s.preloader
|
||
}
|
||
|
||
// GetCacheManager 获取缓存管理器(用于外部控制)
|
||
func (s *EndpointService) GetCacheManager() *CacheManager {
|
||
return s.cacheManager
|
||
}
|
||
|
||
// GetDataSourceURLCount 获取数据源的URL数量
|
||
func (s *EndpointService) GetDataSourceURLCount(dataSource *model.DataSource) (int, error) {
|
||
// 对于API类型和端点类型的数据源,返回1(因为每次都是实时请求)
|
||
if dataSource.Type == "api_get" || dataSource.Type == "api_post" || dataSource.Type == "endpoint" {
|
||
return 1, nil
|
||
}
|
||
|
||
// 优先从内存缓存获取URL数量,避免触发耗时的网络请求
|
||
cacheKey := fmt.Sprintf("datasource_%d", dataSource.ID)
|
||
if cachedURLs, exists := s.cacheManager.GetFromMemoryCache(cacheKey); exists {
|
||
return len(cachedURLs), nil
|
||
}
|
||
|
||
// 如果缓存中没有数据,返回估算值,避免在统计时触发耗时操作
|
||
switch dataSource.Type {
|
||
case "manual":
|
||
// 手动数据源可以快速解析配置获取数量
|
||
urls, err := s.dataSourceFetcher.FetchURLs(dataSource)
|
||
if err != nil {
|
||
return 0, nil // 返回0而不是错误,避免影响整体统计
|
||
}
|
||
return len(urls), nil
|
||
case "lankong":
|
||
// 兰空图床如果没有缓存,返回估算值
|
||
return 100, nil // 估算值
|
||
default:
|
||
return 0, nil
|
||
}
|
||
}
|