web程序自动安装模版,有兴趣可以体验一下看看

希米  (UID: 5950) [复制链接]
帖子链接已复制到剪贴板
帖子已经有人评论啦,不支持删除!

563 5

引用原文希米密聊 : https://www.dalao.net/thread-60045.htm

- 为我的 ximi-im 聊天程序写了个单页安装程序,代码结构很简单,下面贴上;

- 原理很简单就是使用curl命令从我服务器上直接下载源代码到服务器,然后生成数据库配置文件;

- 对小白用户来说更省事,不需改动代码,但是对于虚拟主机可能不是很友好,很多虚拟机不支持curl扩展,

- 对于不支持curl扩展的依然是可以打包上传文件不影响安装;

预览:

代码:

 - 这是我写的聊天程序,单页安装程序,是直接可以运行的,

 - 代码供参考,更专业一点可以加上文件Hash验证;

 - 如果需要改为适合自己程序,发给ai 提取其中样式结构即可;

<?php
// ========================================================
// 1. 安全拦截:防止重复安装
// ========================================================
if (file_exists(__DIR__ . '/setting.php')) {
    die('<div style="padding:20px;background:#ffebee;color:#c62828;border-radius:5px;font-family:sans-serif;"><b>⛔ 安装程序已经运行过了!</b><br>如需重新安装,请删除目录内的 <code>setting.php</code> 以及原有数据库文件夹及文件,然后刷新此页面。</div>');
}

// ========================================================
// 2. 环境依赖检测 (严格区分必需与可选)
// ========================================================
$required_extensions = ['pdo', 'pdo_sqlite', 'json', 'mbstring'];
$optional_extensions = ['curl'];
$env_status = [];
$install_ok = true; // 默认允许安装

// 检查必需扩展
foreach ($required_extensions as $ext) {
    $loaded = extension_loaded($ext);
    $env_status[$ext] = ['name' => $ext, 'type' => 'required', 'loaded' => $loaded];
    if (!$loaded) $install_ok = false;
}

// 检查可选扩展
foreach ($optional_extensions as $ext) {
    $loaded = extension_loaded($ext);
    $env_status[$ext] = ['name' => $ext, 'type' => 'optional', 'loaded' => $loaded];
}

$curl_enabled = extension_loaded('curl');
$file_list = ['db.php', 'admin.php', 'api.php', 'ximi.js', 'web.js', 'crypto-js.min.js', 'index.html', 'favicon.ico'];

// ========================================================
// 3. 异步修复 API 接口 (仅当 curl 开启时有效)
// ========================================================
if (isset($_GET['action']) && $_GET['action'] === 'repair_file') {
    header('Content-Type: application/json');
    if (!$curl_enabled) {
        die(json_encode(['status' => 'error', 'msg' => '缺少CURL扩展']));
    }
    $file = $_POST['file'] ?? '';
    if (!in_array($file, $file_list)) {
        die(json_encode(['status' => 'error', 'msg' => '非法文件']));
    }

    $base_url = "https://www.ximi.me/usr/demo/xm-im/";
    $save_to = __DIR__ . '/' . $file;
    $tmp_zip = $save_to . '.zip';
    
    $ch = curl_init($base_url . $file . ".zip");
    $fp = fopen($tmp_zip, 'w+');
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    $success = curl_exec($ch);
    $error = curl_error($ch);
    curl_close($ch);
    fclose($fp);

    if ($success && file_exists($tmp_zip)) {
        rename($tmp_zip, $save_to);
        echo json_encode(['status' => 'success']);
    } else {
        echo json_encode(['status' => 'error', 'msg' => $error ?: '下载失败']);
    }
    exit;
}

// ========================================================
// 4. 表单提交与配置文件生成
// ========================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!$install_ok) die("环境检测未通过,无法继续安装!");

    function rand_str($len) {
        return substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, $len);
    }

    $dir_name = trim($_POST['dir_name'] ?? '');
    if (empty($dir_name)) $dir_name = rand_str(8);

    $db_name = trim($_POST['db_name'] ?? '');
    if (empty($db_name)) {
        $db_name = rand_str(16) . '.db';
    } else {
        if (substr($db_name, -3) !== '.db') $db_name .= '.db';
    }

    $admin_db_name = 'admin_' . rand_str(16) . '.db';

    $config_content = "<?php\n// 自动生成的配置文件\nreturn [\n"
        . "    'db_dir' => '" . addslashes($dir_name) . "',\n"
        . "    'db_name' => '" . addslashes($db_name) . "',\n"
        . "    'admin_db_name' => '" . addslashes($admin_db_name) . "'\n"
        . "];\n";

    file_put_contents(__DIR__ . '/setting.php', $config_content);

    // 优化后的安装成功页面,保持统一的毛玻璃 UI 风格
    echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script src="https://cdn.tailwindcss.com"></script><title>安装成功 - ximi IM</title></head>
    <body class="bg-gradient-to-br from-slate-100 to-gray-200 flex items-center justify-center min-h-screen p-4">
    <div class="bg-white/70 backdrop-blur-xl border border-white/50 p-10 rounded-3xl shadow-2xl text-center max-w-md w-full">
    <div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6 text-4xl shadow-inner">✅</div>
    <h2 class="text-2xl text-slate-800 font-bold mb-4">安装成功就绪</h2>
    <p class="text-slate-500 mb-8 text-sm">系统环境、数据库目录与配置文件已安全生成,即将带您前往管理后台。</p>
    <div class="w-full bg-gray-200 rounded-full h-1.5 mb-2 overflow-hidden"><div class="bg-green-500 h-1.5 rounded-full animate-[progress_2s_ease-in-out_forwards]"></div></div>
    <div class="text-xs text-gray-400">正在重定向...</div>
    <style>@keyframes progress { 0% { width: 0%; } 100% { width: 100%; } }</style>
    <script>setTimeout(function(){ window.location.href="admin.php"; }, 2000);</script>
    </div></body></html>';
    exit;
}
?>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<title>ximi IM - 部署向导</title>
<style>
    body { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; }
    /* 毛玻璃基础特效 */
    .glass-panel {
        background: rgba(255, 255, 255, 0.75);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
        border: 1px solid rgba(255, 255, 255, 0.6);
    }
    /* 交互式步骤切换动画 */
    .step-content {
        display: none;
        opacity: 0;
        transform: translateX(10px);
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
    }
    .step-content.active {
        display: block;
        opacity: 1;
        transform: translateX(0);
    }
    /* 极简终端风格展示 */
    .term-text { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
</style>
</head>
<body class="bg-gradient-to-br from-slate-100 to-gray-300 min-h-screen flex items-center justify-center p-4 sm:p-8 relative overflow-hidden">

    <div class="absolute top-[-10%] left-[-5%] w-96 h-96 bg-blue-300 rounded-full mix-blend-multiply filter blur-3xl opacity-40"></div>
    <div class="absolute bottom-[-10%] right-[-5%] w-96 h-96 bg-emerald-300 rounded-full mix-blend-multiply filter blur-3xl opacity-40"></div>

    <div class="glass-panel w-full max-w-5xl rounded-[2rem] shadow-2xl flex flex-col md:flex-row overflow-hidden relative z-10 min-h-[600px]">
        
        <div class="w-full md:w-1/3 bg-slate-900/5 p-8 border-b md:border-b-0 md:border-r border-white/40 flex flex-col">
            <div class="mb-12 mt-4">
                <h1 class="text-3xl font-bold text-slate-800 tracking-tight">ximi IM</h1>
                <p class="text-xs text-slate-500 mt-2 tracking-widest uppercase">Secure Setup Wizard</p>
            </div>

            <div class="space-y-8 relative">
                <div class="flex items-start step-indicator" id="nav-step-1">
                    <div class="w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-blue-600 text-white shadow-md z-10 relative nav-icon">1</div>
                    <div class="ml-4">
                        <h3 class="text-sm font-semibold text-slate-800">系统特性</h3>
                        <p class="text-xs text-slate-500 mt-1">了解核心架构机制</p>
                    </div>
                </div>
                <div class="flex items-start step-indicator" id="nav-step-2">
                    <div class="w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-white/60 text-slate-400 z-10 relative nav-icon">2</div>
                    <div class="ml-4">
                        <h3 class="text-sm font-semibold text-slate-800">环境巡检</h3>
                        <p class="text-xs text-slate-500 mt-1">服务器依赖项验证</p>
                    </div>
                </div>
                <div class="flex items-start step-indicator" id="nav-step-3">
                    <div class="w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-white/60 text-slate-400 z-10 relative nav-icon">3</div>
                    <div class="ml-4">
                        <h3 class="text-sm font-semibold text-slate-800">文件校验</h3>
                        <p class="text-xs text-slate-500 mt-1">完整性检测与自动化修复</p>
                    </div>
                </div>
                <div class="flex items-start step-indicator" id="nav-step-4">
                    <div class="w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-white/60 text-slate-400 z-10 relative nav-icon">4</div>
                    <div class="ml-4">
                        <h3 class="text-sm font-semibold text-slate-800">核心配置</h3>
                        <p class="text-xs text-slate-500 mt-1">数据库与存储参数设置</p>
                    </div>
                </div>
                <div class="absolute left-4 top-4 bottom-8 w-px bg-slate-300/50 -z-0"></div>
            </div>
            
            <div class="mt-auto pt-10">
                <p class="text-[10px] text-slate-400">Powered by PHP & SQLite</p>
            </div>
        </div>

        <div class="w-full md:w-2/3 p-8 md:p-12 relative flex flex-col">
            
            <div class="step-content active flex-1" id="step-1">
                <h2 class="text-2xl font-bold text-slate-800 mb-6">欢迎使用</h2>
                <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
                    <div class="bg-white/50 border border-white p-5 rounded-xl shadow-sm hover:shadow-md transition-shadow">
                        <div class="text-xl mb-2">🔐</div>
                        <h3 class="font-semibold text-sm text-slate-700">端到端加密通信</h3>
                        <p class="text-xs text-slate-500 mt-2 leading-relaxed">服务端仅存储密文,彻底杜绝明文数据泄露及中间人监听风险。</p>
                    </div>
                    <div class="bg-white/50 border border-white p-5 rounded-xl shadow-sm hover:shadow-md transition-shadow">
                        <div class="text-xl mb-2">💣</div>
                        <h3 class="font-semibold text-sm text-slate-700">阅后即焚机制</h3>
                        <p class="text-xs text-slate-500 mt-2 leading-relaxed">高度敏感信息读取后立即在服务端及客户端实行物理销毁,不留痕迹。</p>
                    </div>
                    <div class="bg-white/50 border border-white p-5 rounded-xl shadow-sm hover:shadow-md transition-shadow">
                        <div class="text-xl mb-2">📦</div>
                        <h3 class="font-semibold text-sm text-slate-700">无缝轻量架构</h3>
                        <p class="text-xs text-slate-500 mt-2 leading-relaxed">基于原生 PHP 与 SQLite 构建,摆脱繁重的中间件,极简高效。</p>
                    </div>
                    <div class="bg-white/50 border border-white p-5 rounded-xl shadow-sm hover:shadow-md transition-shadow">
                        <div class="text-xl mb-2">🚀</div>
                        <h3 class="font-semibold text-sm text-slate-700">极简极速部署</h3>
                        <p class="text-xs text-slate-500 mt-2 leading-relaxed">无需繁琐命令,单文件上传即用,自动化向导完成所有初始化。</p>
                    </div>
                </div>
                <div class="bg-amber-50/80 border border-amber-100 text-amber-700 px-4 py-3 rounded-lg text-xs flex items-center">
                    <span class="mr-2">💡</span> 强烈建议在生产环境配置 Nginx HTTPS (SSL证书) 以激活完整的安全机制。
                </div>
            </div>

            <div class="step-content flex-1" id="step-2">
                <h2 class="text-2xl font-bold text-slate-800 mb-6">服务器环境巡检</h2>
                <div class="bg-white/60 border border-white rounded-xl shadow-sm overflow-hidden mb-6">
                    <table class="w-full text-sm text-left">
                        <thead class="bg-slate-50/50 text-slate-500 text-xs uppercase border-b border-white">
                            <tr>
                                <th class="px-6 py-3 font-medium">扩展组件名称</th>
                                <th class="px-6 py-3 font-medium text-center">必要性</th>
                                <th class="px-6 py-3 font-medium text-right">状态检测</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-white/60">
                            <?php foreach ($env_status as $ext => $info): ?>
                            <tr class="hover:bg-white/40 transition-colors">
                                <td class="px-6 py-3 font-medium text-slate-700 term-text"><?=$info['name']?></td>
                                <td class="px-6 py-3 text-center text-xs">
                                    <span class="px-2 py-1 rounded-md <?= $info['type'] == 'required' ? 'bg-slate-200 text-slate-700' : 'bg-slate-100 text-slate-500' ?>">
                                        <?= $info['type'] == 'required' ? 'Required' : 'Optional' ?>
                                    </span>
                                </td>
                                <td class="px-6 py-3 text-right">
                                    <?php if ($info['loaded']): ?>
                                        <span class="inline-flex items-center text-emerald-600 bg-emerald-50 px-2 py-1 rounded-md text-xs font-semibold"><span class="w-1.5 h-1.5 bg-emerald-500 rounded-full mr-1.5"></span>PASS</span>
                                    <?php else: ?>
                                        <span class="inline-flex items-center <?= $info['type'] == 'required' ? 'text-red-600 bg-red-50' : 'text-amber-600 bg-amber-50' ?> px-2 py-1 rounded-md text-xs font-semibold"><span class="w-1.5 h-1.5 <?= $info['type'] == 'required' ? 'bg-red-500' : 'bg-amber-500' ?> rounded-full mr-1.5"></span>FAIL</span>
                                    <?php endif; ?>
                                </td>
                            </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
                <li class="" style="
    margin-top: -9px;
    padding-bottom: 0.5rem;
    color: #88909a;
    font-size: 14px;
    margin-left: 22px;
">
                    curl为非必需扩展,仅用于自动修复文件缺失!
                </li>
                <?php if (!$install_ok): ?>
                <div class="bg-red-50 border border-red-100 text-red-600 px-4 py-3 rounded-lg text-sm mb-4">
                    ❌ 核心必需扩展缺失,请配置 php.ini 或在服务器面板(如 1Panel/Synology)中安装后重试。
                </div>
                <?php else: ?>
                <div class="bg-emerald-50 border border-emerald-100 text-emerald-700 px-4 py-3 rounded-lg text-sm mb-4">
                    ✅ 核心环境依赖已就绪,准许安装。
                </div>
                <?php endif; ?>
            </div>

            <div class="step-content flex-1" id="step-3">
                <div class="flex justify-between items-end mb-6">
                    <div>
                        <h2 class="text-2xl font-bold text-slate-800">系统文件校验</h2>
                        <p class="text-xs text-slate-500 mt-1">检测程序主体文件完整性,支持自动从云端获取缺失项。</p>
                    </div>
                    <?php if ($curl_enabled): ?>
                    <button id="repair-all-btn" class="bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white transition-colors px-3 py-1.5 rounded-lg text-xs font-medium shadow-sm">一键修复全部</button>
                    <?php endif; ?>
                </div>

                <div id="file-list" class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6">
                    <?php foreach ($file_list as $file): 
                        $exists = file_exists(__DIR__ . '/' . $file);
                    ?>
                    <div class="bg-white/60 border border-white p-3 rounded-xl flex justify-between items-center shadow-sm" data-file="<?=$file?>">
                        <span class="text-sm font-medium text-slate-700 term-text"><?=$file?></span>
                        <div class="file-status text-xs">
                            <?php if ($exists): ?>
                                <span class="text-emerald-500 font-semibold bg-emerald-50 px-2 py-1 rounded">Normal</span>
                            <?php elseif ($curl_enabled): ?>
                                <button onclick="repairFile('<?=$file?>')" class="text-blue-500 hover:text-blue-700 font-medium bg-blue-50 px-2 py-1 rounded transition-colors">自动修复</button>
                            <?php else: ?>
                                <span class="text-slate-400 italic">缺失 (需手动上传)</span>
                            <?php endif; ?>
                        </div>
                    </div>
                    <?php endforeach; ?>
                </div>
            </div>

            <div class="step-content flex-1" id="step-4">
                <h2 class="text-2xl font-bold text-slate-800 mb-2">安全配置</h2>
                <p class="text-xs text-slate-500 mb-8">设定您的存储路径,保持留空将由算法自动生成高强度乱码命名以防止穷举探测。</p>
                
                <form id="install-form" method="POST" class="space-y-5">
                    <div class="bg-white/50 border border-white p-6 rounded-2xl shadow-sm">
                        <div class="mb-5">
                            <label class="block text-sm font-medium text-slate-700 mb-1">物理数据存放目录 <span class="text-slate-400 text-xs font-normal">(db_dir)</span></label>
                            <input type="text" name="dir_name" placeholder="建议留空,自动生成随机目录" class="w-full bg-white/70 border border-slate-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all term-text">
                        </div>
                        <div>
                            <label class="block text-sm font-medium text-slate-700 mb-1">前端数据库文件名 <span class="text-slate-400 text-xs font-normal">(db_name)</span></label>
                            <input type="text" name="db_name" placeholder="建议留空,自动生成随机库名" class="w-full bg-white/70 border border-slate-200 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all term-text">
                        </div>
                        
                        <div class="mt-4 pt-4 border-t border-slate-200/50 flex items-center justify-between">
                            <span class="text-xs text-slate-500">管理端独立数据库 (admin_db) 将强制执行随机生成。</span>
                            <span class="text-xs bg-slate-100 text-slate-500 px-2 py-1 rounded term-text">Auto</span>
                        </div>
                    </div>
                </form>
            </div>

            <div class="mt-auto pt-6 border-t border-white/50 flex justify-between items-center">
                <button id="btn-prev" class="px-5 py-2.5 rounded-xl text-sm font-medium text-slate-600 hover:bg-white/50 transition-colors hidden">
                    返回上一步
                </button>
                <div class="flex-1"></div> <button id="btn-next" class="px-8 py-2.5 rounded-xl text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 shadow-lg shadow-blue-500/30 transition-all">
                    下一步
                </button>
                <button id="btn-install" class="px-8 py-2.5 rounded-xl text-sm font-medium text-white bg-emerald-600 hover:bg-emerald-700 shadow-lg shadow-emerald-500/30 transition-all hidden" onclick="document.getElementById('install-form').submit()">
                    执行系统安装
                </button>
            </div>

        </div>
    </div>

<script>
// --- 交互式步骤导航逻辑 ---
let currentStep = 1;
const totalSteps = 4;
const envPassed = <?= $install_ok ? 'true' : 'false' ?>;

function updateUI() {
    // 内容区切换
    document.querySelectorAll('.step-content').forEach(el => el.classList.remove('active'));
    document.getElementById(`step-${currentStep}`).classList.add('active');

    // 侧边栏状态更新
    for (let i = 1; i <= totalSteps; i++) {
        const icon = document.querySelector(`#nav-step-${i} .nav-icon`);
        if (i < currentStep) {
            icon.className = 'w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-emerald-500 text-white shadow-md z-10 relative nav-icon';
            icon.innerHTML = '✓';
        } else if (i === currentStep) {
            icon.className = 'w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-blue-600 text-white shadow-md z-10 relative nav-icon';
            icon.innerHTML = i;
        } else {
            icon.className = 'w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors duration-300 bg-white/60 text-slate-400 z-10 relative nav-icon';
            icon.innerHTML = i;
        }
    }

    // 按钮显隐逻辑
    document.getElementById('btn-prev').style.display = currentStep > 1 ? 'block' : 'none';
    
    if (currentStep === totalSteps) {
        document.getElementById('btn-next').style.display = 'none';
        document.getElementById('btn-install').style.display = 'block';
    } else {
        document.getElementById('btn-next').style.display = 'block';
        document.getElementById('btn-install').style.display = 'none';
    }

    // 环境检测未通过,锁定第2步
    if (currentStep === 2 && !envPassed) {
        document.getElementById('btn-next').disabled = true;
        document.getElementById('btn-next').classList.add('opacity-50', 'cursor-not-allowed');
        document.getElementById('btn-next').innerHTML = '环境未就绪';
    } else {
        document.getElementById('btn-next').disabled = false;
        document.getElementById('btn-next').classList.remove('opacity-50', 'cursor-not-allowed');
        document.getElementById('btn-next').innerHTML = '下一步';
    }
}

document.getElementById('btn-next').addEventListener('click', () => {
    if (currentStep < totalSteps) { currentStep++; updateUI(); }
});

document.getElementById('btn-prev').addEventListener('click', () => {
    if (currentStep > 1) { currentStep--; updateUI(); }
});

// --- 原有的异步文件修复逻辑无缝接入 ---
async function repairFile(filename) {
    const el = document.querySelector(`[data-file="${filename}"] .file-status`);
    el.innerHTML = '<span class="text-amber-500 bg-amber-50 px-2 py-1 rounded font-medium animate-pulse">Pulling...</span>';
    const formData = new FormData();
    formData.append('file', filename);
    try {
        const response = await fetch('?action=repair_file', { method: 'POST', body: formData });
        const result = await response.json();
        if (result.status === 'success') {
            el.innerHTML = '<span class="text-emerald-500 font-semibold bg-emerald-50 px-2 py-1 rounded">Fixed</span>';
        } else {
            el.innerHTML = `<button class="text-red-500 hover:text-red-700 font-medium bg-red-50 px-2 py-1 rounded" onclick="repairFile('${filename}')">重试</button>`;
        }
    } catch (e) {
        el.innerHTML = `<button class="text-red-500 hover:text-red-700 font-medium bg-red-50 px-2 py-1 rounded" onclick="repairFile('${filename}')">Error(重试)</button>`;
    }
}

document.getElementById('repair-all-btn')?.addEventListener('click', async function() {
    const btn = this;
    btn.disabled = true;
    btn.innerHTML = '修复进行中...';
    btn.classList.add('opacity-50', 'cursor-not-allowed');
    
    const buttons = document.querySelectorAll('#file-list .file-status button');
    for (const actionBtn of buttons) { 
        actionBtn.click(); 
        await new Promise(r => setTimeout(r, 800)); 
    }
    
    setTimeout(() => {
        btn.innerHTML = '执行完毕';
        btn.classList.remove('text-blue-600', 'bg-blue-50');
        btn.classList.add('text-emerald-600', 'bg-emerald-50');
    }, 1000);
});

// 初始化界面
updateUI();
</script>
</body>
</html>

 

博客:www.ximi.me
已有评论 ( 5 )
  • 头像
    希米 (UID: 5950)
    (博客:www.ximi.me)
    1楼

      ,你上次不是没有安装成功吗,用这个就可以直接安装了,文件我放在国内的博客服务器上了,不需要魔法访问了;

  • 头像
    希米 (UID: 5950)
    (博客:www.ximi.me)
    2楼

    我这是为了偷懒,没写数据库创建,因为首次运行后台会自动创建数据库,不需要那么麻烦

  • 头像
    希米 (UID: 5950)
    (博客:www.ximi.me)
    3楼
    六哥 https://haoma.id/你给我看看咋回

    是否使用的是用的最新版,安装的时候扩展都满足吗

  • 头像
    希米 (UID: 5950)
    (博客:www.ximi.me)
    4楼
    Parse error: syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting ')' in /www/wwwroot/haoma.id/api.php on line 358

    看了下报错信息与日志提示,报的是语法错误,

    但是我本地检查没有问题,我自己本地服务器都能运行;

    发给ai审查提示有错误的注释符合与大括号,我检查看不到,可能是幽灵符号,复制ai给的代码时候出现的,重新生成了一份,

    你替换api.php试试看,还有记得给主目录755或是777权限;

    api.php

    <?php
    ini_set('display_errors', 0);
    error_reporting(E_ALL);
    // 强制设置响应头为 JSON
    header('Content-Type: application/json; charset=utf-8');
    date_default_timezone_set('Asia/Shanghai');
    
    // 【新增】配置文件检测逻辑
    $settingFile = __DIR__ . '/setting.php';
    if (!file_exists($settingFile)) {
        // 返回一个特定的 JSON 状态码,告知前端系统尚未安装
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode([
            'code' => 503, 
            'msg' => '系统未安装', 
            'redirect' => 'install.php'
        ]);
        exit; // 终止后续数据库连接等操作,防止报错
    }
    
    // 如果存在,则继续载入配置
    $config = require $settingFile;
    
    /*
    // 💡 【安全限制】:只允许您信任的域进行跨域访问
    $allowed_origins = ['https://app.hhqq.net','http://192.168.1.10:555', 'http://www.ximi.me']; 
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
    if (in_array($origin, $allowed_origins)) {
        header("Access-Control-Allow-Origin: $origin");
        header("Access-Control-Allow-Credentials: true");
        header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
        header("Access-Control-Allow-Headers: Content-Type, Authorization");
    }
    
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit(0); }
    
    */
    // ========================================================
    // 核心模块 1:全局黑名单拦截 (Req 7)
    // ========================================================
    $blacklistFile = __DIR__ . '/blacklist.json';
    $failedAttemptsFile = __DIR__ . '/failed_attempts.json';
    if (!file_exists($blacklistFile)) file_put_contents($blacklistFile, json_encode([]));
    
    function get_client_ip() {
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }
    
    $current_ip = get_client_ip();
    $blacklist = json_decode(file_get_contents($blacklistFile), true) ?? [];
    if (in_array($current_ip, $blacklist)) {
        header('HTTP/1.0 403 Forbidden');
        exit(json_encode(['code' => 403, 'msg' => 'Access Denied: IP Banned']));
    }
    
    function ban_ip($reason) {
        global $blacklistFile, $current_ip;
        $blacklist = json_decode(file_get_contents($blacklistFile), true) ?? [];
        if (!in_array($current_ip, $blacklist)) {
            $blacklist[] = $current_ip;
            file_put_contents($blacklistFile, json_encode($blacklist));
        }
        error_log("[SECURITY ALERT] IP: $current_ip Banned. Reason: $reason");
        echo json_encode(['code' => 403, 'msg' => '严重违规:您的请求触发了防御机制,IP 已被永久拦截。']);
        exit;
    }
    
    // ========================================================
    // 核心模块 2:非法攻击计数与熔断 (Req 3)
    // ========================================================
    function record_failed_attempt() {
        global $failedAttemptsFile, $current_ip;
        $attempts = file_exists($failedAttemptsFile) ? json_decode(file_get_contents($failedAttemptsFile), true) : [];
        $attempts[$current_ip] = ($attempts[$current_ip] ?? 0) + 1;
        file_put_contents($failedAttemptsFile, json_encode($attempts));
        
        if ($attempts[$current_ip] >= 10) {
            ban_ip("非法字符尝试攻击超过10次");
        }
    }
    
    function validate_input_or_fail($str, $regex) {
        if (!preg_match($regex, $str)) {
            record_failed_attempt();
            echo json_encode(['code' => 400, 'msg' => '输入包含非法字符或长度不符,,请求被拦截, 至少4位用户名,6位密码']);
            exit;
        }
        return true;
    }
    
    // ========================================================
    // 核心模块 3:数据库初始化与连接 (Req 1, 9, 10)
    // ========================================================
    require_once __DIR__ . '/db.php'; // 需确保 db.php 中正确实例化了 $pdo
    
    // 请将此段代码放在 api.php 中 $pdo 连接定义之后(通常在 db.php 引入之后)
    function check_upload_enabled() {
        global $pdo; // 关键:声明使用全局的 $pdo 对象
        try {
            $stmt = $pdo->query("SELECT value FROM settings WHERE key='upload_enabled'");
            $res = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($res && $res['value'] == '0') {
                // 返回 JSON 并立即停止执行
                echo json_encode(['code' => 403, 'msg' => '系统全局文件上传通道已关闭!']);
                exit;
            }
        } catch (Exception $e) {
            // 如果表不存在,默认视为开启(防止报错)
            return;
        }
    }
    
    
    
    try {
        // Req 1 & 9: 自动创建新结构的用户表
        $pdo->exec("CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT NOT NULL UNIQUE,
            password_text TEXT NOT NULL,
            public_key TEXT,
            nickname TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            last_ip TEXT,
            stop_user INTEGER DEFAULT 0
        )");
        
        // Req 10: 保持 messages 表不变
        $pdo->exec("CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            sender_id INTEGER NOT NULL,
            receiver_id INTEGER NOT NULL,
            msg_type TEXT NOT NULL,
            encrypt_iv TEXT NOT NULL,
            encrypted_aes_key TEXT NOT NULL,
            encrypted_content TEXT NOT NULL,
            is_downloaded INTEGER DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )");
    } catch (PDOException $e) {
        exit(json_encode(['code' => 500, 'msg' => '初始化数据表失败']));
    }
    
    // ========================================================
    // 核心模块 4:IP与账号状态鉴权体系 (Req 4, 7)
    // ========================================================
    function verify_user_auth($pdo, $userId) {
        global $current_ip;
        if (!$userId) {
            exit(json_encode(['code' => 400, 'msg' => '鉴权失败:缺少UID']));
        }
        $stmt = $pdo->prepare("SELECT last_ip, stop_user FROM users WHERE id = :id");
        $stmt->execute([':id' => $userId]);
        $user = $stmt->fetch();
        
        if (!$user) exit(json_encode(['code' => 404, 'msg' => '用户不存在']));
        if ($user['stop_user'] == 1) exit(json_encode(['code' => 403, 'msg' => '账号已被封禁']));
        if ($user['last_ip'] !== $current_ip) exit(json_encode(['code' => 403, 'msg' => '登录 IP 不匹配,拒绝执行越权操作']));
    }
    
    // ========================================================
    // 核心模块 5:底层物理防御沙盒 (Req 5, 6)
    // ========================================================
    define('UPLOADS_DIR', __DIR__ . '/uploads');
    if (!file_exists(UPLOADS_DIR)) mkdir(UPLOADS_DIR, 0755, true);
    
    function enforce_sandbox($pdo, $userId, $target_path) {
        global $current_ip;
        $base_dir = realpath(UPLOADS_DIR);
        $real_target = realpath($target_path);
    
        // 如果路径存在,并且其物理绝对路径不是以 uploads 目录开头 -> 绝对是越权/跳跃目录攻击
        if ($real_target !== false && strpos($real_target, $base_dir) !== 0) {
            
            // 1. 禁用账号
            if ($userId) {
                $pdo->prepare("UPDATE users SET stop_user = 1 WHERE id = ?")->execute([$userId]);
            }
            
            // 2. 记录错误日志 (ero.json)
            $logFile = __DIR__ . '/ero.json';
            $logData = file_exists($logFile) ? json_decode(file_get_contents($logFile), true) : [];
            $logData[] = [
                'time' => date('Y-m-d H:i:s'),
                'user_id' => $userId ?? 'unknown',
                'ip' => $current_ip,
                'attempted_path' => $target_path,
                'resolved_path' => $real_target
            ];
            file_put_contents($logFile, json_encode($logData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
            
            // 3. 封禁IP并熔断
            ban_ip("检测到致命的越权文件操作尝试");
        }
    }
    
    // 删除文件夹工具(带沙盒防御)
    function delete_dir_safe($pdo, $userId, $dir) {
        enforce_sandbox($pdo, $userId, $dir);
        if (!is_dir($dir)) return;
        $files = array_diff(scandir($dir), ['.', '..']);
        foreach ($files as $file) {
            $path = $dir . DIRECTORY_SEPARATOR . $file;
            is_dir($path) ? delete_dir_safe($pdo, $userId, $path) : @unlink($path);
        }
        @rmdir($dir);
    }
    
    // ========================================================
    // 路由处理入口
    // ========================================================
    $input = json_decode(file_get_contents('php://input'), true) ?? [];
    $action = $_GET['action'] ?? '';
    
    // ------------------------------------------
    // 1. 用户注册 (Req 1, 2, 3)
    // ------------------------------------------
    if ($action === 'register') {
        $username = trim($input['username'] ?? '');
        $password = trim($input['password'] ?? '');
        $publicKey = trim($input['public_key'] ?? ''); 
        $nickname = trim($input['nickname'] ?? '');
        if (empty($nickname)) $nickname = $username; 
    
        // Req 2 & 3: 严格正则验证
        // 用户名:只允许英文、数字,4-20位
        validate_input_or_fail($username, '/^[a-zA-Z0-9]{4,20}$/');
        // 密码:允许大小写字母、数字及常见安全标点,不允许 < > ? 等
        validate_input_or_fail($password, '/^[a-zA-Z0-9!@#\$%\^&\*\(\)_\+\-\=\[\]\{\}\|;\':",\.\/~` ]{6,64}$/');
        // 昵称:中文、英文、数字、下划线,3-16位
        validate_input_or_fail($nickname, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9_]{3,16}$/u');
    
        try {
            $md5_password = md5($password);
            $stmt = $pdo->prepare("INSERT INTO users (username, password_text, public_key, nickname, last_ip, stop_user) VALUES (:username, :password, :public_key, :nickname, :last_ip, 0)");
            $stmt->execute([
                ':username' => $username,
                ':password' => $md5_password,
                ':public_key' => $publicKey,
                ':nickname' => $nickname,
                ':last_ip' => $current_ip
            ]);
            echo json_encode(['code' => 200, 'msg' => '注册成功']);
        } catch (PDOException $e) {
            echo json_encode(['code' => $e->getCode() == '23000' ? 400 : 500, 'msg' => $e->getCode() == '23000' ? '用户名已存在' : '注册失败']);
        }
        exit;
    }
    
    // ------------------------------------------
    // 2. 用户登录 (Req 1, 7)
    // ------------------------------------------
    if ($action === 'login') {
        $username = trim($input['username'] ?? '');
        $password = trim($input['password'] ?? '');
    
        // 防爆破:同样对登录输入进行合法性校验
        validate_input_or_fail($username, '/^[a-zA-Z0-9]{4,20}$/');
    
        $stmt = $pdo->prepare("SELECT id, username, password_text, public_key, nickname, stop_user FROM users WHERE username = :username");
        $stmt->execute([':username' => $username]);
        $user = $stmt->fetch();
    
        if ($user) {
            if ($user['stop_user'] == 1) {
                exit(json_encode(['code' => 403, 'msg' => '该账号因严重违规已被管理员或系统封禁']));
            }
            if ($user['password_text'] === md5($password)) {
                // 更新最后登录IP
                $pdo->prepare("UPDATE users SET last_ip = :ip WHERE id = :id")->execute([':ip' => $current_ip, ':id' => $user['id']]);
                echo json_encode([
                    'code' => 200,
                    'msg' => '登录成功',
                    'data' => ['id' => $user['id'], 'username' => $user['username'], 'public_key' => $user['public_key'], 'nickname' => $user['nickname']]
                ]);
                exit;
            }
        }
        
        // 登录失败增加错误记录
        record_failed_attempt();
        echo json_encode(['code' => 400, 'msg' => '用户名或密码错误']);
        exit;
    }
    
    // ------------------------------------------
    // 3. 获取公钥
    // ------------------------------------------
    if ($action === 'get_public_key') {
        $userId = intval($_GET['user_id'] ?? 0);
        $stmt = $pdo->prepare("SELECT public_key FROM users WHERE id = :id");
        $stmt->execute([':id' => $userId]);
        $user = $stmt->fetch();
        if ($user) echo json_encode(['code' => 200, 'data' => ['public_key' => $user['public_key']]]);
        else echo json_encode(['code' => 404, 'msg' => '未找到该用户']);
        exit;
    }
    
    // ------------------------------------------
    // 4. 发送消息/上传单文件 (Req 4)
    // ------------------------------------------
    if ($action === 'send_message') {
        $inputData = (strpos($_SERVER['CONTENT_TYPE'] ?? '', 'application/json') !== false) ? $input : $_POST;
        
        $sender_id = intval($inputData['sender_id'] ?? 0);
        $receiver_id = intval($inputData['receiver_id'] ?? 0);
        $msg_type = trim($inputData['msg_type'] ?? 'text');
        $encrypted_content = trim($inputData['encrypted_content'] ?? '');
    
        verify_user_auth($pdo, $sender_id);
    
    if ($msg_type === 'file') { check_upload_enabled();   }
    
        if ($msg_type === 'file' && isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
            $originalName = basename($_FILES['file']['name']);
            if (!in_array(strtolower(pathinfo($originalName, PATHINFO_EXTENSION)), ['enc', 'json'])) {
                ban_ip("尝试上传非法格式后缀文件");
            }
    
            $safeFileName = time() . '_' . uniqid() . '.enc';
            $targetPath = UPLOADS_DIR . '/' . $safeFileName;
    
            if (move_uploaded_file($_FILES['file']['tmp_name'], $targetPath)) {
                $encrypted_content = base64_encode(urlencode($originalName) . '|uploads/' . $safeFileName); 
            } else {
                exit(json_encode(['code' => 500, 'msg' => '文件保存失败']));
            }
        }
    
        try {
            $stmt = $pdo->prepare("INSERT INTO messages (sender_id, receiver_id, msg_type, encrypt_iv, encrypted_aes_key, encrypted_content) VALUES (?, ?, ?, ?, ?, ?)");
            $stmt->execute([$sender_id, $receiver_id, $msg_type, $inputData['encrypt_iv'] ?? '', $inputData['encrypted_aes_key'] ?? '', $encrypted_content]);
            echo json_encode(['code' => 200, 'msg' => '发送成功']);
        } catch (PDOException $e) {
            echo json_encode(['code' => 500, 'msg' => '写入失败']);
        }
        exit;
    }
    
    // ------------------------------------------
    // 5. 拉取及列表路由
    // ------------------------------------------
    if ($action === 'list_users') {
        $stmt = $pdo->query("SELECT id, username, nickname FROM users ORDER BY id ASC");
        echo json_encode(['code' => 200, 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
        exit;
    }
    
    if ($action === 'pull_messages') {
        $userId = intval($_GET['user_id'] ?? 0);
        verify_user_auth($pdo, $userId);
    
        $pdo->prepare("DELETE FROM messages WHERE receiver_id = ? AND is_downloaded = 1")->execute([$userId]);
        $stmt = $pdo->prepare("SELECT * FROM messages WHERE receiver_id = ? AND is_downloaded = 0");
        $stmt->execute([$userId]);
        $messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
        $textIds = array_column(array_filter($messages, fn($m) => $m['msg_type'] === 'text'), 'id');
        if ($textIds) $pdo->exec("UPDATE messages SET is_downloaded = 1 WHERE id IN (" . implode(',', $textIds) . ")");
    
        echo json_encode(['code' => 200, 'data' => $messages]); exit;
    }
    
    // ------------------------------------------
    // 6. 清除列队及关联文件 (Req 4, 5, 6)
    // ------------------------------------------
    if ($action === 'clear_server_queue') {
        $userId = intval($input['user_id'] ?? 0);
        verify_user_auth($pdo, $userId);
    
        $stmt = $pdo->prepare("SELECT encrypted_content FROM messages WHERE receiver_id = ? AND msg_type = 'file'");
        $stmt->execute([$userId]);
        
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $info) {
            $parts = explode('|', base64_decode($info['encrypted_content']));
            if (count($parts) >= 2) {
                $pathStr = trim($parts[1]);
                
                // 安全沙盒检测:如果是完整的文件夹(分片),或者是单个文件
                if (preg_match('/^[a-f0-9]{32}$/i', $pathStr)) {
                    $targetDir = UPLOADS_DIR . '/' . $pathStr;
    
    
                    delete_dir_safe($pdo, $userId, $targetDir);
                } else if (strpos($pathStr, 'uploads/') === 0) {
                    // 防止传入 uploads/../../index.php
                    $targetFile = __DIR__ . '/' . $pathStr;
                    enforce_sandbox($pdo, $userId, $targetFile);
                    if (file_exists($targetFile)) @unlink($targetFile);
                }
            }
        }
        $pdo->prepare("DELETE FROM messages WHERE receiver_id = ?")->execute([$userId]);
        echo json_encode(['code' => 200, 'msg' => '队列及相关文件已安全清空']);
        exit;
    }
    
    // ------------------------------------------
    // 7. 分片上传与清单管理
    // ------------------------------------------
    if ($action === 'upload_chunk') {
        $dirId = trim($_GET['id'] ?? '');
        $chunkName = trim($_POST['chunk_name'] ?? '');
    
    check_upload_enabled(); // 修正:不要传任何参数
    
        // 强正则限制文件夹和文件名格式,从源头切断 ../ 的可能
        if (!preg_match('/^[a-f0-9]{32}$/i', $dirId) || !preg_match('/^chunk_\d+\.enc$/', $chunkName)) {
            ban_ip("非法的分片上传格式参数");
        }
    
        $uploadDir = UPLOADS_DIR . "/$dirId/";
        if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
        
        if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
            $targetPath = $uploadDir . $chunkName;
            move_uploaded_file($_FILES['file']['tmp_name'], $targetPath);
            echo json_encode(['code' => 200]);
        } else {
            echo json_encode(['code' => 500]);
        }
        exit;
    }
    
    
    if ($action === 'save_manifest') {
        // 补上这行,这才是阻断非法上传的核心!
        check_upload_enabled(); 
    
        $dirId = trim($_POST['id'] ?? '');
        if (!preg_match('/^[a-f0-9]{32}$/i', $dirId)) exit;
        
        $uploadDir = UPLOADS_DIR . "/$dirId/";
        if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
        
        file_put_contents($uploadDir . "manifest.json", $_POST['manifest'] ?? '');
        echo json_encode(['code' => 200]); exit;
    }
    
    if ($action === 'get_manifest') {
        $dirId = $_GET['id'] ?? '';
        if (!preg_match('/^[a-f0-9]{32}$/i', $dirId)) exit;
        
        $file = UPLOADS_DIR . "/$dirId/manifest.json";
        if (file_exists($file)) echo file_get_contents($file);
        exit;
    }
    
    // ------------------------------------------
    // 8. 物理文件删除 (Req 4, 5, 6)
    // ------------------------------------------
    if ($action === 'delete_files') {
        // 【重要变更】:此接口现在强制要求传入操作者的 user_id 用于鉴权
        $userId = intval($_GET['user_id'] ?? 0);
        verify_user_auth($pdo, $userId);
    
        $dirId = trim($_GET['id'] ?? '');
        $messageId = intval($_GET['message_id'] ?? 0);
        
        // 双重保险:格式校验 + 沙盒检测
        if (!preg_match('/^[a-f0-9]{32}$/i', $dirId)) {
            ban_ip("尝试使用非标准标识符删除文件");
        }
    
    
    if (is_dir($targetDir)) {
        $files = array_diff(scandir($targetDir), ['.', '..']);
        foreach ($files as $file) { @unlink($targetDir . '/' . $file); }
        @rmdir($targetDir);
    } else if (file_exists($targetDir)) {
        @unlink($targetDir); 
    }
    
        delete_dir_safe($pdo, $userId, $targetDir);
        
        if ($messageId > 0) {
        $pdo->prepare("DELETE FROM messages WHERE id = ?")->execute([$messageId]);
    }
        echo json_encode(['code' => 200]); exit;
    }
    // ========================================================
    // 安全加固模块:双重加密身份安全验证体系 (RSA + AES)
    // ========================================================
    // ========================================================
    // 安全加固模块:双重加密身份安全验证体系 (RSA + AES) —— 兼容传参自适应版
    // ========================================================
    
    // 1. 预验证接口:生成 128 位随机字符 Token 并进行混合加密
    if ($action === 'pre_verify') {
        if (session_status() === PHP_SESSION_NONE) { session_start(); }
        
        // 【双保险获取 UID】:优先从前端传参或 Session 中获取
        $userId = intval($_GET['user_id'] ?? $_SESSION['user_id'] ?? 0);
        
        if (!$userId) {
            echo json_encode(['code' => 403, 'msg' => '鉴权失败:未提供有效的操作用户ID']);
            exit;
        }
    
        // 强行将有效的 UID 写入当前 Session,为接下来的敏感操作铺路
        $_SESSION['user_id'] = $userId;
    
        // 从 users 表提取当前用户的公钥
        $stmt = $pdo->prepare("SELECT public_key FROM users WHERE id = ?");
        $stmt->execute([$userId]);
        $pubKey = $stmt->fetchColumn();
    
        if (!$pubKey) {
            echo json_encode(['code' => 500, 'msg' => '您的账号未配置安全公钥,无法完成身份双重验证']);
            exit;
        }
    
        // 生成安全算法物料
        $aesKey = bin2hex(random_bytes(8));   // 生成 16 位文本字符作为 AES-128 密钥
        $token = bin2hex(random_bytes(64));   // 生成 128 位纯随机字符作为 Token
        $_SESSION['temp_token'] = $token;      // 将原始明文 Token 安全存入服务端 Session 备查
    
        // 使用用户的 RSA 公钥加密 AES 密钥
        $encryptedAesKey = '';
        $rsaSuccess = openssl_public_encrypt($aesKey, $encryptedAesKey, $pubKey, OPENSSL_PKCS1_PADDING);
        
        if (!$rsaSuccess) {
            echo json_encode(['code' => 500, 'msg' => '服务端内部异常:RSA 公钥加密失败']);
            exit;
        }
    
        // 使用 AES-128-ECB 模式加密 Token 字符
        $encryptedToken = openssl_encrypt($token, "AES-128-ECB", $aesKey, 0);
    
        echo json_encode([
            'code' => 200,
            'aes_key_enc' => base64_encode($encryptedAesKey),
            'token_enc' => $encryptedToken
        ]);
        exit;
    }
    
    // 2. 核心敏感操作执行接口:解密比对成功后,才执行真实的修改或注销
    if ($action === 'update_profile' || $action === 'delete_account') {
        if (session_status() === PHP_SESSION_NONE) { session_start(); }
        
        $input = json_decode(file_get_contents('php://input'), true) ?? [];
        
        // 强制阻断比对:检验前端通过私钥层层解密带回来的 Token 凭证
        if (!isset($input['verify_token']) || !isset($_SESSION['temp_token']) || $input['verify_token'] !== $_SESSION['temp_token']) {
            echo json_encode(['code' => 403, 'msg' => '核心身份验证失败:令牌无效、篡改或已过期']);
            exit;
        }
        
        // 熔断机制:验证成功后立即彻底销毁 Session 令牌,防止重放攻击
        unset($_SESSION['temp_token']);
        
        // 获取当前合法的操作人 ID
        $userId = intval($_SESSION['user_id'] ?? $input['user_id'] ?? 0);
        if (!$userId) {
            echo json_encode(['code' => 403, 'msg' => '鉴权失败:无法锁定操作人身份']);
            exit;
        }
        
        // --- 验证全通过,开始安全执行数据库事务 ---
        if ($action === 'update_profile') {
            $nickname = trim($input['nickname'] ?? '');
            $password = trim($input['password'] ?? '');
            
            if (empty($nickname) && empty($password)) {
                echo json_encode(['code' => 400, 'msg' => '未传入任何实质性修改内容']);
                exit;
            }
            
            // 业务分支 A:修改昵称
            if (!empty($nickname)) {
                $stmt = $pdo->prepare("UPDATE users SET nickname = ? WHERE id = ?");
                $stmt->execute([$nickname, $userId]);
            }
            
            // 业务分支 B:修改密码 (保持你项目原有的 md5 散列存储规范)
            if (!empty($password)) {
                $md5_password = md5($password);
                $stmt = $pdo->prepare("UPDATE users SET password_text = ? WHERE id = ?");
                $stmt->execute([$md5_password, $userId]);
            }
            
            echo json_encode(['code' => 200, 'msg' => '账号隐私数据已安全同步更新']);
        } 
        elseif ($action === 'delete_account') {
            // 业务分支 C:注销账号逻辑
            $stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
            $stmt->execute([$userId]);
            session_destroy(); // 彻底销毁当前登录会话
            echo json_encode(['code' => 200, 'msg' => '您的账号及全部关联数据已被物理抹除']);
        }
        exit;
    }
    
    echo json_encode(['code' => 404, 'msg' => '无对应接口']);
  • 头像
    希米 (UID: 5950)
    (博客:www.ximi.me)
    5楼
    六哥 好的 上次的就是没有数据库啊

    不是没有数据库,数据库路径我设置的是上一级目录,这版本改了,如果还是不行你就用这个单文件安装,记得提前设置好目录为777权限避免报错;

    https://www.ximi.me/usr/demo/xm-im/install.php.zip

域名市场
   域名载入中...
创建新帖
自助推广购买广告位
确认删除
确定要删除这篇帖子吗?删除后将无法恢复。
删除成功
帖子已成功删除,页面将自动刷新。
删除失败
删除帖子时发生错误,请稍后再试。