Commit e8094d15a5542c1822b834548c281895580fc072

Authored by 谭苏航
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