- 新增 OkHttp 客户端配置及依赖 - 添加 SearXNG 搜索引擎集成配置 - 创建基础分页查询类 BasePageQuery - 实现网络搜索增强顾问 NetworkSearchAdvisor - 增加聊天历史消息和对话的分页查询接口 - 添加对话摘要重命名与删除功能 - 配置 MyBatis Plus 分页插件支持 - 引入 Jsoup用于网页内容解析 - 新增 Hutool 工具库依赖 - 实现搜索结果内容抓取服务 - 添加搜索结果 DTO 和相关服务接口 - 扩展响应码枚举支持对话不存在情况 - 新增多个 VO 类用于请求和响应数据传输
111 lines
4.4 KiB
Java
111 lines
4.4 KiB
Java
package com.hanserwei.airobot.service.impl;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.hanserwei.airobot.model.dto.SearchResultDTO;
|
|
import com.hanserwei.airobot.service.SearXNGService;
|
|
import jakarta.annotation.Resource;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import okhttp3.HttpUrl;
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.StreamSupport;
|
|
|
|
/**
|
|
* SearXNG 搜索服务实现类
|
|
* <p>
|
|
* 该类通过调用 SearXNG 的 API 实现聚合搜索引擎功能,支持从多个搜索引擎获取结果并按评分排序。
|
|
* </p>
|
|
*/
|
|
@Service
|
|
@Slf4j
|
|
public class SearXNGServiceImpl implements SearXNGService {
|
|
@Resource
|
|
private OkHttpClient okHttpClient;
|
|
@Resource
|
|
private ObjectMapper objectMapper;
|
|
@Value("${searxng.url}")
|
|
private String searxngUrl;
|
|
@Value("${searxng.count}")
|
|
private int count;
|
|
|
|
/**
|
|
* 根据关键词执行搜索操作
|
|
*
|
|
* @param query 搜索关键词,不能为空
|
|
* @return 搜索结果列表,每个元素包含 URL 和评分;若发生异常或无结果则返回空列表
|
|
*/
|
|
@Override
|
|
public List<SearchResultDTO> search(String query) {
|
|
// 构建 SearXNG API 请求 URL
|
|
HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse(searxngUrl)).newBuilder()
|
|
.addQueryParameter("q", query) // 设置搜索关键词
|
|
.addQueryParameter("format", "json") // 指定返回 JSON 格式
|
|
.addQueryParameter("engines", "wolframalpha,presearch,seznam,mwmbl,encyclosearch,bpb,mojeek,right dao,wikimini,crowdview,searchmysite,bing,naver,360search") // 指定聚合的目标搜索引擎(配置本地网络能够访问的通的搜索引擎)
|
|
.build();
|
|
|
|
// 创建 HTTP GET 请求
|
|
Request request = new Request.Builder()
|
|
.url(httpUrl)
|
|
.get()
|
|
.build();
|
|
|
|
// 发送 HTTP 请求
|
|
try (Response response = okHttpClient.newCall(request).execute()) {
|
|
// 判断请求是否成功
|
|
if (response.isSuccessful()) {
|
|
// 拿到返回结果
|
|
String result = response.body().string();
|
|
log.info("## SearXNG 搜索结果: {}", result);
|
|
|
|
// 解析 JSON 响应
|
|
JsonNode root = objectMapper.readTree(result);
|
|
JsonNode results = root.get("results"); // 获取结果数组节点
|
|
|
|
// 定义 Record 类型:用于临时存储分数和节点引用
|
|
record NodeWithUrlAndScore(double score, JsonNode node) {
|
|
}
|
|
|
|
// 处理搜索结果流:
|
|
// 1. 提取评分
|
|
// 2. 按评分降序排序
|
|
// 3. 限制返回结果数量
|
|
List<NodeWithUrlAndScore> nodesWithScore = StreamSupport.stream(results.spliterator(), false)
|
|
.map(node -> {
|
|
// 只提取分数,避免构建完整对象
|
|
double score = node.path("score").asDouble(0.0); // 提取评分
|
|
return new NodeWithUrlAndScore(score, node);
|
|
})
|
|
.sorted(Comparator.comparingDouble(NodeWithUrlAndScore::score).reversed()) // 按评分降序
|
|
.limit(count) // 限制返回结果数量
|
|
.toList();
|
|
|
|
// 转换为 SearchResult 对象集合
|
|
return nodesWithScore.stream()
|
|
.map(n -> {
|
|
JsonNode node = n.node();
|
|
String originalUrl = node.path("url").asText(""); // 提取 URL
|
|
return SearchResultDTO.builder()
|
|
.url(originalUrl)
|
|
.score(n.score()) // 保留评分
|
|
.build();
|
|
})
|
|
.collect(Collectors.toList());
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("", e);
|
|
}
|
|
// 返回空集合
|
|
return Collections.emptyList();
|
|
}
|
|
}
|