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 '', -- 本地图标文件名 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); // 是否为空库(用于判断是否写入默认数据) $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 $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], ['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], ]; $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); }