NewHome/includes/db.php

294 lines
14 KiB
PHP
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.

<?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
)');
$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 = <<<MD
## 分区与文章
后台「文章管理」页可新增、编辑、删除文章,正文使用标准 Markdown 书写,支持分区与置顶。
## 三栏阅读
- 左列:全站文章标题栏,按分区展示,可折叠;
- 中列:当前文章的段落目录,点击可快速定位;
- 右列文章正文Markdown 渲染)。
### 图片引用
图片文件上传到 data/articles/img 目录,正文中写作:
![图片说明](data/articles/img/文件名.png)
渲染时会自动补全站内前缀,无需手工填写完整网址。
## Markdown 支持
支持**加粗**、*斜体*、`行内代码`、列表、表格与引用等常规语法。
MD;
$now = date('Y-m-d H:i:s');
$stA = $pdo->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;
}