给增加了个手动 开机 关机的功能。 这样,不用的时候,可以一直关机。啥时候想要点一下就行 后端双重拦截: Pending 拦截:只要状态是 Pending/Starting/Stopping,直接返回提示“请稍后刷新页面”,防止并发冲突。 保活拦截:如果开启了保活且尝试关机,返回“操作被拒绝”。 为了实现“手工控制服务器开关机”,我们需要对 前端 (HTML + Vue.js) 和 后端 (PHP) 同时进行修改。HTML 模板部分 请找到代码中 <!-- 替换原有的阈值显示区域 --> 附近的代码(大约在 <div class="mt-6 pt-6 border-t... 这一行开始),将原有的阈值显示块替换为以下代码。 这段代码增加了“手动控制”区域,并根据状态显示“开机”或“关机”按钮,同时包含加载动画。 <!-- 替换原有的阈值显示区域 --> <div class="mt-6 pt-6 border-t border-gray-200/40 flex flex-col gap-3"> <!-- 原有的阈值显示 (保留) --> <div class="flex justify-between items-center"> <span class="text-xs font-medium text-gray-400">当前阈值</span> <span class="text-xs font-medium text-gray-400">{{ item.threshold }}%</span> </div> <!-- 新增:手工控制区域 --> <div class="flex justify-between items-center pt-2"> <span class="text-xs font-medium text-gray-400">手动控制</span> <!-- 状态:运行中 -> 显示关机按钮 --> <button v-if="item.instanceStatus === 'Running'" @click="toggleInstance(item.id, 'Stop', index)" :disabled="item.isOperating" class="tap-effect px-3 py-1 text-xs font-bold text-red-500 bg-red-50 border border-red-100 rounded-lg hover:bg-red-100 hover:border-red-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1"> <svg v-if="!item.isOperating" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-3.5 h-3.5"> <path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1012.728 0M12 3v9" /> </svg> <svg v-else class="animate-spin h-3.5 w-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> 关机 </button> <!-- 状态:已停止 -> 显示开机按钮 --> <button v-else @click="toggleInstance(item.id, 'Start', index)" :disabled="item.isOperating" class="tap-effect px-3 py-1 text-xs font-bold text-green-600 bg-green-50 border border-green-100 rounded-lg hover:bg-green-100 hover:border-green-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1"> <svg v-if="!item.isOperating" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-3.5 h-3.5"> <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" /> </svg> <svg v-else class="animate-spin h-3.5 w-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> 开机 </button> </div> </div>
JavaScript 逻辑部分 在你的 <script> 标签内的 setup() 函数中,找到 const { createApp, ref, ... } = Vue; 这一行。 添加响应式变量:在 const refreshingMap = ref({}); 下方添加: const isOperatingMap = ref({}); // 用于追踪每个实例的操作状态 添加控制方法:在 const refreshSingle = ... 方法下方添加以下代码: const toggleInstance = async (id, action, index) => { // action 参数将是 'Start' 或 'Stop' const actionText = action === 'Start' ? '开机' : '关机'; if (!confirm(`确定要${actionText}该实例吗?`)) return; // 设置当前实例为“操作中”状态,禁用按钮并显示 Loading isOperatingMap.value[index] = true; try { const res = await fetch('index.php?action=control_instance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, action: action // 发送 Start 或 Stop }) }); const data = await res.json(); if (data.success) { // 乐观更新 UI:立即改变状态显示,无需等待下一次轮询 const targetItem = statusData.value.find(item => item.id === id); if (targetItem) { targetItem.instanceStatus = action === 'Start' ? 'Starting' : 'Stopping'; } } else { alert('操作失败: ' + (data.message || '未知错误')); } } catch (e) { console.error(e); alert('网络请求失败'); } finally { // 移除“操作中”状态 isOperatingMap.value[index] = false; } }; 暴露方法:在 return 语句中添加 isOperatingMap 和 toggleInstance: return { // ... 其他变量 ... isOperatingMap, toggleInstance, // ... 其他方法 ... }
后端修改 (PHP) 你需要修改 index.php 文件,添加对 control_instance 动作的处理逻辑。 请在 // ---------------- 需鉴权接口 ---------------- 这一行之后,if ($action === 'get_config') 之前,插入以下代码: // 新增:控制实例开关机 if ($action === 'control_instance') { $data = json_decode(file_get_contents('php://input'), true); $id = $data['id'] ?? 0; $actionType = $data['action'] ?? ''; // Start 或 Stop if (!$id || !$actionType) { echo json_encode(['success' => false, 'message' => '参数缺失']); exit; } try { // 调用 AliyunTrafficCheck 类中的方法 $result = $app->controlInstance($id, $actionType); if ($result === true) { echo json_encode(['success' => true, 'message' => '指令发送成功']); } else { echo json_encode(['success' => false, 'message' => $result]); } } catch (Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; }
核心逻辑补充 (AliyunTrafficCheck.php) 最后,你需要确保 AliyunTrafficCheck.php 类中有 controlInstance 方法。请在该文件中添加以下方法(建议放在 refreshAccount 方法附近): /** * 公共接口:控制实例开关机 * @param int $id 账户ID * @param string $action 'Start' 或 'Stop' */ public function controlInstance($id, $action) { // 1. 强制设置 JSON 响应头 header('Content-Type: application/json'); if ($this->initError) { echo json_encode(['success' => false, 'message' => "系统未初始化"]); exit; } // 2. 获取账户配置 $account = $this->configManager->getAccountById($id); if (!$account) { echo json_encode(['success' => false, 'message' => "账户配置未找到"]); exit; } // 3. 获取当前实例状态 (从数据库读取最新状态,确保实时性) $currentStatus = $account['instance_status'] ?? 'Unknown'; // --- 拦截逻辑 1: 状态冲突拦截 (Pending/Starting/Stopping) --- // 防止在状态变更中重复发送指令 $transientStates = ['Pending', 'Starting', 'Stopping']; if (in_array($currentStatus, $transientStates)) { echo json_encode([ 'success' => false, 'message' => "实例状态更新中 ({$currentStatus}),请稍后刷新页面查看最新状态,不要重复操作。" ]); exit; } // --- 拦截逻辑 2: 保活模式拦截 --- // 如果开启了“实例保活”,且用户尝试关机,则拒绝操作 $keepAlive = $this->configManager->get('keep_alive', '0') === '1'; if ($keepAlive && strtolower($action) === 'stop') { $this->db->addLog('warning', "拒绝手动关机请求 [{$account['access_key_id']}]: 实例保活功能已开启"); echo json_encode([ 'success' => false, 'message' => "操作被拒绝:当前开启了“实例保活”模式,不允许手动关机。" ]); exit; } // 4. 获取关机模式配置 (仅 Stop 时有效) $shutdownMode = $this->configManager->get('shutdown_mode', 'KeepCharging'); // 5. 调用内部安全方法执行操作 $result = $this->safeControlInstance($account, strtolower($action), $shutdownMode); if ($result === true) { $this->db->addLog('info', "手动控制实例 [{$account['access_key_id']}] 执行: {$action}"); echo json_encode(['success' => true, 'message' => '指令发送成功']); } else { echo json_encode(['success' => false, 'message' => "操作执行失败: " . $result]); } exit; }
php 少了个时区设置。导致服务器时区有问题的时候,定时任务因时区问题无法启动
<?php
// 设置默认时区为中国时区
date_default_timezone_set('Asia/Shanghai');
头部加个这个,免得 date 获取的时间不稳定
@penode #122 额,用PHP方式部署在国内服务器没有这个问题,docker的话自动修正时区,抽空我加上
docker 部署的,运行正常
access的权限也给了的,有看到调用记录
~~为何流量没有,已经1个多小时了
难道是域名用了cdn? ~~、
刚开始有好几个小时的延迟
@fsfwzxxwf #124 docker部署的你直接用端口加ip看看正常不,看看是不是你CDN缓存策略问题
给增加了个手动 开机 关机的功能。
这样,不用的时候,可以一直关机。啥时候想要点一下就行
后端双重拦截:
Pending 拦截:只要状态是 Pending/Starting/Stopping,直接返回提示“请稍后刷新页面”,防止并发冲突。
保活拦截:如果开启了保活且尝试关机,返回“操作被拒绝”。
为了实现“手工控制服务器开关机”,我们需要对 前端 (HTML + Vue.js) 和 后端 (PHP) 同时进行修改。HTML 模板部分
请找到代码中 <!-- 替换原有的阈值显示区域 --> 附近的代码(大约在 <div class="mt-6 pt-6 border-t... 这一行开始),将原有的阈值显示块替换为以下代码。
这段代码增加了“手动控制”区域,并根据状态显示“开机”或“关机”按钮,同时包含加载动画。
JavaScript 逻辑部分
在你的 <script> 标签内的 setup() 函数中,找到 const { createApp, ref, ... } = Vue; 这一行。
添加响应式变量:在 const refreshingMap = ref({}); 下方添加:
添加控制方法:在 const refreshSingle = ... 方法下方添加以下代码:
暴露方法:在 return 语句中添加 isOperatingMap 和 toggleInstance:
后端修改 (PHP)
你需要修改 index.php 文件,添加对 control_instance 动作的处理逻辑。
请在 // ---------------- 需鉴权接口 ---------------- 这一行之后,if ($action === 'get_config') 之前,插入以下代码:
核心逻辑补充 (AliyunTrafficCheck.php)
最后,你需要确保 AliyunTrafficCheck.php 类中有 controlInstance 方法。请在该文件中添加以下方法(建议放在 refreshAccount 方法附近):
ai 辅助写的,效果非常不错,建议收入到改版中