SecHub/db.php
2026-05-30 10:20:22 +08:00

353 lines
10 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
/**
* SecHub 数据库管理类
* 负责JSON数据到SQLite的转换和管理
*/
class SecHubDatabase {
private $dbPath;
private $jsonDir;
private $db;
public function __construct($dbPath, $jsonDir) {
$this->dbPath = $dbPath;
$this->jsonDir = $jsonDir;
$this->initDatabase();
}
/**
* 初始化数据库连接
*/
private function initDatabase() {
try {
$this->db = new PDO('sqlite:' . $this->dbPath);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
// 创建同步日志表
$this->createSyncLogTable();
} catch (PDOException $e) {
error_log("数据库连接失败: " . $e->getMessage());
throw $e;
}
}
/**
* 创建同步日志表
*/
private function createSyncLogTable() {
$sql = "CREATE TABLE IF NOT EXISTS json_sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
json_filename TEXT UNIQUE NOT NULL,
table_name TEXT NOT NULL,
last_sync_time DATETIME NOT NULL,
json_file_mtime INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)";
$this->db->exec($sql);
}
/**
* 检查并同步JSON数据到数据库
*/
public function syncJsonToDatabase() {
$jsonFiles = glob($this->jsonDir . '*.json');
foreach ($jsonFiles as $file) {
$this->syncSingleFile($file);
}
}
/**
* 同步单个JSON文件到数据库
*/
private function syncSingleFile($filePath) {
$filename = basename($filePath);
$tableName = pathinfo($filename, PATHINFO_FILENAME);
// 检查是否需要更新
if (!$this->shouldUpdate($filePath, $tableName)) {
return;
}
// 读取JSON数据
$data = $this->loadJsonData($filePath);
if (empty($data)) {
return;
}
// 创建或更新表
$this->createTable($tableName, $data[0]);
// 清空旧数据
$this->clearTable($tableName);
// 插入新数据跳过第一个section项
$items = array_slice($data, 1);
foreach ($items as $item) {
$this->insertItem($tableName, $item, $data[0]['section'] ?? $tableName);
}
// 更新同步日志
$this->updateSyncLog($filename, $tableName);
}
/**
* 更新同步日志
*/
private function updateSyncLog($filename, $tableName) {
$jsonFile = $this->jsonDir . $filename;
$jsonModified = filemtime($jsonFile);
$syncTime = date('Y-m-d H:i:s');
// 检查是否已存在记录
$sql = "SELECT id FROM json_sync_log WHERE json_filename = :filename";
$stmt = $this->db->prepare($sql);
$stmt->execute([':filename' => $filename]);
$exists = $stmt->fetch();
if ($exists) {
// 更新现有记录
$sql = "UPDATE json_sync_log
SET table_name = :table_name,
last_sync_time = :sync_time,
json_file_mtime = :mtime
WHERE json_filename = :filename";
} else {
// 插入新记录
$sql = "INSERT INTO json_sync_log (json_filename, table_name, last_sync_time, json_file_mtime)
VALUES (:filename, :table_name, :sync_time, :mtime)";
}
$stmt = $this->db->prepare($sql);
$stmt->execute([
':filename' => $filename,
':table_name' => $tableName,
':sync_time' => $syncTime,
':mtime' => $jsonModified
]);
}
/**
* 判断是否需要更新
*/
private function shouldUpdate($jsonFile, $tableName) {
$filename = basename($jsonFile);
$jsonModified = filemtime($jsonFile);
// 查询该JSON文件的同步记录
$sql = "SELECT * FROM json_sync_log WHERE json_filename = :filename";
$stmt = $this->db->prepare($sql);
$stmt->execute([':filename' => $filename]);
$log = $stmt->fetch();
// 如果没有同步记录,需要更新
if (!$log) {
return true;
}
// 计算时间差(秒)
$timeDiff = $jsonModified - $log['json_file_mtime'];
// 如果JSON文件修改时间比记录的晚至少5分钟300秒则需要更新
if ($timeDiff >= 300) {
return true;
}
// 否则不需要更新
return false;
}
/**
* 加载JSON数据
*/
private function loadJsonData($filePath) {
if (!file_exists($filePath)) {
return [];
}
$content = file_get_contents($filePath);
if ($content === false) {
return [];
}
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("JSON解析错误: " . json_last_error_msg());
return [];
}
return is_array($data) ? $data : [];
}
/**
* 创建数据表
*/
private function createTable($tableName, $firstItem) {
// 清理表名,只保留字母、数字和下划线
$tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName);
$sql = "CREATE TABLE IF NOT EXISTS {$tableName} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
section TEXT NOT NULL,
name TEXT NOT NULL,
url TEXT,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)";
$this->db->exec($sql);
}
/**
* 清空表数据
*/
private function clearTable($tableName) {
$tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName);
$this->db->exec("DELETE FROM {$tableName}");
}
/**
* 插入数据项
*/
private function insertItem($tableName, $item, $section) {
$tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName);
$sql = "INSERT INTO {$tableName} (section, name, url, description)
VALUES (:section, :name, :url, :description)";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':section' => $section,
':name' => $item['name'] ?? '',
':url' => $item['url'] ?? '',
':description' => $item['description'] ?? ''
]);
}
/**
* 全局搜索
*/
public function globalSearch($keyword) {
if (empty($keyword)) {
return [];
}
$tables = $this->getAllTables();
$results = [];
foreach ($tables as $table) {
$tableName = $table['name'];
$sql = "SELECT *, '{$tableName}' as source_table FROM {$tableName}
WHERE name LIKE :keyword
OR description LIKE :keyword
OR url LIKE :keyword
ORDER BY name";
$stmt = $this->db->prepare($sql);
$stmt->execute([':keyword' => '%' . $keyword . '%']);
$items = $stmt->fetchAll();
if (!empty($items)) {
$results[$tableName] = [
'section' => $items[0]['section'] ?? $tableName,
'items' => $items
];
}
}
return $results;
}
/**
* 按栏目搜索
*/
public function searchBySection($section, $keyword) {
if (empty($keyword)) {
return [];
}
$tables = $this->getAllTables();
$results = [];
foreach ($tables as $table) {
$tableName = $table['name'];
$sql = "SELECT * FROM {$tableName}
WHERE section = :section
AND (name LIKE :keyword
OR description LIKE :keyword
OR url LIKE :keyword)
ORDER BY name";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':section' => $section,
':keyword' => '%' . $keyword . '%'
]);
$items = $stmt->fetchAll();
if (!empty($items)) {
$results[] = $items;
}
}
return array_merge(...$results);
}
/**
* 获取所有栏目配置
*/
public function getSectionsConfig() {
$tables = $this->getAllTables();
$sections = [];
foreach ($tables as $table) {
$tableName = $table['name'];
$sql = "SELECT DISTINCT section FROM {$tableName} LIMIT 1";
$stmt = $this->db->query($sql);
$row = $stmt->fetch();
if ($row) {
$sections[$tableName] = [
'title' => $row['section'],
'table' => $tableName
];
}
}
return $sections;
}
/**
* 获取指定栏目的所有项目
*/
public function getItemsBySection($tableName) {
$tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName);
$sql = "SELECT * FROM {$tableName} ORDER BY name";
$stmt = $this->db->query($sql);
return $stmt->fetchAll();
}
/**
* 获取所有表名
*/
private function getAllTables() {
$sql = "SELECT name FROM sqlite_master
WHERE type='table'
AND name NOT LIKE 'sqlite_%'
AND name != 'json_sync_log'
ORDER BY name";
$stmt = $this->db->query($sql);
return $stmt->fetchAll();
}
/**
* 关闭数据库连接
*/
public function close() {
$this->db = null;
}
}