mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +08:00
fix(search): bundled SearXNG sidecar actually works out of the box
This commit is contained in:
parent
178b9306d9
commit
52a9a785c1
@ -29,6 +29,10 @@ JWT_SECRET=
|
|||||||
# 若留空,服务器会允许所有 origin 并在启动日志里 WARN。生产部署务必设置。
|
# 若留空,服务器会允许所有 origin 并在启动日志里 WARN。生产部署务必设置。
|
||||||
MATECLAW_CORS_ALLOWED_ORIGINS=
|
MATECLAW_CORS_ALLOWED_ORIGINS=
|
||||||
|
|
||||||
|
# SearXNG 会话密钥(容器内部用,留空会用开发默认值)。生产部署请设成 32+ 位随机串。
|
||||||
|
# openssl rand -hex 32
|
||||||
|
SEARXNG_SECRET=
|
||||||
|
|
||||||
# ==================== 浏览器工具(可选) ====================
|
# ==================== 浏览器工具(可选) ====================
|
||||||
#
|
#
|
||||||
# Docker 镜像已经把 Chromium 打进去了,默认零配置可用。
|
# Docker 镜像已经把 Chromium 打进去了,默认零配置可用。
|
||||||
|
|||||||
@ -31,18 +31,30 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
# SearXNG 搜索引擎(keyless 搜索 provider,零配置可用)
|
# SearXNG 搜索引擎(keyless 搜索 provider)
|
||||||
|
#
|
||||||
|
# ⚠️ deploy/searxng/settings.yml is bind-mounted because the upstream image
|
||||||
|
# ships with JSON format disabled and the anti-bot Limiter plugin enabled —
|
||||||
|
# both of which would silently break mateclaw's SearXNGSearchProvider.
|
||||||
|
# Do NOT remove that bind mount.
|
||||||
searxng:
|
searxng:
|
||||||
image: searxng/searxng:latest
|
image: searxng/searxng:latest
|
||||||
container_name: mateclaw-searxng
|
container_name: mateclaw-searxng
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- SEARXNG_BASE_URL=http://searxng:8080
|
- SEARXNG_BASE_URL=http://searxng:8080
|
||||||
|
- SEARXNG_SECRET=${SEARXNG_SECRET:-mateclaw-dev-searxng-secret-change-me}
|
||||||
|
- UWSGI_WORKERS=2
|
||||||
|
- UWSGI_THREADS=4
|
||||||
volumes:
|
volumes:
|
||||||
- searxng_data:/etc/searxng
|
- searxng_data:/etc/searxng
|
||||||
|
# This override MUST be mounted AFTER the named volume so it shadows
|
||||||
|
# the image's default settings.yml (enables JSON, disables limiter).
|
||||||
|
- ./deploy/searxng/settings.yml:/etc/searxng/settings.yml:ro
|
||||||
ports:
|
ports:
|
||||||
- "8088:8080"
|
- "8088:8080"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
# Healthz needs json format, so this also doubles as an integration check.
|
||||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/healthz"]
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/healthz"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
|||||||
@ -81,24 +81,42 @@ public class SearXNGSearchProvider implements SearchProvider {
|
|||||||
urlBuilder.append("&time_range=").append(searchQuery.freshness().toLowerCase());
|
urlBuilder.append("&time_range=").append(searchQuery.freshness().toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = HttpUtil.createGet(urlBuilder.toString())
|
var resp = HttpUtil.createGet(urlBuilder.toString())
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.timeout(15000)
|
.timeout(15000)
|
||||||
.execute()
|
.execute();
|
||||||
.body();
|
int status = resp.getStatus();
|
||||||
|
String response = resp.body();
|
||||||
|
String contentType = resp.header("Content-Type");
|
||||||
|
|
||||||
log.debug("SearXNG result for '{}': length={}", searchQuery.query(), response != null ? response.length() : 0);
|
log.debug("SearXNG response for '{}': status={}, contentType={}, length={}",
|
||||||
return parseResponse(response, searchQuery.resolvedCount());
|
searchQuery.query(), status, contentType, response != null ? response.length() : 0);
|
||||||
|
return parseResponse(response, status, contentType, searchQuery.resolvedCount(), urlBuilder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SearchResult> parseResponse(String response, int limit) {
|
private List<SearchResult> parseResponse(String response, int status, String contentType,
|
||||||
|
int limit, String requestUrl) {
|
||||||
List<SearchResult> results = new ArrayList<>();
|
List<SearchResult> results = new ArrayList<>();
|
||||||
if (response == null || response.isBlank()) return results;
|
if (response == null || response.isBlank()) {
|
||||||
|
log.warn("SearXNG returned empty body (status={}, url={})", status, requestUrl);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
if (status >= 400) {
|
||||||
|
log.warn("SearXNG returned HTTP {} — preview: {}", status, preview(response));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
if (contentType != null && !contentType.contains("json")) {
|
||||||
|
// Most common cause: settings.yml has no `json` under search.formats
|
||||||
|
// or the Limiter plugin rewrote the response to HTML.
|
||||||
|
log.warn("SearXNG did not return JSON (contentType={}). Check settings.yml has search.formats including 'json' and server.limiter: false. Preview: {}",
|
||||||
|
contentType, preview(response));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject json = JSONUtil.parseObj(response);
|
JSONObject json = JSONUtil.parseObj(response);
|
||||||
JSONArray items = json.getJSONArray("results");
|
JSONArray items = json.getJSONArray("results");
|
||||||
if (items == null) return results;
|
if (items == null || items.isEmpty()) return results;
|
||||||
|
|
||||||
limit = Math.min(items.size(), limit);
|
limit = Math.min(items.size(), limit);
|
||||||
for (int i = 0; i < limit; i++) {
|
for (int i = 0; i < limit; i++) {
|
||||||
@ -114,11 +132,17 @@ public class SearXNGSearchProvider implements SearchProvider {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("SearXNG 结果解析失败: {}", e.getMessage());
|
log.warn("SearXNG parse failed: {} — preview: {}", e.getMessage(), preview(response));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String preview(String body) {
|
||||||
|
if (body == null) return "";
|
||||||
|
String flat = body.replaceAll("\\s+", " ").trim();
|
||||||
|
return flat.length() > 200 ? flat.substring(0, 200) + "..." : flat;
|
||||||
|
}
|
||||||
|
|
||||||
private String extractDomain(String url) {
|
private String extractDomain(String url) {
|
||||||
try {
|
try {
|
||||||
return URI.create(url).getHost();
|
return URI.create(url).getHost();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user