按反馈逐条处理: ① 界面美化 - 前台重做:品牌区(logo + 站名 + 副标题)、胶囊式引擎切换、圆角卡片与 悬停浮起、聚焦光晕、合集标签页淡入动画;版心改为 shell 变量 (--pad-x = max(gutter, (100% - wrap)/2)),与顶栏左右边缘严格对齐。 - 后台:表格窄屏可横向滚动、状态胶囊、自定义徽章、图标预览框。 ② 自动更新图标质量差 + 允许自定义图标 - 抓取改为阶梯择优:manifest 512/192 → apple-touch-icon → <link rel=icon> (按 sizes 降序,SVG 视为最高清)→ 第三方聚合服务(favicon.yandex.net 实测 100% 可用;icon.horse 可达时最高 256px)→ 站点自身 /favicon.ico。 - 逐个候选实测像素,达到 120px 即停;候选上限 8 个 + 10s 总预算,避免超时。 - 用 GD 只缩不放,统一存成 128×128 PNG;识别并丢弃第三方服务的 「灰底字母」占位块。 - 新增 sites.icon_custom 列(带幂等迁移):可上传图标或填图标地址; 一旦自定义,「全部更新」只刷新名称、跳过图标。 ③ 后台引擎「可见/不可见」不好用 - 小图标换成内联 SVG(原来用 emoji,跨字体显示成方块)。 - 切换改为 AJAX 原地生效 + 状态胶囊同步,不再点一下就跳页; 所有切换/移动表单都带 p 参数,无 JS 时也能正确回到当前页。 - 修掉一个隐蔽的成因:本站此前完全不发 Cache-Control/Expires/ETag, 浏览器只能自行猜测缓存策略,导致「改完刷新还是旧的」。现在所有动态页 统一发 no-store/no-cache。 ④ deploy/ 与 data/ 误入版本库 - 两目录整目录忽略,部署脚本移出仓库(存 _tooling/),已 git rm --cached。 (历史提交里仍含这些文件,如需彻底抹除需 git filter-repo,待确认。) 附带修复(线上实测驱动) - 服务器侧页面缓存:宝塔「网站加速」Apache(mod_lua) 版是一层基于 memcached 的 30 秒整页缓存,且**读不到 FastCGI 返回的 Cache-Control**,所以 no-store 挡不住它;唯一信号是请求 cookie X_CACHE=1。已在根 .htaccess 用 RequestHeader 在请求侧注入该标记(实测任何访客均 X-Cache: MISS), PHP 侧另发一颗 cookie 作为 Nginx 环境的兜底(BYPASS_PAGE_CACHE 开关)。 - 静态资源改为「长缓存 + 版本号」:asset()/icon_src() 拼 mtime+size, assets/.htaccess 与根 .htaccess 给出 max-age=31536000 immutable。 - 修 absolutize() 未还原 JSON 转义 \/ 导致部分图标 URL 抓不下来的问题。 - 修部署脚本 md5sum 在 Git Bash 下输出 "hash *path" 导致 38 项假「缺失文件」。
117 lines
3.9 KiB
PHP
117 lines
3.9 KiB
PHP
<?php
|
|
/**
|
|
* 简航 SimpleNav —— 搜索联想代理
|
|
* 浏览器直接调用各搜索引擎的联想接口会有跨域/JSONP 限制,这里由服务端代理并统一输出 JSON。
|
|
* 用法:suggest.php?slug=bing&q=关键词
|
|
*/
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/functions.php';
|
|
|
|
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']) : '';
|
|
|
|
if ($q === '') {
|
|
echo json_encode(['q' => '', 'items' => []]);
|
|
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);
|
|
}
|
|
}
|
|
} 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);
|
|
exit;
|
|
|
|
/** 按引擎类型解析联想结果 */
|
|
function parse_suggest(string $type, string $body, string $q): array
|
|
{
|
|
$body = trim($body);
|
|
$out = [];
|
|
|
|
switch ($type) {
|
|
case 'bing_osjson':
|
|
case 'google_firefox':
|
|
// ["query", ["s1","s2",...]]
|
|
$j = json_decode($body, true);
|
|
if (is_array($j) && isset($j[1]) && is_array($j[1])) {
|
|
$out = $j[1];
|
|
}
|
|
break;
|
|
|
|
case 'baidu_sugrec':
|
|
// {"q":"x","g":[{"type":"..","q":".."}], ...}
|
|
$j = json_decode($body, true);
|
|
if (is_array($j)) {
|
|
if (!empty($j['g']) && is_array($j['g'])) {
|
|
foreach ($j['g'] as $it) { if (isset($it['q'])) $out[] = $it['q']; }
|
|
} elseif (!empty($j['s']) && is_array($j['s'])) {
|
|
$out = $j['s'];
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'so360':
|
|
// {"s":[".."]} 或 {"result":[{"word":".."}]}
|
|
$j = json_decode($body, true);
|
|
if (is_array($j)) {
|
|
if (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; }
|
|
elseif (!empty($j['result']) && is_array($j['result'])) {
|
|
foreach ($j['result'] as $it) { if (isset($it['word'])) $out[] = $it['word']; }
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'sogou_ajaj':
|
|
// {"query":"x","items":["a","b"]}(可能是 GBK,已在上面转码)
|
|
$j = json_decode($body, true);
|
|
if (is_array($j) && !empty($j['items']) && is_array($j['items'])) {
|
|
$out = $j['items'];
|
|
} else {
|
|
// 兜底:正则抓引号字符串
|
|
if (preg_match_all('#"([^"]{1,40})"#u', $body, $m)) {
|
|
$out = array_slice($m[1], 1); // 跳过 query 自身
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'ddg':
|
|
// [{"phrase":".."},...]
|
|
$j = json_decode($body, true);
|
|
if (is_array($j)) {
|
|
foreach ($j as $it) {
|
|
if (is_array($it) && isset($it['phrase'])) { $out[] = $it['phrase']; }
|
|
elseif (is_string($it)) { $out[] = $it; }
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// 通用兜底:尝试 JSON,再退化正则
|
|
$j = json_decode($body, true);
|
|
if (is_array($j)) {
|
|
if (isset($j[1]) && is_array($j[1])) { $out = $j[1]; }
|
|
elseif (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; }
|
|
}
|
|
}
|
|
return $out;
|
|
}
|