207 lines
9.0 KiB
PHP
207 lines
9.0 KiB
PHP
<?php
|
||
/**
|
||
* db.php —— SQLite 连接与首次初始化
|
||
* 提供 db() 单例;首次运行时自动建库建表并写入默认分类与设置。
|
||
*/
|
||
defined('ROOT_DIR') || define('ROOT_DIR', dirname(__DIR__));
|
||
defined('DATA_DIR') || define('DATA_DIR', ROOT_DIR . '/data');
|
||
|
||
if (!is_dir(DATA_DIR)) {
|
||
@mkdir(DATA_DIR, 0777, true);
|
||
}
|
||
|
||
function db(): PDO
|
||
{
|
||
static $pdo = null;
|
||
if ($pdo instanceof PDO) {
|
||
return $pdo;
|
||
}
|
||
$file = DATA_DIR . '/homepage.db';
|
||
try {
|
||
$pdo = new PDO('sqlite:' . $file);
|
||
$pdo->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
|
||
)');
|
||
|
||
// 兼容旧库:为已存在的 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 ''");
|
||
|
||
// ---------- 默认分类种子 ----------
|
||
$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);
|
||
}
|
||
}
|
||
|
||
// ---------- 默认设置种子 ----------
|
||
$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 段库文本;空 = 使用默认内置列表
|
||
];
|
||
$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'],
|
||
];
|
||
$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]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 标记内容已更新(首页展示"上次更新时间") */
|
||
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;
|
||
}
|