setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $pdo->exec('PRAGMA journal_mode=WAL;'); db_init($pdo); } catch (Throwable $e) { http_response_code(500); die('数据库初始化失败:' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8')); } return $pdo; } /** 若旧表缺少某列则补充(SQLite 兼容迁移) */ function db_ensure_column(PDO $pdo, string $table, string $column, string $definition): void { $cols = $pdo->query('PRAGMA table_info(' . $table . ')')->fetchAll(); foreach ($cols as $col) { if (strtolower((string)$col['name']) === strtolower($column)) { return; } } $pdo->exec('ALTER TABLE ' . $table . ' ADD COLUMN ' . $column . ' ' . $definition); } function db_init(PDO $pdo): void { $pdo->exec('CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL )'); $pdo->exec('CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT )'); $pdo->exec('CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE NOT NULL, name TEXT NOT NULL, icon TEXT DEFAULT \'\', description TEXT DEFAULT \'\', sort INTEGER DEFAULT 0 )'); $pdo->exec('CREATE TABLE IF NOT EXISTS nav_blocks ( id INTEGER PRIMARY KEY AUTOINCREMENT, cat_id INTEGER NOT NULL, title TEXT NOT NULL, sort INTEGER DEFAULT 0 )'); $pdo->exec('CREATE TABLE IF NOT EXISTS nav_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, block_id INTEGER NOT NULL, label TEXT NOT NULL, url TEXT NOT NULL, note TEXT DEFAULT \'\', sort INTEGER DEFAULT 0 )'); $pdo->exec('CREATE TABLE IF NOT EXISTS func_tools ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, icon TEXT DEFAULT \'\', note TEXT DEFAULT \'\', description TEXT DEFAULT \'\', url TEXT NOT NULL DEFAULT \'\', is_external INTEGER NOT NULL DEFAULT 0, enabled INTEGER NOT NULL DEFAULT 1, sort INTEGER NOT NULL DEFAULT 0 )'); $pdo->exec('CREATE TABLE IF NOT EXISTS quick_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, url TEXT NOT NULL DEFAULT \'\', icon TEXT DEFAULT \'\', note TEXT DEFAULT \'\', enabled INTEGER NOT NULL DEFAULT 1, sort INTEGER NOT NULL DEFAULT 0 )'); $pdo->exec('CREATE TABLE IF NOT EXISTS ext_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, icon TEXT DEFAULT \'\', url TEXT NOT NULL DEFAULT \'\', description TEXT DEFAULT \'\', grp TEXT DEFAULT \'\', cat_keys TEXT DEFAULT \'\', enabled INTEGER NOT NULL DEFAULT 1, sort INTEGER NOT NULL DEFAULT 0 )'); $pdo->exec('CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, part TEXT NOT NULL DEFAULT \'未分类\', title TEXT NOT NULL, summary TEXT DEFAULT \'\', markdown TEXT DEFAULT \'\', pinned INTEGER NOT NULL DEFAULT 0, enabled INTEGER NOT NULL DEFAULT 1, sort INTEGER NOT NULL DEFAULT 0, board_cats TEXT DEFAULT \'\', created_at TEXT DEFAULT \'\', updated_at TEXT DEFAULT \'\' )'); // 兼容旧库:categories 补充 icon 列 db_ensure_column($pdo, 'categories', 'icon', "TEXT DEFAULT ''"); // 兼容旧库:quick_links / func_tools 若缺列一并补齐 db_ensure_column($pdo, 'quick_links', 'note', "TEXT DEFAULT ''"); db_ensure_column($pdo, 'func_tools', 'note', "TEXT DEFAULT ''"); // 兼容旧库:工具与文章的“库首页分发范围”(board_cats,逗号分隔库 key;空=通用) db_ensure_column($pdo, 'func_tools', 'board_cats', "TEXT DEFAULT ''"); db_ensure_column($pdo, 'articles', 'board_cats', "TEXT DEFAULT ''"); // 兼容旧库:外部工具的“分区/栏目”(grp,空=不分组平铺) db_ensure_column($pdo, 'ext_links', 'grp', "TEXT DEFAULT ''"); // ---------- 默认分类种子 ---------- $count = (int)$pdo->query('SELECT COUNT(*) FROM categories')->fetchColumn(); if ($count === 0) { $seedCats = [ ['popular', '科普库', '📖', 1, '面向大众与初学者的计算机科学、网络安全与前沿技术科普,帮你建立体系化的安全认知。'], ['red', '红队库', '⚔️', 2, '面向渗透测试与攻防演练的资源集,汇聚漏洞利用、武器化、情报收集与红队基础设施相关的高质量工具与文档。'], ['blue', '蓝队库', '🛡️', 3, '面向防御侧的资源集,汇集应急响应、威胁狩猎、流量分析与取证溯源相关的工具与知识。'], ['tool', '工具库', '🧰', 4, '日常效率工具与安全实用小工具都在这里,点击直达功能区。'], ]; $st = $pdo->prepare('INSERT INTO categories (key, name, icon, sort, description) VALUES (?,?,?,?,?)'); foreach ($seedCats as $c) { $st->execute($c); } } // ---------- 文章库板块分类(第五大板块,旧库缺失时追加) ---------- $hasArticleCat = (int)$pdo->query("SELECT COUNT(*) FROM categories WHERE key = 'article'")->fetchColumn(); if ($hasArticleCat === 0) { $stC = $pdo->prepare('INSERT INTO categories (key, name, icon, description, sort) VALUES (?,?,?,?,?)'); $stC->execute(['article', '文章库', '📚', '站长原创与整理收录的图文与 Markdown 文章合集。', 5]); } // ---------- 默认设置种子 ---------- $hasSettings = (int)$pdo->query('SELECT COUNT(*) FROM settings')->fetchColumn(); if ($hasSettings === 0) { $defaults = [ 'site_name' => '知识导航站', 'site_slogan' => '汇聚科普、攻防与效率工具的实用首页', 'site_logo' => '', // 空 = 使用默认 assets/img/logo.svg 'icp_no' => '京ICP备00000000号-1', 'gongan_no' => '京公网安备11000000000000号', 'gongan_link' => '', // 公安备案号链接(可空则不显示为链接) 'copyright' => 'Copyright © 2026 知识导航站 版权所有。本站内容仅供学习研究使用。', 'footer_text' => '', 'search_engines'=> '', // 搜索引擎列表 JSON;空 = 使用默认 4 个 'last_updated' => '', 'hero_bg' => '', // 首页顶部标识栏背景(纯色 / CSS 渐变),空 = 主题默认 'cdn_ranges' => '', // CDN 段库文本;空 = 使用默认内置列表 'article_note' => '', // 文章区首页顶部注要(Markdown 风格,纯文本) ]; $st = $pdo->prepare('INSERT INTO settings (key, value) VALUES (?,?)'); foreach ($defaults as $k => $v) { $st->execute([$k, $v]); } } // ---------- 默认管理员 ---------- $userCount = (int)$pdo->query('SELECT COUNT(*) FROM users')->fetchColumn(); if ($userCount === 0) { // 密码算法:先 base64 再 md5(按原始需求实现) $st = $pdo->prepare('INSERT INTO users (username, password_hash) VALUES (?,?)'); $st->execute(['admin', md5(base64_encode('admin123'))]); } // ---------- 功能区工具默认种子 ---------- $toolCount = (int)$pdo->query('SELECT COUNT(*) FROM func_tools')->fetchColumn(); if ($toolCount === 0) { $seedTools = [ ['编码 / 加解密', '🔐', 'Base64 / Base32 / URL / Unicode / MD5 / SHA 系列', 'codec.php', 0], ['随机密码生成', '🔑', '自定义字符集与长度,一次生成多组', 'password.php', 0], ['二维码生成器', '▦', '任意内容生成二维码,可放大并下载 PNG', 'qrcode.php', 0], ['杀软识别', '🛡', '粘贴 tasklist 输出,匹配进程对应的安全软件', 'av.php', 0], ]; $st = $pdo->prepare('INSERT INTO func_tools (name, icon, description, url, is_external, enabled, sort) VALUES (?,?,?,?,?,1,?)'); $sort = 0; foreach ($seedTools as $t) { $st->execute([$t[0], $t[1], $t[2], $t[3], $t[4], ++$sort]); } } // ---------- 后期新增功能页默认登记(按 url 去重,允许后台再停用/删除) ---------- $extraTools = [ ['国密加解密', '🔏', 'SM2 非对称加解密 / SM3 摘要 / SM4 对称加解密(SM1 为不公开硬件算法)', 'gmcodec.php'], ['IP 地址处理', '🌐', '提取日志中的全部 IPv4 并统计出现次数,再按内网 / CDN / 公网区分展示', 'ip.php'], ['辅助维权', '🛠️', '反弹 Shell 构建 / Windows SAM·mimikatz·票据提取 / Linux 自启服务(systemd 与 init.d)', 'persist.php'], ]; $chkUrl = $pdo->prepare('SELECT COUNT(*) FROM func_tools WHERE url = ?'); $insTool = $pdo->prepare('INSERT INTO func_tools (name, icon, description, url, is_external, enabled, sort) VALUES (?,?,?,?,0,1,?)'); foreach ($extraTools as $t) { $chkUrl->execute([$t[3]]); if ((int)$chkUrl->fetchColumn() === 0) { $mx = (int)$pdo->query('SELECT COALESCE(MAX(sort), 0) FROM func_tools')->fetchColumn(); $insTool->execute([$t[0], $t[1], $t[2], $t[3], $mx + 1]); } } // ---------- 库首页“外部工具区”默认种子(cat_keys 为空=展示于所有库首页;grp=分区/栏目,留空不分) ---------- $extCount = (int)$pdo->query('SELECT COUNT(*) FROM ext_links')->fetchColumn(); if ($extCount === 0) { $seedExt = [ // [分区/栏目, 名称, 网址, 简介] ['在线检测', 'VirusTotal', 'https://www.virustotal.com', '多引擎文件与 URL 检测'], ['在线检测', 'Shodan', 'https://www.shodan.io', '互联网设备与端口测绘'], ['在线检测', 'URLScan', 'https://urlscan.io', '网站访问行为分析'], ['情报与数据', 'CyberChef', 'https://gchq.github.io/CyberChef/', '数据加工与编解码台'], ['情报与数据', 'Exploit-DB', 'https://www.exploit-db.com', '公开漏洞利用库'], ['情报与数据', 'Have I Been Pwned', 'https://haveibeenpwned.com', '泄露账户与密码查询'], ]; $stExt = $pdo->prepare('INSERT INTO ext_links (name, url, description, grp, cat_keys, enabled, sort) VALUES (?,?,?,?,?,1,?)'); $extSort = 0; foreach ($seedExt as $e) { $stExt->execute([$e[1], $e[2], $e[3], $e[0], '', ++$extSort]); } } // ---------- 文章库示例文章(首次建库时提供一篇使用说明) ---------- $artCount = (int)$pdo->query('SELECT COUNT(*) FROM articles')->fetchColumn(); if ($artCount === 0) { $sampleMd = <<prepare('INSERT INTO articles (part, title, summary, markdown, pinned, enabled, sort, created_at, updated_at) VALUES (?,?,?,?,1,1,1,?,?)'); $stA->execute(['使用说明', '文章库使用说明', '了解分区、三栏阅读与 Markdown 图片引用的基本用法。', $sampleMd, $now, $now]); } } /** 标记内容已更新(首页展示"上次更新时间") */ function touch_last_updated(): void { try { $st = db()->prepare('INSERT INTO settings (key, value) VALUES (\'last_updated\', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'); $st->execute([date('Y-m-d H:i:s')]); } catch (Throwable $e) { // 不阻断主流程 } } /** 读取一条设置 */ function setting_get(string $key, string $default = ''): string { static $cache = null; if ($cache === null) { $cache = []; foreach (db()->query('SELECT key, value FROM settings') as $row) { $cache[$row['key']] = (string)$row['value']; } } return array_key_exists($key, $cache) ? $cache[$key] : $default; }