Commit e8094d15a5542c1822b834548c281895580fc072
1 parent
acf4dc92
fix: make admin portal robust against db errors
Showing
1 changed file
with
115 additions
and
114 deletions
| @@ -5,15 +5,14 @@ require_once __DIR__ . '/../vendor/autoload.php'; | @@ -5,15 +5,14 @@ require_once __DIR__ . '/../vendor/autoload.php'; | ||
| 5 | if (file_exists(__DIR__ . '/../.env')) { | 5 | if (file_exists(__DIR__ . '/../.env')) { |
| 6 | $lines = file(__DIR__ . '/../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); | 6 | $lines = file(__DIR__ . '/../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); |
| 7 | foreach ($lines as $line) { | 7 | foreach ($lines as $line) { |
| 8 | - if (strpos(trim($line), '#') === 0) | ||
| 9 | - continue; | 8 | + if (strpos(trim($line), '#') === 0) continue; |
| 10 | list($name, $value) = explode('=', $line, 2); | 9 | list($name, $value) = explode('=', $line, 2); |
| 11 | $_ENV[trim($name)] = trim($value); | 10 | $_ENV[trim($name)] = trim($value); |
| 12 | } | 11 | } |
| 13 | } | 12 | } |
| 14 | 13 | ||
| 15 | -// 简单的一致性检查 (实际生产环境应加上 Session 登录验证) | ||
| 16 | -// 这里假设通过 Basic Auth 或内网访问 | 14 | +$pdo = null; |
| 15 | +$message = ''; | ||
| 17 | 16 | ||
| 18 | // 连接数据库 | 17 | // 连接数据库 |
| 19 | try { | 18 | try { |
| @@ -21,152 +20,154 @@ try { | @@ -21,152 +20,154 @@ try { | ||
| 21 | $pdo = new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASS']); | 20 | $pdo = new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASS']); |
| 22 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | 21 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
| 23 | } catch (PDOException $e) { | 22 | } catch (PDOException $e) { |
| 24 | - die("Database connection failed: " . $e->getMessage()); | 23 | + // FATAL: Do NOT use die() in Workerman, it kills the worker process! |
| 24 | + $message = "<div style='color: red; padding: 20px; text-align: center; border: 1px solid red; background: #ffe6e6;'>Database Connection Failed: " . htmlspecialchars($e->getMessage()) . "</div>"; | ||
| 25 | + $pdo = null; | ||
| 25 | } | 26 | } |
| 26 | 27 | ||
| 27 | // 处理绑定表单提交 | 28 | // 处理绑定表单提交 |
| 28 | -$message = ''; | ||
| 29 | -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bind') { | 29 | +if ($pdo && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bind') { |
| 30 | $deviceId = trim($_POST['device_id']); | 30 | $deviceId = trim($_POST['device_id']); |
| 31 | $phone = trim($_POST['phone']); | 31 | $phone = trim($_POST['phone']); |
| 32 | $isPrimary = isset($_POST['is_primary']) ? 1 : 0; | 32 | $isPrimary = isset($_POST['is_primary']) ? 1 : 0; |
| 33 | 33 | ||
| 34 | if ($deviceId && $phone) { | 34 | if ($deviceId && $phone) { |
| 35 | // 1. 查找用户 | 35 | // 1. 查找用户 |
| 36 | - $stmt = $pdo->prepare("SELECT id, nickname FROM users WHERE phone = ?"); | ||
| 37 | - $stmt->execute([$phone]); | ||
| 38 | - $user = $stmt->fetch(PDO::FETCH_ASSOC); | ||
| 39 | - | ||
| 40 | - if ($user) { | ||
| 41 | - // 2. 插入/更新绑定 | ||
| 42 | - // 先检查是否已存在 | ||
| 43 | - $check = $pdo->prepare("SELECT id FROM user_device_bindings WHERE user_id = ? AND device_id = ?"); | ||
| 44 | - $check->execute([$user['id'], $deviceId]); | ||
| 45 | - | ||
| 46 | - if (!$check->fetch()) { | ||
| 47 | - $bind = $pdo->prepare("INSERT INTO user_device_bindings (user_id, device_id, is_primary, created_at) VALUES (?, ?, ?, NOW())"); | ||
| 48 | - $bind->execute([$user['id'], $deviceId, $isPrimary]); | ||
| 49 | - $message = "<div style='color: green; margin-bottom: 20px;'>✅ 成功将设备 <b>$deviceId</b> 绑定给用户 <b>{$user['nickname']}</b></div>"; | 36 | + try { |
| 37 | + $stmt = $pdo->prepare("SELECT id, nickname FROM users WHERE phone = ?"); | ||
| 38 | + $stmt->execute([$phone]); | ||
| 39 | + $user = $stmt->fetch(PDO::FETCH_ASSOC); | ||
| 40 | + | ||
| 41 | + if ($user) { | ||
| 42 | + // 2. 插入/更新绑定 | ||
| 43 | + // 先检查是否已存在 | ||
| 44 | + $check = $pdo->prepare("SELECT id FROM user_device_bindings WHERE user_id = ? AND device_id = ?"); | ||
| 45 | + $check->execute([$user['id'], $deviceId]); | ||
| 46 | + | ||
| 47 | + if (!$check->fetch()) { | ||
| 48 | + $bind = $pdo->prepare("INSERT INTO user_device_bindings (user_id, device_id, is_primary, created_at) VALUES (?, ?, ?, NOW())"); | ||
| 49 | + $bind->execute([$user['id'], $deviceId, $isPrimary]); | ||
| 50 | + $message = "<div style='color: green; margin-bottom: 20px; background: #e6fffa; padding: 10px; border-radius: 4px;'>✅ 成功将设备 <b>$deviceId</b> 绑定给用户 <b>{$user['nickname']}</b></div>"; | ||
| 51 | + } else { | ||
| 52 | + $message = "<div style='color: orange; margin-bottom: 20px;'>⚠️ 该用户已经绑定过此设备,无需重复操作。</div>"; | ||
| 53 | + } | ||
| 50 | } else { | 54 | } else { |
| 51 | - $message = "<div style='color: orange; margin-bottom: 20px;'>⚠️ 该用户已经绑定过此设备,无需重复操作。</div>"; | 55 | + $message = "<div style='color: red; margin-bottom: 20px;'>❌ 手机号 <b>$phone</b> 未找到。请确保用户已在小程序登录过。</div>"; |
| 52 | } | 56 | } |
| 53 | - } else { | ||
| 54 | - $message = "<div style='color: red; margin-bottom: 20px;'>❌ 手机号 <b>$phone</b> 未找到。请确保用户已在小程序登录过。</div>"; | 57 | + } catch (Exception $e) { |
| 58 | + $message = "<div style='color: red;'>Operation Failed: " . $e->getMessage() . "</div>"; | ||
| 55 | } | 59 | } |
| 56 | } | 60 | } |
| 57 | } | 61 | } |
| 58 | 62 | ||
| 59 | // 获取设备列表 (Mock Data + Binding Count) | 63 | // 获取设备列表 (Mock Data + Binding Count) |
| 60 | -// 实际项目应从 Redis 或 devices 表获取在线状态,这里先从 bindings 表反查活跃情况 | ||
| 61 | -// 为了简化,我们列出 distinct device_id from bindings,或者列出 bindings | ||
| 62 | -$bindings = $pdo->query(" | ||
| 63 | - SELECT b.id, u.nickname, u.phone, b.device_id, b.is_primary, b.created_at | ||
| 64 | - FROM user_device_bindings b | ||
| 65 | - JOIN users u ON b.user_id = u.id | ||
| 66 | - ORDER BY b.created_at DESC | ||
| 67 | -")->fetchAll(PDO::FETCH_ASSOC); | 64 | +$bindings = []; |
| 65 | +if ($pdo) { | ||
| 66 | + try { | ||
| 67 | + $bindings = $pdo->query(" | ||
| 68 | + SELECT b.id, u.nickname, u.phone, b.device_id, b.is_primary, b.created_at | ||
| 69 | + FROM user_device_bindings b | ||
| 70 | + JOIN users u ON b.user_id = u.id | ||
| 71 | + ORDER BY b.created_at DESC | ||
| 72 | + ")->fetchAll(PDO::FETCH_ASSOC); | ||
| 73 | + } catch (Exception $e) { | ||
| 74 | + if (empty($message)) $message = "<div style='color: red;'>Load Error: " . $e->getMessage() . "</div>"; | ||
| 75 | + } | ||
| 76 | +} | ||
| 68 | 77 | ||
| 69 | ?> | 78 | ?> |
| 70 | <!DOCTYPE html> | 79 | <!DOCTYPE html> |
| 71 | <html lang="zh-CN"> | 80 | <html lang="zh-CN"> |
| 72 | - | ||
| 73 | <head> | 81 | <head> |
| 74 | <meta charset="UTF-8"> | 82 | <meta charset="UTF-8"> |
| 75 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 83 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 76 | <title>Moltbot 管理后台</title> | 84 | <title>Moltbot 管理后台</title> |
| 77 | <link rel="stylesheet" href="style.css"> | 85 | <link rel="stylesheet" href="style.css"> |
| 86 | + <link rel="icon" href="data:,"> | ||
| 78 | </head> | 87 | </head> |
| 79 | - | ||
| 80 | <body> | 88 | <body> |
| 81 | 89 | ||
| 82 | - <div class="header"> | ||
| 83 | - <div class="brand">Moltbot Admin</div> | ||
| 84 | - <div> | ||
| 85 | - <?php echo date('Y-m-d H:i'); ?> | ||
| 86 | - </div> | ||
| 87 | - </div> | ||
| 88 | - | ||
| 89 | - <div class="container"> | 90 | +<div class="header"> |
| 91 | + <div class="brand">Moltbot Admin</div> | ||
| 92 | + <div><?php echo date('Y-m-d H:i'); ?></div> | ||
| 93 | +</div> | ||
| 90 | 94 | ||
| 91 | - <?php echo $message; ?> | 95 | +<div class="container"> |
| 96 | + | ||
| 97 | + <?php echo $message; ?> | ||
| 92 | 98 | ||
| 93 | - <!-- 新增绑定卡片 --> | 99 | + <?php if (!$pdo): ?> |
| 94 | <div class="card"> | 100 | <div class="card"> |
| 95 | - <div class="title">新增绑定</div> | ||
| 96 | - <form method="POST" action=""> | ||
| 97 | - <input type="hidden" name="action" value="bind"> | ||
| 98 | - <div style="display: flex; gap: 20px;"> | ||
| 99 | - <div class="form-group" style="flex: 1;"> | ||
| 100 | - <label class="form-label">设备 ID</label> | ||
| 101 | - <input type="text" name="device_id" class="form-control" placeholder="例如: dev_test_001" | ||
| 102 | - required> | ||
| 103 | - </div> | ||
| 104 | - <div class="form-group" style="flex: 1;"> | ||
| 105 | - <label class="form-label">用户手机号</label> | ||
| 106 | - <input type="text" name="phone" class="form-control" placeholder="输入用户注册手机号" required> | ||
| 107 | - </div> | 101 | + <h3 style="color: red;">System Error</h3> |
| 102 | + <p>Cannot connect to the database. Please check your configuration.</p> | ||
| 103 | + </div> | ||
| 104 | + <?php else: ?> | ||
| 105 | + | ||
| 106 | + <!-- 新增绑定卡片 --> | ||
| 107 | + <div class="card"> | ||
| 108 | + <div class="title">新增绑定</div> | ||
| 109 | + <form method="POST" action=""> | ||
| 110 | + <input type="hidden" name="action" value="bind"> | ||
| 111 | + <div style="display: flex; gap: 20px;"> | ||
| 112 | + <div class="form-group" style="flex: 1;"> | ||
| 113 | + <label class="form-label">设备 ID</label> | ||
| 114 | + <input type="text" name="device_id" class="form-control" placeholder="例如: dev_test_001" required> | ||
| 108 | </div> | 115 | </div> |
| 109 | - <div class="form-group"> | ||
| 110 | - <label> | ||
| 111 | - <input type="checkbox" name="is_primary" value="1" checked> 设为主设备 (默认) | ||
| 112 | - </label> | 116 | + <div class="form-group" style="flex: 1;"> |
| 117 | + <label class="form-label">用户手机号</label> | ||
| 118 | + <input type="text" name="phone" class="form-control" placeholder="输入用户注册手机号" required> | ||
| 113 | </div> | 119 | </div> |
| 114 | - <button type="submit" class="btn btn-primary">立即绑定</button> | ||
| 115 | - </form> | ||
| 116 | - </div> | ||
| 117 | - | ||
| 118 | - <!-- 绑定列表卡片 --> | ||
| 119 | - <div class="card"> | ||
| 120 | - <div class="title">绑定记录 ( | ||
| 121 | - <?php echo count($bindings); ?>) | ||
| 122 | </div> | 120 | </div> |
| 123 | - <table> | ||
| 124 | - <thead> | ||
| 125 | - <tr> | ||
| 126 | - <th>用户</th> | ||
| 127 | - <th>手机号</th> | ||
| 128 | - <th>设备 ID</th> | ||
| 129 | - <th>主设备</th> | ||
| 130 | - <th>绑定时间</th> | ||
| 131 | - <th>操作</th> | ||
| 132 | - </tr> | ||
| 133 | - </thead> | ||
| 134 | - <tbody> | ||
| 135 | - <?php foreach ($bindings as $row): ?> | ||
| 136 | - <tr> | ||
| 137 | - <td> | ||
| 138 | - <?php echo htmlspecialchars($row['nickname']); ?> | ||
| 139 | - </td> | ||
| 140 | - <td> | ||
| 141 | - <?php echo htmlspecialchars($row['phone']); ?> | ||
| 142 | - </td> | ||
| 143 | - <td> | ||
| 144 | - <?php echo htmlspecialchars($row['device_id']); ?> | ||
| 145 | - </td> | ||
| 146 | - <td> | ||
| 147 | - <?php if ($row['is_primary']): ?> | ||
| 148 | - <span style="color: var(--primary-color);">✔</span> | ||
| 149 | - <?php endif; ?> | ||
| 150 | - </td> | ||
| 151 | - <td> | ||
| 152 | - <?php echo $row['created_at']; ?> | ||
| 153 | - </td> | ||
| 154 | - <td> | ||
| 155 | - <a href="#" style="color: red; font-size: 12px; text-decoration: none;">解绑</a> | ||
| 156 | - </td> | ||
| 157 | - </tr> | ||
| 158 | - <?php endforeach; ?> | ||
| 159 | - <?php if (empty($bindings)): ?> | ||
| 160 | - <tr> | ||
| 161 | - <td colspan="6" style="text-align: center; color: #999;">暂无数据</td> | ||
| 162 | - </tr> | ||
| 163 | - <?php endif; ?> | ||
| 164 | - </tbody> | ||
| 165 | - </table> | ||
| 166 | - </div> | 121 | + <div class="form-group"> |
| 122 | + <label> | ||
| 123 | + <input type="checkbox" name="is_primary" value="1" checked> 设为主设备 (默认) | ||
| 124 | + </label> | ||
| 125 | + </div> | ||
| 126 | + <button type="submit" class="btn btn-primary">立即绑定</button> | ||
| 127 | + </form> | ||
| 128 | + </div> | ||
| 167 | 129 | ||
| 130 | + <!-- 绑定列表卡片 --> | ||
| 131 | + <div class="card"> | ||
| 132 | + <div class="title">绑定记录 (<?php echo count($bindings); ?>)</div> | ||
| 133 | + <table> | ||
| 134 | + <thead> | ||
| 135 | + <tr> | ||
| 136 | + <th>用户</th> | ||
| 137 | + <th>手机号</th> | ||
| 138 | + <th>设备 ID</th> | ||
| 139 | + <th>主设备</th> | ||
| 140 | + <th>绑定时间</th> | ||
| 141 | + <th>操作</th> | ||
| 142 | + </tr> | ||
| 143 | + </thead> | ||
| 144 | + <tbody> | ||
| 145 | + <?php foreach ($bindings as $row): ?> | ||
| 146 | + <tr> | ||
| 147 | + <td><?php echo htmlspecialchars($row['nickname']); ?></td> | ||
| 148 | + <td><?php echo htmlspecialchars($row['phone']); ?></td> | ||
| 149 | + <td><?php echo htmlspecialchars($row['device_id']); ?></td> | ||
| 150 | + <td> | ||
| 151 | + <?php if ($row['is_primary']): ?> | ||
| 152 | + <span style="color: var(--primary-color);">✔</span> | ||
| 153 | + <?php endif; ?> | ||
| 154 | + </td> | ||
| 155 | + <td><?php echo $row['created_at']; ?></td> | ||
| 156 | + <td> | ||
| 157 | + <a href="#" style="color: red; font-size: 12px; text-decoration: none;">解绑</a> | ||
| 158 | + </td> | ||
| 159 | + </tr> | ||
| 160 | + <?php endforeach; ?> | ||
| 161 | + <?php if (empty($bindings)): ?> | ||
| 162 | + <tr><td colspan="6" style="text-align: center; color: #999;">暂无数据</td></tr> | ||
| 163 | + <?php endif; ?> | ||
| 164 | + </tbody> | ||
| 165 | + </table> | ||
| 168 | </div> | 166 | </div> |
| 167 | + | ||
| 168 | + <?php endif; ?> | ||
| 169 | 169 | ||
| 170 | -</body> | 170 | +</div> |
| 171 | 171 | ||
| 172 | +</body> | ||
| 172 | </html> | 173 | </html> |
Please
register
or
login
to post a comment