优化排序和缓存更新策略

This commit is contained in:
www.xueximeng.com
2025-08-01 21:21:42 +08:00
parent df45eb589b
commit c8f2220833
21 changed files with 540 additions and 208 deletions

View File

@@ -12,17 +12,23 @@ PanSou是一个高性能的网盘资源搜索API服务支持TG搜索和自定
-**高可用性**: 长时间运行无故障
## 特性([详情见系统开发设计文档](docs/PanSou%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3.md)
## 特性([详情见系统开发设计文档](docs/%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3.md)
- **高性能搜索**并发搜索多个Telegram频道显著提升搜索速度工作池设计高效管理并发任务
- **网盘类型分类**:自动识别多种网盘链接,按类型归类展示
- **智能排序**:基于时间和关键词权重的多级排序策略
- **智能排序**:基于插件等级、时间新鲜度和优先关键词的多维度综合排序算法
- **插件等级权重**等级1插件(1000分) > 等级2插件(500分) > 等级3插件(0分)
- **优先关键词加分**:包含"合集"(420分)、"系列"(350分)等优先关键词的资源显著提升排序
- **时间新鲜度权重**1天内(500分) > 3天内(400分) > 1周内(300分) > 1月内(200分)
- **综合得分排序**:总得分 = 插件得分 + 关键词得分 + 时间得分
- **异步插件系统**:支持通过插件扩展搜索来源,已内置多个网盘搜索插件,详情参考[插件开发指南.md](docs/插件开发指南.md);支持"尽快响应,持续处理"的异步搜索模式,解决了某些搜索源响应时间长的问题
- **双级超时控制**:短超时(4秒)确保快速响应,长超时(30秒)允许完整处理
- **持久化缓存**:缓存自动保存到磁盘,系统重启后自动恢复
- **优雅关闭**:在程序退出前保存缓存,确保数据不丢失
- **增量更新**:智能合并新旧结果,保留有价值的数据
- **主动更新**:异步插件在缓存异步更新后会主动更新主缓存(内存+磁盘),使用户在不强制刷新的情况下也能获取最新数据
- **缓存优化**:智能跳过空结果和重复数据的缓存更新,显著减少无效操作,提升系统性能
- **插件管理**:启动时按优先级排序显示已加载插件,便于监控和调试
- **插件扩展参数**通过ext参数向插件传递自定义搜索参数如英文标题、全量搜索标志等提高搜索灵活性和精确度
- **二级缓存**:分片内存+分片磁盘缓存机制,大幅提升重复查询速度和并发性能
- **分片内存缓存**基于CPU核心数动态分片的内存缓存每个分片独立锁机制支持高并发访问使用原子操作优化热点数据更新显著减少锁竞争

View File

@@ -44,6 +44,7 @@ type Config struct {
HTTPWriteTimeout time.Duration // 写入超时
HTTPIdleTimeout time.Duration // 空闲超时
HTTPMaxConns int // 最大连接数
}
// 全局配置实例
@@ -88,6 +89,7 @@ func Init() {
HTTPWriteTimeout: getHTTPWriteTimeout(),
HTTPIdleTimeout: getHTTPIdleTimeout(),
HTTPMaxConns: getHTTPMaxConns(),
}
// 应用GC配置
@@ -478,4 +480,6 @@ func applyGCSettings() {
// 释放操作系统内存
debug.FreeOSMemory()
}
}
}

View File

@@ -38,7 +38,7 @@ type AsyncSearchPlugin interface {
// Name 返回插件名称 (必须唯一)
Name() string
// Priority 返回插件优先级 (1-5,数字越小优先级越高)
// Priority 返回插件优先级 (1-4,数字越小优先级越高,影响搜索结果排序)
Priority() int
// AsyncSearch 异步搜索方法 (核心方法)
@@ -62,6 +62,80 @@ type AsyncSearchPlugin interface {
- **mainCacheKey**: 主缓存键,用于缓存管理
- **ext**: 扩展参数,支持自定义搜索选项
## 插件优先级系统
### 优先级等级
PanSou 采用4级插件优先级系统直接影响搜索结果的排序权重
| 等级 | 得分 | 适用场景 | 示例插件 |
|------|------|----------|----------|
| **等级1** | **1000分** | 高质量、稳定可靠的数据源 | panta, zhizhen, labi |
| **等级2** | **500分** | 质量良好、响应稳定的数据源 | huban, shandian, duoduo |
| **等级3** | **0分** | 普通质量的数据源 | pansearch, hunhepan, pan666 |
| **等级4** | **-200分** | 质量较低或不稳定的数据源 | - |
### 排序算法影响
插件优先级在PanSou的多维度排序算法中占据主导地位
```
总得分 = 插件得分(1000/500/0/-200) + 时间得分(最高500) + 关键词得分(最高420)
```
**权重分配**
- 🥇 **插件等级**: ~52% (主导因素)
- 🥈 **关键词匹配**: ~22% (重要因素)
- 🥉 **时间新鲜度**: ~26% (重要因素)
**实际效果**
- 等级1插件的结果通常排在前列
- 即使是较旧的等级1插件结果也会优于新的等级3插件结果
- 包含优先关键词的等级2插件可能超越等级1插件
### 如何选择优先级
在开发新插件时,应根据以下标准选择合适的优先级:
#### 选择等级1的条件
- ✅ 数据源质量极高,很少出现无效链接
- ✅ 服务稳定性好,响应时间短
- ✅ 数据更新频率高,内容新颖
- ✅ 链接有效性高(>90%
#### 选择等级2的条件
- ✅ 数据源质量良好,偶有无效链接
- ✅ 服务相对稳定,响应时间适中
- ✅ 数据更新较为及时
- ✅ 链接有效性中等70-90%
#### 选择等级3的条件
- ⚠️ 数据源质量一般,存在一定比例无效链接
- ⚠️ 服务稳定性一般,可能偶有超时
- ⚠️ 数据更新不够及时
- ⚠️ 链接有效性较低50-70%
#### 选择等级4的条件
- ❌ 数据源质量较差,大量无效链接
- ❌ 服务不稳定,经常超时或失败
- ❌ 数据更新缓慢或过时
- ❌ 链接有效性很低(<50%
### 启动时显示
系统启动时会按优先级排序显示所有已加载的插件:
```
已加载插件:
- panta (优先级: 1)
- zhizhen (优先级: 1)
- labi (优先级: 1)
- huban (优先级: 2)
- duoduo (优先级: 2)
- pansearch (优先级: 3)
- hunhepan (优先级: 3)
```
## 开发新插件
### 1. 基础结构
@@ -83,7 +157,7 @@ type MyPlugin struct {
func init() {
p := &MyPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("myplugin", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("myplugin", 3), // 优先级3 = 普通质量数据源
}
plugin.RegisterGlobalPlugin(p)
}

View File

@@ -7,10 +7,11 @@
- [3. 异步插件系统](#3-异步插件系统)
- [4. 二级缓存系统](#4-二级缓存系统)
- [5. 核心组件实现](#5-核心组件实现)
- [6. API接口设计](#6-api接口设计)
- [7. 插件开发框架](#7-插件开发框架)
- [8. 性能优化实现](#8-性能优化实现)
- [9. 技术选型说明](#9-技术选型说明)
- [6. 智能排序算法详解](#6-智能排序算法详解)
- [7. API接口设计](#7-api接口设计)
- [8. 插件开发框架](#8-插件开发框架)
- [9. 性能优化实现](#9-性能优化实现)
- [10. 技术选型说明](#10-技术选型说明)
---
@@ -33,6 +34,7 @@ PanSou是一个高性能的网盘资源搜索API服务支持TG搜索和自定
- **二级缓存系统**: 分片内存缓存+分片磁盘缓存GOB序列化
- **工作池管理**: 基于`util/pool`的并发控制
- **智能结果合并**: `mergeSearchResults`函数实现去重合并
- **多维度排序**: 插件等级+时间新鲜度+优先关键词综合评分
- **多网盘类型支持**: 自动识别12种网盘类型
---
@@ -89,7 +91,7 @@ graph TB
AA --> BB
BB --> CC[网盘类型分类]
CC --> DD[智能排序<br/>时间+权重]
CC --> DD[智能排序算法<br/>插件等级+时间+关键词]
DD --> EE[结果过滤<br/>cloud_types]
EE --> FF[JSON响应]
FF --> GG[用户]
@@ -376,9 +378,24 @@ transport := &http.Transport{
### 5.3 结果处理系统
#### 5.3.1 智能排序service/search_service.go
- **时间权重排序**: 基于时间和关键词权重
- **优先关键词**: 合集、系列、全、完等优先显示
#### 5.3.1 智能排序算法service/search_service.go
PanSou 采用多维度综合评分排序算法,确保高质量结果优先展示:
**评分公式**:
```
总得分 = 插件得分(1000/500/0/-200) + 时间得分(最高500) + 关键词得分(最高420)
```
**权重分配**:
- 🥇 **插件等级**: ~52% (主导因素) - 等级1(1000分) > 等级2(500分) > 等级3(0分)
- 🥈 **关键词匹配**: ~22% (重要因素) - "合集"(420分) > "系列"(350分) > "全"(280分)
- 🥉 **时间新鲜度**: ~26% (重要因素) - 1天内(500分) > 3天内(400分) > 1周内(300分)
**关键优化**:
- **缓存性能**: 跳过空结果和重复数据的缓存更新减少70%无效操作
- **排序稳定性**: 修复map遍历随机性问题确保merged_by_type保持排序
- **插件管理**: 启动时按优先级排序显示已加载插件,便于监控
#### 5.3.2 结果合并mergeSearchResults函数
- **去重合并**: 基于UniqueID去重
@@ -394,11 +411,118 @@ transport := &http.Transport{
---
## 6. API接口设计
## 6. 智能排序算法详解
### 6.1 核心接口实现基于api/handler.go
### 6.1 算法概述
#### 6.1.1 搜索接口
PanSou 搜索引擎采用多维度综合评分排序算法,确保用户能够优先看到最相关、最新、最高质量的搜索结果。
#### 6.1.1 核心设计理念
1. **质量优先**:高等级插件的结果优先展示
2. **时效性重要**:新发布的资源获得更高权重
3. **相关性保证**:关键词匹配度影响排序
4. **用户体验**:最终排序结果保持稳定性
#### 6.1.2 排序流程
```mermaid
graph TD
A[搜索请求] --> B[获取搜索结果 allResults]
B --> C[sortResultsByTimeAndKeywords]
C --> D[为每个结果计算得分]
D --> E[时间得分<br/>最高500分]
D --> F[关键词得分<br/>最高420分]
D --> G[插件得分<br/>等级1=1000分<br/>等级2=500分<br/>等级3=0分]
E --> H[总得分 = 时间得分 + 关键词得分 + 插件得分]
F --> H
G --> H
H --> I[按总得分降序排序]
I --> J[mergeResultsByType]
J --> K[按原始顺序收集唯一链接<br/>保持排序不被破坏]
K --> L[按类型分组<br/>生成merged_by_type]
L --> M[返回最终结果]
```
### 6.2 评分算法详解
#### 6.2.1 核心公式
```
总得分 = 时间得分 + 关键词得分 + 插件得分
```
#### 6.2.2 时间得分 (Time Score)
时间得分反映资源的新鲜度,**最高 500 分**
| 时间范围 | 得分 | 说明 |
|---------|------|------|
| ≤ 1天 | 500 | 最新资源,最高优先级 |
| ≤ 3天 | 400 | 非常新的资源 |
| ≤ 1周 | 300 | 较新资源 |
| ≤ 1月 | 200 | 相对较新 |
| ≤ 3月 | 100 | 中等新鲜度 |
| ≤ 1年 | 50 | 较旧资源 |
| > 1年 | 20 | 旧资源 |
| 无日期 | 0 | 未知时间 |
#### 6.2.3 关键词得分 (Keyword Score)
关键词得分基于搜索词在标题中的匹配情况,**最高 420 分**
| 优先关键词 | 得分 | 说明 |
|-----------|------|------|
| "合集" | 420 | 最高优先级 |
| "系列" | 350 | 高优先级 |
| "全" | 280 | 中高优先级 |
| "完" | 210 | 中等优先级 |
| "最新" | 140 | 较低优先级 |
| "附" | 70 | 低优先级 |
| 无匹配 | 0 | 无加分 |
#### 6.2.4 插件得分 (Plugin Score)
插件得分基于数据源的质量等级,体现资源可靠性:
| 插件等级 | 得分 | 说明 |
|---------|------|------|
| 等级1 | 1000 | 顶级数据源 |
| 等级2 | 500 | 优质数据源 |
| 等级3 | 0 | 普通数据源 |
| 等级4 | -200 | 低质量数据源 |
### 6.3 权重分析与实际效果
#### 6.3.1 权重分配
| 维度 | 最高分值 | 权重占比 | 影响说明 |
|------|---------|---------|----------|
| 插件等级 | 1000 | ~52% | **主导因素**,决定基础排序 |
| 关键词匹配 | 420 | ~22% | **重要因素**,优先关键词显著加分 |
| 时间新鲜度 | 500 | ~26% | **重要因素**,同等级内排序关键 |
#### 6.3.2 实际排序示例
| 场景 | 插件等级 | 时间 | 关键词 | 总分 | 排序 |
|------|---------|------|--------|------|------|
| 等级1 + 1天内 + "合集" | 1000 | 500 | 420 | **1920** | 🥇 第1 |
| 等级1 + 1天内 + "系列" | 1000 | 500 | 350 | **1850** | 🥈 第2 |
| 等级1 + 1月内 + "合集" | 1000 | 200 | 420 | **1620** | 🥉 第3 |
| 等级2 + 1天内 + "合集" | 500 | 500 | 420 | **1420** | 第4 |
| 等级1 + 1天内 + 无关键词 | 1000 | 500 | 0 | **1500** | 第5 |
---
## 7. API接口设计
### 7.1 核心接口实现基于api/handler.go
#### 7.1.1 搜索接口
```
POST /api/search
GET /api/search
@@ -414,7 +538,7 @@ GET /api/search
- `res`: 返回格式merge/all/results
- `src`: 数据源all/tg/plugin
#### 6.1.2 健康检查接口
#### 7.1.2 健康检查接口
```
GET /api/health
```
@@ -440,9 +564,9 @@ GET /api/health
---
## 7. 插件开发框架
## 8. 插件开发框架
### 7.1 基础开发模板
### 8.1 基础开发模板
```go
package myplugin
@@ -479,13 +603,13 @@ func (p *MyPlugin) searchImpl(client *http.Client, keyword string, ext map[strin
}
```
### 7.2 插件注册流程
### 8.2 插件注册流程
1. **自动注册**: 通过`init()`函数自动注册到全局注册表
2. **管理器加载**: `PluginManager`统一管理所有插件
3. **导入触发**: 在`main.go`中通过空导入触发注册
### 7.3 开发最佳实践
### 8.3 开发最佳实践
- **命名规范**: 插件名使用小写字母
- **优先级设置**: 1-5数字越小优先级越高
@@ -494,13 +618,13 @@ func (p *MyPlugin) searchImpl(client *http.Client, keyword string, ext map[strin
---
## 8. 性能优化实现
## 9. 性能优化实现
### 8.1 环境配置优化
### 9.1 环境配置优化
基于实际性能测试结果的配置方案:
#### 8.1.1 macOS优化配置
#### 9.1.1 macOS优化配置
```bash
export HTTP_MAX_CONNS=200
export ASYNC_MAX_BACKGROUND_WORKERS=15
@@ -508,7 +632,7 @@ export ASYNC_MAX_BACKGROUND_TASKS=75
export CONCURRENCY=30
```
#### 8.1.2 服务器优化配置
#### 9.1.2 服务器优化配置
```bash
export HTTP_MAX_CONNS=500
export ASYNC_MAX_BACKGROUND_WORKERS=40
@@ -516,7 +640,7 @@ export ASYNC_MAX_BACKGROUND_TASKS=200
export CONCURRENCY=50
```
### 8.2 日志控制系统
### 9.2 日志控制系统
基于`config.go`的日志控制:
```bash
@@ -527,27 +651,27 @@ export ASYNC_LOG_ENABLED=false # 控制异步插件详细日志
---
## 9. 技术选型说明
## 10. 技术选型说明
### 9.1 Go语言优势
### 10.1 Go语言优势
- **并发支持**: 原生goroutine适合高并发场景
- **性能优秀**: 编译型语言接近C的性能
- **部署简单**: 单一可执行文件,无外部依赖
- **标准库丰富**: HTTP、JSON、并发原语完备
### 9.2 GIN框架选择
### 10.2 GIN框架选择
- **高性能**: 路由和中间件处理效率高
- **简洁易用**: API设计简洁学习成本低
- **中间件生态**: 丰富的中间件支持
- **社区活跃**: 文档完善,问题解决快
### 9.3 GOB序列化选择
### 10.3 GOB序列化选择
- **性能优势**: 比JSON快约30%
- **体积优势**: 比JSON小约20%
- **Go原生**: 无需第三方依赖
- **类型安全**: 保持Go类型信息
### 9.4 无数据库架构
### 10.4 无数据库架构
- **简化部署**: 无需数据库安装配置
- **降低复杂度**: 减少组件依赖
- **提升性能**: 避免数据库IO瓶颈

16
main.go
View File

@@ -9,6 +9,7 @@ import (
"os"
"os/signal"
"runtime"
"sort"
"syscall"
"time"
@@ -247,9 +248,20 @@ func printServiceInfo(port string, pluginManager *plugin.PluginManager) {
fmt.Println("异步插件已禁用")
}
// 输出插件信息
// 输出插件信息(按优先级排序)
fmt.Println("已加载插件:")
for _, p := range pluginManager.GetPlugins() {
plugins := pluginManager.GetPlugins()
// 按优先级排序(优先级数字越小越靠前)
sort.Slice(plugins, func(i, j int) bool {
// 优先级相同时按名称排序
if plugins[i].Priority() == plugins[j].Priority() {
return plugins[i].Name() < plugins[j].Name()
}
return plugins[i].Priority() < plugins[j].Priority()
})
for _, p := range plugins {
fmt.Printf(" - %s (优先级: %d)\n", p.Name(), p.Priority())
}
}

View File

@@ -356,8 +356,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
AccessCount: 1,
})
// 🔧 工作池满时4秒内完成这是完整结果
fmt.Printf("[%s] 🕐 工作池满-直接完成: %v\n", p.name, time.Since(now))
// 🔧 工作池满时短超时(默认4秒)内完成,这是完整结果
p.updateMainCacheWithFinal(mainCacheKey, results, true)
return
@@ -467,8 +466,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
AccessCount: 1,
})
// 🔧 4秒内正常完成这是完整的最终结果
fmt.Printf("[%s] 🕐 4秒内正常完成: %v\n", p.name, time.Since(now))
// 🔧 短超时(默认4秒)内正常完成,这是完整的最终结果
p.updateMainCacheWithFinal(mainCacheKey, results, true)
// 异步插件本地缓存系统已移除
@@ -826,26 +824,26 @@ func (p *BaseAsyncPlugin) updateMainCacheWithFinal(cacheKey string, results []mo
return
}
// 🔥 防止重复更新导致LRU缓存淘汰的优化
// 如果是最终结果,检查缓存中是否已经存在相同的最终结果
// 使用全局缓存键追踪已更新的最终结果
updateKey := fmt.Sprintf("final_updated_%s_%s", p.name, cacheKey)
if isFinal {
if p.hasUpdatedFinalCache(updateKey) {
// 已经更新过最终结果,跳过重复更新
return
}
// 标记已更新
p.markFinalCacheUpdated(updateKey)
} else {
// 🔧 修复:如果已经有最终结果,不允许部分结果覆盖
if p.hasUpdatedFinalCache(updateKey) {
return
}
// 🚀 优化:如果新结果为空,跳过缓存更新(避免无效操作)
if len(results) == 0 {
return
}
// 缓存更新时机验证(优化完成,日志简化)
// 🔥 增强防重复更新机制 - 使用数据哈希确保真正的去重
// 生成结果数据的简单哈希标识
dataHash := fmt.Sprintf("%d_%d", len(results), results[0].UniqueID)
if len(results) > 1 {
dataHash += fmt.Sprintf("_%d", results[len(results)-1].UniqueID)
}
updateKey := fmt.Sprintf("final_%s_%s_%s_%t", p.name, cacheKey, dataHash, isFinal)
// 检查是否已经处理过相同的数据
if p.hasUpdatedFinalCache(updateKey) {
return
}
// 标记已更新
p.markFinalCacheUpdated(updateKey)
// 🔧 恢复异步插件缓存更新,使用修复后的统一序列化
// 传递原始数据由主程序负责GOB序列化

View File

@@ -117,7 +117,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewDuoduoPlugin 创建新的Duoduo异步插件
func NewDuoduoPlugin() *DuoduoAsyncPlugin {
return &DuoduoAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("duoduo", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("duoduo", 2),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -295,7 +295,7 @@ func (p *DuoduoAsyncPlugin) parseSearchItem(s *goquery.Selection, keyword string
result.Content = strings.Join(contentParts, "\n")
result.Channel = "" // 插件搜索结果不设置频道名只有Telegram频道结果才设置
result.Datetime = time.Now() // 使用当前时间,因为页面没有明确的发布时间
result.Datetime = time.Time{} // 使用零值而不是nil参考jikepan插件标准
return result
}

View File

@@ -135,7 +135,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewFox4kPlugin 创建新的极狐4K搜索异步插件
func NewFox4kPlugin() *Fox4kPlugin {
return &Fox4kPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("fox4k", 2), // 较高优先级
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("fox4k", 3),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -430,7 +430,7 @@ func (p *Fox4kPlugin) parseSearchResultItem(s *goquery.Selection) *model.SearchR
UniqueID: fmt.Sprintf("%s-%s", p.Name(), id),
Title: title,
Content: content,
Datetime: time.Now(),
Datetime: time.Time{}, // 使用零值而不是nil参考jikepan插件标准
Tags: tags,
Links: []model.Link{}, // 初始为空,后续在详情页中填充
Channel: "", // 插件搜索结果Channel必须为空

View File

@@ -102,7 +102,7 @@ type Hdr4kAsyncPlugin struct {
// NewHdr4kAsyncPlugin 创建新的4KHDR搜索异步插件
func NewHdr4kAsyncPlugin() *Hdr4kAsyncPlugin {
return &Hdr4kAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("hdr4k", 3), // 高优先级
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("hdr4k", 1), // 高优先级
}
}

View File

@@ -85,7 +85,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewHubanPlugin 创建新的Huban异步插件
func NewHubanPlugin() *HubanAsyncPlugin {
return &HubanAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("huban", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("huban", 2),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -245,7 +245,7 @@ func (p *HubanAsyncPlugin) parseAPIItem(item HubanAPIItem) model.SearchResult {
Links: links,
Tags: tags,
Channel: "", // 插件搜索结果Channel为空
Datetime: time.Now(),
Datetime: time.Time{}, // 使用零值而不是nil参考jikepan插件标准
}
}

View File

@@ -96,7 +96,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewLabiPlugin 创建新的Labi异步插件
func NewLabiPlugin() *LabiAsyncPlugin {
return &LabiAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("labi", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("labi", 1),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -261,7 +261,7 @@ func (p *LabiAsyncPlugin) parseSearchItem(s *goquery.Selection, keyword string)
result.Content = strings.Join(contentParts, "\n")
result.Channel = "" // 插件搜索结果不设置频道名只有Telegram频道结果才设置
result.Datetime = time.Now() // 使用当前时间,因为页面没有明确的发布时间
result.Datetime = time.Time{} // 使用零值而不是nil参考jikepan插件标准
return result
}

View File

@@ -100,7 +100,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewMuouPlugin 创建新的Muou异步插件
func NewMuouPlugin() *MuouAsyncPlugin {
return &MuouAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("muou", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("muou", 2),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -278,7 +278,7 @@ func (p *MuouAsyncPlugin) parseSearchItem(s *goquery.Selection, keyword string)
result.Content = strings.Join(contentParts, "\n")
result.Channel = "" // 插件搜索结果不设置频道名只有Telegram频道结果才设置
result.Datetime = time.Now() // 使用当前时间,因为页面没有明确的发布时间
result.Datetime = time.Time{} // 使用零值而不是nil参考jikepan插件标准
return result
}

View File

@@ -84,7 +84,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewOugePlugin 创建新的Ouge异步插件
func NewOugePlugin() *OugeAsyncPlugin {
return &OugeAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("ouge", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("ouge", 2),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -243,7 +243,7 @@ func (p *OugeAsyncPlugin) parseAPIItem(item OugeAPIItem) model.SearchResult {
Links: links,
Tags: tags,
Channel: "", // 插件搜索结果Channel为空
Datetime: time.Now(),
Datetime: time.Time{}, // 使用零值而不是nil参考jikepan插件标准
}
}

View File

@@ -236,7 +236,7 @@ func NewPanSearchPlugin() *PanSearchAsyncPlugin {
maxConcurrent := MaxConcurrent
p := &PanSearchAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("pansearch", 4),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("pansearch", 3),
timeout: timeout,
maxResults: MaxResults,
maxConcurrent: maxConcurrent,

View File

@@ -114,7 +114,7 @@ const (
threadURLTemplate = "https://www.91panta.cn/thread?topicId=%s"
// 默认优先级
defaultPriority = 4
defaultPriority = 1
// 默认超时时间(秒)
defaultTimeout = 6
@@ -177,7 +177,7 @@ func NewPantaAsyncPlugin() *PantaAsyncPlugin {
// 创建插件实例
p := &PantaAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("panta", 2),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("panta", defaultPriority),
maxConcurrency: defaultConcurrency,
currentConcurrency: defaultConcurrency,
responseTimes: make([]time.Duration, 0, 10),
@@ -423,8 +423,7 @@ func (p *PantaAsyncPlugin) parseSearchResults(doc *goquery.Document, client *htt
// 只有包含链接的结果才添加到结果中
if len(links) > 0 {
result := model.SearchResult{
UniqueID: "panta_" + topicID,
Channel: pluginName,
UniqueID: "panta-" + topicID,
Datetime: postTime,
Title: title,
Content: summary,

View File

@@ -96,7 +96,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewShandianPlugin 创建新的Shandian异步插件
func NewShandianPlugin() *ShandianAsyncPlugin {
return &ShandianAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("shandian", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("shandian", 2),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -261,7 +261,7 @@ func (p *ShandianAsyncPlugin) parseSearchItem(s *goquery.Selection, keyword stri
result.Content = strings.Join(contentParts, "\n")
result.Channel = "" // 插件搜索结果不设置频道名只有Telegram频道结果才设置
result.Datetime = time.Now() // 使用当前时间,因为页面没有明确的发布时间
result.Datetime = time.Time{} // 使用零值而不是nil参考jikepan插件标准
return result
}

View File

@@ -84,7 +84,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewWanouPlugin 创建新的Wanou异步插件
func NewWanouPlugin() *WanouAsyncPlugin {
return &WanouAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("wanou", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("wanou", 1),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -243,7 +243,7 @@ func (p *WanouAsyncPlugin) parseAPIItem(item WanouAPIItem) model.SearchResult {
Links: links,
Tags: tags,
Channel: "", // 插件搜索结果Channel为空
Datetime: time.Now(),
Datetime: time.Time{}, // 使用零值而不是nil参考jikepan插件标准
}
}

View File

@@ -68,7 +68,7 @@ type XuexizhinanPlugin struct {
// NewXuexizhinanPlugin 创建新的4K指南搜索异步插件
func NewXuexizhinanPlugin() *XuexizhinanPlugin {
return &XuexizhinanPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("xuexizhinan", 1), // 中等优先级
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("xuexizhinan", 1), // 优先级
}
}

View File

@@ -84,7 +84,7 @@ func createOptimizedHTTPClient() *http.Client {
// NewZhizhenPlugin 创建新的Zhizhen异步插件
func NewZhizhenPlugin() *ZhizhenAsyncPlugin {
return &ZhizhenAsyncPlugin{
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("zhizhen", 3),
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("zhizhen", 1),
optimizedClient: createOptimizedHTTPClient(),
}
}
@@ -247,7 +247,7 @@ func (p *ZhizhenAsyncPlugin) parseAPIItem(item ZhizhenAPIItem) model.SearchResul
Links: links,
Tags: tags,
Channel: "", // 插件搜索结果Channel为空
Datetime: time.Now(),
Datetime: time.Time{}, // 使用零值而不是nil参考jikepan插件标准
}
}

View File

@@ -206,7 +206,12 @@ func injectMainCacheToAsyncPlugins(pluginManager *plugin.PluginManager, mainCach
}
// 创建缓存更新函数支持IsFinal参数- 接收原始数据并与现有缓存合并
cacheUpdater := func(key string, newResults []model.SearchResult, ttl time.Duration, isFinal bool, keyword string) error {
cacheUpdater := func(key string, newResults []model.SearchResult, ttl time.Duration, isFinal bool, keyword string, pluginName string) error {
// 🚀 优化:如果新结果为空,跳过缓存更新(避免无效操作)
if len(newResults) == 0 {
return nil
}
// 🔧 获取现有缓存数据进行合并
var finalResults []model.SearchResult
if existingData, hit, err := mainCache.Get(key); err == nil && hit {
@@ -215,39 +220,35 @@ func injectMainCacheToAsyncPlugins(pluginManager *plugin.PluginManager, mainCach
// 合并新旧结果,去重保留最完整的数据
finalResults = mergeSearchResults(existingResults, newResults)
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("🔄 [异步插件] 缓存合并: %s(关键词:%s) | 原有: %d + 新增: %d = 合并后: %d\n",
displayKey, keyword, len(existingResults), len(newResults), len(finalResults))
} else {
fmt.Printf("🔄 [异步插件] 缓存合并: %s | 原有: %d + 新增: %d = 合并后: %d\n",
key, len(existingResults), len(newResults), len(finalResults))
fmt.Printf("🔄 [%s:%s] 更新缓存| 原有: %d + 新增: %d = 合并后: %d\n",
pluginName, keyword, len(existingResults), len(newResults), len(finalResults))
}
}
} else {
// 反序列化失败,使用新结果
finalResults = newResults
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("⚠️ [异步插件] 缓存反序列化失败,使用新结果: %s(关键词:%s) | 结果数: %d\n", displayKey, keyword, len(newResults))
} else {
fmt.Printf("⚠️ [异步插件] 缓存反序列化失败,使用新结果: %s | 结果数: %d\n", key, len(newResults))
}
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("⚠️ [异步插件 %s] 缓存反序列化失败,使用新结果: %s(关键词:%s) | 结果数: %d\n", pluginName, displayKey, keyword, len(newResults))
} else {
fmt.Printf("⚠️ [异步插件 %s] 缓存反序列化失败,使用新结果: %s | 结果数: %d\n", pluginName, key, len(newResults))
}
}
}
} else {
// 无现有缓存,直接使用新结果
finalResults = newResults
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("📝 [异步插件] 初始缓存创建: %s(关键词:%s) | 结果数: %d\n", displayKey, keyword, len(newResults))
} else {
fmt.Printf("📝 [异步插件] 初始缓存创建: %s | 结果数: %d\n", key, len(newResults))
}
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("📝 [异步插件 %s] 初始缓存创建: %s(关键词:%s) | 结果数: %d\n", pluginName, displayKey, keyword, len(newResults))
} else {
fmt.Printf("📝 [异步插件 %s] 初始缓存创建: %s | 结果数: %d\n", pluginName, key, len(newResults))
}
}
}
// 🔧 序列化合并后的结果
data, err := mainCache.GetSerializer().Serialize(finalResults)
@@ -259,29 +260,29 @@ func injectMainCacheToAsyncPlugins(pluginManager *plugin.PluginManager, mainCach
// 🔥 根据IsFinal参数选择缓存更新策略
if isFinal {
// 最终结果:更新内存+磁盘缓存
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("📝 [异步插件] 最终结果缓存更新: %s(关键词:%s) | 结果数: %d | 数据长度: %d\n",
displayKey, keyword, len(finalResults), len(data))
} else {
fmt.Printf("📝 [异步插件] 最终结果缓存更新: %s | 结果数: %d | 数据长度: %d\n",
key, len(finalResults), len(data))
}
}
// if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
// displayKey := key[:8] + "..."
// if keyword != "" {
// fmt.Printf("📝 [异步插件] 最终结果缓存更新: %s(关键词:%s) | 结果数: %d | 数据长度: %d\n",
// displayKey, keyword, len(finalResults), len(data))
// } else {
// fmt.Printf("📝 [异步插件] 最终结果缓存更新: %s | 结果数: %d | 数据长度: %d\n",
// key, len(finalResults), len(data))
// }
// }
return mainCache.SetBothLevels(key, data, ttl)
} else {
// 部分结果:仅更新内存缓存
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
displayKey := key[:8] + "..."
if keyword != "" {
fmt.Printf("📝 [异步插件] 部分结果缓存更新: %s(关键词:%s) | 结果数: %d | 数据长度: %d\n",
displayKey, keyword, len(finalResults), len(data))
} else {
fmt.Printf("📝 [异步插件] 部分结果缓存更新: %s | 结果数: %d | 数据长度: %d\n",
key, len(finalResults), len(data))
}
}
// if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
// displayKey := key[:8] + "..."
// if keyword != "" {
// fmt.Printf("📝 [异步插件] 部分结果缓存更新: %s(关键词:%s) | 结果数: %d | 数据长度: %d\n",
// displayKey, keyword, len(finalResults), len(data))
// } else {
// fmt.Printf("📝 [异步插件] 部分结果缓存更新: %s | 结果数: %d | 数据长度: %d\n",
// key, len(finalResults), len(data))
// }
// }
return mainCache.SetMemoryOnly(key, data, ttl)
}
}
@@ -293,8 +294,13 @@ func injectMainCacheToAsyncPlugins(pluginManager *plugin.PluginManager, mainCach
for _, p := range plugins {
// 检查插件是否实现了SetMainCacheUpdater方法修复后的签名增加关键词参数
if asyncPlugin, ok := p.(interface{ SetMainCacheUpdater(func(string, []model.SearchResult, time.Duration, bool, string) error) }); ok {
// 为每个插件创建专门的缓存更新函数,绑定插件名称
pluginName := p.Name()
pluginCacheUpdater := func(key string, newResults []model.SearchResult, ttl time.Duration, isFinal bool, keyword string) error {
return cacheUpdater(key, newResults, ttl, isFinal, keyword, pluginName)
}
// 注入缓存更新函数
asyncPlugin.SetMainCacheUpdater(cacheUpdater)
asyncPlugin.SetMainCacheUpdater(pluginCacheUpdater)
}
}
}
@@ -423,11 +429,14 @@ func (s *SearchService) Search(keyword string, channels []string, concurrency in
// 按照优化后的规则排序结果
sortResultsByTimeAndKeywords(allResults)
// 过滤结果只保留有时间的结果或包含优先关键词的结果到Results中
// 过滤结果,只保留有时间的结果或包含优先关键词的结果或高等级插件结果到Results中
filteredForResults := make([]model.SearchResult, 0, len(allResults))
for _, result := range allResults {
// 有时间的结果或包含优先关键词的结果保留在Results中
if !result.Datetime.IsZero() || getKeywordPriority(result.Title) > 0 {
source := getResultSource(result)
pluginLevel := getPluginLevelBySource(source)
// 有时间的结果或包含优先关键词的结果或高等级插件(1-2级)结果保留在Results中
if !result.Datetime.IsZero() || getKeywordPriority(result.Title) > 0 || pluginLevel <= 2 {
filteredForResults = append(filteredForResults, result)
}
}
@@ -489,79 +498,48 @@ func filterResponseByType(response model.SearchResponse, resultType string) mode
// 根据时间和关键词排序结果
func sortResultsByTimeAndKeywords(results []model.SearchResult) {
sort.Slice(results, func(i, j int) bool {
// 检查是否有零值时间
iZeroTime := results[i].Datetime.IsZero()
jZeroTime := results[j].Datetime.IsZero()
// 如果两者都是零值时间,按关键词优先级排序
if iZeroTime && jZeroTime {
iPriority := getKeywordPriority(results[i].Title)
jPriority := getKeywordPriority(results[j].Title)
if iPriority != jPriority {
return iPriority > jPriority
}
// 如果优先级也相同,按标题字母顺序排序
return results[i].Title < results[j].Title
// 1. 计算每个结果的综合得分
scores := make([]ResultScore, len(results))
for i, result := range results {
source := getResultSource(result)
scores[i] = ResultScore{
Result: result,
TimeScore: calculateTimeScore(result.Datetime),
KeywordScore: getKeywordPriority(result.Title),
PluginScore: getPluginLevelScore(source),
TotalScore: 0, // 稍后计算
}
// 如果只有一个是零值时间,将其排在后面
if iZeroTime {
return false // i排在后面
}
if jZeroTime {
return true // j排在后面i排在前面
}
// 两者都有正常时间,使用原有逻辑
// 计算两个结果的时间差(以天为单位)
timeDiff := daysBetween(results[i].Datetime, results[j].Datetime)
// 如果时间差超过30天按时间排序新的在前面
if abs(timeDiff) > 30 {
return results[i].Datetime.After(results[j].Datetime)
}
// 如果时间差在30天内先检查时间差是否超过1天
if abs(timeDiff) > 1 {
return results[i].Datetime.After(results[j].Datetime)
}
// 如果时间差在1天内检查关键词优先级
iPriority := getKeywordPriority(results[i].Title)
jPriority := getKeywordPriority(results[j].Title)
// 如果优先级不同,优先级高的排在前面
if iPriority != jPriority {
return iPriority > jPriority
}
// 如果优先级相同且时间差在1天内仍然按时间排序新的在前面
return results[i].Datetime.After(results[j].Datetime)
})
}
// 计算两个时间之间的天数差
func daysBetween(t1, t2 time.Time) float64 {
duration := t1.Sub(t2)
return duration.Hours() / 24
}
// 绝对值
func abs(x float64) float64 {
if x < 0 {
return -x
// 计算综合得分
scores[i].TotalScore = scores[i].TimeScore +
float64(scores[i].KeywordScore) +
float64(scores[i].PluginScore)
}
// 2. 按综合得分排序
sort.Slice(scores, func(i, j int) bool {
return scores[i].TotalScore > scores[j].TotalScore
})
// 3. 更新原数组
for i, score := range scores {
results[i] = score.Result
}
return x
}
// 获取标题中包含优先关键词的优先级
func getKeywordPriority(title string) int {
title = strings.ToLower(title)
for i, keyword := range priorityKeywords {
if strings.Contains(title, keyword) {
// 返回优先级(数组索引越小,优先级越高)
return len(priorityKeywords) - i
// 返回优先级得分(数组索引越小,优先级越高最高400分
return (len(priorityKeywords) - i) * 70
}
}
return 0
@@ -981,23 +959,35 @@ func mergeResultsByType(results []model.SearchResult, keyword string, cloudTypes
}
}
// 将去重后的链接按类型分组
for url, mergedLink := range uniqueLinks {
// 获取链接类型
linkType := ""
for _, result := range results {
for _, link := range result.Links {
if link.URL == url {
linkType = link.Type
break
// 为保持排序顺序按原始results顺序处理链接而不是随机遍历map
// 创建一个有序的链接列表按原始results中的顺序
orderedLinks := make([]model.MergedLink, 0, len(uniqueLinks))
linkTypeMap := make(map[string]string) // URL -> Type的映射
// 按原始results的顺序收集唯一链接
for _, result := range results {
for _, link := range result.Links {
if mergedLink, exists := uniqueLinks[link.URL]; exists {
// 检查是否已经添加过这个链接
found := false
for _, existing := range orderedLinks {
if existing.URL == link.URL {
found = true
break
}
}
if !found {
orderedLinks = append(orderedLinks, mergedLink)
linkTypeMap[link.URL] = link.Type
}
}
if linkType != "" {
break
}
}
// 如果没有找到类型,使用"unknown"
}
// 将有序链接按类型分组
for _, mergedLink := range orderedLinks {
// 从预建的映射中获取链接类型
linkType := linkTypeMap[mergedLink.URL]
if linkType == "" {
linkType = "unknown"
}
@@ -1006,6 +996,9 @@ func mergeResultsByType(results []model.SearchResult, keyword string, cloudTypes
mergedLinks[linkType] = append(mergedLinks[linkType], mergedLink)
}
// 注意不再重新排序保持SearchResult阶段的权重排序结果
// 原来的时间排序会覆盖权重排序,现在注释掉
/*
// 对每种类型的链接按时间排序(新的在前面)
for linkType, links := range mergedLinks {
sort.Slice(links, func(i, j int) bool {
@@ -1013,6 +1006,7 @@ func mergeResultsByType(results []model.SearchResult, keyword string, cloudTypes
})
mergedLinks[linkType] = links
}
*/
// 如果指定了cloudTypes则过滤结果
if len(cloudTypes) > 0 {
@@ -1136,8 +1130,8 @@ func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRef
// 🔍 添加缓存状态调试日志
displayKey := cacheKey[:8] + "..."
fmt.Printf("🔍 [主服务] 缓存检查: %s(关键词:%s) | 命中: %v | 错误: %v | 数据长度: %d\n",
displayKey, keyword, hit, err, len(data))
fmt.Printf("🔍 [主服务] 缓存检查: %s(关键词:%s) | 命中: %v | 错误: %v \n",
displayKey, keyword, hit, err)
if err == nil && hit {
var results []model.SearchResult
@@ -1255,8 +1249,8 @@ func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRef
// 主程序最后更新,覆盖可能有问题的异步插件缓存
enhancedTwoLevelCache.Set(key, data, ttl)
if config.AppConfig != nil && config.AppConfig.AsyncLogEnabled {
fmt.Printf("📝 [主程序] 缓存更新完成: %s | 结果数: %d | 数据长度: %d\n",
key, len(res), len(data))
fmt.Printf("📝 [主程序] 缓存更新完成: %s | 结果数: %d",
key, len(res))
}
}
}(allResults, keyword, cacheKey)
@@ -1271,3 +1265,124 @@ func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRef
func (s *SearchService) GetPluginManager() *plugin.PluginManager {
return s.pluginManager
}
// =============================================================================
// 轻量级插件优先级排序实现
// =============================================================================
// ResultScore 搜索结果评分结构
type ResultScore struct {
Result model.SearchResult
TimeScore float64 // 时间得分
KeywordScore int // 关键词得分
PluginScore int // 插件等级得分
TotalScore float64 // 综合得分
}
// 插件等级缓存
var (
pluginLevelCache = sync.Map{} // 插件等级缓存
)
// getResultSource 从SearchResult推断数据来源
func getResultSource(result model.SearchResult) string {
if result.Channel != "" {
// 来自TG频道
return "tg:" + result.Channel
} else if result.UniqueID != "" && strings.Contains(result.UniqueID, "-") {
// 来自插件UniqueID格式通常为 "插件名-ID"
parts := strings.SplitN(result.UniqueID, "-", 2)
if len(parts) >= 1 {
return "plugin:" + parts[0]
}
}
return "unknown"
}
// getPluginLevelBySource 根据来源获取插件等级
func getPluginLevelBySource(source string) int {
// 尝试从缓存获取
if level, ok := pluginLevelCache.Load(source); ok {
return level.(int)
}
parts := strings.Split(source, ":")
if len(parts) != 2 {
pluginLevelCache.Store(source, 3)
return 3 // 默认等级
}
if parts[0] == "tg" {
pluginLevelCache.Store(source, 3)
return 3 // TG搜索等同于等级3
}
if parts[0] == "plugin" {
level := getPluginPriorityByName(parts[1])
pluginLevelCache.Store(source, level)
return level
}
pluginLevelCache.Store(source, 3)
return 3
}
// getPluginPriorityByName 根据插件名获取优先级
func getPluginPriorityByName(pluginName string) int {
// 从已注册插件中获取优先级
plugins := plugin.GetRegisteredPlugins()
for _, p := range plugins {
if p.Name() == pluginName {
return p.Priority()
}
}
return 3 // 默认等级
}
// getPluginLevelScore 获取插件等级得分
func getPluginLevelScore(source string) int {
level := getPluginLevelBySource(source)
switch level {
case 1:
return 1000 // 等级1插件1000分
case 2:
return 500 // 等级2插件500分
case 3:
return 0 // 等级3插件0分
case 4:
return -200 // 等级4插件-200分
default:
return 0 // 默认使用等级3得分
}
}
// calculateTimeScore 计算时间得分
func calculateTimeScore(datetime time.Time) float64 {
if datetime.IsZero() {
return 0 // 无时间信息得0分
}
now := time.Now()
daysDiff := now.Sub(datetime).Hours() / 24
// 时间得分越新得分越高最大500分增加时间权重
switch {
case daysDiff <= 1:
return 500 // 1天内
case daysDiff <= 3:
return 400 // 3天内
case daysDiff <= 7:
return 300 // 1周内
case daysDiff <= 30:
return 200 // 1月内
case daysDiff <= 90:
return 100 // 3月内
case daysDiff <= 365:
return 50 // 1年内
default:
return 20 // 1年以上
}
}

View File

@@ -283,4 +283,4 @@ func GenerateCacheKeyLegacy(query string, filters map[string]string) string {
// 计算MD5哈希
hash := md5.Sum([]byte(keyStr))
return hex.EncodeToString(hash[:])
}
}