This commit is contained in:
MasonLiu 2026-06-01 23:57:52 +08:00
parent 977ce66110
commit c4b1f8fd1b
17 changed files with 720 additions and 48 deletions

562
admin.php Normal file
View File

@ -0,0 +1,562 @@
<?php
/**
* SecHub - JSON文件排序管理页面
*/
// 访问密码配置
define('ADMIN_PASSWORD', 'sechub2024'); // 请修改为你的密码
session_start();
// 定义路径
$jsonDir = __DIR__ . '/assets/json/';
$dbDir = __DIR__ . '/assets/db/';
$dbPath = $dbDir . 'sechub.db';
// 确保数据库目录存在
if (!is_dir($dbDir)) {
mkdir($dbDir, 0755, true);
}
// 引入数据库类
require_once __DIR__ . '/db.php';
// 处理登录
if (isset($_POST['login'])) {
if ($_POST['password'] === ADMIN_PASSWORD) {
$_SESSION['authenticated'] = true;
header('Location: admin.php');
exit;
} else {
$error = '密码错误!';
}
}
// 处理登出
if (isset($_GET['logout'])) {
session_destroy();
header('Location: admin.php');
exit;
}
// 检查是否已登录
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SecHub - 管理登录</title>
</head>
<body>
<div class="login-container">
<h1>🔐 SecHub 管理后台</h1>
<?php if (isset($error)): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label for="password">访问密码</label>
<input type="password" id="password" name="password" required autofocus>
</div>
<button type="submit" name="login">登录</button>
</form>
</div>
</body>
</html>
<?php
exit;
}
// 处理保存排序
if (isset($_POST['save_order']) && isset($_POST['orders'])) {
$success = false;
$message = '';
try {
// orders 数组已经按拖拽后的顺序排列
$newOrder = 1;
foreach ($_POST['orders'] as $filename) {
$filePath = $jsonDir . $filename;
if (file_exists($filePath)) {
// 读取JSON文件保持原始格式
$content = file_get_contents($filePath);
$data = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($data) && !empty($data)) {
// 只更新第一个数据项的 no 字段
$data[0]['no'] = $newOrder;
// 写回JSON文件保持原有格式不转义
$newContent = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents($filePath, $newContent . "\n");
$newOrder++;
}
}
}
$success = true;
$message = '排序保存成功!';
// 重新同步数据库
$database = new SecHubDatabase($dbPath, $jsonDir);
$database->syncJsonToDatabase();
} catch (Exception $e) {
$message = '保存失败: ' . $e->getMessage();
}
}
// 处理删除数据库
if (isset($_POST['delete_db'])) {
if (file_exists($dbPath)) {
unlink($dbPath);
$success = true;
$message = '数据库已删除!刷新页面后将重新创建。';
} else {
$message = '数据库文件不存在。';
}
}
// 获取所有JSON文件信息
$jsonFiles = glob($jsonDir . '*.json');
$fileInfos = [];
foreach ($jsonFiles as $filePath) {
$filename = basename($filePath);
$content = file_get_contents($filePath);
$data = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($data) && !empty($data)) {
$firstItem = $data[0];
$fileInfos[] = [
'filename' => $filename,
'section' => $firstItem['section'] ?? pathinfo($filename, PATHINFO_FILENAME),
'no' => $firstItem['no'] ?? 0,
'item_count' => count($data) - 1 // 减去第一个配置项
];
}
}
// 按当前 no 排序
usort($fileInfos, function($a, $b) {
return $a['no'] - $b['no'];
});
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="./assets/imgs/favicon.ico" type="image/x-icon">
<title>SecHub - 排序管理</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #f5f7fa;
--bg-secondary: #ffffff;
--text-primary: #2c3e50;
--text-secondary: #7f8c8d;
--border-color: #e0e6ed;
--accent-color: #3498db;
--success-color: #27ae60;
--warning-color: #f39c12;
--hover-bg: #f0f4f8;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 30px 20px;
}
header {
background: var(--bg-secondary);
padding: 30px;
border-radius: 12px;
box-shadow: var(--shadow);
margin-bottom: 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
font-size: 1.8em;
color: var(--text-primary);
}
.header-info {
display: flex;
gap: 15px;
align-items: center;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.95em;
font-weight: 500;
transition: all 0.2s ease;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: var(--accent-color);
color: white;
}
.btn-primary:hover {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}
.btn-success {
background: var(--success-color);
color: white;
}
.btn-success:hover {
background: #229954;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}
.btn-danger {
background: #e74c3c;
color: white;
}
.btn-danger:hover {
background: #c0392b;
}
.alert {
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.table-container {
background: var(--bg-secondary);
border-radius: 12px;
box-shadow: var(--shadow);
overflow: hidden;
}
table {
width: 100%;
border-collapse: collapse;
}
thead {
background: var(--bg-primary);
}
th {
padding: 15px 20px;
text-align: left;
font-weight: 600;
color: var(--text-primary);
border-bottom: 2px solid var(--border-color);
}
td {
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
}
tbody tr:hover {
background: var(--hover-bg);
}
tbody tr:last-child td {
border-bottom: none;
}
.draggable-row {
cursor: move;
user-select: none;
}
.draggable-row:hover {
background: var(--hover-bg);
}
.draggable-row.dragging {
opacity: 0.5;
background: #e3f2fd;
}
.drag-handle {
color: var(--text-secondary);
font-size: 1.2em;
cursor: move;
padding: 0 10px;
}
.order-number {
font-weight: 600;
color: var(--accent-color);
font-size: 1.1em;
}
.filename {
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: var(--text-secondary);
}
.section-name {
font-weight: 600;
color: var(--text-primary);
}
.item-count {
color: var(--text-secondary);
font-size: 0.9em;
}
.actions {
display: flex;
gap: 10px;
}
.btn-small {
padding: 6px 12px;
font-size: 0.85em;
}
.tip {
background: #e3f2fd;
border-left: 4px solid var(--accent-color);
padding: 15px 20px;
margin-bottom: 20px;
border-radius: 4px;
color: #1976d2;
}
@media (max-width: 768px) {
.container {
padding: 15px;
}
header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.header-info {
flex-direction: column;
width: 100%;
}
.btn {
width: 100%;
}
table {
font-size: 0.85em;
}
th, td {
padding: 10px;
}
.actions {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div>
<h1>📊 SecHub 排序管理</h1>
<p style="color: var(--text-secondary); margin-top: 5px;">调整栏目显示顺序</p>
</div>
<div class="header-info">
<a href="?logout=1" class="btn btn-danger">退出登录</a>
</div>
</header>
<?php if (isset($success) && $success): ?>
<div class="alert alert-success">
<?= htmlspecialchars($message) ?>
</div>
<?php elseif (isset($message)): ?>
<div class="alert alert-warning">
⚠️ <?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<div class="tip">
💡 <strong>提示:</strong>拖动行左侧的 ⋮⋮ 图标来调整顺序,序号会自动更新。修改后点击“保存排序”按钮即可生效。
</div>
<div class="table-container">
<form method="POST">
<table>
<thead>
<tr>
<th style="width: 50px;"></th>
<th style="width: 80px;">序列</th>
<th>文件名称</th>
<th>项目名称</th>
<th style="width: 120px;">内容条数</th>
</tr>
</thead>
<tbody id="sortable-tbody">
<?php foreach ($fileInfos as $index => $info): ?>
<tr class="draggable-row" data-filename="<?= htmlspecialchars($info['filename']) ?>" draggable="true">
<td>
<span class="drag-handle">⋮⋮</span>
</td>
<td>
<span class="order-number"><?= $index + 1 ?></span>
</td>
<td>
<span class="filename"><?= htmlspecialchars($info['filename']) ?></span>
</td>
<td>
<span class="section-name"><?= htmlspecialchars($info['section']) ?></span>
</td>
<td>
<span class="item-count"><?= $info['item_count'] ?> 个工具</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- 隐藏表单字段,用于提交排序 -->
<div id="order-inputs" style="display: none;"></div>
<div style="padding: 20px; text-align: center; border-top: 2px solid var(--border-color); display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;">
<button type="submit" name="save_order" class="btn btn-success" style="font-size: 1.1em; padding: 12px 40px;">
💾 保存排序
</button>
<button type="submit" name="delete_db" class="btn btn-danger" style="font-size: 1.1em; padding: 12px 40px;" onclick="return confirm('确定要删除数据库吗?这将导致下次访问时重新同步所有数据。')">
🗑️ 重置数据库
</button>
</div>
</form>
</div>
</div>
<script>
// 拖拽排序功能
let draggedRow = null;
const tbody = document.getElementById('sortable-tbody');
// 添加拖拽事件监听
tbody.addEventListener('dragstart', function(e) {
draggedRow = e.target.closest('.draggable-row');
if (draggedRow) {
draggedRow.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', draggedRow.innerHTML);
}
});
tbody.addEventListener('dragend', function(e) {
if (draggedRow) {
draggedRow.classList.remove('dragging');
draggedRow = null;
updateOrderNumbers();
}
});
tbody.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const afterElement = getDragAfterElement(tbody, e.clientY);
if (afterElement == null) {
tbody.appendChild(draggedRow);
} else {
tbody.insertBefore(draggedRow, afterElement);
}
});
// 获取拖拽位置后的元素
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.draggable-row:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
// 更新序号显示
function updateOrderNumbers() {
const rows = tbody.querySelectorAll('.draggable-row');
rows.forEach((row, index) => {
const orderSpan = row.querySelector('.order-number');
if (orderSpan) {
orderSpan.textContent = index + 1;
}
});
}
// 表单提交前生成隐藏输入字段
document.querySelector('form').addEventListener('submit', function(e) {
const orderInputs = document.getElementById('order-inputs');
orderInputs.innerHTML = ''; // 清空旧数据
const rows = tbody.querySelectorAll('.draggable-row');
rows.forEach((row, index) => {
const filename = row.getAttribute('data-filename');
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'orders[]';
input.value = filename;
orderInputs.appendChild(input);
});
});
</script>
</body>
</html>

Binary file not shown.

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 2,
"section": "甲方/蓝队工具" "section": "甲方/蓝队工具"
}, },
{ {
@ -21,5 +22,10 @@
"name": "AppScan", "name": "AppScan",
"url": "https://github.com/TongchengOpenSource/AppScan", "url": "https://github.com/TongchengOpenSource/AppScan",
"description": "企业级自动化App隐私合规检测工具" "description": "企业级自动化App隐私合规检测工具"
},
{
"name": "蓝队工具箱",
"url": "http://github.com/abc123info/BlueTeamTools",
"description": "蓝队分析研判工具箱(小工具集)"
} }
] ]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 1,
"section": "合集/导航" "section": "合集/导航"
}, },
{ {
@ -17,4 +18,4 @@
"url": "https://git.masonliu.com/MasonLiu/SecHub", "url": "https://git.masonliu.com/MasonLiu/SecHub",
"description": "由本人开发的网安工具集网站" "description": "由本人开发的网安工具集网站"
} }
] ]

46
assets/json/info.json Normal file
View File

@ -0,0 +1,46 @@
[
{
"no": 5,
"section": "信息收集"
},
{
"name": "ENScan Go",
"url": "https://github.com/wgpsec/ENScan_GO",
"description": "一键收集控股公司ICP备案、APP、小程序、微信公众号等信息聚合导出"
},
{
"name": "EHole棱洞",
"url": "https://github.com/EdgeSecurityTeam/EHole",
"description": "快速/高效的网站指纹识别组件,搭配子域名挖掘程序更好用"
},
{
"name": "Subfinder",
"url": "https://github.com/projectdiscovery/subfinder",
"description": "子域名资产收集发现程序,可以发现更多的隐藏资产"
},
{
"name": "Masscan",
"url": "https://github.com/robertdavidgraham/masscan",
"description": "快速的网络端口扫描工具"
},
{
"name": "nmap",
"url": "https://nmap.org/",
"description": "经典网络端口扫描工具"
},
{
"name": "RustScan",
"url": "https://github.com/bee-san/RustScan",
"description": "Rust编写的高速网络端口扫描工具"
},
{
"name": "JSFinder",
"url": "https://github.com/Threezh1/JSFinder",
"description": "提取网站文件中的域名信息"
},
{
"name": "LinkFinder",
"url": "https://github.com/GerbenJavado/LinkFinder",
"description": "提取网站文件中的路径信息"
}
]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 8,
"section": "内网渗透工具" "section": "内网渗透工具"
}, },
{ {
@ -46,5 +47,10 @@
"name": "Pillager", "name": "Pillager",
"url": "https://github.com/qwqdanchun/Pillager", "url": "https://github.com/qwqdanchun/Pillager",
"description": "适用于后渗透期间的信息收集工具" "description": "适用于后渗透期间的信息收集工具"
},
{
"name": "Mimikatz",
"url": "https://github.com/gentilkiwi/mimikatz",
"description": "获取目标机器权限后提取密码哈希值"
} }
] ]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 11,
"section": "移动端渗透工具" "section": "移动端渗透工具"
}, },
{ {
@ -12,4 +13,4 @@
"url": "https://github.com/frida/frida", "url": "https://github.com/frida/frida",
"description": "移动端内核Hook工具" "description": "移动端内核Hook工具"
} }
] ]

16
assets/json/other.json Normal file
View File

@ -0,0 +1,16 @@
[
{
"no": 13,
"section": "其他"
},
{
"name": "红队安防守则",
"url": "https://github.com/qingluoyu/Pentest_baseline",
"description": "红队反溯源基线核查手册"
},
{
"name": "f8x",
"url": "https://github.com/ffffffff0x/f8x",
"description": "红/蓝队环境自动化部署工具,支持多种场景"
}
]

View File

@ -1,6 +1,7 @@
[ [
{ {
"section": "插件/非工具" "no": 12,
"section": "插件/非独立工具"
}, },
{ {
"name": "HaE", "name": "HaE",
@ -11,5 +12,10 @@
"name": "BurpCrypto", "name": "BurpCrypto",
"url": "https://github.com/whwlsfb/BurpCrypto", "url": "https://github.com/whwlsfb/BurpCrypto",
"description": "支持多种加密算法或直接执行JS代码的用于爆破前端加密的BurpSuite插件" "description": "支持多种加密算法或直接执行JS代码的用于爆破前端加密的BurpSuite插件"
},
{
"name": "FindSomething",
"url": "https://github.com/momosecurity/FindSomething",
"description": "浏览器用JS信息搜寻插件"
} }
] ]

View File

@ -1,12 +1,8 @@
[ [
{ {
"no": 7,
"section": "POC/EXP" "section": "POC/EXP"
}, },
{
"name": "CVE-2026-31431",
"url": "https://copy.fail/",
"description": "基于复制功能的Linux系统提权漏洞"
},
{ {
"name": "MS17-010检测工具", "name": "MS17-010检测工具",
"url": "https://github.com/TeskeVirtualSystem/MS17010Test", "url": "https://github.com/TeskeVirtualSystem/MS17010Test",
@ -22,4 +18,4 @@
"url": "https://github.com/Dliv3/redis-rogue-server", "url": "https://github.com/Dliv3/redis-rogue-server",
"description": "Redis未授权访问漏洞利用工具Redis 4.x/5.x RCE" "description": "Redis未授权访问漏洞利用工具Redis 4.x/5.x RCE"
} }
] ]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 10,
"section": "代理工具/集成平台" "section": "代理工具/集成平台"
}, },
{ {
@ -27,4 +28,4 @@
"url": "https://www.proxifier.com/", "url": "https://www.proxifier.com/",
"description": "功能强大的网络代理工具支持HTTP/HTTPS/SOCKS代理" "description": "功能强大的网络代理工具支持HTTP/HTTPS/SOCKS代理"
} }
] ]

21
assets/json/right.json Normal file
View File

@ -0,0 +1,21 @@
[
{
"no": 9,
"section": "提权"
},
{
"name": "Windows",
"url": "https://github.com/SecWiki/windows-kernel-exploits",
"description": "Windows提权工具集"
},
{
"name": "Linux",
"url": "https://github.com/SecWiki/linux-kernel-exploits",
"description": "Linux提权工具集"
},
{
"name": "CVE-2026-31431",
"url": "https://copy.fail/",
"description": "基于复制功能的Linux系统提权漏洞"
}
]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 4,
"section": "多功能扫描器" "section": "多功能扫描器"
}, },
{ {
@ -37,4 +38,4 @@
"url": "https://gobies.org/", "url": "https://gobies.org/",
"description": "自动化漏洞扫描工具,建议自行上网搜索红队破解版" "description": "自动化漏洞扫描工具,建议自行上网搜索红队破解版"
} }
] ]

View File

@ -1,14 +1,15 @@
[ [
{ {
"no": 3,
"section": "Shell管理工具" "section": "Shell管理工具"
}, },
{ {
"name": "AntSword蚁剑", "name": "AntSword蚁剑",
"url": "https://github.com/AntSwordProject/antSword", "url": "https://github.com/AntSwordProject/antSword",
"description": "AntSword蚁剑是一个功能强大的Shell管理工具尤其是在PHP场景下" "description": "AntSword蚁剑是一个功能强大的Shell管理工具尤其是在PHP场景下"
}, },
{ {
"name": "Godzilla哥斯拉", "name": "Godzilla哥斯拉",
"url": "https://github.com/BeichenDream/Godzilla", "url": "https://github.com/BeichenDream/Godzilla",
"description": "多语言支持的强大Shell管理工具" "description": "多语言支持的强大Shell管理工具"
}, },
@ -21,5 +22,10 @@
"name": "ShellcodeLoader", "name": "ShellcodeLoader",
"url": "https://github.com/knownsec/shellcodeloader", "url": "https://github.com/knownsec/shellcodeloader",
"description": "shell免杀加密程序" "description": "shell免杀加密程序"
},
{
"name": "Metasploit",
"url": "https://github.com/rapid7/metasploit-framework",
"description": "超级漏洞利用框架"
} }
] ]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": 6,
"section": "外网/打点工具" "section": "外网/打点工具"
}, },
{ {
@ -17,29 +18,14 @@
"url": "https://github.com/sqlmapproject/sqlmap", "url": "https://github.com/sqlmapproject/sqlmap",
"description": "主流的强大SQL注入神器" "description": "主流的强大SQL注入神器"
}, },
{
"name": "ENScan Go",
"url": "https://github.com/wgpsec/ENScan_GO",
"description": "一键收集控股公司ICP备案、APP、小程序、微信公众号等信息聚合导出"
},
{ {
"name": "dddd", "name": "dddd",
"url": "https://github.com/SleepingBag945/dddd", "url": "https://github.com/SleepingBag945/dddd",
"description": "用法简单的批量信息收集,供应链漏洞探测工具" "description": "用法简单的批量信息收集,供应链漏洞探测工具"
}, },
{
"name": "EHole棱洞",
"url": "https://github.com/EdgeSecurityTeam/EHole",
"description": "快速/高效的网站指纹识别组件,搭配子域名挖掘程序更好用"
},
{
"name": "Subfinder",
"url": "https://github.com/projectdiscovery/subfinder",
"description": "子域名资产收集发现程序,可以发现更多的隐藏资产"
},
{ {
"name": "MDUT", "name": "MDUT",
"url": "https://github.com/SafeGroceryStore/MDUT", "url": "https://github.com/SafeGroceryStore/MDUT",
"description": "支持大量类型的数据库管理工具" "description": "支持大量类型的数据库管理工具"
} }
] ]

View File

@ -1,5 +1,6 @@
[ [
{ {
"no": "0",
"section": "栏目名称" "section": "栏目名称"
}, },
{ {
@ -8,13 +9,8 @@
"description": "工具描述" "description": "工具描述"
}, },
{ {
"name": "工具名称", "name": "",
"url": "工具链接", "url": "",
"description": "工具描述" "description": ""
},
{
"name": "工具名称",
"url": "工具链接",
"description": "工具描述"
} }
] ]

35
db.php
View File

@ -66,6 +66,7 @@ class SecHubDatabase {
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
json_filename TEXT UNIQUE NOT NULL, json_filename TEXT UNIQUE NOT NULL,
table_name TEXT NOT NULL, table_name TEXT NOT NULL,
section_no INTEGER DEFAULT 0,
last_sync_time DATETIME NOT NULL, last_sync_time DATETIME NOT NULL,
json_file_mtime INTEGER NOT NULL, json_file_mtime INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP created_at DATETIME DEFAULT CURRENT_TIMESTAMP
@ -115,6 +116,9 @@ class SecHubDatabase {
return; return;
} }
// 获取排序号(从第一个数据项的 no 字段)
$sectionNo = $data[0]['no'] ?? 0;
// 创建或更新表 // 创建或更新表
$this->createTable($tableName, $data[0]); $this->createTable($tableName, $data[0]);
@ -127,14 +131,14 @@ class SecHubDatabase {
$this->insertItem($tableName, $item, $data[0]['section'] ?? $tableName); $this->insertItem($tableName, $item, $data[0]['section'] ?? $tableName);
} }
// 更新同步日志 // 更新同步日志(包含排序号)
$this->updateSyncLog($filename, $tableName); $this->updateSyncLog($filename, $tableName, $sectionNo);
} }
/** /**
* 更新同步日志 * 更新同步日志
*/ */
private function updateSyncLog($filename, $tableName) { private function updateSyncLog($filename, $tableName, $sectionNo = 0) {
$jsonFile = $this->jsonDir . $filename; $jsonFile = $this->jsonDir . $filename;
$jsonModified = filemtime($jsonFile); $jsonModified = filemtime($jsonFile);
$syncTime = date('Y-m-d H:i:s'); $syncTime = date('Y-m-d H:i:s');
@ -148,20 +152,22 @@ class SecHubDatabase {
if ($exists) { if ($exists) {
// 更新现有记录 // 更新现有记录
$sql = "UPDATE json_sync_log $sql = "UPDATE json_sync_log
SET table_name = :table_name, SET table_name = :table_name,
section_no = :section_no,
last_sync_time = :sync_time, last_sync_time = :sync_time,
json_file_mtime = :mtime json_file_mtime = :mtime
WHERE json_filename = :filename"; WHERE json_filename = :filename";
} else { } else {
// 插入新记录 // 插入新记录
$sql = "INSERT INTO json_sync_log (json_filename, table_name, last_sync_time, json_file_mtime) $sql = "INSERT INTO json_sync_log (json_filename, table_name, section_no, last_sync_time, json_file_mtime)
VALUES (:filename, :table_name, :sync_time, :mtime)"; VALUES (:filename, :table_name, :section_no, :sync_time, :mtime)";
} }
$stmt = $this->db->prepare($sql); $stmt = $this->db->prepare($sql);
$stmt->execute([ $stmt->execute([
':filename' => $filename, ':filename' => $filename,
':table_name' => $tableName, ':table_name' => $tableName,
':section_no' => $sectionNo,
':sync_time' => $syncTime, ':sync_time' => $syncTime,
':mtime' => $jsonModified ':mtime' => $jsonModified
]); ]);
@ -344,6 +350,15 @@ class SecHubDatabase {
foreach ($tables as $table) { foreach ($tables as $table) {
$tableName = $table['name']; $tableName = $table['name'];
// 从同步日志中获取排序号
$sql = "SELECT section_no FROM json_sync_log WHERE table_name = :table_name LIMIT 1";
$stmt = $this->db->prepare($sql);
$stmt->execute([':table_name' => $tableName]);
$log = $stmt->fetch();
$sectionNo = $log ? $log['section_no'] : 0;
// 获取栏目名称
$sql = "SELECT DISTINCT section FROM {$tableName} LIMIT 1"; $sql = "SELECT DISTINCT section FROM {$tableName} LIMIT 1";
$stmt = $this->db->query($sql); $stmt = $this->db->query($sql);
$row = $stmt->fetch(); $row = $stmt->fetch();
@ -351,11 +366,17 @@ class SecHubDatabase {
if ($row) { if ($row) {
$sections[$tableName] = [ $sections[$tableName] = [
'title' => $row['section'], 'title' => $row['section'],
'table' => $tableName 'table' => $tableName,
'no' => $sectionNo
]; ];
} }
} }
// 按 no 字段排序
uasort($sections, function($a, $b) {
return ($a['no'] ?? 0) - ($b['no'] ?? 0);
});
return $sections; return $sections;
} }