Files
SimpleNav/README.md
T
laoyang 4ba512b147 fix: 图标文件名按「整条网址」命名,同域名不同路径不再互相覆盖
https://nas.yangyq.net/calendar 与 https://nas.yangyq.net/ 这类「同域名、
不同路径」的条目,图标文件名原先只由域名决定(md5(域名)),两条记录指向
同一个文件:改其中一个的自定义图标,另一个跟着变;保存时还会 unlink /
覆盖掉对方的文件,直接把对方搞成破图。

- 新增 icon_key($url):按整条网址取键(协议与主机名转小写、空路径补 /、
  查询串与锚点保留),语义是「不同网址 → 不同图标文件」。
- 4 处调用点全部改用它:site_save(上传图标 / 自定义地址)、fetch_meta、
  update_all;键一律取自「记录里存的那条 URL」,不用跳转后的 final_url,
  保证同一个网站每次算出来的键都一样(否则每次重下 + 留下孤儿文件)。
- download_best_icon() / save_custom_icon() 增加空键保护,避免键为空时
  所有图标都落到同一个文件名上。
- README 新增「图标文件的命名规则」一节。
- .gitattributes 统一按 LF 存放,防止 Windows 的 core.autocrlf 在下次
  checkout 时把 PHP/JS/CSS 换成 CRLF。
- e2e 新增「同域名不同路径」回归场景:用三张颜色不同的图,比对两条记录
  落盘图标的内容,验证改 A 不会波及 B;并已做反向验证(把 icon_key 改回
  按域名取键后,这些断言确实失败)。
2026-09-28 17:26:40 +08:00

112 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SimpleNav · 简航
简航(SimpleNav),一个面向个人的**轻量级网站导航系统**。PHP + SQLite,零外部依赖、无需 Composer,上传即用。
## 特点
- **轻量**:纯 PHP + SQLite,不依赖 MySQL / 框架 / Composer;前端无 Bootstrap / jQuery,原生 CSS + JS。
- **个人向**:单管理员,页面就是「你自己常用网站的起始页」。
- **搜索引擎可切换 + 联想下拉**:后台勾选喜欢的搜索引擎即可显示在顶部;输入时下拉建议词由服务端代理(规避跨域)。默认**联想词统一取百度、搜索走各自引擎**,并带自动兜底,任何引擎都保证有下拉词(详见下节实测表)。后台的「显示/隐藏」按钮点一下**原地生效、不跳页**,且无 JS 时也能正常提交。
- **添加即补全**:填网址后先访问该网址拿到标题,再按「高清优先」的顺序去网上找相关图标(`manifest` 512 → `apple-touch-icon` → `<link rel=icon>` → 第三方聚合服务),下载后统一规范化为 **128×128 PNG** 存到服务器本地。
- **图标可自定义,且自定义后不再被覆盖**:可为任一网站上传图标或直接填图标地址;一旦自定义,该网站的图标会被标记保护,「一键全部更新」只刷新它的名称、跳过图标。图标按**整条网址**分别存档,所以同一域名下的不同路径(如 `/calendar` 与 `/`)各自独立、互不影响。
- **显示简称**:每个网站有「名称(自动)」和「简称(手动)」两个字段,页面展示你自定义的简称。
- **自定义排序**:网站 / 板块 / 搜索引擎都可上移下移排序。
- **一键全部更新**:批量刷新所有网站的图标与名称,**不会改动你自定义的简称,也不会覆盖你自定义的图标**。
- **合集标签页**:把「合集」做成标签页,点标签平铺该合集下的全部链接。
- **前后台永远一致**:所有动态页面都发 `Cache-Control: no-store, no-cache`,静态资源走「长缓存 + 版本号」(文件名不变、URL 带 `?v=mtime-size`)。
## 页面结构(复刻 n.yangyq.net 风格)
1. 顶部:搜索引擎切换 + 搜索框(带联想下拉)
2. 中部:网格板块(如「常用」「工具」)
3. 下部:「合集」标签页
## 目录结构
```
SimpleNav/
├─ index.php 前台首页
├─ suggest.php 搜索联想代理(多引擎,服务端请求)
├─ login.php / logout.php 管理员登录 / 首次初始化
├─ db.php 数据库初始化(建表 + 默认数据 + 幂等列迁移)
├─ config.php 基础配置
├─ functions.php 公共函数(抓取元信息、下载图标、资源版本号、不缓存响应头)
├─ .htaccess 根目录安全规则(禁止访问 data/ 与 .git/)
├─ admin/ 后台(网站 / 板块 / 搜索引擎 / 设置)
│ ├─ index.php 后台页面
│ ├─ actions.php 增删改 / 排序处理(支持 AJAX 原地切换)
│ ├─ fetch_meta.php 添加网站时自动抓取名称与图标
│ └─ update_all.php 一键“全部更新”(分批)
├─ assets/ CSS / JS / 图标 / logo
│ └─ .htaccess 静态资源长缓存(配合 ?v= 版本号)
├─ uploads/icons/ 抓取到的网站图标(需可写)
└─ data/ SQLite 数据库(需可写,禁止 Web 访问)
```
## 部署
1. 上传全部文件到站点目录(如 `/www/wwwroot/n.yangyq.net/simplenav`)。
2. 确保 web 用户对 `data/` 与 `uploads/icons/` 有写权限。
3. 服务器需开启 PHP 的 **GD 扩展**(图标规范化要用)与 `curl`。
4. 浏览器打开站点 → 首次访问进入「初始化管理员」,创建账号后即可在后台管理。
5. 若服务器是 **Apache**(宝塔默认可能装 Apache 或 Nginx),`data/` 的访问拦截由仓库自带的
`data/.htaccess` + 根目录 `.htaccess` 完成,无需改 vhost;若是 Nginx,请把
`nginx-simplenav-deny-data.conf`(见本机 `_tooling/simplenav-deploy/`)里的规则加进 `server{}`。
服务器上创建最小权限部署账号:以 root 运行 `setup-simplenav-user.sh`(同样在 `_tooling/simplenav-deploy/`)。
> 需要额外说明的是:**服务器上若装了宝塔「网站加速」之类的页面缓存插件,本站也不需要为它做任何特殊配置**。
> 动态页发的 `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`):
| 类型 | 说明 | 返回形态 |
|---|---|---|
| `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":"…"}]` |
## 图标文件的命名规则
`uploads/icons/` 下的文件名 = `md5(icon_key(网址))` + 真实扩展名(`.png` / `.svg`)。
`icon_key()` **按整条网址**算,只做极少的归一化:协议与主机名转小写、空路径补成 `/`,
查询串与锚点(`#/calendar` 这类 SPA 路由)**原样保留**。
也就是说:**不同网址 → 不同图标文件**。同域名下的不同路径是两个独立的网站条目,例如
`https://nas.example.com/calendar` 与 `https://nas.example.com/` 各自拥有独立图标,
改一个不会影响另一个;`https://a.com` 与 `https://a.com/` 则视为同一个网址,共用一张图标。
> 早期版本曾用 `md5(域名)` 命名,导致同域名不同路径的两条记录指向同一个文件,
> 改其中一条的自定义图标会把另一条一起改掉、甚至把对方的文件删成破图。
## 许可
Apache License 2.0