更新首页显示

This commit is contained in:
Kerwin
2025-07-15 12:50:24 +08:00
parent ec29d8888b
commit 917f8f8d5d
12 changed files with 691 additions and 74 deletions

View File

@@ -20,6 +20,7 @@ type ResourceRepository interface {
FindByIsValid(isValid bool) ([]entity.Resource, error)
FindByIsPublic(isPublic bool) ([]entity.Resource, error)
Search(query string, categoryID *uint, page, limit int) ([]entity.Resource, int64, error)
SearchByPanID(query string, panID uint, page, limit int) ([]entity.Resource, int64, error)
IncrementViewCount(id uint) error
FindWithTags() ([]entity.Resource, error)
UpdateWithTags(resource *entity.Resource, tagIDs []uint) error
@@ -99,7 +100,7 @@ func (r *ResourceRepositoryImpl) FindByCategoryIDPaginated(categoryID uint, page
var total int64
offset := (page - 1) * limit
db := r.db.Model(&entity.Resource{}).Where("category_id = ?", categoryID).Preload("Category").Preload("Tags")
db := r.db.Model(&entity.Resource{}).Where("category_id = ?", categoryID).Preload("Category").Preload("Tags").Order("updated_at DESC")
// 获取总数
if err := db.Count(&total).Error; err != nil {
@@ -124,7 +125,7 @@ func (r *ResourceRepositoryImpl) FindByPanIDPaginated(panID uint, page, limit in
var total int64
offset := (page - 1) * limit
db := r.db.Model(&entity.Resource{}).Where("pan_id = ?", panID).Preload("Category").Preload("Tags")
db := r.db.Model(&entity.Resource{}).Where("pan_id = ?", panID).Preload("Category").Preload("Tags").Order("updated_at DESC")
// 获取总数
if err := db.Count(&total).Error; err != nil {
@@ -172,8 +173,31 @@ func (r *ResourceRepositoryImpl) Search(query string, categoryID *uint, page, li
return nil, 0, err
}
// 获取分页数据
err := db.Offset(offset).Limit(limit).Find(&resources).Error
// 获取分页数据,按更新时间倒序
err := db.Order("updated_at DESC").Offset(offset).Limit(limit).Find(&resources).Error
return resources, total, err
}
// SearchByPanID 在指定平台内搜索资源
func (r *ResourceRepositoryImpl) SearchByPanID(query string, panID uint, page, limit int) ([]entity.Resource, int64, error) {
var resources []entity.Resource
var total int64
offset := (page - 1) * limit
db := r.db.Model(&entity.Resource{}).Preload("Category").Preload("Tags").Where("pan_id = ?", panID)
// 构建查询条件
if query != "" {
db = db.Where("title ILIKE ? OR description ILIKE ?", "%"+query+"%", "%"+query+"%")
}
// 获取总数
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
// 获取分页数据,按更新时间倒序
err := db.Order("updated_at DESC").Offset(offset).Limit(limit).Find(&resources).Error
return resources, total, err
}

269
doc/AUTO_PROCESS_FIX.md Normal file
View File

@@ -0,0 +1,269 @@
# 待处理资源自动处理功能修复说明
## 问题描述
在管理后台开启"待处理资源自动处理"功能后,系统没有自动开始任务,并且出现外键约束错误:
```
ERROR: insert or update on table "resources" violates foreign key constraint "fk_resources_pan" (SQLSTATE 23503)
```
## 问题原因
1. **UpdateSchedulerStatus方法参数不完整**`utils/global_scheduler.go` 中的 `UpdateSchedulerStatus` 方法只接收了 `autoFetchHotDramaEnabled` 参数,没有处理 `autoProcessReadyResources` 参数。
2. **UpdateSystemConfig调用参数不完整**`handlers/system_config_handler.go` 中的 `UpdateSystemConfig` 函数只传递了热播剧相关的参数,没有传递待处理资源相关的参数。
3. **调度器间隔时间硬编码**`utils/scheduler.go` 中的 `StartReadyResourceScheduler` 方法使用了硬编码的5分钟间隔而不是使用系统配置中的 `AutoProcessInterval`
4. **平台匹配机制优化**:使用 `serviceType` 来匹配平台,并添加平台映射缓存提高性能。
## 修复内容
### 1. 修复 UpdateSchedulerStatus 方法
**文件**: `utils/global_scheduler.go`
**修改前**:
```go
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool) {
// 只处理热播剧功能
}
```
**修改后**:
```go
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool, autoProcessReadyResources bool) {
// 处理热播剧自动拉取功能
if autoFetchHotDramaEnabled {
if !gs.scheduler.IsRunning() {
log.Println("系统配置启用自动拉取热播剧,启动定时任务")
gs.scheduler.StartHotDramaScheduler()
}
} else {
if gs.scheduler.IsRunning() {
log.Println("系统配置禁用自动拉取热播剧,停止定时任务")
gs.scheduler.StopHotDramaScheduler()
}
}
// 处理待处理资源自动处理功能
if autoProcessReadyResources {
if !gs.scheduler.IsReadyResourceRunning() {
log.Println("系统配置启用自动处理待处理资源,启动定时任务")
gs.scheduler.StartReadyResourceScheduler()
}
} else {
if gs.scheduler.IsReadyResourceRunning() {
log.Println("系统配置禁用自动处理待处理资源,停止定时任务")
gs.scheduler.StopReadyResourceScheduler()
}
}
}
```
### 2. 修复 UpdateSystemConfig 函数
**文件**: `handlers/system_config_handler.go`
**修改前**:
```go
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled)
```
**修改后**:
```go
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled, req.AutoProcessReadyResources)
```
### 3. 修复调度器间隔时间配置
**文件**: `utils/scheduler.go`
**修改前**:
```go
ticker := time.NewTicker(5 * time.Minute) // 每5分钟检查一次
```
**修改后**:
```go
// 获取系统配置中的间隔时间
config, err := s.systemConfigRepo.GetOrCreateDefault()
interval := 5 * time.Minute // 默认5分钟
if err == nil && config.AutoProcessInterval > 0 {
interval = time.Duration(config.AutoProcessInterval) * time.Minute
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
log.Printf("待处理资源自动处理任务已启动,间隔时间: %v", interval)
```
### 4. 优化平台匹配机制
**文件**: `utils/scheduler.go`
**新增平台映射缓存**:
```go
type Scheduler struct {
// ... 其他字段 ...
// 平台映射缓存
panCache map[string]*uint // serviceType -> panID
panCacheOnce sync.Once
}
```
**新增初始化缓存方法**:
```go
// initPanCache 初始化平台映射缓存
func (s *Scheduler) initPanCache() {
s.panCacheOnce.Do(func() {
// 获取所有平台数据
pans, err := s.panRepo.FindAll()
if err != nil {
log.Printf("初始化平台缓存失败: %v", err)
return
}
// 建立 ServiceType 到 PanID 的映射
serviceTypeToPanName := map[string]string{
"quark": "quark",
"alipan": "aliyun", // 阿里云盘在数据库中的名称是 aliyun
"baidu": "baidu",
"uc": "uc",
"unknown": "other",
}
// 创建平台名称到ID的映射
panNameToID := make(map[string]*uint)
for _, pan := range pans {
panID := pan.ID
panNameToID[pan.Name] = &panID
}
// 建立 ServiceType 到 PanID 的映射
for serviceType, panName := range serviceTypeToPanName {
if panID, exists := panNameToID[panName]; exists {
s.panCache[serviceType] = panID
log.Printf("平台映射缓存: %s -> %s (ID: %d)", serviceType, panName, *panID)
} else {
log.Printf("警告: 未找到平台 %s 对应的数据库记录", panName)
}
}
// 确保有默认的 other 平台
if otherID, exists := panNameToID["other"]; exists {
s.panCache["unknown"] = otherID
}
log.Printf("平台映射缓存初始化完成,共 %d 个映射", len(s.panCache))
})
}
```
**新增根据服务类型获取平台ID的方法**:
```go
// getPanIDByServiceType 根据服务类型获取平台ID
func (s *Scheduler) getPanIDByServiceType(serviceType panutils.ServiceType) *uint {
s.initPanCache()
serviceTypeStr := serviceType.String()
if panID, exists := s.panCache[serviceTypeStr]; exists {
return panID
}
// 如果找不到,返回 other 平台的ID
if otherID, exists := s.panCache["other"]; exists {
log.Printf("未找到服务类型 %s 的映射,使用默认平台 other", serviceTypeStr)
return otherID
}
log.Printf("未找到服务类型 %s 的映射且没有默认平台返回nil", serviceTypeStr)
return nil
}
```
**修改资源创建逻辑**:
```go
// 在 convertReadyResourceToResource 方法中
resource := &entity.Resource{
Title: title,
Description: readyResource.Description,
URL: shareURL,
PanID: s.getPanIDByServiceType(serviceType), // 使用 serviceType 匹配
IsValid: true,
IsPublic: true,
}
```
### 5. 添加 PanRepository 依赖
**文件**: `utils/scheduler.go`
**修改前**:
```go
type Scheduler struct {
doubanService *DoubanService
hotDramaRepo repo.HotDramaRepository
readyResourceRepo repo.ReadyResourceRepository
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
stopChan chan bool
isRunning bool
readyResourceRunning bool
processingMutex sync.Mutex
hotDramaMutex sync.Mutex
}
```
**修改后**:
```go
type Scheduler struct {
doubanService *DoubanService
hotDramaRepo repo.HotDramaRepository
readyResourceRepo repo.ReadyResourceRepository
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
panRepo repo.PanRepository
stopChan chan bool
isRunning bool
readyResourceRunning bool
processingMutex sync.Mutex
hotDramaMutex sync.Mutex
// 平台映射缓存
panCache map[string]*uint // serviceType -> panID
panCacheOnce sync.Once
}
```
## 修复效果
现在当您在管理后台开启"待处理资源自动处理"功能时:
1. **系统会立即启动调度器** - 不再需要重启服务器
2. **使用配置的间隔时间** - 不再是固定的5分钟
3. **支持实时开关** - 可以随时开启或关闭功能
4. **正确的外键关联** - 不再出现外键约束错误
5. **智能平台识别** - 根据 serviceType 自动识别对应的平台
6. **高性能缓存** - 平台映射缓存,避免重复数据库查询
## 测试方法
运行测试脚本验证修复效果:
```bash
# 测试自动处理功能
chmod +x test-auto-process.sh
./test-auto-process.sh
# 测试平台匹配机制
chmod +x test-pan-mapping.sh
./test-pan-mapping.sh
```
## 注意事项
1. 确保数据库中有默认的平台数据
2. 确保系统配置中的 `auto_process_interval` 设置合理
3. 如果仍有问题,检查日志中的错误信息

View File

@@ -0,0 +1,161 @@
# 首页完整修复说明
## 问题描述
1. 首页默认显示100条数据
2. 今日更新没有显示
3. 总资源数没有显示
4. 首页没有默认加载数据
5. 获取分类失败导致错误
## 问题原因分析
### 1. 数据加载问题
- 首页初始化时调用 `store.fetchResources()` 没有传递分页参数
- Store 中的 `fetchResources` 方法没有设置默认参数
- 后端API需要 `page``page_size` 参数才能正确返回数据
### 2. 数据显示问题
- 模板中使用 `visibleResources` 但该变量没有正确设置
- `visibleResources` 是空数组,导致页面显示"暂无数据"
- 统计数据计算依赖 `safeResources`,但数据没有正确加载
### 3. 类型错误问题
- TypeScript 类型检查错误API 返回的数据类型不明确
### 4. 分类获取问题
- 分类API返回undefined导致错误
- 首页不需要分类功能,应该移除相关调用
## 修复内容
### 1. 修复数据加载参数
**文件**: `web/pages/index.vue`
```javascript
// 修复前
const resourcesPromise = store.fetchResources().then((data: any) => {
localResources.value = data.resources || []
return data
})
// 修复后
const resourcesPromise = store.fetchResources({
page: 1,
page_size: 100
}).then((data: any) => {
localResources.value = data.resources || []
return data
})
```
### 2. 修复visibleResources计算属性
**文件**: `web/pages/index.vue`
```javascript
// 修复前
const visibleResources = ref<any[]>([])
const pageSize = ref(20)
// 修复后
const visibleResources = computed(() => safeResources.value)
const pageSize = ref(100) // 修改为100条数据
```
### 3. 修复Store中的fetchResources方法
**文件**: `web/stores/resource.ts`
```javascript
// 修复前
async fetchResources(params?: any) {
this.loading = true
try {
const { getResources } = useResourceApi()
const data = await getResources(params)
this.resources = data.resources
this.currentPage = data.page
this.totalPages = Math.ceil(data.total / data.limit)
} catch (error) {
console.error('获取资源失败:', error)
} finally {
this.loading = false
}
}
// 修复后
async fetchResources(params?: any) {
this.loading = true
try {
const { getResources } = useResourceApi()
// 确保有默认参数
const defaultParams = {
page: 1,
page_size: 100,
...params
}
const data = await getResources(defaultParams) as any
this.resources = data.resources || []
this.currentPage = data.page || 1
this.totalPages = Math.ceil((data.total || 0) / (data.page_size || 100))
} catch (error) {
console.error('获取资源失败:', error)
} finally {
this.loading = false
}
}
```
### 4. 修复TypeScript类型错误
**文件**: `web/stores/resource.ts`
```javascript
// 为所有API调用添加类型断言
const data = await getResources(defaultParams) as any
const stats = await getStats() as any
```
### 5. 移除分类获取功能
**文件**: `web/pages/index.vue`
```javascript
// 移除分类获取调用
// 移除 localCategories 状态管理
// 简化 safeCategories 计算属性
```
## 修复要点总结
1. **参数传递**: 确保首页初始化时传递正确的分页参数
2. **默认值设置**: Store 方法中设置合理的默认参数
3. **计算属性**: 将 `visibleResources` 改为计算属性,直接使用 `safeResources`
4. **数据量调整**: 将默认显示数据量从20条改为100条
5. **类型安全**: 添加类型断言解决TypeScript错误
6. **字段修正**: 使用正确的字段名 `page_size` 而不是 `limit`
7. **移除分类**: 移除不必要的分类获取功能避免API错误
## 测试验证
运行测试脚本验证修复效果:
```bash
chmod +x test-homepage-fix.sh
./test-homepage-fix.sh
```
## 预期效果
修复后,首页应该能够:
1. ✅ 页面加载时自动显示前100条资源数据
2. ✅ 正确显示今日更新数量(基于当前加载的数据计算)
3. ✅ 正确显示总资源数从统计API获取
4. ✅ 平台筛选功能正常工作
5. ✅ 搜索功能正常工作
6. ✅ "加载更多"功能继续正常工作
7. ✅ 不再出现分类获取错误
## 注意事项
1. **数据计算**: 今日更新数量基于当前加载的100条数据计算如果需要更准确的统计需要加载所有数据或使用专门的统计API
2. **性能考虑**: 加载100条数据可能影响页面加载速度可根据实际需求调整
3. **缓存策略**: 考虑添加数据缓存以提高用户体验
4. **错误处理**: 确保网络错误时有合适的降级处理
## 后续优化建议
1. **虚拟滚动**: 对于大量数据,考虑实现虚拟滚动
2. **分页优化**: 实现更智能的分页策略
3. **缓存机制**: 添加数据缓存减少重复请求
4. **加载状态**: 优化加载状态的用户体验

View File

@@ -16,6 +16,7 @@ func GetResources(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
categoryID := c.Query("category_id")
panID := c.Query("pan_id")
search := c.Query("search")
var resources []entity.Resource
@@ -25,9 +26,19 @@ func GetResources(c *gin.Context) {
// 设置响应头,启用缓存
c.Header("Cache-Control", "public, max-age=300") // 5分钟缓存
if search != "" {
if search != "" && panID != "" {
// 平台内搜索
panIDUint, _ := strconv.ParseUint(panID, 10, 32)
resources, total, err = repoManager.ResourceRepository.SearchByPanID(search, uint(panIDUint), page, pageSize)
} else if search != "" {
// 全局搜索
resources, total, err = repoManager.ResourceRepository.Search(search, nil, page, pageSize)
} else if panID != "" {
// 按平台筛选
panIDUint, _ := strconv.ParseUint(panID, 10, 32)
resources, total, err = repoManager.ResourceRepository.FindByPanIDPaginated(uint(panIDUint), page, pageSize)
} else if categoryID != "" {
// 按分类筛选
categoryIDUint, _ := strconv.ParseUint(categoryID, 10, 32)
resources, total, err = repoManager.ResourceRepository.FindByCategoryIDPaginated(uint(categoryIDUint), page, pageSize)
} else {

View File

@@ -14,6 +14,7 @@ func GetSchedulerStatus(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
status := gin.H{
@@ -31,6 +32,7 @@ func StartHotDramaScheduler(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
if scheduler.IsHotDramaSchedulerRunning() {
ErrorResponse(c, "热播剧定时任务已在运行中", http.StatusBadRequest)
@@ -47,6 +49,7 @@ func StopHotDramaScheduler(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
if !scheduler.IsHotDramaSchedulerRunning() {
ErrorResponse(c, "热播剧定时任务未在运行", http.StatusBadRequest)
@@ -63,6 +66,7 @@ func TriggerHotDramaScheduler(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
scheduler.StartHotDramaScheduler() // 直接启动一次
SuccessResponse(c, gin.H{"message": "手动触发热播剧定时任务成功"})
@@ -75,6 +79,7 @@ func FetchHotDramaNames(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
names, err := scheduler.GetHotDramaNames()
if err != nil {
@@ -91,6 +96,7 @@ func StartReadyResourceScheduler(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
if scheduler.IsReadyResourceRunning() {
ErrorResponse(c, "待处理资源自动处理任务已在运行中", http.StatusBadRequest)
@@ -107,6 +113,7 @@ func StopReadyResourceScheduler(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
if !scheduler.IsReadyResourceRunning() {
ErrorResponse(c, "待处理资源自动处理任务未在运行", http.StatusBadRequest)
@@ -123,6 +130,7 @@ func TriggerReadyResourceScheduler(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
// 手动触发一次处理
scheduler.ProcessReadyResources()

View File

@@ -139,9 +139,10 @@ func UpdateSystemConfig(c *gin.Context) {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
if scheduler != nil {
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled)
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled, req.AutoProcessReadyResources)
}
// 返回更新后的配置

View File

@@ -35,6 +35,7 @@ func main() {
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
)
// 检查系统配置,决定是否启动待处理资源自动处理任务

View File

@@ -18,10 +18,10 @@ var (
)
// GetGlobalScheduler 获取全局调度器实例(单例模式)
func GetGlobalScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository) *GlobalScheduler {
func GetGlobalScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository, panRepo repo.PanRepository) *GlobalScheduler {
once.Do(func() {
globalScheduler = &GlobalScheduler{
scheduler: NewScheduler(hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo),
scheduler: NewScheduler(hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo, panRepo),
}
})
return globalScheduler
@@ -110,10 +110,11 @@ func (gs *GlobalScheduler) ProcessReadyResources() {
}
// UpdateSchedulerStatus 根据系统配置更新调度器状态
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool) {
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool, autoProcessReadyResources bool) {
gs.mutex.Lock()
defer gs.mutex.Unlock()
// 处理热播剧自动拉取功能
if autoFetchHotDramaEnabled {
if !gs.scheduler.IsRunning() {
log.Println("系统配置启用自动拉取热播剧,启动定时任务")
@@ -125,4 +126,17 @@ func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool)
gs.scheduler.StopHotDramaScheduler()
}
}
// 处理待处理资源自动处理功能
if autoProcessReadyResources {
if !gs.scheduler.IsReadyResourceRunning() {
log.Println("系统配置启用自动处理待处理资源,启动定时任务")
gs.scheduler.StartReadyResourceScheduler()
}
} else {
if gs.scheduler.IsReadyResourceRunning() {
log.Println("系统配置禁用自动处理待处理资源,停止定时任务")
gs.scheduler.StopReadyResourceScheduler()
}
}
}

View File

@@ -18,26 +18,33 @@ type Scheduler struct {
readyResourceRepo repo.ReadyResourceRepository
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
panRepo repo.PanRepository
stopChan chan bool
isRunning bool
readyResourceRunning bool
processingMutex sync.Mutex // 防止ready_resource任务重叠执行
hotDramaMutex sync.Mutex // 防止热播剧任务重叠执行
// 平台映射缓存
panCache map[string]*uint // serviceType -> panID
panCacheOnce sync.Once
}
// NewScheduler 创建新的定时任务管理器
func NewScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository) *Scheduler {
func NewScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository, panRepo repo.PanRepository) *Scheduler {
return &Scheduler{
doubanService: NewDoubanService(),
hotDramaRepo: hotDramaRepo,
readyResourceRepo: readyResourceRepo,
resourceRepo: resourceRepo,
systemConfigRepo: systemConfigRepo,
panRepo: panRepo,
stopChan: make(chan bool),
isRunning: false,
readyResourceRunning: false,
processingMutex: sync.Mutex{},
hotDramaMutex: sync.Mutex{},
panCache: make(map[string]*uint),
}
}
@@ -208,9 +215,18 @@ func (s *Scheduler) StartReadyResourceScheduler() {
log.Println("启动待处理资源自动处理任务")
go func() {
ticker := time.NewTicker(5 * time.Minute) // 每5分钟检查一次
// 获取系统配置中的间隔时间
config, err := s.systemConfigRepo.GetOrCreateDefault()
interval := 5 * time.Minute // 默认5分钟
if err == nil && config.AutoProcessInterval > 0 {
interval = time.Duration(config.AutoProcessInterval) * time.Minute
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
log.Printf("待处理资源自动处理任务已启动,间隔时间: %v", interval)
// 立即执行一次
s.processReadyResources()
@@ -399,7 +415,7 @@ func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyRes
Title: title,
Description: readyResource.Description,
URL: shareURL,
PanID: s.determinePanID(readyResource.URL),
PanID: s.getPanIDByServiceType(serviceType),
IsValid: true,
IsPublic: true,
}
@@ -419,26 +435,6 @@ func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyRes
return nil
}
// determinePanID 根据URL确定平台ID
func (s *Scheduler) determinePanID(url string) *uint {
url = strings.ToLower(url)
// 这里可以根据你的平台配置来判断
// 示例逻辑,你需要根据实际情况调整
if strings.Contains(url, "pan.baidu.com") {
panID := uint(1) // 百度网盘
return &panID
} else if strings.Contains(url, "www.aliyundrive.com") {
panID := uint(2) // 阿里云盘
return &panID
} else if strings.Contains(url, "pan.quark.cn") {
panID := uint(3) // 夸克网盘
return &panID
}
return nil
}
// getOrCreateCategory 获取或创建分类
func (s *Scheduler) getOrCreateCategory(categoryName string) (uint, error) {
// 这里需要实现分类的查找和创建逻辑
@@ -447,6 +443,70 @@ func (s *Scheduler) getOrCreateCategory(categoryName string) (uint, error) {
return 0, nil
}
// initPanCache 初始化平台映射缓存
func (s *Scheduler) initPanCache() {
s.panCacheOnce.Do(func() {
// 获取所有平台数据
pans, err := s.panRepo.FindAll()
if err != nil {
log.Printf("初始化平台缓存失败: %v", err)
return
}
// 建立 ServiceType 到 PanID 的映射
serviceTypeToPanName := map[string]string{
"quark": "quark",
"alipan": "aliyun", // 阿里云盘在数据库中的名称是 aliyun
"baidu": "baidu",
"uc": "uc",
"unknown": "other",
}
// 创建平台名称到ID的映射
panNameToID := make(map[string]*uint)
for _, pan := range pans {
panID := pan.ID
panNameToID[pan.Name] = &panID
}
// 建立 ServiceType 到 PanID 的映射
for serviceType, panName := range serviceTypeToPanName {
if panID, exists := panNameToID[panName]; exists {
s.panCache[serviceType] = panID
log.Printf("平台映射缓存: %s -> %s (ID: %d)", serviceType, panName, *panID)
} else {
log.Printf("警告: 未找到平台 %s 对应的数据库记录", panName)
}
}
// 确保有默认的 other 平台
if otherID, exists := panNameToID["other"]; exists {
s.panCache["unknown"] = otherID
}
log.Printf("平台映射缓存初始化完成,共 %d 个映射", len(s.panCache))
})
}
// getPanIDByServiceType 根据服务类型获取平台ID
func (s *Scheduler) getPanIDByServiceType(serviceType panutils.ServiceType) *uint {
s.initPanCache()
serviceTypeStr := serviceType.String()
if panID, exists := s.panCache[serviceTypeStr]; exists {
return panID
}
// 如果找不到,返回 other 平台的ID
if otherID, exists := s.panCache["other"]; exists {
log.Printf("未找到服务类型 %s 的映射,使用默认平台 other", serviceTypeStr)
return otherID
}
log.Printf("未找到服务类型 %s 的映射且没有默认平台返回nil", serviceTypeStr)
return nil
}
// IsReadyResourceRunning 检查待处理资源自动处理任务是否在运行
func (s *Scheduler) IsReadyResourceRunning() bool {
return s.readyResourceRunning

View File

@@ -18,6 +18,15 @@ export const parseApiResponse = <T>(response: any): T => {
// 检查是否是包含success字段的响应格式如登录接口
if (response && typeof response === 'object' && 'success' in response && 'data' in response) {
if (response.success) {
// 特殊处理资源接口返回的data.list格式转换为resources格式
if (response.data && response.data.list && Array.isArray(response.data.list)) {
return {
resources: response.data.list,
total: response.data.total,
page: response.data.page,
page_size: response.data.limit
} as T
}
return response.data
} else {
throw new Error(response.message || '请求失败')

View File

@@ -168,7 +168,7 @@
</a>
</td>
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-gray-500" :title="resource.updated_at">
{{ formatRelativeTime(resource.updated_at) }}
<span v-html="formatRelativeTime(resource.updated_at)"></span>
</td>
</tr>
</tbody>
@@ -231,13 +231,6 @@
</div>
</div>
<!-- 添加资源模态框 -->
<!-- <ResourceModal
v-if="showAddResourceModal"
:resource="editingResource"
@close="closeModal"
@save="handleSaveResource"
/> -->
</div>
<!-- 页脚 -->
@@ -259,10 +252,10 @@ const pageLoading = ref(true) // 添加页面加载状态
const systemConfig = ref<SystemConfig | null>(null) // 添加系统配置状态
// 虚拟滚动相关
const visibleResources = ref<any[]>([])
const visibleResources = computed(() => safeResources.value)
const hasMoreData = ref(true)
const currentPage = ref(1)
const pageSize = ref(20)
const pageSize = ref(100) // 修改为100条数据
const isLoadingMore = ref(false)
// 延迟初始化store避免SSR过程中的错误
@@ -271,7 +264,6 @@ let userStore: any = null
// 本地状态管理避免SSR过程中的store访问
const localResources = ref<any[]>([])
const localCategories = ref<any[]>([])
const localStats = ref<any>({ total_resources: 0, total_categories: 0, total_tags: 0, total_views: 0 })
const localLoading = ref(false)
@@ -293,12 +285,12 @@ const safeCategories = computed(() => {
try {
if (process.client && store) {
const storeRefs = storeToRefs(store)
return (storeRefs as any).categories?.value || localCategories.value
return (storeRefs as any).categories?.value || []
}
return localCategories.value
return []
} catch (error) {
console.error('获取categories时出错:', error)
return localCategories.value
return []
}
})
@@ -534,11 +526,21 @@ onMounted(async () => {
// 使用Promise.race来添加超时机制并优化请求顺序
try {
// 首先加载最重要的数据(资源列表)
const resourcesPromise = store.fetchResources().then((data: any) => {
localResources.value = data.resources || []
const resourcesPromise = store.fetchResources({
page: 1,
page_size: 100
}).then((data: any) => {
console.log('首页 - 资源数据:', data)
// 从store中获取数据而不是从返回的data中获取
if (store && store.resources) {
localResources.value = store.resources || []
} else {
localResources.value = data?.resources || []
}
return data
}).catch((e: any) => {
console.error('获取资源失败:', e)
localResources.value = []
return { resources: [] }
})
@@ -547,13 +549,6 @@ onMounted(async () => {
// 然后并行加载其他数据
const otherDataPromise = Promise.allSettled([
store.fetchCategories().then((data: any) => {
localCategories.value = data.categories || []
return data
}).catch((e: any) => {
console.error('获取分类失败:', e)
return { categories: [] }
}),
store.fetchStats().then((data: any) => {
localStats.value = data || { total_resources: 0, total_categories: 0, total_tags: 0, total_views: 0 }
return data
@@ -604,20 +599,50 @@ const fetchPlatforms = async () => {
}
// 搜索处理
const handleSearch = () => {
const handleSearch = async () => {
try {
if (!store || !process.client) {
console.error('store未初始化或不在客户端')
return
}
const platformId = selectedPlatform.value ? parseInt(selectedPlatform.value) : undefined
store.searchResources(searchQuery.value, platformId).then((data: any) => {
localResources.value = data.resources || []
}).catch((error: any) => {
console.error('搜索失败:', error)
})
// 使用标准的资源API传递pan_id参数
const { useResourceApi } = await import('~/composables/useApi')
const resourceApi = useResourceApi()
const params: any = {
page: 1,
page_size: 100
}
if (platformId) {
params.pan_id = platformId
}
if (searchQuery.value) {
params.search = searchQuery.value
}
console.log('搜索参数:', params)
const response = await resourceApi.getResources(params) as any
console.log('搜索结果:', response)
// 强制设置搜索结果到store确保显示正确
if (store && store.setResources) {
store.setResources(response.resources || [])
} else {
// 如果没有setResources方法直接设置localResources
localResources.value = response.resources || []
}
} catch (error) {
console.error('搜索处理时出错:', error)
// 出错时也要清空结果
if (store && store.setResources) {
store.setResources([])
} else {
localResources.value = []
}
}
}
@@ -688,15 +713,15 @@ const formatRelativeTime = (dateString: string) => {
return `<span class="text-pink-600 font-medium flex items-center"><i class="fas fa-circle-dot text-xs mr-1 animate-pulse"></i>${diffHour}小时前</span>`
}
} else if (diffDay < 1) {
return `${diffHour}小时前`
return `<span class="text-gray-600">${diffHour}小时前</span>`
} else if (diffDay < 7) {
return `${diffDay}天前`
return `<span class="text-gray-600">${diffDay}天前</span>`
} else if (diffWeek < 4) {
return `${diffWeek}周前`
return `<span class="text-gray-600">${diffWeek}周前</span>`
} else if (diffMonth < 12) {
return `${diffMonth}个月前`
return `<span class="text-gray-600">${diffMonth}个月前</span>`
} else {
return `${diffYear}年前`
return `<span class="text-gray-600">${diffYear}年前</span>`
}
}

View File

@@ -60,12 +60,40 @@ export const useResourceStore = defineStore('resource', {
this.loading = true
try {
const { getResources } = useResourceApi()
const data = await getResources(params)
this.resources = data.resources
this.currentPage = data.page
this.totalPages = Math.ceil(data.total / data.limit)
// 确保有默认参数
const defaultParams = {
page: 1,
page_size: 100,
...params
}
console.log('fetchResources - 请求参数:', defaultParams)
const data = await getResources(defaultParams) as any
console.log('fetchResources - 返回数据:', data)
// 添加更详细的错误检查
if (!data) {
console.error('fetchResources - 数据为空')
this.resources = []
return
}
if (!data.resources) {
console.error('fetchResources - 缺少resources字段:', data)
this.resources = []
return
}
this.resources = data.resources || []
this.currentPage = data.page || 1
this.totalPages = Math.ceil((data.total || 0) / (data.page_size || 100))
console.log('fetchResources - 设置成功:', {
resourcesCount: this.resources.length,
currentPage: this.currentPage,
totalPages: this.totalPages
})
} catch (error) {
console.error('获取资源失败:', error)
this.resources = []
} finally {
this.loading = false
}
@@ -74,7 +102,7 @@ export const useResourceStore = defineStore('resource', {
async fetchCategories() {
try {
const { getCategories } = useCategoryApi()
this.categories = await getCategories()
this.categories = await getCategories() as any
} catch (error) {
console.error('获取分类失败:', error)
}
@@ -83,7 +111,7 @@ export const useResourceStore = defineStore('resource', {
async fetchStats() {
try {
const { getStats } = useStatsApi()
this.stats = await getStats()
this.stats = await getStats() as any
} catch (error) {
console.error('获取统计失败:', error)
}
@@ -94,8 +122,8 @@ export const useResourceStore = defineStore('resource', {
try {
const { searchResources } = useResourceApi()
const params = { q: query, category_id: categoryId }
const data = await searchResources(params)
this.resources = data.resources
const data = await searchResources(params) as any
this.resources = data.resources || []
this.searchQuery = query
this.selectedCategory = categoryId || null
} catch (error) {
@@ -171,6 +199,12 @@ export const useResourceStore = defineStore('resource', {
}
},
// 直接设置资源列表,用于搜索结果显示
setResources(resources: Resource[]) {
this.resources = resources
console.log('setResources - 设置资源:', resources.length)
},
clearSearch() {
this.searchQuery = ''
this.selectedCategory = null