2026-09-28 15:48:35 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 简航 SimpleNav —— 数据库(PDO SQLite)
|
|
|
|
|
|
* 首次访问自动建表并写入默认数据(含从 n.yangyq.net 迁移的常用/工具/合集链接)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
|
|
|
|
|
|
|
|
|
|
function db(): PDO
|
|
|
|
|
|
{
|
|
|
|
|
|
static $pdo = null;
|
|
|
|
|
|
if ($pdo === null) {
|
|
|
|
|
|
if (!is_dir(DATA_PATH)) {
|
|
|
|
|
|
@mkdir(DATA_PATH, 0755, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
$pdo = new PDO('sqlite:' . DB_PATH);
|
|
|
|
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
|
|
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
|
|
|
|
|
$pdo->exec('PRAGMA foreign_keys = ON');
|
|
|
|
|
|
db_init($pdo);
|
|
|
|
|
|
}
|
|
|
|
|
|
return $pdo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function db_init(PDO $pdo): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$pdo->exec(<<<'SQL'
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS groups (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
|
slug TEXT DEFAULT '',
|
|
|
|
|
|
display TEXT NOT NULL DEFAULT 'grid', -- grid=普通网格板块 tabs=合集标签页
|
|
|
|
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
is_visible INTEGER NOT NULL DEFAULT 1
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS sites (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
group_id INTEGER NOT NULL,
|
|
|
|
|
|
url TEXT NOT NULL,
|
|
|
|
|
|
name TEXT NOT NULL DEFAULT '', -- 自动抓取的网站名称
|
|
|
|
|
|
short_name TEXT NOT NULL DEFAULT '', -- 用户自定义简称(页面展示用)
|
|
|
|
|
|
icon TEXT NOT NULL DEFAULT '', -- 本地图标文件名
|
2026-09-28 16:43:10 +08:00
|
|
|
|
icon_custom INTEGER NOT NULL DEFAULT 0, -- 1=用户自定义图标("全部更新"不再覆盖)
|
2026-09-28 15:48:35 +08:00
|
|
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
is_visible INTEGER NOT NULL DEFAULT 1,
|
|
|
|
|
|
created_at TEXT DEFAULT '',
|
|
|
|
|
|
updated_at TEXT DEFAULT '',
|
|
|
|
|
|
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS engines (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
|
slug TEXT NOT NULL,
|
|
|
|
|
|
search_url TEXT NOT NULL,
|
|
|
|
|
|
param TEXT NOT NULL DEFAULT 'q',
|
|
|
|
|
|
suggest_url TEXT NOT NULL DEFAULT '',
|
|
|
|
|
|
suggest_type TEXT NOT NULL DEFAULT '',
|
|
|
|
|
|
color TEXT NOT NULL DEFAULT '#2F6BF7',
|
|
|
|
|
|
is_active INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
sort_order INTEGER NOT NULL DEFAULT 0
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
|
|
|
|
k TEXT PRIMARY KEY,
|
|
|
|
|
|
v TEXT NOT NULL DEFAULT ''
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
username TEXT UNIQUE NOT NULL,
|
|
|
|
|
|
password_hash TEXT NOT NULL,
|
|
|
|
|
|
created_at TEXT NOT NULL DEFAULT ''
|
|
|
|
|
|
);
|
|
|
|
|
|
SQL);
|
|
|
|
|
|
|
2026-09-28 16:43:10 +08:00
|
|
|
|
// 老库升级:为已存在的 sites 表补 icon_custom 列
|
|
|
|
|
|
$cols = [];
|
|
|
|
|
|
foreach ($pdo->query('PRAGMA table_info(sites)') as $c) {
|
|
|
|
|
|
$cols[(string)$c['name']] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isset($cols['icon_custom'])) {
|
|
|
|
|
|
$pdo->exec('ALTER TABLE sites ADD COLUMN icon_custom INTEGER NOT NULL DEFAULT 0');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 17:13:10 +08:00
|
|
|
|
// 老库升级:清掉「搜狗联想」这个已经彻底失效的默认值
|
|
|
|
|
|
// (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}']);
|
|
|
|
|
|
|
2026-09-28 15:48:35 +08:00
|
|
|
|
// 是否为空库(用于判断是否写入默认数据)
|
|
|
|
|
|
$isEmpty = (int)$pdo->query('SELECT COUNT(*) FROM groups')->fetchColumn() === 0;
|
|
|
|
|
|
|
|
|
|
|
|
seed_settings($pdo);
|
|
|
|
|
|
seed_engines($pdo);
|
|
|
|
|
|
if ($isEmpty) {
|
|
|
|
|
|
seed_groups_and_sites($pdo);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function seed_settings(PDO $pdo): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$defaults = [
|
|
|
|
|
|
'site_name' => '简航',
|
|
|
|
|
|
'subtitle' => '个人网站导航',
|
|
|
|
|
|
'default_engine' => 'bing',
|
|
|
|
|
|
'primary_color' => '#2F6BF7',
|
|
|
|
|
|
'open_blank' => '1',
|
|
|
|
|
|
];
|
|
|
|
|
|
$stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)');
|
|
|
|
|
|
foreach ($defaults as $k => $v) {
|
|
|
|
|
|
$stmt->execute([$k, $v]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function seed_engines(PDO $pdo): void
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((int)$pdo->query('SELECT COUNT(*) FROM engines')->fetchColumn() > 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// name, slug, search_url, param, suggest_url, suggest_type, color, active, sort
|
2026-09-28 17:13:10 +08:00
|
|
|
|
// ⚠️ suggest_url 留空的含义:该引擎自己没有可用的联想接口。
|
|
|
|
|
|
// 反正默认「联想词来源 = 百度」,留空只会让 auto 模式下少一次无谓的超时等待。
|
|
|
|
|
|
// 搜狗(suggnew/ajajjson 已失效)、谷歌 / DuckDuckGo(国内不可达)都不填。
|
2026-09-28 15:48:35 +08:00
|
|
|
|
$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],
|
2026-09-28 17:13:10 +08:00
|
|
|
|
['谷歌', 'google', 'https://www.google.com/search', 'q', '', '', '#3B83FA', 0, 3],
|
|
|
|
|
|
['搜狗', 'sogou', 'https://www.sogou.com/web', 'query', '', '', '#FE6B14', 0, 4],
|
2026-09-28 15:48:35 +08:00
|
|
|
|
['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],
|
2026-09-28 17:13:10 +08:00
|
|
|
|
['DuckGo','ddg', 'https://duckduckgo.com/', 'q', '', '', '#DE5833', 0, 6],
|
2026-09-28 15:48:35 +08:00
|
|
|
|
];
|
|
|
|
|
|
$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) {
|
|
|
|
|
|
$stmt->execute($r);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function seed_groups_and_sites(PDO $pdo): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// 板块定义: [名称, 类型, 排序, [ [名称, 网址], ... ] ]
|
|
|
|
|
|
$data = [
|
|
|
|
|
|
['常用', 'grid', 1, [
|
|
|
|
|
|
['海大', 'http://www.ouc.edu.cn/'],
|
|
|
|
|
|
['阿里云', 'https://www.aliyun.com/'],
|
|
|
|
|
|
['知乎', 'https://www.zhihu.com/'],
|
|
|
|
|
|
['天翼云盘', 'https://cloud.189.cn/'],
|
|
|
|
|
|
['快科技', 'http://www.mydrivers.com/'],
|
|
|
|
|
|
['轻舟', 'https://yangjh.net/'],
|
|
|
|
|
|
['老杨', 'https://yangyq.net/'],
|
|
|
|
|
|
['红围脖', 'https://hongbowei.com/'],
|
|
|
|
|
|
]],
|
|
|
|
|
|
['工具', 'grid', 2, [
|
|
|
|
|
|
['宝塔', 'https://bt.yangyq.net:8868/44d5b346'],
|
|
|
|
|
|
['海大VPN', 'https://otrust.ouc.edu.cn'],
|
|
|
|
|
|
['BB系统', 'https://wlkc.ouc.edu.cn/'],
|
|
|
|
|
|
['教务系统', 'http://jwgl.ouc.edu.cn/'],
|
|
|
|
|
|
['信息门户', 'http://my.ouc.edu.cn/'],
|
|
|
|
|
|
['信息学部', 'http://it.ouc.edu.cn/'],
|
|
|
|
|
|
['淘宝', 'https://www.taobao.com/'],
|
|
|
|
|
|
['京东', 'https://www.jd.com/'],
|
|
|
|
|
|
['日历', 'https://nas.yangyq.net/calendar'],
|
|
|
|
|
|
['办公Nas', 'https://nas.yangyq.net/'],
|
|
|
|
|
|
['家庭Nas', 'https://home.yangyq.net/'],
|
|
|
|
|
|
['Git', 'https://git.yangyq.net/'],
|
|
|
|
|
|
['网盘', 'https://nas.yangyq.net/drive/'],
|
|
|
|
|
|
['电影', 'https://movie.yangyq.net/'],
|
|
|
|
|
|
['相册', 'https://home.yangyq.net/photo'],
|
|
|
|
|
|
['文件', 'https://nas.yangyq.net/file'],
|
|
|
|
|
|
['bilibili', 'https://www.bilibili.com/'],
|
|
|
|
|
|
['优酷', 'https://www.youku.com/'],
|
|
|
|
|
|
['爱奇艺', 'https://www.iqiyi.com/'],
|
|
|
|
|
|
['腾讯视频', 'https://v.qq.com/'],
|
|
|
|
|
|
['芒果TV', 'https://www.mgtv.com/'],
|
|
|
|
|
|
['Draw.IO', 'https://draw.yangyq.net'],
|
|
|
|
|
|
['稿定PS', 'https://ps.gaoding.com/'],
|
|
|
|
|
|
['密码生成', 'https://n.yangyq.net/password/'],
|
|
|
|
|
|
['PDF工具', 'https://pdf.wps.cn/'],
|
|
|
|
|
|
['微信读书', 'https://weread.qq.com/'],
|
|
|
|
|
|
['高德地图', 'https://gaode.com/'],
|
|
|
|
|
|
['百度地图', 'https://map.baidu.com/'],
|
|
|
|
|
|
['建设银行', 'http://ccb.com/cn/home/index.html'],
|
|
|
|
|
|
['海大财务', 'http://hdcw.ouc.edu.cn'],
|
|
|
|
|
|
['MOOC', 'https://www.icourse163.org/'],
|
|
|
|
|
|
]],
|
|
|
|
|
|
['常用软件', 'tabs', 10, [
|
|
|
|
|
|
['WPS', 'https://www.wps.cn/'],
|
|
|
|
|
|
['Chrome', 'https://www.google.cn/chrome/index.html'],
|
|
|
|
|
|
['VS Code', 'https://code.visualstudio.com/'],
|
|
|
|
|
|
['Draw.IO', 'https://github.com/jgraph/drawio-desktop/releases'],
|
|
|
|
|
|
['火绒', 'https://www.huorong.cn/'],
|
|
|
|
|
|
['7-Zip', 'https://sparanoid.com/lab/7z/'],
|
|
|
|
|
|
['Tabby', 'https://github.com/Eugeny/tabby/'],
|
|
|
|
|
|
['GitAhead', 'https://gitahead.github.io/gitahead.com/'],
|
|
|
|
|
|
['Everything', 'https://www.voidtools.com/zh-cn/'],
|
|
|
|
|
|
['Wox', 'https://github.com/Wox-launcher/Wox'],
|
|
|
|
|
|
['BCUninstaller', 'https://github.com/Klocman/Bulk-Crap-Uninstaller'],
|
|
|
|
|
|
['DBeaver', 'https://github.com/dbeaver/dbeaver'],
|
|
|
|
|
|
['Thunderbird', 'https://www.thunderbird.net/zh-CN/'],
|
|
|
|
|
|
['FDM', 'https://www.freedownloadmanager.org/zh/'],
|
|
|
|
|
|
['GIMP', 'https://www.gimp.org/'],
|
|
|
|
|
|
['VLC', 'https://www.videolan.org/vlc/'],
|
|
|
|
|
|
['Zotero', 'https://www.zotero.org/'],
|
|
|
|
|
|
['QQ音乐', 'https://y.qq.com/'],
|
|
|
|
|
|
['微信', 'https://weixin.qq.com/'],
|
|
|
|
|
|
['TIM', 'https://tim.qq.com/'],
|
|
|
|
|
|
['Shotcut', 'https://github.com/mltframework/shotcut'],
|
|
|
|
|
|
]],
|
|
|
|
|
|
['图片资源', 'tabs', 11, [
|
|
|
|
|
|
['图标库', 'https://www.iconfont.cn/'],
|
|
|
|
|
|
['插画库', 'https://undraw.co/illustrations'],
|
|
|
|
|
|
['Pixabay', 'https://pixabay.com/zh/'],
|
|
|
|
|
|
['Pexels', 'https://www.pexels.com/'],
|
|
|
|
|
|
]],
|
|
|
|
|
|
['科研论文', 'tabs', 12, [
|
|
|
|
|
|
['DeepL翻译', 'https://www.deepl.com/'],
|
|
|
|
|
|
['Linggle', 'https://linggle.com/'],
|
|
|
|
|
|
['Netspeak', 'https://netspeak.org/'],
|
|
|
|
|
|
['Semantic Scholar', 'https://www.semanticscholar.org/'],
|
|
|
|
|
|
['LaTex Table', 'https://www.tablesgenerator.com/'],
|
|
|
|
|
|
]],
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$g = $pdo->prepare('INSERT INTO groups (name, slug, display, sort_order, is_visible) VALUES (?,?,?,?,1)');
|
|
|
|
|
|
$s = $pdo->prepare('INSERT INTO sites (group_id, url, name, short_name, icon, sort_order, is_visible, created_at, updated_at) VALUES (?,?,?,?,\'\',?,1,?,?)');
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
foreach ($data as $row) {
|
|
|
|
|
|
[$name, $display, $order, $sites] = $row;
|
|
|
|
|
|
$g->execute([$name, slugify($name), $display, $order]);
|
|
|
|
|
|
$gid = (int)$pdo->lastInsertId();
|
|
|
|
|
|
$i = 0;
|
|
|
|
|
|
foreach ($sites as $site) {
|
|
|
|
|
|
$s->execute([$gid, $site[1], $site[0], $site[0], $i++, $now, $now]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 生成 slug(中文退化为拼音不可用时用 md5 短串) */
|
|
|
|
|
|
function slugify(string $s): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$s = trim($s);
|
|
|
|
|
|
if (preg_match('/^[a-z0-9\-]+$/i', $s)) {
|
|
|
|
|
|
return strtolower($s);
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'g' . substr(md5($s), 0, 6);
|
|
|
|
|
|
}
|