Commit 47820191eaa5b2806d86055eb0111b6b26425d20

Authored by 谭苏航
1 parent 2154352e

refactor: use phpdotenv for wechat config

Showing 3 changed files with 602 additions and 592 deletions
@@ -28,3 +28,7 @@ REDIS_PREFIX=ai_ @@ -28,3 +28,7 @@ REDIS_PREFIX=ai_
28 # 心跳配置 28 # 心跳配置
29 # HEARTBEAT_INTERVAL=30 29 # HEARTBEAT_INTERVAL=30
30 # HEARTBEAT_CHECK_INTERVAL=10 30 # HEARTBEAT_CHECK_INTERVAL=10
  31 +
  32 +# 微信小程序配置
  33 +WECHAT_APP_ID=wx6daaa8a7e2889a50
  34 +WECHAT_APP_SECRET=d394d5f0f82b328a2754d53027370282
@@ -7,8 +7,8 @@ @@ -7,8 +7,8 @@
7 return [ 7 return [
8 // 微信小程序配置 8 // 微信小程序配置
9 'wechat' => [ 9 'wechat' => [
10 - 'app_id' => getenv('WECHAT_APP_ID') ?: 'wx6daaa8a7e2889a50',  
11 - 'app_secret' => getenv('WECHAT_APP_SECRET') ?: 'd394d5f0f82b328a2754d53027370282', 10 + 'app_id' => getenv('WECHAT_APP_ID') ?: '',
  11 + 'app_secret' => getenv('WECHAT_APP_SECRET') ?: '',
12 ], 12 ],
13 13
14 // Session 配置 14 // Session 配置
1 -<?php  
2 -use Workerman\Worker;  
3 -use Workerman\Timer;  
4 -use Tos\TosClient;  
5 -use Tos\Exception\TosClientException;  
6 -use Tos\Exception\TosServerException;  
7 -use Tos\Model\PutObjectInput;  
8 -  
9 -require_once __DIR__ . '/vendor/autoload.php';  
10 -require_once __DIR__ . '/auth.php';  
11 -require_once __DIR__ . '/device.php';  
12 -  
13 -// 加载配置文件  
14 -$config = require __DIR__ . '/config.php';  
15 -  
16 -// Define Heartbeat Interval  
17 -define('HEARTBEAT_TIME', $config['heartbeat']['interval']);  
18 -  
19 -// Database Connection  
20 -$pdo = null;  
21 -try {  
22 - $db_config = $config['database'];  
23 - $dsn = "mysql:host={$db_config['host']};port={$db_config['port']};dbname={$db_config['database']};charset=utf8mb4";  
24 - $pdo = new PDO($dsn, $db_config['username'], $db_config['password'], [  
25 - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,  
26 - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC  
27 - ]);  
28 - echo " Connected to MySQL at {$db_config['host']}:{$db_config['port']}\n";  
29 -} catch (Exception $e) {  
30 - echo "⚠️ MySQL Connection Failed: " . $e->getMessage() . "\n";  
31 -}  
32 -  
33 -// Redis Connection  
34 -$redis = null;  
35 -try {  
36 - $redis_config = $config['redis'];  
37 -  
38 - $params = [  
39 - 'scheme' => 'tcp',  
40 - 'host' => $redis_config['host'],  
41 - 'port' => $redis_config['port'],  
42 - ];  
43 -  
44 - if (!empty($redis_config['password'])) {  
45 - $params['password'] = $redis_config['password'];  
46 - }  
47 -  
48 - // Predis Client  
49 - $redis = new Predis\Client($params, ['prefix' => $redis_config['prefix']]);  
50 - $redis->connect();  
51 - echo " Connected to Redis at {$redis_config['host']}:{$redis_config['port']} ({$redis_config['prefix']})\n";  
52 -} catch (Exception $e) {  
53 - echo "⚠️ Redis Connection Failed: " . $e->getMessage() . "\n";  
54 -}  
55 -  
56 -// Initialize Services  
57 -$authService = $pdo ? new AuthService($pdo, $config, $redis) : null;  
58 -$deviceService = $pdo ? new DeviceService($pdo, $config) : null;  
59 -  
60 -// Create a WebSocket worker  
61 -$ws_host = $config['server']['websocket_host'];  
62 -$ws_port = $config['server']['websocket_port'];  
63 -$ws_worker = new Worker("websocket://{$ws_host}:{$ws_port}");  
64 -  
65 -// Store connections (Memory for now, can move to Redis later)  
66 -$clients = []; // ClientID -> Connection  
67 -$devices = []; // DeviceID -> Connection  
68 -  
69 -$ws_worker->onWorkerStart = function ($worker) use ($config) {  
70 - $ws_host = $config['server']['websocket_host'];  
71 - $ws_port = $config['server']['websocket_port'];  
72 - echo "Relay Server Started on {$ws_host}:{$ws_port}\n";  
73 -  
74 - // Heartbeat check  
75 - $check_interval = $config['heartbeat']['check_interval'];  
76 - Timer::add($check_interval, function () use ($worker) {  
77 - $time_now = time();  
78 - foreach ($worker->connections as $connection) {  
79 - // Check if connection is alive possibly?  
80 - // Workerman handles basic disconnects, but we can enforce ping logic here if needed  
81 - }  
82 - });  
83 -};  
84 -  
85 -$ws_worker->onConnect = function ($connection) {  
86 - echo "New connection: " . $connection->id . "\n";  
87 - $connection->authVerified = false;  
88 -};  
89 -  
90 -$ws_worker->onMessage = function ($connection, $data) use (&$clients, &$devices, $authService, $deviceService) {  
91 - $msg = json_decode($data, true);  
92 - if (!$msg || !isset($msg['type'])) {  
93 - return;  
94 - }  
95 -  
96 - // 1. Authenticate / Register  
97 - if ($msg['type'] === 'register') {  
98 - if ($msg['role'] === 'device') {  
99 - // Device 注册 - 验证密钥  
100 - $deviceId = $msg['id'] ?? null;  
101 - $secret = $msg['secret'] ?? null;  
102 -  
103 - if (!$deviceId) {  
104 - $connection->send(json_encode(['type' => 'error', 'msg' => 'Device ID required']));  
105 - return;  
106 - }  
107 -  
108 - // 验证设备(如果有 deviceService)  
109 - if ($deviceService && $secret) {  
110 - if (!$deviceService->verifyDevice($deviceId, $secret)) {  
111 - $connection->send(json_encode(['type' => 'error', 'msg' => 'Invalid device credentials']));  
112 - $connection->close();  
113 - return;  
114 - }  
115 - // 更新设备状态为在线  
116 - $deviceService->updateDeviceStatus($deviceId, 'online');  
117 - }  
118 -  
119 - $devices[$deviceId] = $connection;  
120 - $connection->deviceId = $deviceId;  
121 - $connection->role = 'device';  
122 - $connection->authVerified = true;  
123 - $connection->send(json_encode(['type' => 'ack', 'status' => 'registered']));  
124 - echo "Device Registered: $deviceId\n";  
125 -  
126 - } elseif ($msg['role'] === 'client') {  
127 - // Mini Program 注册 - 验证 Token  
128 - $clientId = $msg['id'] ?? null;  
129 - $token = $msg['token'] ?? null;  
130 -  
131 - if (!$clientId) {  
132 - $connection->send(json_encode(['type' => 'error', 'msg' => 'Client ID required']));  
133 - return;  
134 - }  
135 -  
136 - // 验证 Token(如果有 authService 和 token)  
137 - $user = null;  
138 - if ($authService && $token) {  
139 - $user = $authService->verifyToken($token);  
140 - if (!$user) {  
141 - $connection->send(json_encode(['type' => 'error', 'msg' => 'Invalid or expired token']));  
142 - $connection->close();  
143 - return;  
144 - }  
145 - $connection->userId = $user['id'];  
146 - $connection->userPhone = $user['phone'];  
147 - }  
148 -  
149 - $clients[$clientId] = $connection;  
150 - $connection->clientId = $clientId;  
151 - $connection->role = 'client';  
152 - $connection->authVerified = true;  
153 - $connection->send(json_encode([  
154 - 'type' => 'ack',  
155 - 'status' => 'connected',  
156 - 'user' => $user  
157 - ]));  
158 - echo "Client Connected: $clientId" . ($user ? " (User: {$user['phone']})" : "") . "\n";  
159 - }  
160 - return;  
161 - }  
162 -  
163 - if (!$connection->authVerified) {  
164 - $connection->close();  
165 - return;  
166 - }  
167 -  
168 - // 2. Proxy Logic  
169 -  
170 - // Client -> Device  
171 - if ($msg['type'] === 'proxy' && $connection->role === 'client') {  
172 - $targetDeviceId = $msg['targetDeviceId'] ?? null;  
173 -  
174 - // 验证用户是否有权限访问该设备  
175 - if ($deviceService && isset($connection->userId)) {  
176 - if (!$deviceService->canAccessDevice($connection->userId, $targetDeviceId)) {  
177 - $connection->send(json_encode(['type' => 'error', 'msg' => 'No permission to access this device']));  
178 - return;  
179 - }  
180 - }  
181 -  
182 - if ($targetDeviceId && isset($devices[$targetDeviceId])) {  
183 - $payload = $msg['payload'];  
184 - // Wrap it so device knows who sent it  
185 - $forwardMsg = [  
186 - 'type' => 'cmd:execute',  
187 - 'fromClientId' => $connection->clientId,  
188 - 'fromUserId' => $connection->userId ?? null,  
189 - 'payload' => $payload  
190 - ];  
191 - $devices[$targetDeviceId]->send(json_encode($forwardMsg));  
192 - echo "Forwarded msg from Client {$connection->clientId} to Device {$targetDeviceId}\n";  
193 - } else {  
194 - $connection->send(json_encode(['type' => 'error', 'msg' => 'Device offline or not found']));  
195 - }  
196 - }  
197 -  
198 - // Device -> Client  
199 - if ($msg['type'] === 'proxy_response' && $connection->role === 'device') {  
200 - $targetClientId = $msg['targetClientId'] ?? null;  
201 - if ($targetClientId && isset($clients[$targetClientId])) {  
202 - $payload = $msg['payload'];  
203 - $forwardMsg = [  
204 - 'type' => 'response',  
205 - 'fromDeviceId' => $connection->deviceId,  
206 - 'payload' => $payload  
207 - ];  
208 - $clients[$targetClientId]->send(json_encode($forwardMsg));  
209 - echo "Forwarded response from Device {$connection->deviceId} to Client {$targetClientId}\n";  
210 - }  
211 - }  
212 -};  
213 -  
214 -$ws_worker->onClose = function ($connection) use (&$clients, &$devices, $deviceService) {  
215 - if (isset($connection->role)) {  
216 - if ($connection->role === 'device' && isset($connection->deviceId)) {  
217 - unset($devices[$connection->deviceId]);  
218 - // 更新设备状态为离线  
219 - if ($deviceService) {  
220 - $deviceService->updateDeviceStatus($connection->deviceId, 'offline');  
221 - }  
222 - echo "Device disconnected: {$connection->deviceId}\n";  
223 - } elseif ($connection->role === 'client' && isset($connection->clientId)) {  
224 - unset($clients[$connection->clientId]);  
225 - echo "Client disconnected: {$connection->clientId}\n";  
226 - }  
227 - }  
228 -};  
229 -  
230 -// ---------------------------------------------------------  
231 -// [New] HTTP Server for file uploads and static serving  
232 -// ---------------------------------------------------------  
233 -$http_host = $config['server']['http_host'];  
234 -$http_port = $config['server']['http_port'];  
235 -$http_worker = new Worker("http://{$http_host}:{$http_port}");  
236 -$http_worker->count = $config['server']['worker_count'];  
237 -$http_worker->onMessage = function ($connection, $request) use ($config, $authService, $deviceService) {  
238 - $path = $request->path();  
239 - $method = $request->method();  
240 -  
241 - // Helper: JSON Response  
242 - $jsonResponse = function ($data, $status = 200) use ($connection) {  
243 - $connection->send(new \Workerman\Protocols\Http\Response(  
244 - $status,  
245 - ['Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*'],  
246 - json_encode($data)  
247 - ));  
248 - };  
249 -  
250 - // CORS Preflight  
251 - if ($method === 'OPTIONS') {  
252 - $connection->send(new \Workerman\Protocols\Http\Response(200, [  
253 - 'Access-Control-Allow-Origin' => '*',  
254 - 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',  
255 - 'Access-Control-Allow-Headers' => 'Content-Type, Authorization',  
256 - ], ''));  
257 - return;  
258 - }  
259 -  
260 - // ==================== Auth API ====================  
261 -  
262 - // POST /api/auth/login - 微信登录  
263 - if ($path === '/api/auth/login' && $method === 'POST') {  
264 - if (!$authService) {  
265 - $jsonResponse(['ok' => false, 'error' => 'Auth service unavailable'], 500);  
266 - return;  
267 - }  
268 -  
269 - $body = json_decode($request->rawBody(), true) ?: [];  
270 - $code = $body['code'] ?? null;  
271 - $phone = $body['phone'] ?? null;  
272 - $phoneCode = $body['phoneCode'] ?? null;  
273 -  
274 - if (!$code) {  
275 - $jsonResponse(['ok' => false, 'error' => 'code is required'], 400);  
276 - return;  
277 - }  
278 -  
279 - $result = $authService->wechatLogin($code, $phone, $phoneCode);  
280 - $jsonResponse($result, $result['ok'] ? 200 : 400);  
281 - return;  
282 - }  
283 -  
284 - // POST /api/auth/verify - 验证 Token  
285 - if ($path === '/api/auth/verify' && $method === 'POST') {  
286 - if (!$authService) {  
287 - $jsonResponse(['ok' => false, 'error' => 'Auth service unavailable'], 500);  
288 - return;  
289 - }  
290 -  
291 - $body = json_decode($request->rawBody(), true) ?: [];  
292 - $token = $body['token'] ?? null;  
293 -  
294 - if (!$token) {  
295 - $jsonResponse(['ok' => false, 'error' => 'token is required'], 400);  
296 - return;  
297 - }  
298 -  
299 - $user = $authService->verifyToken($token);  
300 - if ($user) {  
301 - $jsonResponse(['ok' => true, 'user' => $user]);  
302 - } else {  
303 - $jsonResponse(['ok' => false, 'error' => 'Invalid or expired token'], 401);  
304 - }  
305 - return;  
306 - }  
307 -  
308 - // POST /api/auth/logout - 注销  
309 - if ($path === '/api/auth/logout' && $method === 'POST') {  
310 - if (!$authService) {  
311 - $jsonResponse(['ok' => false, 'error' => 'Auth service unavailable'], 500);  
312 - return;  
313 - }  
314 -  
315 - $body = json_decode($request->rawBody(), true) ?: [];  
316 - $token = $body['token'] ?? null;  
317 -  
318 - if ($token) {  
319 - $authService->logout($token);  
320 - }  
321 - $jsonResponse(['ok' => true]);  
322 - return;  
323 - }  
324 -  
325 - // ==================== Device API ====================  
326 -  
327 - // Helper: Get user from token  
328 - $getUser = function () use ($request, $authService) {  
329 - $authHeader = $request->header('Authorization');  
330 - if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {  
331 - return null;  
332 - }  
333 - $token = substr($authHeader, 7);  
334 - return $authService ? $authService->verifyToken($token) : null;  
335 - };  
336 -  
337 - // POST /api/device/bind - 绑定设备  
338 - if ($path === '/api/device/bind' && $method === 'POST') {  
339 - if (!$deviceService) {  
340 - $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);  
341 - return;  
342 - }  
343 -  
344 - $user = $getUser();  
345 - if (!$user) {  
346 - $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);  
347 - return;  
348 - }  
349 -  
350 - $body = json_decode($request->rawBody(), true) ?: [];  
351 - $deviceId = $body['deviceId'] ?? null;  
352 - $deviceSecret = $body['deviceSecret'] ?? null;  
353 -  
354 - if (!$deviceId) {  
355 - $jsonResponse(['ok' => false, 'error' => 'deviceId is required'], 400);  
356 - return;  
357 - }  
358 -  
359 - $result = $deviceService->bindDevice($user['id'], $deviceId, $deviceSecret);  
360 - $jsonResponse($result, $result['ok'] ? 200 : 400);  
361 - return;  
362 - }  
363 -  
364 - // POST /api/device/unbind - 解绑设备  
365 - if ($path === '/api/device/unbind' && $method === 'POST') {  
366 - if (!$deviceService) {  
367 - $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);  
368 - return;  
369 - }  
370 -  
371 - $user = $getUser();  
372 - if (!$user) {  
373 - $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);  
374 - return;  
375 - }  
376 -  
377 - $body = json_decode($request->rawBody(), true) ?: [];  
378 - $deviceId = $body['deviceId'] ?? null;  
379 -  
380 - if (!$deviceId) {  
381 - $jsonResponse(['ok' => false, 'error' => 'deviceId is required'], 400);  
382 - return;  
383 - }  
384 -  
385 - $result = $deviceService->unbindDevice($user['id'], $deviceId);  
386 - $jsonResponse($result, $result['ok'] ? 200 : 400);  
387 - return;  
388 - }  
389 -  
390 - // GET /api/device/list - 获取绑定的设备列表  
391 - if ($path === '/api/device/list' && $method === 'GET') {  
392 - if (!$deviceService) {  
393 - $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);  
394 - return;  
395 - }  
396 -  
397 - $user = $getUser();  
398 - if (!$user) {  
399 - $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);  
400 - return;  
401 - }  
402 -  
403 - $devices = $deviceService->getUserDevices($user['id']);  
404 - $jsonResponse(['ok' => true, 'devices' => $devices]);  
405 - return;  
406 - }  
407 -  
408 - // POST /api/device/primary - 设置主设备  
409 - if ($path === '/api/device/primary' && $method === 'POST') {  
410 - if (!$deviceService) {  
411 - $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);  
412 - return;  
413 - }  
414 -  
415 - $user = $getUser();  
416 - if (!$user) {  
417 - $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);  
418 - return;  
419 - }  
420 -  
421 - $body = json_decode($request->rawBody(), true) ?: [];  
422 - $deviceId = $body['deviceId'] ?? null;  
423 -  
424 - if (!$deviceId) {  
425 - $jsonResponse(['ok' => false, 'error' => 'deviceId is required'], 400);  
426 - return;  
427 - }  
428 -  
429 - $result = $deviceService->setPrimaryDevice($user['id'], $deviceId);  
430 - $jsonResponse($result, $result['ok'] ? 200 : 400);  
431 - return;  
432 - }  
433 -  
434 - // ==================== File API ====================  
435 -  
436 - // 1. Static File Serving (Simple implementation)  
437 - if (strpos($path, '/uploads/') === 0) {  
438 - $file = __DIR__ . $path;  
439 - if (is_file($file)) {  
440 - $connection->send(new \Workerman\Protocols\Http\Response(  
441 - 200,  
442 - ['Content-Type' => mime_content_type($file)],  
443 - file_get_contents($file)  
444 - ));  
445 - return;  
446 - }  
447 - }  
448 -  
449 - // 2. Upload Handler  
450 - if ($path === '/upload') {  
451 - $files = $request->file();  
452 -  
453 - if (empty($files['file'])) {  
454 - $connection->send(new \Workerman\Protocols\Http\Response(400, [], json_encode(['ok' => false, 'error' => 'No file'])));  
455 - return;  
456 - }  
457 -  
458 - $file = $files['file'];  
459 -  
460 - // Validate Size  
461 - $max_size = $config['upload']['max_size'];  
462 - if ($file['size'] > $max_size) {  
463 - $max_mb = round($max_size / 1024 / 1024);  
464 - $connection->send(new \Workerman\Protocols\Http\Response(400, [], json_encode(['ok' => false, 'error' => "File too large (Max {$max_mb}MB)"])));  
465 - return;  
466 - }  
467 -  
468 - // Validate Extension  
469 - $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));  
470 - $allowed = $config['upload']['allowed_extensions'];  
471 - if (!in_array($ext, $allowed)) {  
472 - $connection->send(new \Workerman\Protocols\Http\Response(400, [], json_encode(['ok' => false, 'error' => 'File type not allowed'])));  
473 - return;  
474 - }  
475 -  
476 - // TOS Configuration  
477 - $tos_config = $config['tos'];  
478 -  
479 - try {  
480 - $client = new TosClient([  
481 - 'region' => $tos_config['region'],  
482 - 'endpoint' => $tos_config['endpoint'],  
483 - 'ak' => $tos_config['ak'],  
484 - 'sk' => $tos_config['sk'],  
485 - ]);  
486 -  
487 - // Generate Key  
488 - $uuid = bin2hex(random_bytes(8));  
489 - $userPhone = $config['upload']['default_user'];  
490 - $objectKey = "clawdbot/{$userPhone}/{$uuid}.{$ext}";  
491 - $bucket = $tos_config['bucket'];  
492 -  
493 - // Read file content  
494 - $contentFn = fopen($file['tmp_name'], 'r');  
495 -  
496 - // Upload using Object Input  
497 - $input = new PutObjectInput($bucket, $objectKey, $contentFn);  
498 - $input->setACL('public-read');  
499 -  
500 - $client->putObject($input);  
501 -  
502 - if (is_resource($contentFn)) {  
503 - fclose($contentFn);  
504 - }  
505 -  
506 - // Generate URL  
507 - $url = "https://{$bucket}.{$tos_config['endpoint']}/{$objectKey}";  
508 -  
509 - $connection->send(json_encode([  
510 - 'ok' => true,  
511 - 'url' => $url  
512 - ]));  
513 -  
514 - } catch (Exception $e) {  
515 - $connection->send(new \Workerman\Protocols\Http\Response(500, [], json_encode(['ok' => false, 'error' => 'Upload failed: ' . $e->getMessage()])));  
516 - }  
517 - return;  
518 - }  
519 -  
520 - // 3. Generate Pre-Signed URL for Direct Upload  
521 - if (strpos($path, '/tos/sign') === 0) {  
522 - $query = $request->get();  
523 - $filename = $query['filename'] ?? 'file_' . time();  
524 - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));  
525 -  
526 - // Validation  
527 - $allowed = $config['upload']['allowed_extensions'];  
528 - if (!in_array($ext, $allowed) && $ext !== '') {  
529 - // If no extension providing, we might just allow it or fail.  
530 - // Ideally client should provide full filename with extension.  
531 - }  
532 -  
533 - // TOS Configuration  
534 - $tos_config = $config['tos'];  
535 -  
536 - try {  
537 - $client = new TosClient([  
538 - 'region' => $tos_config['region'],  
539 - 'endpoint' => $tos_config['endpoint'],  
540 - 'ak' => $tos_config['ak'],  
541 - 'sk' => $tos_config['sk'],  
542 - ]);  
543 -  
544 - $uuid = bin2hex(random_bytes(8));  
545 - $userPhone = $config['upload']['default_user'];  
546 - // Ensure filename is safe  
547 - $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);  
548 - if (!$safeName)  
549 - $safeName = 'unnamed';  
550 -  
551 - $objectKey = "clawdbot/{$userPhone}/direct_{$uuid}_{$safeName}";  
552 - $bucket = $tos_config['bucket'];  
553 -  
554 - // Generate Pre-Signed PUT URL (Valid for 15 mins)  
555 - // Note: The SDK method name might vary slightly based on version,  
556 - // but `preSignedURL` is standard for TOS PHP SDK v2.  
557 - $input = new \Tos\Model\PreSignedURLInput(  
558 - 'PUT',  
559 - $tos_config['bucket'],  
560 - $objectKey,  
561 - 300 // 5 minutes validity  
562 - );  
563 -  
564 - // Add content-type if known? client will send it.  
565 - // For simple PUT, we just sign the method and resource.  
566 -  
567 - $output = $client->preSignedURL($input);  
568 - $signedUrl = $output->getSignedUrl();  
569 -  
570 - // Public Access URL (Assuming bucket is public-read or we use signed Get URL)  
571 - // For this project, we used public-read ACL in previous code, so we assume public access.  
572 - $publicUrl = "https://{$tos_config['bucket']}.{$tos_config['endpoint']}/{$objectKey}";  
573 -  
574 - $connection->send(json_encode([  
575 - 'ok' => true,  
576 - 'uploadUrl' => $signedUrl,  
577 - 'publicUrl' => $publicUrl,  
578 - 'key' => $objectKey  
579 - ]));  
580 -  
581 - } catch (Exception $e) {  
582 - $connection->send(new \Workerman\Protocols\Http\Response(500, [], json_encode(['ok' => false, 'error' => $e->getMessage()])));  
583 - }  
584 - return;  
585 - }  
586 -  
587 - $connection->send("Moltbot Relay HTTP Server");  
588 -};  
589 -  
590 -Worker::runAll(); 1 +<?php
  2 +use Workerman\Worker;
  3 +use Workerman\Timer;
  4 +use Tos\TosClient;
  5 +use Tos\Exception\TosClientException;
  6 +use Tos\Exception\TosServerException;
  7 +use Tos\Model\PutObjectInput;
  8 +
  9 +require_once __DIR__ . '/vendor/autoload.php';
  10 +require_once __DIR__ . '/auth.php';
  11 +require_once __DIR__ . '/device.php';
  12 +
  13 +// Load .env
  14 +if (class_exists('Dotenv\Dotenv') && file_exists(__DIR__ . '/.env')) {
  15 + $dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
  16 + $dotenv->load();
  17 +}
  18 +
  19 +// 加载配置文件
  20 +$config = require __DIR__ . '/config.php';
  21 +
  22 +// Define Heartbeat Interval
  23 +define('HEARTBEAT_TIME', $config['heartbeat']['interval']);
  24 +
  25 +// Database Connection
  26 +$pdo = null;
  27 +try {
  28 + $db_config = $config['database'];
  29 + $dsn = "mysql:host={$db_config['host']};port={$db_config['port']};dbname={$db_config['database']};charset=utf8mb4";
  30 + $pdo = new PDO($dsn, $db_config['username'], $db_config['password'], [
  31 + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  32 + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
  33 + ]);
  34 + echo " Connected to MySQL at {$db_config['host']}:{$db_config['port']}\n";
  35 +} catch (Exception $e) {
  36 + echo "⚠️ MySQL Connection Failed: " . $e->getMessage() . "\n";
  37 +}
  38 +
  39 +// Redis Connection
  40 +$redis = null;
  41 +try {
  42 + $redis_config = $config['redis'];
  43 +
  44 + $params = [
  45 + 'scheme' => 'tcp',
  46 + 'host' => $redis_config['host'],
  47 + 'port' => $redis_config['port'],
  48 + ];
  49 +
  50 + if (!empty($redis_config['password'])) {
  51 + $params['password'] = $redis_config['password'];
  52 + }
  53 +
  54 + // Predis Client
  55 + $redis = new Predis\Client($params, ['prefix' => $redis_config['prefix']]);
  56 + $redis->connect();
  57 + echo " Connected to Redis at {$redis_config['host']}:{$redis_config['port']} ({$redis_config['prefix']})\n";
  58 +} catch (Exception $e) {
  59 + echo "⚠️ Redis Connection Failed: " . $e->getMessage() . "\n";
  60 +}
  61 +
  62 +// Initialize Services
  63 +$authService = $pdo ? new AuthService($pdo, $config, $redis) : null;
  64 +$deviceService = $pdo ? new DeviceService($pdo, $config) : null;
  65 +
  66 +// Create a WebSocket worker
  67 +$ws_host = $config['server']['websocket_host'];
  68 +$ws_port = $config['server']['websocket_port'];
  69 +$ws_worker = new Worker("websocket://{$ws_host}:{$ws_port}");
  70 +
  71 +// Store connections (Memory for now, can move to Redis later)
  72 +$clients = []; // ClientID -> Connection
  73 +$devices = []; // DeviceID -> Connection
  74 +
  75 +$ws_worker->onWorkerStart = function ($worker) use ($config) {
  76 + $ws_host = $config['server']['websocket_host'];
  77 + $ws_port = $config['server']['websocket_port'];
  78 + echo "Relay Server Started on {$ws_host}:{$ws_port}\n";
  79 +
  80 + // Heartbeat check
  81 + $check_interval = $config['heartbeat']['check_interval'];
  82 + Timer::add($check_interval, function () use ($worker) {
  83 + $time_now = time();
  84 + foreach ($worker->connections as $connection) {
  85 + // Check if connection is alive possibly?
  86 + // Workerman handles basic disconnects, but we can enforce ping logic here if needed
  87 + }
  88 + });
  89 +};
  90 +
  91 +$ws_worker->onConnect = function ($connection) {
  92 + echo "New connection: " . $connection->id . "\n";
  93 + $connection->authVerified = false;
  94 +};
  95 +
  96 +$ws_worker->onMessage = function ($connection, $data) use (&$clients, &$devices, $authService, $deviceService) {
  97 + $msg = json_decode($data, true);
  98 + if (!$msg || !isset($msg['type'])) {
  99 + return;
  100 + }
  101 +
  102 + // 1. Authenticate / Register
  103 + if ($msg['type'] === 'register') {
  104 + if ($msg['role'] === 'device') {
  105 + // Device 注册 - 验证密钥
  106 + $deviceId = $msg['id'] ?? null;
  107 + $secret = $msg['secret'] ?? null;
  108 +
  109 + if (!$deviceId) {
  110 + $connection->send(json_encode(['type' => 'error', 'msg' => 'Device ID required']));
  111 + return;
  112 + }
  113 +
  114 + // 验证设备(如果有 deviceService)
  115 + if ($deviceService && $secret) {
  116 + if (!$deviceService->verifyDevice($deviceId, $secret)) {
  117 + $connection->send(json_encode(['type' => 'error', 'msg' => 'Invalid device credentials']));
  118 + $connection->close();
  119 + return;
  120 + }
  121 + // 更新设备状态为在线
  122 + $deviceService->updateDeviceStatus($deviceId, 'online');
  123 + }
  124 +
  125 + $devices[$deviceId] = $connection;
  126 + $connection->deviceId = $deviceId;
  127 + $connection->role = 'device';
  128 + $connection->authVerified = true;
  129 + $connection->send(json_encode(['type' => 'ack', 'status' => 'registered']));
  130 + echo "Device Registered: $deviceId\n";
  131 +
  132 + } elseif ($msg['role'] === 'client') {
  133 + // Mini Program 注册 - 验证 Token
  134 + $clientId = $msg['id'] ?? null;
  135 + $token = $msg['token'] ?? null;
  136 +
  137 + if (!$clientId) {
  138 + $connection->send(json_encode(['type' => 'error', 'msg' => 'Client ID required']));
  139 + return;
  140 + }
  141 +
  142 + // 验证 Token(如果有 authService 和 token)
  143 + $user = null;
  144 + if ($authService && $token) {
  145 + $user = $authService->verifyToken($token);
  146 + if (!$user) {
  147 + $connection->send(json_encode(['type' => 'error', 'msg' => 'Invalid or expired token']));
  148 + $connection->close();
  149 + return;
  150 + }
  151 + $connection->userId = $user['id'];
  152 + $connection->userPhone = $user['phone'];
  153 + }
  154 +
  155 + $clients[$clientId] = $connection;
  156 + $connection->clientId = $clientId;
  157 + $connection->role = 'client';
  158 + $connection->authVerified = true;
  159 + $connection->send(json_encode([
  160 + 'type' => 'ack',
  161 + 'status' => 'connected',
  162 + 'user' => $user
  163 + ]));
  164 + echo "Client Connected: $clientId" . ($user ? " (User: {$user['phone']})" : "") . "\n";
  165 + }
  166 + return;
  167 + }
  168 +
  169 + if (!$connection->authVerified) {
  170 + $connection->close();
  171 + return;
  172 + }
  173 +
  174 + // 2. Proxy Logic
  175 +
  176 + // Client -> Device
  177 + if ($msg['type'] === 'proxy' && $connection->role === 'client') {
  178 + $targetDeviceId = $msg['targetDeviceId'] ?? null;
  179 +
  180 + // 验证用户是否有权限访问该设备
  181 + if ($deviceService && isset($connection->userId)) {
  182 + if (!$deviceService->canAccessDevice($connection->userId, $targetDeviceId)) {
  183 + $connection->send(json_encode(['type' => 'error', 'msg' => 'No permission to access this device']));
  184 + return;
  185 + }
  186 + }
  187 +
  188 + if ($targetDeviceId && isset($devices[$targetDeviceId])) {
  189 + $payload = $msg['payload'];
  190 + // Wrap it so device knows who sent it
  191 + $forwardMsg = [
  192 + 'type' => 'cmd:execute',
  193 + 'fromClientId' => $connection->clientId,
  194 + 'fromUserId' => $connection->userId ?? null,
  195 + 'payload' => $payload
  196 + ];
  197 + $devices[$targetDeviceId]->send(json_encode($forwardMsg));
  198 + echo "Forwarded msg from Client {$connection->clientId} to Device {$targetDeviceId}\n";
  199 + } else {
  200 + $connection->send(json_encode(['type' => 'error', 'msg' => 'Device offline or not found']));
  201 + }
  202 + }
  203 +
  204 + // Device -> Client
  205 + if ($msg['type'] === 'proxy_response' && $connection->role === 'device') {
  206 + $targetClientId = $msg['targetClientId'] ?? null;
  207 + if ($targetClientId && isset($clients[$targetClientId])) {
  208 + $payload = $msg['payload'];
  209 + $forwardMsg = [
  210 + 'type' => 'response',
  211 + 'fromDeviceId' => $connection->deviceId,
  212 + 'payload' => $payload
  213 + ];
  214 + $clients[$targetClientId]->send(json_encode($forwardMsg));
  215 + echo "Forwarded response from Device {$connection->deviceId} to Client {$targetClientId}\n";
  216 + }
  217 + }
  218 +};
  219 +
  220 +$ws_worker->onClose = function ($connection) use (&$clients, &$devices, $deviceService) {
  221 + if (isset($connection->role)) {
  222 + if ($connection->role === 'device' && isset($connection->deviceId)) {
  223 + unset($devices[$connection->deviceId]);
  224 + // 更新设备状态为离线
  225 + if ($deviceService) {
  226 + $deviceService->updateDeviceStatus($connection->deviceId, 'offline');
  227 + }
  228 + echo "Device disconnected: {$connection->deviceId}\n";
  229 + } elseif ($connection->role === 'client' && isset($connection->clientId)) {
  230 + unset($clients[$connection->clientId]);
  231 + echo "Client disconnected: {$connection->clientId}\n";
  232 + }
  233 + }
  234 +};
  235 +
  236 +// ---------------------------------------------------------
  237 +// [New] HTTP Server for file uploads and static serving
  238 +// ---------------------------------------------------------
  239 +$http_host = $config['server']['http_host'];
  240 +$http_port = $config['server']['http_port'];
  241 +$http_worker = new Worker("http://{$http_host}:{$http_port}");
  242 +$http_worker->count = $config['server']['worker_count'];
  243 +$http_worker->onMessage = function ($connection, $request) use ($config, $authService, $deviceService) {
  244 + $path = $request->path();
  245 + $method = $request->method();
  246 +
  247 + // Helper: JSON Response
  248 + $jsonResponse = function ($data, $status = 200) use ($connection) {
  249 + $connection->send(new \Workerman\Protocols\Http\Response(
  250 + $status,
  251 + ['Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*'],
  252 + json_encode($data)
  253 + ));
  254 + };
  255 +
  256 + // CORS Preflight
  257 + if ($method === 'OPTIONS') {
  258 + $connection->send(new \Workerman\Protocols\Http\Response(200, [
  259 + 'Access-Control-Allow-Origin' => '*',
  260 + 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
  261 + 'Access-Control-Allow-Headers' => 'Content-Type, Authorization',
  262 + ], ''));
  263 + return;
  264 + }
  265 +
  266 + // ==================== Auth API ====================
  267 +
  268 + // POST /api/auth/login - 微信登录
  269 + if ($path === '/api/auth/login' && $method === 'POST') {
  270 + if (!$authService) {
  271 + $jsonResponse(['ok' => false, 'error' => 'Auth service unavailable'], 500);
  272 + return;
  273 + }
  274 +
  275 + $body = json_decode($request->rawBody(), true) ?: [];
  276 + $code = $body['code'] ?? null;
  277 + $phone = $body['phone'] ?? null;
  278 + $phoneCode = $body['phoneCode'] ?? null;
  279 +
  280 + if (!$code) {
  281 + $jsonResponse(['ok' => false, 'error' => 'code is required'], 400);
  282 + return;
  283 + }
  284 +
  285 + $result = $authService->wechatLogin($code, $phone, $phoneCode);
  286 + $jsonResponse($result, $result['ok'] ? 200 : 400);
  287 + return;
  288 + }
  289 +
  290 + // POST /api/auth/verify - 验证 Token
  291 + if ($path === '/api/auth/verify' && $method === 'POST') {
  292 + if (!$authService) {
  293 + $jsonResponse(['ok' => false, 'error' => 'Auth service unavailable'], 500);
  294 + return;
  295 + }
  296 +
  297 + $body = json_decode($request->rawBody(), true) ?: [];
  298 + $token = $body['token'] ?? null;
  299 +
  300 + if (!$token) {
  301 + $jsonResponse(['ok' => false, 'error' => 'token is required'], 400);
  302 + return;
  303 + }
  304 +
  305 + $user = $authService->verifyToken($token);
  306 + if ($user) {
  307 + $jsonResponse(['ok' => true, 'user' => $user]);
  308 + } else {
  309 + $jsonResponse(['ok' => false, 'error' => 'Invalid or expired token'], 401);
  310 + }
  311 + return;
  312 + }
  313 +
  314 + // POST /api/auth/logout - 注销
  315 + if ($path === '/api/auth/logout' && $method === 'POST') {
  316 + if (!$authService) {
  317 + $jsonResponse(['ok' => false, 'error' => 'Auth service unavailable'], 500);
  318 + return;
  319 + }
  320 +
  321 + $body = json_decode($request->rawBody(), true) ?: [];
  322 + $token = $body['token'] ?? null;
  323 +
  324 + if ($token) {
  325 + $authService->logout($token);
  326 + }
  327 + $jsonResponse(['ok' => true]);
  328 + return;
  329 + }
  330 +
  331 + // ==================== Device API ====================
  332 +
  333 + // Helper: Get user from token
  334 + $getUser = function () use ($request, $authService) {
  335 + $authHeader = $request->header('Authorization');
  336 + if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {
  337 + return null;
  338 + }
  339 + $token = substr($authHeader, 7);
  340 + return $authService ? $authService->verifyToken($token) : null;
  341 + };
  342 +
  343 + // POST /api/device/bind - 绑定设备
  344 + if ($path === '/api/device/bind' && $method === 'POST') {
  345 + if (!$deviceService) {
  346 + $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);
  347 + return;
  348 + }
  349 +
  350 + $user = $getUser();
  351 + if (!$user) {
  352 + $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);
  353 + return;
  354 + }
  355 +
  356 + $body = json_decode($request->rawBody(), true) ?: [];
  357 + $deviceId = $body['deviceId'] ?? null;
  358 + $deviceSecret = $body['deviceSecret'] ?? null;
  359 +
  360 + if (!$deviceId) {
  361 + $jsonResponse(['ok' => false, 'error' => 'deviceId is required'], 400);
  362 + return;
  363 + }
  364 +
  365 + $result = $deviceService->bindDevice($user['id'], $deviceId, $deviceSecret);
  366 + $jsonResponse($result, $result['ok'] ? 200 : 400);
  367 + return;
  368 + }
  369 +
  370 + // POST /api/device/unbind - 解绑设备
  371 + if ($path === '/api/device/unbind' && $method === 'POST') {
  372 + if (!$deviceService) {
  373 + $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);
  374 + return;
  375 + }
  376 +
  377 + $user = $getUser();
  378 + if (!$user) {
  379 + $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);
  380 + return;
  381 + }
  382 +
  383 + $body = json_decode($request->rawBody(), true) ?: [];
  384 + $deviceId = $body['deviceId'] ?? null;
  385 +
  386 + if (!$deviceId) {
  387 + $jsonResponse(['ok' => false, 'error' => 'deviceId is required'], 400);
  388 + return;
  389 + }
  390 +
  391 + $result = $deviceService->unbindDevice($user['id'], $deviceId);
  392 + $jsonResponse($result, $result['ok'] ? 200 : 400);
  393 + return;
  394 + }
  395 +
  396 + // GET /api/device/list - 获取绑定的设备列表
  397 + if ($path === '/api/device/list' && $method === 'GET') {
  398 + if (!$deviceService) {
  399 + $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);
  400 + return;
  401 + }
  402 +
  403 + $user = $getUser();
  404 + if (!$user) {
  405 + $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);
  406 + return;
  407 + }
  408 +
  409 + $devices = $deviceService->getUserDevices($user['id']);
  410 + $jsonResponse(['ok' => true, 'devices' => $devices]);
  411 + return;
  412 + }
  413 +
  414 + // POST /api/device/primary - 设置主设备
  415 + if ($path === '/api/device/primary' && $method === 'POST') {
  416 + if (!$deviceService) {
  417 + $jsonResponse(['ok' => false, 'error' => 'Device service unavailable'], 500);
  418 + return;
  419 + }
  420 +
  421 + $user = $getUser();
  422 + if (!$user) {
  423 + $jsonResponse(['ok' => false, 'error' => 'Unauthorized'], 401);
  424 + return;
  425 + }
  426 +
  427 + $body = json_decode($request->rawBody(), true) ?: [];
  428 + $deviceId = $body['deviceId'] ?? null;
  429 +
  430 + if (!$deviceId) {
  431 + $jsonResponse(['ok' => false, 'error' => 'deviceId is required'], 400);
  432 + return;
  433 + }
  434 +
  435 + $result = $deviceService->setPrimaryDevice($user['id'], $deviceId);
  436 + $jsonResponse($result, $result['ok'] ? 200 : 400);
  437 + return;
  438 + }
  439 +
  440 + // ==================== File API ====================
  441 +
  442 + // 1. Static File Serving (Simple implementation)
  443 + if (strpos($path, '/uploads/') === 0) {
  444 + $file = __DIR__ . $path;
  445 + if (is_file($file)) {
  446 + $connection->send(new \Workerman\Protocols\Http\Response(
  447 + 200,
  448 + ['Content-Type' => mime_content_type($file)],
  449 + file_get_contents($file)
  450 + ));
  451 + return;
  452 + }
  453 + }
  454 +
  455 + // 2. Upload Handler
  456 + if ($path === '/upload') {
  457 + $files = $request->file();
  458 +
  459 + if (empty($files['file'])) {
  460 + $connection->send(new \Workerman\Protocols\Http\Response(400, [], json_encode(['ok' => false, 'error' => 'No file'])));
  461 + return;
  462 + }
  463 +
  464 + $file = $files['file'];
  465 +
  466 + // Validate Size
  467 + $max_size = $config['upload']['max_size'];
  468 + if ($file['size'] > $max_size) {
  469 + $max_mb = round($max_size / 1024 / 1024);
  470 + $connection->send(new \Workerman\Protocols\Http\Response(400, [], json_encode(['ok' => false, 'error' => "File too large (Max {$max_mb}MB)"])));
  471 + return;
  472 + }
  473 +
  474 + // Validate Extension
  475 + $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
  476 + $allowed = $config['upload']['allowed_extensions'];
  477 + if (!in_array($ext, $allowed)) {
  478 + $connection->send(new \Workerman\Protocols\Http\Response(400, [], json_encode(['ok' => false, 'error' => 'File type not allowed'])));
  479 + return;
  480 + }
  481 +
  482 + // TOS Configuration
  483 + $tos_config = $config['tos'];
  484 +
  485 + try {
  486 + $client = new TosClient([
  487 + 'region' => $tos_config['region'],
  488 + 'endpoint' => $tos_config['endpoint'],
  489 + 'ak' => $tos_config['ak'],
  490 + 'sk' => $tos_config['sk'],
  491 + ]);
  492 +
  493 + // Generate Key
  494 + $uuid = bin2hex(random_bytes(8));
  495 + $userPhone = $config['upload']['default_user'];
  496 + $objectKey = "clawdbot/{$userPhone}/{$uuid}.{$ext}";
  497 + $bucket = $tos_config['bucket'];
  498 +
  499 + // Read file content
  500 + $contentFn = fopen($file['tmp_name'], 'r');
  501 +
  502 + // Upload using Object Input
  503 + $input = new PutObjectInput($bucket, $objectKey, $contentFn);
  504 + $input->setACL('public-read');
  505 +
  506 + $client->putObject($input);
  507 +
  508 + if (is_resource($contentFn)) {
  509 + fclose($contentFn);
  510 + }
  511 +
  512 + // Generate URL
  513 + $url = "https://{$bucket}.{$tos_config['endpoint']}/{$objectKey}";
  514 +
  515 + $connection->send(json_encode([
  516 + 'ok' => true,
  517 + 'url' => $url
  518 + ]));
  519 +
  520 + } catch (Exception $e) {
  521 + $connection->send(new \Workerman\Protocols\Http\Response(500, [], json_encode(['ok' => false, 'error' => 'Upload failed: ' . $e->getMessage()])));
  522 + }
  523 + return;
  524 + }
  525 +
  526 + // 3. Generate Pre-Signed URL for Direct Upload
  527 + if (strpos($path, '/tos/sign') === 0) {
  528 + $query = $request->get();
  529 + $filename = $query['filename'] ?? 'file_' . time();
  530 + $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  531 +
  532 + // Validation
  533 + $allowed = $config['upload']['allowed_extensions'];
  534 + if (!in_array($ext, $allowed) && $ext !== '') {
  535 + // If no extension providing, we might just allow it or fail.
  536 + // Ideally client should provide full filename with extension.
  537 + }
  538 +
  539 + // TOS Configuration
  540 + $tos_config = $config['tos'];
  541 +
  542 + try {
  543 + $client = new TosClient([
  544 + 'region' => $tos_config['region'],
  545 + 'endpoint' => $tos_config['endpoint'],
  546 + 'ak' => $tos_config['ak'],
  547 + 'sk' => $tos_config['sk'],
  548 + ]);
  549 +
  550 + $uuid = bin2hex(random_bytes(8));
  551 + $userPhone = $config['upload']['default_user'];
  552 + // Ensure filename is safe
  553 + $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
  554 + if (!$safeName)
  555 + $safeName = 'unnamed';
  556 +
  557 + $objectKey = "clawdbot/{$userPhone}/direct_{$uuid}_{$safeName}";
  558 + $bucket = $tos_config['bucket'];
  559 +
  560 + // Generate Pre-Signed PUT URL (Valid for 15 mins)
  561 + // Note: The SDK method name might vary slightly based on version,
  562 + // but `preSignedURL` is standard for TOS PHP SDK v2.
  563 + $input = new \Tos\Model\PreSignedURLInput(
  564 + 'PUT',
  565 + $tos_config['bucket'],
  566 + $objectKey,
  567 + 300 // 5 minutes validity
  568 + );
  569 +
  570 + // Add content-type if known? client will send it.
  571 + // For simple PUT, we just sign the method and resource.
  572 +
  573 + $output = $client->preSignedURL($input);
  574 + $signedUrl = $output->getSignedUrl();
  575 +
  576 + // Public Access URL (Assuming bucket is public-read or we use signed Get URL)
  577 + // For this project, we used public-read ACL in previous code, so we assume public access.
  578 + $publicUrl = "https://{$tos_config['bucket']}.{$tos_config['endpoint']}/{$objectKey}";
  579 +
  580 + $connection->send(json_encode([
  581 + 'ok' => true,
  582 + 'uploadUrl' => $signedUrl,
  583 + 'publicUrl' => $publicUrl,
  584 + 'key' => $objectKey
  585 + ]));
  586 +
  587 + } catch (Exception $e) {
  588 + $connection->send(new \Workerman\Protocols\Http\Response(500, [], json_encode(['ok' => false, 'error' => $e->getMessage()])));
  589 + }
  590 + return;
  591 + }
  592 +
  593 + $connection->send("Moltbot Relay HTTP Server");
  594 +};
  595 +
  596 +Worker::runAll();
Please register or login to post a comment