fix: 联想建议词改成「统一来源 + 自动兜底」,任何引擎都能出词
问题:除百度/360 外,其它引擎的联想下拉基本是空的。 实测(2026-09-28 在站点服务器上逐个探测): 百度 sugrec 200 / 0.12s / 1137B ✅ 最快最稳 360 sug.so.360.cn 200 / 0.20s / 714B ✅ 必应 api.bing.com 200 / 0.5~1.6s ✅ 可用但明显慢 搜狗 ajajjson 200 / **0 字节** ❌ 联想域名 w.sugg.sogou.com 已不解析 谷歌 suggestqueries 连接超时 ❌ 国内不可达 DuckDuckGo 连接超时 ❌ 国内不可达 按「联想统一取百度、搜索仍走各自引擎」实现: - 新增设置 suggest_source(设置页可选 百度/必应/360/各引擎自己的接口),默认百度。 - 任何情况下都带自动兜底链:首选来源取不到词(接口挂/超时/返回空) 就依次回退 百度 → 必应 → 360;单个候选 3 秒超时,绝不让下拉框卡住。 - 候选来源不过滤 is_active —— 作为联想来源的引擎可能在前台被隐藏。 - 响应里带 src 字段标明这次实际用的是谁,便于排查。 顺带修一个更隐蔽的 bug:前台 <form id="search-form"> 原本**没有 data-slug**, 而 JS 只在「点击引擎标签」时才把它写上 → 一进站、没点过引擎时 slug 为空, suggest.php 查不到引擎,联想**全都不出词**。现在表单自带默认引擎的 slug。 其它: - 清掉种子里已失效的搜狗联想地址;加一条幂等迁移,只匹配默认种子值, 用户自己改过的地址不动。谷歌/DDG 也不再预设(前端不可达)。 - app.js:请求加序号,防止先发的慢响应覆盖后发的快响应;切换引擎时 若输入框已有内容就顺带换一份建议。 - e2e 新增「联想来源」场景(本地桩服务器 + 独立端口,不依赖外网): 8 条断言覆盖 默认来源 / auto 模式 / 回退 / 空关键词 / 响应头。 验证:本地 e2e 57/57;部署 38 文件哈希逐字节一致、双版本 php -l、37 条 静态断言全过;线上实测 google/sogou/ddg 三个「自家接口不可用」的引擎 均返回 10 条百度建议(src=baidu),中英文都正常。
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
- **轻量**:纯 PHP + SQLite,不依赖 MySQL / 框架 / Composer;前端无 Bootstrap / jQuery,原生 CSS + JS。
|
||||
- **个人向**:单管理员,页面就是「你自己常用网站的起始页」。
|
||||
- **搜索引擎可切换 + 联想下拉**:后台勾选喜欢的搜索引擎即可显示在顶部;输入时下拉联想词由服务端代理(规避跨域)。后台的「显示/隐藏」按钮点一下**原地生效、不跳页**,且无 JS 时也能正常提交。
|
||||
- **搜索引擎可切换 + 联想下拉**:后台勾选喜欢的搜索引擎即可显示在顶部;输入时下拉建议词由服务端代理(规避跨域)。默认**联想词统一取百度、搜索走各自引擎**,并带自动兜底,任何引擎都保证有下拉词(详见下节实测表)。后台的「显示/隐藏」按钮点一下**原地生效、不跳页**,且无 JS 时也能正常提交。
|
||||
- **添加即补全**:填网址后先访问该网址拿到标题,再按「高清优先」的顺序去网上找相关图标(`manifest` 512 → `apple-touch-icon` → `<link rel=icon>` → 第三方聚合服务),下载后统一规范化为 **128×128 PNG** 存到服务器本地。
|
||||
- **图标可自定义,且自定义后不再被覆盖**:可为任一网站上传图标或直接填图标地址;一旦自定义,该网站的图标会被标记保护,「一键全部更新」只刷新它的名称、跳过图标。
|
||||
- **显示简称**:每个网站有「名称(自动)」和「简称(手动)」两个字段,页面展示你自定义的简称。
|
||||
@@ -58,20 +58,39 @@ SimpleNav/
|
||||
> 需要额外说明的是:**服务器上若装了宝塔「网站加速」之类的页面缓存插件,本站也不需要为它做任何特殊配置**。
|
||||
> 动态页发的 `Cache-Control: ... no-cache ...` 会被这类插件视为「不缓存」而直接跳过写入。
|
||||
|
||||
## 搜索引擎联想接口说明
|
||||
## 搜索联想(建议词)说明
|
||||
|
||||
联想接口需服务端代理(浏览器直接调用会跨域)。内置解析类型:
|
||||
联想接口必须由**服务端代理**(浏览器直接调用会跨域)。2026-09-28 在本站服务器实测各家的可用性:
|
||||
|
||||
| 类型 | 平台 | 联想接口 |
|
||||
| 引擎 | 联想接口 | 实测 | 结论 |
|
||||
|---|---|---|---|
|
||||
| 百度 | `www.baidu.com/sugrec?prod=pc&wd={q}` | 200 / 0.12s | ✅ 最快最稳 |
|
||||
| 360 | `sug.so.360.cn/suggest?...&word={q}` | 200 / 0.20s | ✅ |
|
||||
| 必应 | `api.bing.com/osjson.aspx?query={q}` | 200 / 0.5~1.6s | ✅ 可用但明显慢 |
|
||||
| 搜狗 | `www.sogou.com/suggnew/ajajjson?...` | 200 / **0 字节** | ❌ 已失效(联想域名不再解析) |
|
||||
| 谷歌 | `suggestqueries.google.com/complete/search?...` | 连接超时 | ❌ 国内不可达 |
|
||||
| DuckDuckGo | `duckduckgo.com/ac/?...` | 连接超时 | ❌ 国内不可达 |
|
||||
|
||||
因此默认策略是——**联想词统一取百度,搜索仍走各自引擎**:点哪个引擎就用哪个引擎去搜,
|
||||
只是输入框下面的建议来自百度,这样切到任何引擎都有下拉词。
|
||||
|
||||
后台「设置 → 联想词来源」可切换:`百度`(默认)/ `必应` / `360` / `各搜索引擎自己的接口`。
|
||||
任何情况下都带**自动兜底链**:首选来源取不到词(接口挂了、超时、返回空)就依次回退
|
||||
`百度 → 必应 → 360`,所以下拉框不会因为某家接口失效而变空。
|
||||
|
||||
> 想在某个引擎上恢复「用自己的联想接口」,把来源改成「各搜索引擎自己的接口」,
|
||||
> 再在该引擎的「联想接口 / 联想解析类型」里填好地址即可(解析类型见下)。
|
||||
|
||||
内置的解析类型(`suggest_type`):
|
||||
|
||||
| 类型 | 说明 | 返回形态 |
|
||||
|---|---|---|
|
||||
| `bing_osjson` | 必应 | `https://api.bing.com/osjson.aspx?query={q}` |
|
||||
| `baidu_sugrec` | 百度 | `https://www.baidu.com/sugrec?prod=pc&wd={q}` |
|
||||
| `google_firefox` | 谷歌 | `https://suggestqueries.google.com/complete/search?client=firefox&q={q}` |
|
||||
| `sogou_ajaj` | 搜狗 | `https://www.sogou.com/suggnew/ajajjson?type=web&key={q}` |
|
||||
| `so360` | 360 | `https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word={q}` |
|
||||
| `ddg` | DuckDuckGo | `https://duckduckgo.com/ac/?type=list&q={q}` |
|
||||
|
||||
> 注:国内服务器访问 Google / DuckDuckGo 可能不通,联想会优雅降级为空(不影响搜索跳转)。
|
||||
| `baidu_sugrec` | 百度 | `{"g":[{"q":"…"}]}` |
|
||||
| `so360` | 360 | `{"result":[{"word":"…"}]}` |
|
||||
| `bing_osjson` | 必应 | `["q",["s1","s2"]]` |
|
||||
| `google_firefox` | 谷歌 | `["q",["s1","s2"]]` |
|
||||
| `sogou_ajaj` | 搜狗 | `{"items":[…]}`(GBK,已自动转码) |
|
||||
| `ddg` | DuckDuckGo | `[{"phrase":"…"}]` |
|
||||
|
||||
## 许可
|
||||
|
||||
|
||||
@@ -208,6 +208,8 @@ try {
|
||||
|
||||
/* ---------- 设置 ---------- */
|
||||
case 'settings_save': {
|
||||
$srcRaw = (string)($_POST['suggest_source'] ?? 'baidu');
|
||||
$src = in_array($srcRaw, ['auto', 'baidu', 'bing', 'so360'], true) ? $srcRaw : 'baidu';
|
||||
$map = [
|
||||
'site_name' => trim((string)($_POST['site_name'] ?? '')),
|
||||
'subtitle' => trim((string)($_POST['subtitle'] ?? '')),
|
||||
@@ -215,6 +217,7 @@ try {
|
||||
'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7',
|
||||
'open_blank' => isset($_POST['open_blank']) ? '1' : '0',
|
||||
'icon_services' => isset($_POST['icon_services']) ? '1' : '0',
|
||||
'suggest_source' => $src,
|
||||
];
|
||||
$st = $pdo->prepare('INSERT INTO settings (k,v) VALUES (?,?) ON CONFLICT(k) DO UPDATE SET v=excluded.v');
|
||||
foreach ($map as $k => $v) { $st->execute([$k, $v]); }
|
||||
|
||||
@@ -241,6 +241,11 @@ elseif ($p === 'engines'): ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="chk"><input type="checkbox" name="is_active" id="e-active"> 启用(显示在首页顶部)</label>
|
||||
<p class="hint span2">
|
||||
这两项只在「设置 → 联想词来源」选<b>各搜索引擎自己的接口</b>时才会用到;
|
||||
默认是统一用百度出建议(搜索本身仍走各自的搜索地址)。
|
||||
这里填的接口只要通了就会被优先使用,没通就自动回退到百度。
|
||||
</p>
|
||||
<div class="span2 form-actions"><button class="btn primary">保存</button><button type="button" class="btn ghost" id="e-reset">清空</button></div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -296,6 +301,26 @@ else: // settings ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>联想词来源
|
||||
<select name="suggest_source">
|
||||
<?php
|
||||
$cur = setting('suggest_source', 'baidu');
|
||||
$opts = [['baidu', '百度(推荐,最快)'], ['bing', '必应'], ['so360', '360'], ['auto', '各搜索引擎自己的接口']];
|
||||
$known = array_column($engines, 'slug');
|
||||
foreach ($opts as $o):
|
||||
if ($o[0] !== 'auto' && !in_array($o[0], $known, true)) { continue; }
|
||||
?>
|
||||
<option value="<?= e($o[0]) ?>" <?= $cur === $o[0] ? 'selected' : '' ?>><?= e($o[1]) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<p class="hint span2">
|
||||
搜索时仍然用你选中的那个引擎,这里只决定<b>下拉建议词</b>从哪儿来。
|
||||
实测(2026-09-28):<b>百度 0.12s、360 0.20s、必应 0.5~1.6s</b> 可用;
|
||||
<b>搜狗接口已失效</b>(返回 0 字节)、<b>谷歌与 DuckDuckGo 国内连不上</b>。
|
||||
所以默认「统一用百度」——这样切到任何引擎下拉都有词。选「各搜索引擎自己的接口」则恢复成
|
||||
各用各的(没恢复好的引擎会自动回退到百度的建议)。
|
||||
</p>
|
||||
<label>主题色<input type="text" name="primary_color" value="<?= e(setting('primary_color', '#2F6BF7')) ?>"></label>
|
||||
<label class="chk"><input type="checkbox" name="open_blank" <?= setting('open_blank', '1') === '1' ? 'checked' : '' ?>> 搜索结果新窗口打开</label>
|
||||
<label class="chk"><input type="checkbox" name="icon_services" <?= setting('icon_services', '1') === '1' ? 'checked' : '' ?>> 联网搜索高清图标(第三方聚合服务)</label>
|
||||
|
||||
+19
-12
@@ -18,6 +18,8 @@
|
||||
if (form) { form.setAttribute('action', search); form.setAttribute('data-slug', slug); }
|
||||
if (input) { input.setAttribute('name', param); input.setAttribute('placeholder', ph); }
|
||||
hideWord();
|
||||
// 已经输了字就顺手换一份建议(auto 模式下各引擎来源不同)
|
||||
if (input && input.value.trim() !== '') { askSuggest(); }
|
||||
}
|
||||
tabs.forEach(function (el) {
|
||||
el.addEventListener('click', function () { activateEngine(el); input && input.focus(); });
|
||||
@@ -26,7 +28,8 @@
|
||||
/* ---------- 联想下拉 ---------- */
|
||||
var word = document.getElementById('word');
|
||||
var timer = null;
|
||||
var lastQ = '';
|
||||
var lastQ = null;
|
||||
var seq = 0; // 请求序号:防止先发的慢响应覆盖后发的快响应
|
||||
|
||||
function hideWord() { if (word) { word.innerHTML = ''; word.style.display = 'none'; } }
|
||||
function showWord(items) {
|
||||
@@ -40,20 +43,24 @@
|
||||
word.style.display = 'block';
|
||||
}
|
||||
|
||||
function askSuggest() {
|
||||
if (!input) return;
|
||||
var q = input.value.trim();
|
||||
if (q === '') { lastQ = null; hideWord(); return; }
|
||||
if (q === lastQ) { return; }
|
||||
lastQ = q;
|
||||
var mine = ++seq;
|
||||
var slug = form ? (form.getAttribute('data-slug') || '') : '';
|
||||
fetch(cfg.suggest + '?slug=' + encodeURIComponent(slug) + '&q=' + encodeURIComponent(q))
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) { if (mine === seq) { showWord(data && data.items); } })
|
||||
.catch(function () { if (mine === seq) { hideWord(); } });
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.addEventListener('input', function () {
|
||||
var q = input.value.trim();
|
||||
if (q === '') { hideWord(); return; }
|
||||
if (q === lastQ) return;
|
||||
lastQ = q;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
var slug = form ? (form.getAttribute('data-slug') || '') : '';
|
||||
fetch(cfg.suggest + '?slug=' + encodeURIComponent(slug) + '&q=' + encodeURIComponent(q))
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) { showWord(data && data.items); })
|
||||
.catch(function () { hideWord(); });
|
||||
}, 180);
|
||||
timer = setTimeout(askSuggest, 180);
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', function (e) {
|
||||
|
||||
@@ -81,6 +81,12 @@ SQL);
|
||||
$pdo->exec('ALTER TABLE sites ADD COLUMN icon_custom INTEGER NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
// 老库升级:清掉「搜狗联想」这个已经彻底失效的默认值
|
||||
// (2026-09-28 实测:suggnew/ajajjson 返回 200 但 0 字节,联想域名 w.sugg.sogou.com 已不解析)
|
||||
// 只匹配默认种子里的那个 URL —— 用户自己改过的地址绝不动。
|
||||
$pdo->prepare('UPDATE engines SET suggest_url=?, suggest_type=? WHERE slug=? AND suggest_url=?')
|
||||
->execute(['', '', 'sogou', 'https://www.sogou.com/suggnew/ajajjson?type=web&key={q}']);
|
||||
|
||||
// 是否为空库(用于判断是否写入默认数据)
|
||||
$isEmpty = (int)$pdo->query('SELECT COUNT(*) FROM groups')->fetchColumn() === 0;
|
||||
|
||||
@@ -112,13 +118,16 @@ function seed_engines(PDO $pdo): void
|
||||
return;
|
||||
}
|
||||
// name, slug, search_url, param, suggest_url, suggest_type, color, active, sort
|
||||
// ⚠️ suggest_url 留空的含义:该引擎自己没有可用的联想接口。
|
||||
// 反正默认「联想词来源 = 百度」,留空只会让 auto 模式下少一次无谓的超时等待。
|
||||
// 搜狗(suggnew/ajajjson 已失效)、谷歌 / DuckDuckGo(国内不可达)都不填。
|
||||
$rows = [
|
||||
['必应', 'bing', 'https://cn.bing.com/search', 'q', 'https://api.bing.com/osjson.aspx?query={q}', 'bing_osjson', '#60A504', 1, 1],
|
||||
['百度', 'baidu', 'https://www.baidu.com/s', 'wd', 'https://www.baidu.com/sugrec?prod=pc&wd={q}', 'baidu_sugrec', '#2100E0', 1, 2],
|
||||
['谷歌', 'google', 'https://www.google.com/search', 'q', 'https://suggestqueries.google.com/complete/search?client=firefox&q={q}', 'google_firefox','#3B83FA', 0, 3],
|
||||
['搜狗', 'sogou', 'https://www.sogou.com/web', 'query', 'https://www.sogou.com/suggnew/ajajjson?type=web&key={q}', 'sogou_ajaj', '#FE6B14', 0, 4],
|
||||
['谷歌', 'google', 'https://www.google.com/search', 'q', '', '', '#3B83FA', 0, 3],
|
||||
['搜狗', 'sogou', 'https://www.sogou.com/web', 'query', '', '', '#FE6B14', 0, 4],
|
||||
['360', 'so360', 'https://www.so.com/s', 'q', 'https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word={q}', 'so360', '#1BB04A', 0, 5],
|
||||
['DuckGo','ddg', 'https://duckduckgo.com/', 'q', 'https://duckduckgo.com/ac/?type=list&q={q}', 'ddg', '#DE5833', 0, 6],
|
||||
['DuckGo','ddg', 'https://duckduckgo.com/', 'q', '', '', '#DE5833', 0, 6],
|
||||
];
|
||||
$stmt = $pdo->prepare('INSERT INTO engines (name,slug,search_url,param,suggest_url,suggest_type,color,is_active,sort_order) VALUES (?,?,?,?,?,?,?,?,?)');
|
||||
foreach ($rows as $r) {
|
||||
|
||||
@@ -103,7 +103,8 @@ function site_tile(array $s): void
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<form id="search-form" action="<?= e($default['search_url'] ?? 'https://cn.bing.com/search') ?>" method="get" target="<?= setting('open_blank', '1') === '1' ? '_blank' : '_self' ?>" rel="noopener">
|
||||
<form id="search-form" action="<?= e($default['search_url'] ?? 'https://cn.bing.com/search') ?>" method="get" target="<?= setting('open_blank', '1') === '1' ? '_blank' : '_self' ?>" rel="noopener"
|
||||
data-slug="<?= e($default['slug'] ?? '') ?>">
|
||||
<input type="text" id="search-text" name="<?= e($default['param'] ?? 'q') ?>"
|
||||
placeholder="<?= e(($default['name'] ?? '搜索') . '搜索') ?>" autocomplete="off">
|
||||
<button class="submit" type="submit" aria-label="搜索">
|
||||
|
||||
+92
-16
@@ -2,7 +2,23 @@
|
||||
/**
|
||||
* 简航 SimpleNav —— 搜索联想代理
|
||||
* 浏览器直接调用各搜索引擎的联想接口会有跨域/JSONP 限制,这里由服务端代理并统一输出 JSON。
|
||||
*
|
||||
* ⚠️ 各家联想接口在国内服务器上的可用性差别很大。2026-09-28 在本站服务器实测:
|
||||
*
|
||||
* 百度 sugrec?prod=pc&wd= 200 / 0.12s / 1137B ✅ 最快最稳 → 默认来源
|
||||
* 360 sug.so.360.cn/suggest 200 / 0.20s / 714B ✅
|
||||
* 必应 api.bing.com/osjson 200 / 0.5~1.6s / 408B ✅ 可用但明显慢
|
||||
* 搜狗 suggnew/ajajjson 200 / **0 字节** ❌ 联想域名已不解析
|
||||
* 谷歌 suggestqueries.google 连接超时 ❌ 国内不可达
|
||||
* DuckDuckGo 连接超时 ❌ 国内不可达
|
||||
*
|
||||
* 所以默认策略是「**联想词统一取百度,搜索仍走各自引擎**」:
|
||||
* 点哪个引擎就用哪个引擎搜索,只是输入框下面的建议来自百度。这样任何一个引擎
|
||||
* 都不会出现「下拉是空的」。想恢复「各引擎用自己的接口」,把设置里的
|
||||
* 「联想词来源」改成 auto 即可(见 admin 设置页)。
|
||||
*
|
||||
* 用法:suggest.php?slug=bing&q=关键词
|
||||
* slug = 当前选中的引擎(auto 模式下用它自己的接口;其它模式下只作为兜底候选)
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/functions.php';
|
||||
@@ -11,36 +27,96 @@ header('Content-Type: application/json; charset=utf-8');
|
||||
send_no_cache();
|
||||
|
||||
$slug = isset($_GET['slug']) ? preg_replace('/[^a-z0-9\-_]/i', '', (string)$_GET['slug']) : '';
|
||||
$q = isset($_GET['q']) ? trim((string)$_GET['q']) : '';
|
||||
$q = isset($_GET['q']) ? trim((string)$_GET['q']) : '';
|
||||
|
||||
if ($q === '') {
|
||||
echo json_encode(['q' => '', 'items' => []]);
|
||||
echo json_encode(['q' => '', 'items' => [], 'src' => ''], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$items = [];
|
||||
try {
|
||||
$st = db()->prepare('SELECT * FROM engines WHERE slug=? AND is_active=1 LIMIT 1');
|
||||
$st->execute([$slug]);
|
||||
$engine = $st->fetch();
|
||||
if ($engine && $engine['suggest_url'] !== '') {
|
||||
$u = str_replace('{q}', rawurlencode($q), $engine['suggest_url']);
|
||||
$resp = http_get($u, 100000, 5);
|
||||
if ($resp !== null) {
|
||||
$items = parse_suggest($engine['suggest_type'], ensure_utf8($resp['body']), $q);
|
||||
}
|
||||
$used = '';
|
||||
foreach (suggest_order($slug) as $engine) {
|
||||
$items = fetch_suggest($engine, $q);
|
||||
if ($items) {
|
||||
$used = (string)$engine['slug'];
|
||||
break;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$items = [];
|
||||
}
|
||||
|
||||
// 去重、限长、裁剪
|
||||
$items = array_values(array_unique(array_filter(array_map('trim', $items))));
|
||||
$items = array_slice($items, 0, 10);
|
||||
|
||||
echo json_encode(['q' => $q, 'items' => $items], JSON_UNESCAPED_UNICODE);
|
||||
echo json_encode(['q' => $q, 'items' => $items, 'src' => $used], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
|
||||
/* ========================================================================== */
|
||||
|
||||
/** 取一个引擎行(**不过滤 is_active** —— 作为联想来源的引擎可能在前台被隐藏了) */
|
||||
function engine_row(string $slug): ?array
|
||||
{
|
||||
static $cache = [];
|
||||
if ($slug === '') {
|
||||
return null;
|
||||
}
|
||||
if (!array_key_exists($slug, $cache)) {
|
||||
$st = db()->prepare('SELECT * FROM engines WHERE slug=? LIMIT 1');
|
||||
$st->execute([$slug]);
|
||||
$r = $st->fetch();
|
||||
$cache[$slug] = $r ?: null;
|
||||
}
|
||||
return $cache[$slug];
|
||||
}
|
||||
|
||||
/** 抓一次联想并解析;任何异常都退化成空数组,绝不让下拉框卡住 */
|
||||
function fetch_suggest(array $engine, string $q): array
|
||||
{
|
||||
if ((string)($engine['suggest_url'] ?? '') === '') {
|
||||
return [];
|
||||
}
|
||||
$u = str_replace('{q}', rawurlencode($q), (string)$engine['suggest_url']);
|
||||
// 3 秒。既然有兜底链,宁快速失败也不要让输入框等太久。
|
||||
$resp = http_get($u, 100000, 3);
|
||||
if ($resp === null) {
|
||||
return [];
|
||||
}
|
||||
return parse_suggest((string)$engine['suggest_type'], ensure_utf8((string)$resp['body']), $q);
|
||||
}
|
||||
|
||||
/**
|
||||
* 排出「先试谁、再试谁」的候选顺序。
|
||||
*
|
||||
* setting('suggest_source') = 'auto' → 先用当前引擎自己的接口,再走兜底链
|
||||
* 其它值(默认 'baidu') → 统一用该来源,失败才走兜底链
|
||||
*
|
||||
* 兜底链固定为「百度 → 必应 → 360」(实测可用的三家,按快慢排序),
|
||||
* 且只保留库里真实存在、且配了 suggest_url 的引擎。
|
||||
*/
|
||||
function suggest_order(string $slug): array
|
||||
{
|
||||
$src = trim(setting('suggest_source', 'baidu'));
|
||||
$slugs = [];
|
||||
|
||||
if ($src === 'auto') {
|
||||
if ($slug !== '') { $slugs[] = $slug; }
|
||||
} else {
|
||||
$slugs[] = $src;
|
||||
}
|
||||
foreach (['baidu', 'bing', 'so360'] as $s) {
|
||||
$slugs[] = $s;
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach (array_unique($slugs) as $s) {
|
||||
$row = engine_row($s);
|
||||
if ($row !== null && (string)$row['suggest_url'] !== '') {
|
||||
$out[] = $row;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 按引擎类型解析联想结果 */
|
||||
function parse_suggest(string $type, string $body, string $q): array
|
||||
{
|
||||
@@ -112,5 +188,5 @@ function parse_suggest(string $type, string $body, string $q): array
|
||||
elseif (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; }
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
return is_array($out) ? $out : [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user