mirror of
https://github.com/fish2018/pansou.git
synced 2025-11-25 19:37:43 +08:00
支持ext自定义扩展参数
This commit is contained in:
13
README.md
13
README.md
@@ -7,12 +7,13 @@ PanSou是一个高性能的网盘资源搜索API服务,支持TG搜索和自定
|
|||||||
- **高性能搜索**:并发搜索多个Telegram频道,显著提升搜索速度;工作池设计,高效管理并发任务
|
- **高性能搜索**:并发搜索多个Telegram频道,显著提升搜索速度;工作池设计,高效管理并发任务
|
||||||
- **网盘类型分类**:自动识别多种网盘链接,按类型归类展示
|
- **网盘类型分类**:自动识别多种网盘链接,按类型归类展示
|
||||||
- **智能排序**:基于时间和关键词权重的多级排序策略
|
- **智能排序**:基于时间和关键词权重的多级排序策略
|
||||||
- **异步插件系统**:支持通过插件扩展搜索来源,已内置多个网盘搜索插件,详情参考[插件开发指南.md](docs/插件开发指南.md);支持"尽快响应,持续处理"的异步搜索模,解决了某些搜索源响应时间长的问题
|
- **异步插件系统**:支持通过插件扩展搜索来源,已内置多个网盘搜索插件,详情参考[插件开发指南.md](docs/插件开发指南.md);支持"尽快响应,持续处理"的异步搜索模式,解决了某些搜索源响应时间长的问题
|
||||||
- **双级超时控制**:短超时(4秒)确保快速响应,长超时(30秒)允许完整处理
|
- **双级超时控制**:短超时(4秒)确保快速响应,长超时(30秒)允许完整处理
|
||||||
- **持久化缓存**:缓存自动保存到磁盘,系统重启后自动恢复
|
- **持久化缓存**:缓存自动保存到磁盘,系统重启后自动恢复
|
||||||
- **优雅关闭**:在程序退出前保存缓存,确保数据不丢失
|
- **优雅关闭**:在程序退出前保存缓存,确保数据不丢失
|
||||||
- **增量更新**:智能合并新旧结果,保留有价值的数据
|
- **增量更新**:智能合并新旧结果,保留有价值的数据
|
||||||
- **主动更新**:异步插件在缓存异步更新后会主动更新主缓存(内存+磁盘),使用户在不强制刷新的情况下也能获取最新数据
|
- **主动更新**:异步插件在缓存异步更新后会主动更新主缓存(内存+磁盘),使用户在不强制刷新的情况下也能获取最新数据
|
||||||
|
- **插件扩展参数**:通过ext参数向插件传递自定义搜索参数,如英文标题、全量搜索标志等,提高搜索灵活性和精确度
|
||||||
- **二级缓存**:内存+分片磁盘缓存机制,大幅提升重复查询速度和并发性能
|
- **二级缓存**:内存+分片磁盘缓存机制,大幅提升重复查询速度和并发性能
|
||||||
- **分片磁盘缓存**:将缓存数据分散到多个子目录,减少锁竞争,通过哈希算法将缓存键均匀分布到不同分片,提高高并发场景下的性能
|
- **分片磁盘缓存**:将缓存数据分散到多个子目录,减少锁竞争,通过哈希算法将缓存键均匀分布到不同分片,提高高并发场景下的性能
|
||||||
- **序列化器接口**:Gob序列化提供更高性能和更小的结果大小
|
- **序列化器接口**:Gob序列化提供更高性能和更小的结果大小
|
||||||
@@ -218,6 +219,7 @@ server {
|
|||||||
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
||||||
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
||||||
| plugins | string[] | 否 | 指定搜索的插件列表,不指定则搜索全部插件 |
|
| plugins | string[] | 否 | 指定搜索的插件列表,不指定则搜索全部插件 |
|
||||||
|
| ext | object | 否 | 扩展参数,用于传递给插件的自定义参数,如{"title_en":"English Title", "is_all":true} |
|
||||||
|
|
||||||
**GET请求参数**:
|
**GET请求参数**:
|
||||||
|
|
||||||
@@ -230,6 +232,7 @@ server {
|
|||||||
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
||||||
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
||||||
| plugins | string | 否 | 指定搜索的插件列表,使用英文逗号分隔多个插件名,不指定则搜索全部插件 |
|
| plugins | string | 否 | 指定搜索的插件列表,使用英文逗号分隔多个插件名,不指定则搜索全部插件 |
|
||||||
|
| ext | string | 否 | JSON格式的扩展参数,用于传递给插件的自定义参数,如{"title_en":"English Title", "is_all":true} |
|
||||||
|
|
||||||
**POST请求示例**:
|
**POST请求示例**:
|
||||||
|
|
||||||
@@ -241,14 +244,18 @@ server {
|
|||||||
"refresh": true,
|
"refresh": true,
|
||||||
"res": "merge",
|
"res": "merge",
|
||||||
"src": "all",
|
"src": "all",
|
||||||
"plugins": ["jikepan"]
|
"plugins": ["jikepan"],
|
||||||
|
"ext": {
|
||||||
|
"title_en": "Fast and Furious",
|
||||||
|
"is_all": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**GET请求示例**:
|
**GET请求示例**:
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/search?kw=速度与激情&channels=tgsearchers2,xxx&conc=2&refresh=true&res=merge&src=tg
|
GET /api/search?kw=速度与激情&channels=tgsearchers2,xxx&conc=2&refresh=true&res=merge&src=tg&ext={"title_en":"Fast and Furious","is_all":true}
|
||||||
```
|
```
|
||||||
|
|
||||||
**成功响应**:
|
**成功响应**:
|
||||||
|
|||||||
@@ -90,6 +90,25 @@ func SearchHandler(c *gin.Context) {
|
|||||||
plugins = nil
|
plugins = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理ext参数,JSON格式
|
||||||
|
var ext map[string]interface{}
|
||||||
|
extStr := c.Query("ext")
|
||||||
|
if extStr != "" && extStr != " " {
|
||||||
|
// 处理特殊情况:ext={}
|
||||||
|
if extStr == "{}" {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
if err := jsonutil.Unmarshal([]byte(extStr), &ext); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.NewErrorResponse(400, "无效的ext参数格式: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
req = model.SearchRequest{
|
req = model.SearchRequest{
|
||||||
Keyword: keyword,
|
Keyword: keyword,
|
||||||
Channels: channels,
|
Channels: channels,
|
||||||
@@ -98,6 +117,7 @@ func SearchHandler(c *gin.Context) {
|
|||||||
ResultType: resultType,
|
ResultType: resultType,
|
||||||
SourceType: sourceType,
|
SourceType: sourceType,
|
||||||
Plugins: plugins,
|
Plugins: plugins,
|
||||||
|
Ext: ext,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// POST方式:从请求体获取
|
// POST方式:从请求体获取
|
||||||
@@ -144,7 +164,7 @@ func SearchHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行搜索
|
// 执行搜索
|
||||||
result, err := searchService.Search(req.Keyword, req.Channels, req.Concurrency, req.ForceRefresh, req.ResultType, req.SourceType, req.Plugins)
|
result, err := searchService.Search(req.Keyword, req.Channels, req.Concurrency, req.ForceRefresh, req.ResultType, req.SourceType, req.Plugins, req.Ext)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := model.NewErrorResponse(500, "搜索失败: "+err.Error())
|
response := model.NewErrorResponse(500, "搜索失败: "+err.Error())
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ PanSou采用模块化的分层架构设计,主要包括以下几个层次:
|
|||||||
- **响应生成**:生成标准化的JSON响应
|
- **响应生成**:生成标准化的JSON响应
|
||||||
- **中间件**:跨域处理、日志记录、压缩等
|
- **中间件**:跨域处理、日志记录、压缩等
|
||||||
- **参数规范化**:统一处理不同形式但语义相同的参数
|
- **参数规范化**:统一处理不同形式但语义相同的参数
|
||||||
|
- **扩展参数处理**:支持通过`ext`参数向插件传递自定义搜索参数
|
||||||
|
|
||||||
#### 2.2.2 服务层
|
#### 2.2.2 服务层
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ PanSou采用模块化的分层架构设计,主要包括以下几个层次:
|
|||||||
- **缓存管理**:管理搜索结果的缓存策略,支持TG和插件搜索的独立缓存
|
- **缓存管理**:管理搜索结果的缓存策略,支持TG和插件搜索的独立缓存
|
||||||
- **缓存键生成**:基于所有影响结果的参数生成一致的缓存键
|
- **缓存键生成**:基于所有影响结果的参数生成一致的缓存键
|
||||||
- **主缓存注入**:将主缓存系统注入到异步插件中,实现统一的缓存更新
|
- **主缓存注入**:将主缓存系统注入到异步插件中,实现统一的缓存更新
|
||||||
|
- **参数传递**:将自定义参数从API层传递到插件层
|
||||||
|
|
||||||
#### 2.2.3 插件系统
|
#### 2.2.3 插件系统
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ PanSou采用模块化的分层架构设计,主要包括以下几个层次:
|
|||||||
- **自动注册**:通过init函数实现插件自动注册
|
- **自动注册**:通过init函数实现插件自动注册
|
||||||
- **高性能JSON处理**:使用sonic库优化JSON序列化/反序列化
|
- **高性能JSON处理**:使用sonic库优化JSON序列化/反序列化
|
||||||
- **异步插件**:支持"尽快响应,持续处理"的异步搜索模式
|
- **异步插件**:支持"尽快响应,持续处理"的异步搜索模式
|
||||||
|
- **扩展参数支持**:通过`ext`参数支持插件自定义搜索参数
|
||||||
|
|
||||||
#### 2.2.4 工具层
|
#### 2.2.4 工具层
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,25 @@ func SearchHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理ext参数,JSON格式
|
||||||
|
var ext map[string]interface{}
|
||||||
|
extStr := c.Query("ext")
|
||||||
|
if extStr != "" && extStr != " " {
|
||||||
|
// 处理特殊情况:ext={}
|
||||||
|
if extStr == "{}" {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
if err := jsonutil.Unmarshal([]byte(extStr), &ext); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.NewErrorResponse(400, "无效的ext参数格式: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
req = model.SearchRequest{
|
req = model.SearchRequest{
|
||||||
Keyword: keyword,
|
Keyword: keyword,
|
||||||
Channels: channels,
|
Channels: channels,
|
||||||
@@ -148,6 +167,7 @@ func SearchHandler(c *gin.Context) {
|
|||||||
ResultType: resultType,
|
ResultType: resultType,
|
||||||
SourceType: sourceType,
|
SourceType: sourceType,
|
||||||
Plugins: plugins,
|
Plugins: plugins,
|
||||||
|
Ext: ext,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// POST方式:从请求体获取
|
// POST方式:从请求体获取
|
||||||
@@ -189,7 +209,7 @@ func SearchHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行搜索
|
// 执行搜索
|
||||||
result, err := searchService.Search(req.Keyword, req.Channels, req.Concurrency, req.ForceRefresh, req.ResultType, req.SourceType, req.Plugins)
|
result, err := searchService.Search(req.Keyword, req.Channels, req.Concurrency, req.ForceRefresh, req.ResultType, req.SourceType, req.Plugins, req.Ext)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := model.NewErrorResponse(500, "搜索失败: "+err.Error())
|
response := model.NewErrorResponse(500, "搜索失败: "+err.Error())
|
||||||
@@ -299,6 +319,7 @@ func LoggerMiddleware() gin.HandlerFunc {
|
|||||||
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
||||||
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
||||||
| plugins | string[] | 否 | 指定搜索的插件列表,不指定则搜索全部插件 |
|
| plugins | string[] | 否 | 指定搜索的插件列表,不指定则搜索全部插件 |
|
||||||
|
| ext | object | 否 | 扩展参数,用于传递给插件的自定义参数,如{"title_en":"English Title", "is_all":true} |
|
||||||
|
|
||||||
#### GET请求参数
|
#### GET请求参数
|
||||||
|
|
||||||
@@ -311,6 +332,7 @@ func LoggerMiddleware() gin.HandlerFunc {
|
|||||||
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
| res | string | 否 | 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type),默认为merge |
|
||||||
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
| src | string | 否 | 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件) |
|
||||||
| plugins | string | 否 | 指定搜索的插件列表,使用英文逗号分隔多个插件名,不指定则搜索全部插件 |
|
| plugins | string | 否 | 指定搜索的插件列表,使用英文逗号分隔多个插件名,不指定则搜索全部插件 |
|
||||||
|
| ext | string | 否 | JSON格式的扩展参数,用于传递给插件的自定义参数,如{"title_en":"English Title", "is_all":true} |
|
||||||
|
|
||||||
#### 成功响应
|
#### 成功响应
|
||||||
|
|
||||||
@@ -410,7 +432,30 @@ if c.Request.URL.Query().Has("plugins") {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7.2 参数互斥与规范化处理
|
### 7.2 扩展参数处理
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 处理ext参数,JSON格式
|
||||||
|
var ext map[string]interface{}
|
||||||
|
extStr := c.Query("ext")
|
||||||
|
if extStr != "" && extStr != " " {
|
||||||
|
// 处理特殊情况:ext={}
|
||||||
|
if extStr == "{}" {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
if err := jsonutil.Unmarshal([]byte(extStr), &ext); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.NewErrorResponse(400, "无效的ext参数格式: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 参数互斥与规范化处理
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// 参数互斥逻辑:当src=tg时忽略plugins参数,当src=plugin时忽略channels参数
|
// 参数互斥逻辑:当src=tg时忽略plugins参数,当src=plugin时忽略channels参数
|
||||||
|
|||||||
@@ -82,7 +82,12 @@ func NewSearchService(pluginManager *plugin.PluginManager) *SearchService {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
// Search 执行搜索
|
// Search 执行搜索
|
||||||
func (s *SearchService) Search(keyword string, channels []string, concurrency int, forceRefresh bool, resultType string, sourceType string, plugins []string) (model.SearchResponse, error) {
|
func (s *SearchService) Search(keyword string, channels []string, concurrency int, forceRefresh bool, resultType string, sourceType string, plugins []string, ext map[string]interface{}) (model.SearchResponse, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// 参数预处理
|
// 参数预处理
|
||||||
// 源类型标准化
|
// 源类型标准化
|
||||||
if sourceType == "" {
|
if sourceType == "" {
|
||||||
@@ -175,7 +180,7 @@ func (s *SearchService) Search(keyword string, channels []string, concurrency in
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
// 对于插件搜索,我们总是希望获取最新的缓存数据
|
// 对于插件搜索,我们总是希望获取最新的缓存数据
|
||||||
// 因此,即使forceRefresh=false,我们也需要确保获取到最新的缓存
|
// 因此,即使forceRefresh=false,我们也需要确保获取到最新的缓存
|
||||||
pluginResults, pluginErr = s.searchPlugins(keyword, plugins, forceRefresh, concurrency)
|
pluginResults, pluginErr = s.searchPlugins(keyword, plugins, forceRefresh, concurrency, ext)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +374,12 @@ func (s *SearchService) searchTG(keyword string, channels []string, forceRefresh
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
// searchPlugins 搜索插件
|
// searchPlugins 搜索插件
|
||||||
func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRefresh bool, concurrency int) ([]model.SearchResult, error) {
|
func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRefresh bool, concurrency int, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// 生成缓存键
|
// 生成缓存键
|
||||||
cacheKey := cache.GeneratePluginCacheKey(keyword, plugins)
|
cacheKey := cache.GeneratePluginCacheKey(keyword, plugins)
|
||||||
|
|
||||||
@@ -456,7 +466,7 @@ func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRef
|
|||||||
for _, p := range availablePlugins {
|
for _, p := range availablePlugins {
|
||||||
plugin := p // 创建副本,避免闭包问题
|
plugin := p // 创建副本,避免闭包问题
|
||||||
tasks = append(tasks, func() interface{} {
|
tasks = append(tasks, func() interface{} {
|
||||||
results, err := plugin.Search(keyword)
|
results, err := plugin.Search(keyword, ext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ type SearchPlugin interface {
|
|||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
Search(keyword string) ([]model.SearchResult, error)
|
// ext参数用于传递额外的搜索参数,插件可以根据需要使用或忽略
|
||||||
|
Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error)
|
||||||
|
|
||||||
// Priority 返回插件优先级(可选,用于控制结果排序)
|
// Priority 返回插件优先级(可选,用于控制结果排序)
|
||||||
Priority() int
|
Priority() int
|
||||||
@@ -122,7 +123,7 @@ func (p *JikePanPlugin) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索
|
// Search 执行搜索
|
||||||
func (p *JikePanPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *JikePanPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 实现搜索逻辑
|
// 实现搜索逻辑
|
||||||
// ...
|
// ...
|
||||||
return results, nil
|
return results, nil
|
||||||
@@ -194,9 +195,15 @@ func (p *BaseAsyncPlugin) SetMainCacheUpdater(updater func(string, []byte, time.
|
|||||||
// AsyncSearch 异步搜索基础方法
|
// AsyncSearch 异步搜索基础方法
|
||||||
func (p *BaseAsyncPlugin) AsyncSearch(
|
func (p *BaseAsyncPlugin) AsyncSearch(
|
||||||
keyword string,
|
keyword string,
|
||||||
searchFunc func(*http.Client, string) ([]model.SearchResult, error),
|
searchFunc func(*http.Client, string, map[string]interface{}) ([]model.SearchResult, error),
|
||||||
mainCacheKey string, // 主缓存key参数
|
mainCacheKey string, // 主缓存key参数
|
||||||
|
ext map[string]interface{}, // 扩展参数
|
||||||
) ([]model.SearchResult, error) {
|
) ([]model.SearchResult, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
// 修改缓存键,确保包含插件名称
|
// 修改缓存键,确保包含插件名称
|
||||||
@@ -244,7 +251,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
// 尝试获取工作槽
|
// 尝试获取工作槽
|
||||||
if !acquireWorkerSlot() {
|
if !acquireWorkerSlot() {
|
||||||
// 工作池已满,使用快速响应客户端直接处理
|
// 工作池已满,使用快速响应客户端直接处理
|
||||||
results, err := searchFunc(p.client, keyword)
|
results, err := searchFunc(p.client, keyword, ext)
|
||||||
// 处理结果...
|
// 处理结果...
|
||||||
|
|
||||||
// 更新主缓存系统
|
// 更新主缓存系统
|
||||||
@@ -255,7 +262,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
defer releaseWorkerSlot()
|
defer releaseWorkerSlot()
|
||||||
|
|
||||||
// 执行搜索
|
// 执行搜索
|
||||||
results, err := searchFunc(p.backgroundClient, keyword)
|
results, err := searchFunc(p.backgroundClient, keyword, ext)
|
||||||
|
|
||||||
// 检查是否已经响应
|
// 检查是否已经响应
|
||||||
select {
|
select {
|
||||||
@@ -322,8 +329,9 @@ func (p *BaseAsyncPlugin) SetMainCacheKey(key string) {
|
|||||||
// AsyncSearch 异步搜索基础方法
|
// AsyncSearch 异步搜索基础方法
|
||||||
func (p *BaseAsyncPlugin) AsyncSearch(
|
func (p *BaseAsyncPlugin) AsyncSearch(
|
||||||
keyword string,
|
keyword string,
|
||||||
searchFunc func(*http.Client, string) ([]model.SearchResult, error),
|
searchFunc func(*http.Client, string, map[string]interface{}) ([]model.SearchResult, error),
|
||||||
mainCacheKey string, // 主缓存key参数
|
mainCacheKey string, // 主缓存key参数
|
||||||
|
ext map[string]interface{}, // 扩展参数
|
||||||
) ([]model.SearchResult, error) {
|
) ([]model.SearchResult, error) {
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
@@ -381,13 +389,22 @@ func NewMyAsyncPlugin() *MyAsyncPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 实现搜索接口
|
// Search 实现搜索接口
|
||||||
func (p *MyAsyncPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *MyAsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 使用保存的主缓存键
|
// 使用保存的主缓存键,传递ext参数
|
||||||
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey)
|
return p.BaseAsyncPlugin.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doSearch 执行实际搜索
|
// doSearch 执行实际搜索
|
||||||
func (p *MyAsyncPlugin) doSearch(client *http.Client, keyword string) ([]model.SearchResult, error) {
|
func (p *MyAsyncPlugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 处理ext参数
|
||||||
|
if ext != nil {
|
||||||
|
// 根据需要使用ext参数
|
||||||
|
if customParam, ok := ext["custom_param"].(string); ok && customParam != "" {
|
||||||
|
// 使用自定义参数
|
||||||
|
keyword = fmt.Sprintf("%s %s", keyword, customParam)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 实现搜索逻辑
|
// 实现搜索逻辑
|
||||||
// ...
|
// ...
|
||||||
return results, nil
|
return results, nil
|
||||||
@@ -524,7 +541,16 @@ func (p *MyPlugin) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索
|
// Search 执行搜索
|
||||||
func (p *MyPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *MyPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 处理ext参数
|
||||||
|
if ext != nil {
|
||||||
|
// 根据需要使用ext参数
|
||||||
|
if customParam, ok := ext["custom_param"].(string); ok && customParam != "" {
|
||||||
|
// 使用自定义参数
|
||||||
|
keyword = fmt.Sprintf("%s %s", keyword, customParam)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 实现搜索逻辑
|
// 实现搜索逻辑
|
||||||
// ...
|
// ...
|
||||||
return results, nil
|
return results, nil
|
||||||
@@ -564,13 +590,13 @@ func NewMyAsyncPlugin() *MyAsyncPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 实现搜索接口
|
// Search 实现搜索接口
|
||||||
func (p *MyAsyncPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *MyAsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 使用保存的主缓存键
|
// 使用保存的主缓存键
|
||||||
return p.BaseAsyncPlugin.AsyncSearch(keyword, p.doSearch, p.MainCacheKey)
|
return p.BaseAsyncPlugin.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doSearch 执行实际搜索
|
// doSearch 执行实际搜索
|
||||||
func (p *MyAsyncPlugin) doSearch(client *http.Client, keyword string) ([]model.SearchResult, error) {
|
func (p *MyAsyncPlugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 实现搜索逻辑
|
// 实现搜索逻辑
|
||||||
// ...
|
// ...
|
||||||
return results, nil
|
return results, nil
|
||||||
|
|||||||
@@ -539,6 +539,9 @@ PanSou采用了以下缓存键设计策略:
|
|||||||
- 异步插件和主缓存系统使用相同格式的缓存键
|
- 异步插件和主缓存系统使用相同格式的缓存键
|
||||||
- 确保缓存一致性
|
- 确保缓存一致性
|
||||||
|
|
||||||
|
5. **扩展参数处理**:
|
||||||
|
- 扩展参数(ext)不参与缓存键生成,避免缓存爆炸问题
|
||||||
|
|
||||||
### 4.4 列表参数处理
|
### 4.4 列表参数处理
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ type SearchPlugin interface {
|
|||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
Search(keyword string) ([]model.SearchResult, error)
|
// ext参数用于传递额外的搜索参数,插件可以根据需要使用或忽略
|
||||||
|
Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error)
|
||||||
|
|
||||||
// Priority 返回插件优先级(用于控制结果排序)
|
// Priority 返回插件优先级(用于控制结果排序)
|
||||||
Priority() int
|
Priority() int
|
||||||
@@ -50,9 +51,10 @@ type SearchPlugin interface {
|
|||||||
- 名称应简洁明了,全小写,不含特殊字符
|
- 名称应简洁明了,全小写,不含特殊字符
|
||||||
- 例如:`pansearch`、`hunhepan`、`jikepan`
|
- 例如:`pansearch`、`hunhepan`、`jikepan`
|
||||||
|
|
||||||
2. **Search(keyword string)**
|
2. **Search(keyword string, ext map[string]interface{})**
|
||||||
- 执行搜索并返回结果
|
- 执行搜索并返回结果
|
||||||
- 参数 `keyword` 是用户输入的搜索关键词
|
- 参数 `keyword` 是用户输入的搜索关键词
|
||||||
|
- 参数 `ext` 是扩展参数,用于传递额外的搜索参数,如 `title_en`(英文标题)
|
||||||
- 返回值是搜索结果数组和可能的错误
|
- 返回值是搜索结果数组和可能的错误
|
||||||
- 实现时应处理超时和错误,确保不会无限阻塞
|
- 实现时应处理超时和错误,确保不会无限阻塞
|
||||||
|
|
||||||
@@ -932,6 +934,39 @@ if datetime.IsZero() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 6. 扩展参数处理
|
||||||
|
|
||||||
|
- 正确处理ext参数,提供额外搜索功能
|
||||||
|
- 始终检查ext是否为nil,避免空指针异常
|
||||||
|
- 使用类型断言安全地获取参数值
|
||||||
|
- 在处理ext参数时保持向后兼容性
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 处理ext参数
|
||||||
|
if ext != nil {
|
||||||
|
// 使用类型断言安全地获取参数
|
||||||
|
if titleEn, ok := ext["title_en"].(string); ok && titleEn != "" {
|
||||||
|
// 使用英文标题替换关键词
|
||||||
|
searchKeyword = titleEn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理年份参数
|
||||||
|
if year, ok := ext["year"].(float64); ok && year > 0 {
|
||||||
|
// 将年份添加到搜索条件中
|
||||||
|
searchKeyword = fmt.Sprintf("%s %d", searchKeyword, int(year))
|
||||||
|
} else if yearStr, ok := ext["year"].(string); ok && yearStr != "" {
|
||||||
|
// 处理字符串形式的年份
|
||||||
|
searchKeyword = fmt.Sprintf("%s %s", searchKeyword, yearStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理质量参数
|
||||||
|
if quality, ok := ext["quality"].(string); ok && quality != "" {
|
||||||
|
// 将质量添加到搜索条件中
|
||||||
|
searchKeyword = fmt.Sprintf("%s %s", searchKeyword, quality)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 示例插件
|
## 示例插件
|
||||||
|
|
||||||
以下是一个完整的示例插件实现:
|
以下是一个完整的示例插件实现:
|
||||||
@@ -994,9 +1029,19 @@ func (p *ExamplePlugin) Priority() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *ExamplePlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *ExamplePlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 处理ext参数
|
||||||
|
searchKeyword := keyword
|
||||||
|
if ext != nil {
|
||||||
|
// 使用类型断言安全地获取参数
|
||||||
|
if titleEn, ok := ext["title_en"].(string); ok && titleEn != "" {
|
||||||
|
// 使用英文标题替换关键词
|
||||||
|
searchKeyword = titleEn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 构建请求URL
|
// 构建请求URL
|
||||||
reqURL := fmt.Sprintf("%s?q=%s", ApiURL, url.QueryEscape(keyword))
|
reqURL := fmt.Sprintf("%s?q=%s", ApiURL, url.QueryEscape(searchKeyword))
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
req, err := http.NewRequest("GET", reqURL, nil)
|
req, err := http.NewRequest("GET", reqURL, nil)
|
||||||
|
|||||||
373
docs/插件扩展参数设计.md
Normal file
373
docs/插件扩展参数设计.md
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
# 插件扩展参数设计与过滤逻辑重构
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
### 1.1 背景
|
||||||
|
|
||||||
|
在PanSou搜索系统中,在处理自定义搜索参数(如英文标题搜索)时存在局限性,因为主程序只考虑原始关键词,而不考虑插件可能使用的其他搜索参数。
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
|
||||||
|
1. 支持通过`ext`参数向插件传递自定义搜索参数
|
||||||
|
2. 保持缓存机制的简单高效,ext参数不参与缓存键生成
|
||||||
|
3. 允许各插件根据自身需求处理ext参数
|
||||||
|
|
||||||
|
## 2. 设计方案
|
||||||
|
|
||||||
|
### 2.1 插件接口扩展
|
||||||
|
|
||||||
|
扩展`SearchPlugin`接口的`Search`方法,添加`ext`参数:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type SearchPlugin interface {
|
||||||
|
// Name 返回插件名称
|
||||||
|
Name() string
|
||||||
|
|
||||||
|
// Search 执行搜索并返回结果
|
||||||
|
// ext参数用于传递额外的搜索参数,插件可以根据需要使用或忽略
|
||||||
|
Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error)
|
||||||
|
|
||||||
|
// Priority 返回插件优先级(可选,用于控制结果排序)
|
||||||
|
Priority() int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 插件实现扩展
|
||||||
|
|
||||||
|
#### 2.2.1 普通插件实现
|
||||||
|
|
||||||
|
普通插件直接在Search方法中处理ext参数:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Search 执行搜索并返回结果
|
||||||
|
func (p *SomePlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 直接使用keyword进行搜索,默认忽略ext参数
|
||||||
|
// 处理ext参数
|
||||||
|
// if ext != nil {
|
||||||
|
// if titleEn, ok := ext["title_en"].(string); ok && titleEn != "" {
|
||||||
|
// keyword = titleEn // 使用英文标题替换关键词
|
||||||
|
// }
|
||||||
|
// // 处理其他自定义参数...
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 执行搜索逻辑...
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2.2 异步插件实现
|
||||||
|
|
||||||
|
异步插件需要修改`doSearch`方法签名,添加ext参数:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Search 执行搜索并返回结果
|
||||||
|
func (p *JikepanAsyncV2Plugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 使用保存的主缓存键,传递ext参数
|
||||||
|
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doSearch 实际的搜索实现,添加ext参数
|
||||||
|
func (p *JikepanAsyncV2Plugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 处理ext参数
|
||||||
|
if ext != nil {
|
||||||
|
if titleEn, ok := ext["title_en"].(string); ok && titleEn != "" {
|
||||||
|
keyword = titleEn // 使用英文标题替换关键词
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求
|
||||||
|
reqBody := map[string]interface{}{
|
||||||
|
"name": keyword,
|
||||||
|
"is_all": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其余搜索逻辑...
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2.3 不使用ext参数的插件
|
||||||
|
|
||||||
|
对于不需要使用ext参数的插件,可以简单地忽略该参数:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Search 执行搜索并返回结果
|
||||||
|
func (p *SomePlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 直接使用keyword进行搜索,忽略ext参数
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 API层支持
|
||||||
|
|
||||||
|
在`api/handler.go`中的`SearchHandler`函数中,添加对`ext`参数的处理:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 处理ext参数,JSON格式
|
||||||
|
var ext map[string]interface{}
|
||||||
|
extStr := c.Query("ext")
|
||||||
|
if extStr != "" && extStr != " " {
|
||||||
|
// 处理特殊情况:ext={}
|
||||||
|
if extStr == "{}" {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
if err := jsonutil.Unmarshal([]byte(extStr), &ext); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.NewErrorResponse(400, "无效的ext参数格式: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
req = model.SearchRequest{
|
||||||
|
Keyword: keyword,
|
||||||
|
Channels: channels,
|
||||||
|
Concurrency: concurrency,
|
||||||
|
ForceRefresh: forceRefresh,
|
||||||
|
ResultType: resultType,
|
||||||
|
SourceType: sourceType,
|
||||||
|
Plugins: plugins,
|
||||||
|
Ext: ext,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 BaseAsyncPlugin 修改
|
||||||
|
|
||||||
|
修改`BaseAsyncPlugin.AsyncSearch`方法签名,添加ext参数:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (p *BaseAsyncPlugin) AsyncSearch(
|
||||||
|
keyword string,
|
||||||
|
searchFunc func(*http.Client, string, map[string]interface{}) ([]model.SearchResult, error),
|
||||||
|
mainCacheKey string,
|
||||||
|
ext map[string]interface{},
|
||||||
|
) ([]model.SearchResult, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在调用searchFunc时传递ext参数
|
||||||
|
results, err := searchFunc(p.client, keyword, ext)
|
||||||
|
// ...其余逻辑保持不变
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 实施步骤
|
||||||
|
|
||||||
|
### 3.1 阶段一:基础架构修改
|
||||||
|
|
||||||
|
1. **更新SearchRequest模型**
|
||||||
|
- 在`model/request.go`中的`SearchRequest`结构体添加`Ext map[string]interface{}`字段
|
||||||
|
|
||||||
|
2. **更新API处理函数**
|
||||||
|
- 修改`api/handler.go`中的`SearchHandler`函数,添加ext参数处理
|
||||||
|
- 确保在GET和POST两种请求方式下都能正确处理ext参数
|
||||||
|
|
||||||
|
3. **修改SearchPlugin接口**
|
||||||
|
- 更新`plugin/plugin.go`中的`SearchPlugin`接口,修改`Search`方法签名
|
||||||
|
|
||||||
|
4. **修改BaseAsyncPlugin**
|
||||||
|
- 更新`plugin/baseasyncplugin.go`中的`AsyncSearch`方法签名和实现
|
||||||
|
- 确保在调用searchFunc时传递ext参数
|
||||||
|
|
||||||
|
### 3.2 阶段二:插件实现更新
|
||||||
|
|
||||||
|
1. **更新普通插件**
|
||||||
|
- 修改所有实现`SearchPlugin`接口的普通插件,更新`Search`方法签名
|
||||||
|
|
||||||
|
2. **更新异步插件**
|
||||||
|
- 修改所有异步插件的`doSearch`方法签名,添加ext参数
|
||||||
|
- 更新异步插件的`Search`方法,传递ext参数给`AsyncSearch`
|
||||||
|
|
||||||
|
3. **更新SearchService**
|
||||||
|
- 修改`service/search_service.go`中的`searchPlugins`方法,传递ext参数给插件
|
||||||
|
|
||||||
|
### 3.3 阶段三:测试与优化
|
||||||
|
|
||||||
|
1. **单元测试**
|
||||||
|
- 为ext参数处理添加单元测试,确保参数正确传递
|
||||||
|
- 测试不同类型的ext参数值(字符串、数字、布尔值、数组、对象等)
|
||||||
|
|
||||||
|
2. **集成测试**
|
||||||
|
- 测试API接口传递ext参数
|
||||||
|
- 测试不同插件对ext参数的处理
|
||||||
|
|
||||||
|
3. **性能测试**
|
||||||
|
- 确保添加ext参数后不影响系统性能
|
||||||
|
- 验证缓存机制仍然有效
|
||||||
|
|
||||||
|
## 4. 注意事项
|
||||||
|
|
||||||
|
### 4.1 缓存键生成
|
||||||
|
|
||||||
|
缓存键生成只使用原始关键词,不包含`ext`参数。这是一个有意的设计决策,可以提高缓存命中率,但也意味着使用不同ext参数的搜索可能共享相同缓存。
|
||||||
|
|
||||||
|
### 4.2 参数传递
|
||||||
|
|
||||||
|
确保ext参数在整个调用链中正确传递,从API层到Service层,再到各个插件实现。
|
||||||
|
|
||||||
|
### 4.3 插件兼容性
|
||||||
|
|
||||||
|
所有插件都需要更新接口实现,没有向后兼容的考虑,这是一次全面的改造。
|
||||||
|
|
||||||
|
## 5. 扩展性考虑
|
||||||
|
|
||||||
|
### 5.1 支持更多自定义参数
|
||||||
|
|
||||||
|
本设计使用通用的`map[string]interface{}`类型作为`ext`参数,可以灵活支持各种类型的自定义参数,如:
|
||||||
|
|
||||||
|
- `title_en`: 英文标题搜索
|
||||||
|
- `year`: 年份过滤
|
||||||
|
- `category`: 分类过滤
|
||||||
|
- `quality`: 质量过滤(如1080p、4K等)
|
||||||
|
|
||||||
|
### 5.2 插件特定参数
|
||||||
|
|
||||||
|
不同插件可以根据自身特点支持不同的自定义参数,主程序不需要了解这些参数的具体含义,只负责传递。
|
||||||
|
|
||||||
|
## 6. 示例实现
|
||||||
|
|
||||||
|
### 6.1 SearchRequest 模型更新
|
||||||
|
|
||||||
|
```go
|
||||||
|
// SearchRequest 搜索请求参数
|
||||||
|
type SearchRequest struct {
|
||||||
|
Keyword string `json:"kw" binding:"required"`
|
||||||
|
Channels []string `json:"channels"`
|
||||||
|
Concurrency int `json:"conc"`
|
||||||
|
ForceRefresh bool `json:"refresh"`
|
||||||
|
ResultType string `json:"res"`
|
||||||
|
SourceType string `json:"src"`
|
||||||
|
Plugins []string `json:"plugins"`
|
||||||
|
Ext map[string]interface{} `json:"ext"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 异步插件实现示例
|
||||||
|
|
||||||
|
```go
|
||||||
|
package myplugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"pansou/model"
|
||||||
|
"pansou/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MyAsyncPlugin 自定义异步插件
|
||||||
|
type MyAsyncPlugin struct {
|
||||||
|
*plugin.BaseAsyncPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search 实现搜索接口
|
||||||
|
func (p *MyAsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 使用保存的主缓存键,传递ext参数
|
||||||
|
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doSearch 执行实际搜索
|
||||||
|
func (p *MyAsyncPlugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 处理ext参数
|
||||||
|
if ext != nil {
|
||||||
|
if titleEn, ok := ext["title_en"].(string); ok && titleEn != "" {
|
||||||
|
keyword = titleEn // 使用英文标题
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他自定义参数处理...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现搜索逻辑
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 实际应用示例:jikepan_ext插件
|
||||||
|
|
||||||
|
jikepan_ext插件是一个实际应用ext参数的例子,它使用ext中的title_en参数来替代原始关键词进行搜索:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// doSearch 实际的搜索实现
|
||||||
|
func (p *JikepanAsyncV2Plugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 构建请求
|
||||||
|
reqBody := map[string]interface{}{
|
||||||
|
"name": keyword,
|
||||||
|
"is_all": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查ext中是否包含title_en参数,如果有则使用它
|
||||||
|
if ext != nil {
|
||||||
|
if titleEn, ok := ext["title_en"].(string); ok && titleEn != "" {
|
||||||
|
// 使用title_en替换或补充keyword
|
||||||
|
reqBody["name"] = titleEn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal request failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后续搜索逻辑...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这个实现允许客户端通过ext参数传递英文标题,例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/search?kw=火影忍者&ext={"title_en":"Naruto"}
|
||||||
|
```
|
||||||
|
|
||||||
|
在这个请求中,jikepan_ext插件将使用"Naruto"而不是"火影忍者"作为搜索关键词,这对于搜索外文资源特别有用。
|
||||||
|
|
||||||
|
## 7. 使用示例
|
||||||
|
|
||||||
|
### 7.1 API请求示例
|
||||||
|
|
||||||
|
#### GET请求
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/search?kw=火影忍者&ext={"title_en":"Naruto","year":2002}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST请求
|
||||||
|
|
||||||
|
```json
|
||||||
|
POST /api/search
|
||||||
|
{
|
||||||
|
"kw": "火影忍者",
|
||||||
|
"ext": {
|
||||||
|
"title_en": "Naruto",
|
||||||
|
"year": 2002,
|
||||||
|
"quality": "1080p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 客户端使用示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 使用fetch API发送带ext参数的请求
|
||||||
|
async function search(keyword, ext) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('kw', keyword);
|
||||||
|
if (ext) {
|
||||||
|
params.append('ext', JSON.stringify(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`/api/search?${params}`);
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
search('火影忍者', { title_en: 'Naruto', year: 2002 })
|
||||||
|
.then(results => {
|
||||||
|
console.log(results);
|
||||||
|
});
|
||||||
|
```
|
||||||
@@ -9,4 +9,5 @@ type SearchRequest struct {
|
|||||||
ResultType string `json:"res"` // 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type)
|
ResultType string `json:"res"` // 结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type)
|
||||||
SourceType string `json:"src"` // 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件)
|
SourceType string `json:"src"` // 数据来源类型:all(默认,全部来源)、tg(仅Telegram)、plugin(仅插件)
|
||||||
Plugins []string `json:"plugins"` // 指定搜索的插件列表,不指定则搜索全部插件
|
Plugins []string `json:"plugins"` // 指定搜索的插件列表,不指定则搜索全部插件
|
||||||
|
Ext map[string]interface{} `json:"ext"` // 扩展参数,用于传递给插件的自定义参数
|
||||||
}
|
}
|
||||||
@@ -526,9 +526,15 @@ func (p *BaseAsyncPlugin) Priority() int {
|
|||||||
// AsyncSearch 异步搜索基础方法
|
// AsyncSearch 异步搜索基础方法
|
||||||
func (p *BaseAsyncPlugin) AsyncSearch(
|
func (p *BaseAsyncPlugin) AsyncSearch(
|
||||||
keyword string,
|
keyword string,
|
||||||
searchFunc func(*http.Client, string) ([]model.SearchResult, error),
|
searchFunc func(*http.Client, string, map[string]interface{}) ([]model.SearchResult, error),
|
||||||
mainCacheKey string, // 主缓存key参数
|
mainCacheKey string,
|
||||||
|
ext map[string]interface{},
|
||||||
) ([]model.SearchResult, error) {
|
) ([]model.SearchResult, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
// 修改缓存键,确保包含插件名称
|
// 修改缓存键,确保包含插件名称
|
||||||
@@ -545,7 +551,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
|
|
||||||
// 如果缓存接近过期(已用时间超过TTL的80%),在后台刷新缓存
|
// 如果缓存接近过期(已用时间超过TTL的80%),在后台刷新缓存
|
||||||
if time.Since(cachedResult.Timestamp) > (p.cacheTTL * 4 / 5) {
|
if time.Since(cachedResult.Timestamp) > (p.cacheTTL * 4 / 5) {
|
||||||
go p.refreshCacheInBackground(keyword, pluginSpecificCacheKey, searchFunc, cachedResult, mainCacheKey)
|
go p.refreshCacheInBackground(keyword, pluginSpecificCacheKey, searchFunc, cachedResult, mainCacheKey, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedResult.Results, nil
|
return cachedResult.Results, nil
|
||||||
@@ -559,7 +565,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
// 标记为部分过期
|
// 标记为部分过期
|
||||||
if time.Since(cachedResult.Timestamp) >= p.cacheTTL {
|
if time.Since(cachedResult.Timestamp) >= p.cacheTTL {
|
||||||
// 在后台刷新缓存
|
// 在后台刷新缓存
|
||||||
go p.refreshCacheInBackground(keyword, pluginSpecificCacheKey, searchFunc, cachedResult, mainCacheKey)
|
go p.refreshCacheInBackground(keyword, pluginSpecificCacheKey, searchFunc, cachedResult, mainCacheKey, ext)
|
||||||
|
|
||||||
// 日志记录
|
// 日志记录
|
||||||
fmt.Printf("[%s] 缓存已过期,后台刷新中: %s (已过期: %v)\n",
|
fmt.Printf("[%s] 缓存已过期,后台刷新中: %s (已过期: %v)\n",
|
||||||
@@ -582,7 +588,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
// 尝试获取工作槽
|
// 尝试获取工作槽
|
||||||
if !acquireWorkerSlot() {
|
if !acquireWorkerSlot() {
|
||||||
// 工作池已满,使用快速响应客户端直接处理
|
// 工作池已满,使用快速响应客户端直接处理
|
||||||
results, err := searchFunc(p.client, keyword)
|
results, err := searchFunc(p.client, keyword, ext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case errorChan <- err:
|
case errorChan <- err:
|
||||||
@@ -613,7 +619,7 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
defer releaseWorkerSlot()
|
defer releaseWorkerSlot()
|
||||||
|
|
||||||
// 执行搜索
|
// 执行搜索
|
||||||
results, err := searchFunc(p.backgroundClient, keyword)
|
results, err := searchFunc(p.backgroundClient, keyword, ext)
|
||||||
|
|
||||||
// 检查是否已经响应
|
// 检查是否已经响应
|
||||||
select {
|
select {
|
||||||
@@ -775,10 +781,16 @@ func (p *BaseAsyncPlugin) AsyncSearch(
|
|||||||
func (p *BaseAsyncPlugin) refreshCacheInBackground(
|
func (p *BaseAsyncPlugin) refreshCacheInBackground(
|
||||||
keyword string,
|
keyword string,
|
||||||
cacheKey string,
|
cacheKey string,
|
||||||
searchFunc func(*http.Client, string) ([]model.SearchResult, error),
|
searchFunc func(*http.Client, string, map[string]interface{}) ([]model.SearchResult, error),
|
||||||
oldCache cachedResponse,
|
oldCache cachedResponse,
|
||||||
originalCacheKey string,
|
originalCacheKey string,
|
||||||
|
ext map[string]interface{},
|
||||||
) {
|
) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// 注意:这里的cacheKey已经是插件特定的了,因为是从AsyncSearch传入的
|
// 注意:这里的cacheKey已经是插件特定的了,因为是从AsyncSearch传入的
|
||||||
|
|
||||||
// 检查是否有足够的工作槽
|
// 检查是否有足够的工作槽
|
||||||
@@ -791,7 +803,7 @@ func (p *BaseAsyncPlugin) refreshCacheInBackground(
|
|||||||
refreshStart := time.Now()
|
refreshStart := time.Now()
|
||||||
|
|
||||||
// 执行搜索
|
// 执行搜索
|
||||||
results, err := searchFunc(p.backgroundClient, keyword)
|
results, err := searchFunc(p.backgroundClient, keyword, ext)
|
||||||
if err != nil || len(results) == 0 {
|
if err != nil || len(results) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ func NewHunhepanAsyncPlugin() *HunhepanAsyncPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *HunhepanAsyncPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *HunhepanAsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 使用保存的主缓存键
|
// 使用保存的主缓存键,传递ext参数但不使用
|
||||||
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey)
|
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doSearch 实际的搜索实现
|
// doSearch 实际的搜索实现
|
||||||
func (p *HunhepanAsyncPlugin) doSearch(client *http.Client, keyword string) ([]model.SearchResult, error) {
|
func (p *HunhepanAsyncPlugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 创建结果通道和错误通道
|
// 创建结果通道和错误通道
|
||||||
resultChan := make(chan []HunhepanItem, 3)
|
resultChan := make(chan []HunhepanItem, 3)
|
||||||
errChan := make(chan error, 3)
|
errChan := make(chan error, 3)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"log"
|
||||||
"pansou/model"
|
"pansou/model"
|
||||||
"pansou/plugin"
|
"pansou/plugin"
|
||||||
"pansou/util/json"
|
"pansou/util/json"
|
||||||
@@ -37,19 +37,30 @@ func NewJikepanAsyncV2Plugin() *JikepanAsyncV2Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *JikepanAsyncV2Plugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *JikepanAsyncV2Plugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 使用保存的主缓存键
|
// 使用保存的主缓存键,传递ext参数但不使用
|
||||||
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey)
|
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doSearch 实际的搜索实现
|
// doSearch 实际的搜索实现
|
||||||
func (p *JikepanAsyncV2Plugin) doSearch(client *http.Client, keyword string) ([]model.SearchResult, error) {
|
func (p *JikepanAsyncV2Plugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 构建请求
|
// 构建请求
|
||||||
reqBody := map[string]interface{}{
|
reqBody := map[string]interface{}{
|
||||||
"name": keyword,
|
"name": keyword,
|
||||||
"is_all": false,
|
"is_all": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查ext中是否包含title_en参数,如果有则使用它
|
||||||
|
log.Printf("1111111111111111ext: v+%v", ext)
|
||||||
|
if ext != nil {
|
||||||
|
log.Printf("ext: v+%v", ext)
|
||||||
|
if isAll, ok := ext["is_all"].(bool); ok && isAll {
|
||||||
|
log.Printf("使用全量搜索 v+%v", isAll)
|
||||||
|
// 使用全量搜索,时间大约10秒
|
||||||
|
reqBody["is_all"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jsonData, err := json.Marshal(reqBody)
|
jsonData, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal request failed: %w", err)
|
return nil, fmt.Errorf("marshal request failed: %w", err)
|
||||||
|
|||||||
@@ -50,19 +50,19 @@ type Pan666AsyncPlugin struct {
|
|||||||
// NewPan666AsyncPlugin 创建新的pan666异步插件
|
// NewPan666AsyncPlugin 创建新的pan666异步插件
|
||||||
func NewPan666AsyncPlugin() *Pan666AsyncPlugin {
|
func NewPan666AsyncPlugin() *Pan666AsyncPlugin {
|
||||||
return &Pan666AsyncPlugin{
|
return &Pan666AsyncPlugin{
|
||||||
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("pan666", 3),
|
BaseAsyncPlugin: plugin.NewBaseAsyncPlugin("pan666", 4),
|
||||||
retries: MaxRetries,
|
retries: MaxRetries,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *Pan666AsyncPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *Pan666AsyncPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 使用保存的主缓存键
|
// 使用保存的主缓存键,传递ext参数但不使用
|
||||||
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey)
|
return p.AsyncSearch(keyword, p.doSearch, p.MainCacheKey, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doSearch 实际的搜索实现
|
// doSearch 实际的搜索实现
|
||||||
func (p *Pan666AsyncPlugin) doSearch(client *http.Client, keyword string) ([]model.SearchResult, error) {
|
func (p *Pan666AsyncPlugin) doSearch(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 初始化随机数种子
|
// 初始化随机数种子
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ func (p *PanSearchPlugin) getBaseURL() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *PanSearchPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *PanSearchPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 生成缓存键
|
// 生成缓存键
|
||||||
cacheKey := keyword
|
cacheKey := keyword
|
||||||
|
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ func (p *PantaPlugin) Priority() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *PantaPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *PantaPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 对关键词进行URL编码
|
// 对关键词进行URL编码
|
||||||
encodedKeyword := url.QueryEscape(keyword)
|
encodedKeyword := url.QueryEscape(keyword)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ type SearchPlugin interface {
|
|||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
Search(keyword string) ([]model.SearchResult, error)
|
// ext参数用于传递额外的搜索参数,插件可以根据需要使用或忽略
|
||||||
|
Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error)
|
||||||
|
|
||||||
// Priority 返回插件优先级(可选,用于控制结果排序)
|
// Priority 返回插件优先级(可选,用于控制结果排序)
|
||||||
Priority() int
|
Priority() int
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func (p *QuPanSouPlugin) Priority() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索并返回结果
|
// Search 执行搜索并返回结果
|
||||||
func (p *QuPanSouPlugin) Search(keyword string) ([]model.SearchResult, error) {
|
func (p *QuPanSouPlugin) Search(keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 生成缓存键
|
// 生成缓存键
|
||||||
cacheKey := keyword
|
cacheKey := keyword
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,12 @@ func injectMainCacheToAsyncPlugins(pluginManager *plugin.PluginManager, mainCach
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search 执行搜索
|
// Search 执行搜索
|
||||||
func (s *SearchService) Search(keyword string, channels []string, concurrency int, forceRefresh bool, resultType string, sourceType string, plugins []string) (model.SearchResponse, error) {
|
func (s *SearchService) Search(keyword string, channels []string, concurrency int, forceRefresh bool, resultType string, sourceType string, plugins []string, ext map[string]interface{}) (model.SearchResponse, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// 参数预处理
|
// 参数预处理
|
||||||
// 源类型标准化
|
// 源类型标准化
|
||||||
if sourceType == "" {
|
if sourceType == "" {
|
||||||
@@ -195,7 +200,7 @@ func (s *SearchService) Search(keyword string, channels []string, concurrency in
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
// 对于插件搜索,我们总是希望获取最新的缓存数据
|
// 对于插件搜索,我们总是希望获取最新的缓存数据
|
||||||
// 因此,即使forceRefresh=false,我们也需要确保获取到最新的缓存
|
// 因此,即使forceRefresh=false,我们也需要确保获取到最新的缓存
|
||||||
pluginResults, pluginErr = s.searchPlugins(keyword, plugins, forceRefresh, concurrency)
|
pluginResults, pluginErr = s.searchPlugins(keyword, plugins, forceRefresh, concurrency, ext)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +562,12 @@ func (s *SearchService) searchTG(keyword string, channels []string, forceRefresh
|
|||||||
}
|
}
|
||||||
|
|
||||||
// searchPlugins 搜索插件
|
// searchPlugins 搜索插件
|
||||||
func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRefresh bool, concurrency int) ([]model.SearchResult, error) {
|
func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRefresh bool, concurrency int, ext map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
|
// 确保ext不为nil
|
||||||
|
if ext == nil {
|
||||||
|
ext = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
// 生成缓存键
|
// 生成缓存键
|
||||||
cacheKey := cache.GeneratePluginCacheKey(keyword, plugins)
|
cacheKey := cache.GeneratePluginCacheKey(keyword, plugins)
|
||||||
|
|
||||||
@@ -648,25 +658,25 @@ func (s *SearchService) searchPlugins(keyword string, plugins []string, forceRef
|
|||||||
tasks = append(tasks, func() interface{} {
|
tasks = append(tasks, func() interface{} {
|
||||||
// 检查插件是否为异步插件
|
// 检查插件是否为异步插件
|
||||||
if asyncPlugin, ok := plugin.(interface {
|
if asyncPlugin, ok := plugin.(interface {
|
||||||
AsyncSearch(keyword string, searchFunc func(*http.Client, string) ([]model.SearchResult, error), mainCacheKey string) ([]model.SearchResult, error)
|
AsyncSearch(keyword string, searchFunc func(*http.Client, string, map[string]interface{}) ([]model.SearchResult, error), mainCacheKey string, ext map[string]interface{}) ([]model.SearchResult, error)
|
||||||
SetMainCacheKey(string)
|
SetMainCacheKey(string)
|
||||||
}); ok {
|
}); ok {
|
||||||
// 先设置主缓存键
|
// 先设置主缓存键
|
||||||
asyncPlugin.SetMainCacheKey(cacheKey)
|
asyncPlugin.SetMainCacheKey(cacheKey)
|
||||||
|
|
||||||
// 是异步插件,调用AsyncSearch方法并传递主缓存键
|
// 是异步插件,调用AsyncSearch方法并传递主缓存键和ext参数
|
||||||
results, err := asyncPlugin.AsyncSearch(keyword, func(client *http.Client, kw string) ([]model.SearchResult, error) {
|
results, err := asyncPlugin.AsyncSearch(keyword, func(client *http.Client, kw string, extParams map[string]interface{}) ([]model.SearchResult, error) {
|
||||||
// 这里使用插件的Search方法作为搜索函数
|
// 这里使用插件的Search方法作为搜索函数,传递ext参数
|
||||||
return plugin.Search(kw)
|
return plugin.Search(kw, extParams)
|
||||||
}, cacheKey)
|
}, cacheKey, ext)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
} else {
|
} else {
|
||||||
// 不是异步插件,直接调用Search方法
|
// 不是异步插件,直接调用Search方法,传递ext参数
|
||||||
results, err := plugin.Search(keyword)
|
results, err := plugin.Search(keyword, ext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user