logo NodeSeekbeta

【开源】CDT-Monitor:阿里云 CDT 流量监控与自动化熔断工具。支持多账号管理、阈值自动停机(含节省模式)及抢占式实例保活。

  • php 少了个时区设置。导致服务器时区有问题的时候,定时任务因时区问题无法启动

  • <?php
    // 设置默认时区为中国时区
    date_default_timezone_set('Asia/Shanghai');

    头部加个这个,免得 date 获取的时间不稳定

  • @penode #122 额,用PHP方式部署在国内服务器没有这个问题,docker的话自动修正时区,抽空我加上

  • docker 部署的,运行正常image
    access的权限也给了的,有看到调用记录
    ~~为何流量没有,已经1个多小时了
    难道是域名用了cdn? ~~、

    刚开始有好几个小时的延迟

  • @fsfwzxxwf #124 docker部署的你直接用端口加ip看看正常不,看看是不是你CDN缓存策略问题

  • 给增加了个手动 开机 关机的功能。

    这样,不用的时候,可以一直关机。啥时候想要点一下就行

    unhUAuFweBnieDgVikGgtuK5s0sfCyor.webp

    am78lnndqqPaOAG8h4qpO2EIbeqFMebj.webp

    后端双重拦截:
    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;
        }
    
  • ai 辅助写的,效果非常不错,建议收入到改版中

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有72280位seeker

🎉欢迎新用户🎉