开发日志251211-15
date
Dec 11, 2025
slug
开发日志251211-15
status
Published
tags
AI
计算机
summary
type
Post
昨天研究了一下架构 … 发现要实现我的复杂功能的最好方法是
重构
有点恐怖
问了下AI 给出了以下架构:
multi-control-system/ # 项目根目录
├── backend/ # Node.js 后端服务(大脑)
│ ├── src/
│ │ ├── core/ # 核心调度模块
│ │ │ ├── Scheduler.js # 任务调度器(核心)
│ │ │ ├── DeviceManager.js # 设备管理器
│ │ │ └── TaskChainEngine.js # 任务链引擎
│ │ ├── adapters/ # 适配器层(对接不同执行端)
│ │ │ └── DeviceControlAdapter.js # 统一设备控制适配器
│ │ ├── services/ # 业务服务
│ │ │ ├── AIService.js # AI服务封装
│ │ │ └── StorageService.js # 存储服务
│ │ ├── websocket/ # WebSocket 服务
│ │ │ ├── WSServer.js # WebSocket 服务器
│ │ │ └── WSClient.js # 连接闭源WS的客户端
│ │ ├── routes/ # HTTP API 路由
│ │ └── app.js # Express 主应用
│ ├── package.json
│ └── server.js # 服务入口文件
│
├── frontend/ # Vue 3 前端控制台
│ ├── src/
│ │ ├── views/
│ │ │ ├── Dashboard.vue # 主控制台
│ │ │ ├── TaskEditor.vue # 任务链编辑器
│ │ │ └── DeviceMonitor.vue # 设备监控
│ │ ├── components/ # 通用组件
│ │ ├── stores/ # Pinia 状态管理
│ │ ├── api/ # API 封装
│ │ └── App.vue
│ ├── package.json
│ └── vite.config.js # Vite 配置
│
└── shared/ # 共享定义(前后端通用)
└── types.js # TypeScript/JS类型定义
🔧 核心设计要点他给出的代码:
你说得对,仅靠几个核心类确实无法运行。我将为你提供一个完整的、可立即运行的基础项目框架,包含所有必需的文件、配置和基础通信链路。
🚀 完整可运行代码
1. 后端完整配置
backend/package.json
{
"name": "multi-control-backend",
"version": "1.0.0",
"description": "群控系统后端",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"ws": "^8.14.0",
"socket.io": "^4.7.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"winston": "^3.11.0",
"bull": "^4.11.5",
"redis": "^4.6.10",
"uuid": "^9.0.0"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
backend/.env
NODE_ENV=development
PORT=3000
FRONTEND_URL=http://localhost:5173
DEVICE_CONTROL_WS_URL=ws://192.168.11.199:22221
REDIS_URL=redis://localhost:6379
backend/server.js - 程序入口
require('dotenv').config();
const app = require('./src/app');
const WSServer = require('./src/websocket/WSServer');
const WSClient = require('./src/websocket/WSClient');
const PORT = process.env.PORT || 3000;
// 启动HTTP服务器
const server = app.listen(PORT, () => {
console.log(`🚀 后端服务运行在: <http://localhost>:${PORT}`);
console.log(`📡 WebSocket端点: ws://localhost:${PORT}`);
});
// 启动WebSocket服务器(供前端连接)
const wsServer = new WSServer(server);
// 连接设备控制WebSocket(作为客户端)
const deviceControlWS = new WSClient(process.env.DEVICE_CONTROL_WS_URL);
deviceControlWS.connect();
// 优雅关闭
process.on('SIGTERM', () => {
console.log('收到关闭信号,优雅关闭服务...');
server.close(() => {
console.log('HTTP服务已关闭');
process.exit(0);
});
});
backend/src/app.js - Express主应用
const express = require('express');
const cors = require('cors');
const path = require('path');
const logger = require('./middleware/logger');
// 导入路由
const apiRoutes = require('./routes/api');
const taskChainRoutes = require('./routes/taskChains');
const app = express();
// 中间件
app.use(cors({
origin: process.env.FRONTEND_URL,
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(logger);
// 静态文件(可选,用于托管前端)
// app.use(express.static(path.join(__dirname, '../../frontend/dist')));
// 路由
app.use('/api', apiRoutes);
app.use('/api/task-chains', taskChainRoutes);
// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
service: 'multi-control-backend'
});
});
// 404处理
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
// 错误处理
app.use((err, req, res, next) => {
console.error('全局错误:', err.stack);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});
module.exports = app;
backend/src/websocket/WSServer.js - WebSocket服务器
const { Server } = require('socket.io');
const { v4: uuidv4 } = require('uuid');
const DeviceService = require('../services/DeviceService');
class WSServer {
constructor(httpServer) {
this.io = new Server(httpServer, {
cors: {
origin: process.env.FRONTEND_URL,
methods: ["GET", "POST"]
}
});
this.deviceService = new DeviceService();
this.clients = new Map(); // clientId -> socket
this.deviceSubscriptions = new Map(); // deviceId -> [clientId1, clientId2]
this.setupEventHandlers();
console.log('✅ WebSocket服务器已启动');
}
setupEventHandlers() {
this.io.on('connection', (socket) => {
const clientId = uuidv4();
this.clients.set(clientId, socket);
console.log(`🔗 客户端连接: ${clientId} (总计: ${this.clients.size})`);
// 客户端订阅设备状态
socket.on('subscribe_device', (deviceId) => {
this.subscribeToDevice(clientId, deviceId);
socket.emit('device_status', {
deviceId,
status: this.deviceService.getDeviceStatus(deviceId)
});
});
// 客户端发送控制指令
socket.on('control_device', async ({ deviceId, command, params }) => {
console.log(`📤 控制指令: ${deviceId} -> ${command}`);
const result = await this.deviceService.sendCommand(deviceId, command, params);
socket.emit('command_result', { deviceId, command, result });
});
// 客户端请求设备列表
socket.on('get_devices', () => {
const devices = this.deviceService.getAllDevices();
socket.emit('device_list', devices);
});
// 断开连接
socket.on('disconnect', () => {
this.clients.delete(clientId);
this.cleanupSubscriptions(clientId);
console.log(`🔌 客户端断开: ${clientId} (剩余: ${this.clients.size})`);
});
});
}
subscribeToDevice(clientId, deviceId) {
if (!this.deviceSubscriptions.has(deviceId)) {
this.deviceSubscriptions.set(deviceId, new Set());
}
this.deviceSubscriptions.get(deviceId).add(clientId);
}
// 向所有订阅了该设备的客户端推送状态更新
broadcastDeviceUpdate(deviceId, data) {
const subscribers = this.deviceSubscriptions.get(deviceId);
if (subscribers) {
subscribers.forEach(clientId => {
const socket = this.clients.get(clientId);
if (socket) {
socket.emit('device_update', { deviceId, ...data });
}
});
}
}
cleanupSubscriptions(clientId) {
for (const [deviceId, subscribers] of this.deviceSubscriptions) {
subscribers.delete(clientId);
if (subscribers.size === 0) {
this.deviceSubscriptions.delete(deviceId);
}
}
}
}
module.exports = WSServer;
backend/src/services/DeviceService.js - 设备管理服务
const EventEmitter = require('events');
class DeviceService extends EventEmitter {
constructor() {
super();
this.devices = new Map(); // deviceId -> deviceData
this.initializeMockDevices(); // 开发阶段用模拟数据
}
initializeMockDevices() {
// 模拟3台设备,实际应从数据库或设备控制WS获取
const mockDevices = [
{ id: 'device_001', name: '测试机1', status: 'idle', type: 'android', ip: '192.168.1.101' },
{ id: 'device_002', name: '测试机2', status: 'busy', type: 'android', ip: '192.168.1.102' },
{ id: 'device_003', name: '测试机3', status: 'offline', type: 'android', ip: '192.168.1.103' }
];
mockDevices.forEach(device => {
this.devices.set(device.id, {
...device,
lastSeen: new Date().toISOString(),
battery: Math.floor(Math.random() * 100),
currentTask: null
});
});
}
getAllDevices() {
return Array.from(this.devices.values());
}
getDeviceStatus(deviceId) {
const device = this.devices.get(deviceId);
return device ? device.status : 'not_found';
}
async sendCommand(deviceId, command, params = {}) {
const device = this.devices.get(deviceId);
if (!device) {
throw new Error(`设备不存在: ${deviceId}`);
}
// 这里应该调用实际的设备控制WS
console.log(`发送指令到设备 ${deviceId}: ${command}`, params);
// 模拟执行成功
const result = {
success: true,
command,
deviceId,
timestamp: new Date().toISOString(),
data: { mock: '这是模拟响应,实际应连接设备控制WS' }
};
// 更新设备状态
this.updateDeviceStatus(deviceId, 'busy', { currentCommand: command });
// 模拟延迟后返回空闲
setTimeout(() => {
this.updateDeviceStatus(deviceId, 'idle', { currentCommand: null });
}, 2000);
return result;
}
updateDeviceStatus(deviceId, status, extraData = {}) {
const device = this.devices.get(deviceId);
if (device) {
device.status = status;
device.lastSeen = new Date().toISOString();
Object.assign(device, extraData);
// 触发事件,让WebSocket服务器广播更新
this.emit('device_updated', { deviceId, status, device });
}
}
}
module.exports = DeviceService;
2. 前端完整配置
frontend/package.json
{
"name": "multi-control-frontend",
"version": "1.0.0",
"description": "群控系统前端",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.3.8",
"vue-router": "^4.2.5",
"pinia": "^2.1.7",
"socket.io-client": "^4.7.2",
"axios": "^1.6.2",
"element-plus": "^2.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.0",
"vite": "^5.0.0"
}
}
frontend/vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: '<http://localhost:3000>',
changeOrigin: true
},
'/ws': {
target: 'ws://localhost:3000',
ws: true
}
}
}
})
frontend/src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(ElementPlus)
app.use(router)
app.mount('#app')
frontend/src/App.vue
<template>
<div id="app">
<header class="app-header">
<h1>群控系统控制台</h1>
<div class="status-indicator">
<span :class="['status-dot', connectionStatus]"></span>
<span>{{ connectionText }}</span>
</div>
</header>
<main class="app-main">
<router-view />
</main>
<footer class="app-footer">
<p>设备总数: {{ deviceCount }} | 在线: {{ onlineCount }} | 版本: v1.0.0</p>
</footer>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useDeviceStore } from './stores/deviceStore'
const deviceStore = useDeviceStore()
const connectionStatus = ref('disconnected') // connected, connecting, disconnected
const connectionText = computed(() => {
const texts = {
connected: '已连接',
connecting: '连接中...',
disconnected: '未连接'
}
return texts[connectionStatus.value]
})
const deviceCount = computed(() => deviceStore.devices.length)
const onlineCount = computed(() =>
deviceStore.devices.filter(d => d.status === 'idle' || d.status === 'busy').length
)
onMounted(async () => {
connectionStatus.value = 'connecting'
try {
await deviceStore.connectWebSocket()
connectionStatus.value = 'connected'
await deviceStore.fetchDevices()
} catch (error) {
console.error('连接失败:', error)
connectionStatus.value = 'disconnected'
}
})
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
background: #f5f7fa;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
.app-header {
background: linear-gradient(135deg, #1e3c72, #2a2a68);
color: white;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.app-main {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
}
.app-footer {
background: #2d3748;
color: #a0aec0;
text-align: center;
padding: 0.75rem;
font-size: 0.875rem;
}
.status-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.status-dot.connected {
background-color: #48bb78;
box-shadow: 0 0 10px #48bb78;
}
.status-dot.connecting {
background-color: #ed8936;
box-shadow: 0 0 10px #ed8936;
}
.status-dot.disconnected {
background-color: #f56565;
box-shadow: 0 0 10px #f56565;
}
</style>
frontend/src/stores/deviceStore.js - Pinia状态管理
import { defineStore } from 'pinia'
import { io } from 'socket.io-client'
import { ref, computed } from 'vue'
export const useDeviceStore = defineStore('device', () => {
const devices = ref([])
const socket = ref(null)
const connected = ref(false)
const apiBaseUrl = import.meta.env.VITE_API_URL || '<http://localhost:3000>'
const wsUrl = import.meta.env.VITE_WS_URL || '<http://localhost:3000>'
// 连接WebSocket
async function connectWebSocket() {
return new Promise((resolve, reject) => {
socket.value = io(wsUrl, {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
})
socket.value.on('connect', () => {
connected.value = true
console.log('✅ WebSocket已连接')
resolve()
})
socket.value.on('connect_error', (error) => {
console.error('WebSocket连接错误:', error)
reject(error)
})
socket.value.on('device_list', (deviceList) => {
devices.value = deviceList
})
socket.value.on('device_update', (update) => {
const index = devices.value.findIndex(d => d.id === update.deviceId)
if (index !== -1) {
devices.value[index] = { ...devices.value[index], ...update }
}
})
socket.value.on('command_result', (result) => {
console.log('命令执行结果:', result)
// 可以在这里显示通知
})
})
}
// 获取设备列表
async function fetchDevices() {
if (socket.value?.connected) {
socket.value.emit('get_devices')
} else {
try {
const response = await fetch(`${apiBaseUrl}/api/devices`)
devices.value = await response.json()
} catch (error) {
console.error('获取设备列表失败:', error)
}
}
}
// 订阅设备状态
function subscribeToDevice(deviceId) {
if (socket.value?.connected) {
socket.value.emit('subscribe_device', deviceId)
}
}
// 发送控制指令
async function sendCommand(deviceId, command, params = {}) {
if (socket.value?.connected) {
socket.value.emit('control_device', { deviceId, command, params })
return true
} else {
try {
const response = await fetch(`${apiBaseUrl}/api/devices/${deviceId}/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command, params })
})
return await response.json()
} catch (error) {
console.error('发送命令失败:', error)
return { success: false, error: error.message }
}
}
}
// 按状态筛选设备
const idleDevices = computed(() =>
devices.value.filter(d => d.status === 'idle')
)
const busyDevices = computed(() =>
devices.value.filter(d => d.status === 'busy')
)
const offlineDevices = computed(() =>
devices.value.filter(d => d.status === 'offline')
)
return {
devices,
connected,
idleDevices,
busyDevices,
offlineDevices,
connectWebSocket,
fetchDevices,
subscribeToDevice,
sendCommand
}
})
frontend/src/views/Dashboard.vue - 主控制面板
<template>
<div class="dashboard">
<div class="dashboard-header">
<h2>设备控制面板</h2>
<div class="header-actions">
<el-button type="primary" @click="refreshDevices">刷新</el-button>
<el-button @click="showAddDevice = true">添加设备</el-button>
</div>
</div>
<div class="stats-cards">
<el-card class="stat-card">
<template #header>
<span>总设备数</span>
</template>
<div class="stat-value">{{ deviceStore.devices.length }}</div>
</el-card>
<el-card class="stat-card">
<template #header>
<span>在线设备</span>
</template>
<div class="stat-value">{{ deviceStore.idleDevices.length + deviceStore.busyDevices.length }}</div>
</el-card>
<el-card class="stat-card">
<template #header>
<span>空闲设备</span>
</template>
<div class="stat-value">{{ deviceStore.idleDevices.length }}</div>
</el-card>
<el-card class="stat-card">
<template #header>
<span>运行中</span>
</template>
<div class="stat-value">{{ deviceStore.busyDevices.length }}</div>
</el-card>
</div>
<div class="device-grid">
<DeviceCard
v-for="device in deviceStore.devices"
:key="device.id"
:device="device"
@command="handleDeviceCommand"
/>
</div>
<!-- 添加设备对话框 -->
<el-dialog v-model="showAddDevice" title="添加设备" width="400px">
<el-form :model="newDeviceForm">
<el-form-item label="设备ID">
<el-input v-model="newDeviceForm.id" placeholder="如: device_001" />
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="newDeviceForm.name" placeholder="如: 测试机1" />
</el-form-item>
<el-form-item label="设备IP">
<el-input v-model="newDeviceForm.ip" placeholder="如: 192.168.1.100" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDevice = false">取消</el-button>
<el-button type="primary" @click="addDevice">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useDeviceStore } from '../stores/deviceStore'
import DeviceCard from '../components/DeviceCard.vue'
const deviceStore = useDeviceStore()
const showAddDevice = ref(false)
const newDeviceForm = ref({
id: '',
name: '',
ip: ''
})
onMounted(() => {
deviceStore.fetchDevices()
})
function refreshDevices() {
deviceStore.fetchDevices()
}
function handleDeviceCommand(deviceId, command) {
deviceStore.sendCommand(deviceId, command)
}
function addDevice() {
// 这里应该调用API添加设备
console.log('添加设备:', newDeviceForm.value)
showAddDevice.value = false
newDeviceForm.value = { id: '', name: '', ip: '' }
}
</script>
<style scoped>
.dashboard {
padding: 1rem;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
text-align: center;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: #2d3748;
}
.device-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
</style>
🏃 如何运行这个完整系统
第一步:启动后端服务
cd backend
npm install
npm run dev
# 服务运行在: <http://localhost:3000>
# WebSocket运行在: ws://localhost:3000
第二步:启动前端服务
cd frontend
npm install
npm run dev
# 前端运行在: <http://localhost:5173>
第三步:访问系统
- 打开浏览器访问
http://localhost:5173
- 你会看到一个现代化的控制台界面
- 设备列表会显示3台模拟设备
- 可以点击设备卡片发送控制指令
🔧 如何添加实际功能
1. 连接真实的设备控制WS
修改
backend/src/websocket/WSClient.js:const WebSocket = require('ws');
class WSClient {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectInterval = 5000;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.on('open', () => {
console.log('✅ 已连接到设备控制服务器');
// 发送身份验证或初始化指令
this.send({ action: 'handshake', version: '1.0' });
});
this.ws.on('message', (data) => {
this.handleMessage(data);
});
this.ws.on('error', (error) => {
console.error('设备WS连接错误:', error);
});
this.ws.on('close', () => {
console.log('设备WS连接断开,尝试重连...');
setTimeout(() => this.connect(), this.reconnectInterval);
});
}
send(data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
handleMessage(data) {
try {
const message = JSON.parse(data);
console.log('收到设备消息:', message);
// 处理设备消息,转发给前端或更新设备状态
} catch (error) {
console.error('解析设备消息失败:', error);
}
}
}
2. 添加任务链引擎
创建
backend/src/core/TaskChainEngine.js:class TaskChainEngine {
constructor(stateRepository, deviceService) {
this.stateRepo = stateRepository;
this.deviceService = deviceService;
this.activeChains = new Map();
}
async executeChain(deviceId, chainConfig) {
const chainId = `chain_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const chain = {
id: chainId,
deviceId,
config: chainConfig,
currentStep: 0,
status: 'running',
results: [],
context: {}
};
this.activeChains.set(chainId, chain);
// 顺序执行每个步骤
for (let i = 0; i < chainConfig.steps.length; i++) {
const step = chainConfig.steps[i];
chain.currentStep = i;
// 检查步骤执行条件
if (step.condition && !this.evaluateCondition(step.condition, chain.context)) {
console.log(`步骤 ${step.name} 条件不满足,跳过`);
continue;
}
// 执行步骤
const result = await this.executeStep(deviceId, step, chain.context);
chain.results.push(result);
// 更新上下文
if (result.data) {
chain.context = { ...chain.context, ...result.data };
}
// 检查是否失败
if (!result.success && step.critical) {
chain.status = 'failed';
break;
}
// 步骤间延迟
if (step.delay) {
await this.sleep(step.delay);
}
}
chain.status = chain.status === 'running' ? 'completed' : chain.status;
this.activeChains.delete(chainId);
return chain;
}
async executeStep(deviceId, step, context) {
switch (step.type) {
case 'open_app':
return await this.deviceService.sendCommand(deviceId, 'open_app', {
packageName: step.params.packageName
});
case 'swipe_video':
return await this.deviceService.sendCommand(deviceId, 'swipe', {
direction: step.params.direction || 'up'
});
case 'extract_info':
return await this.deviceService.sendCommand(deviceId, 'extract_info', step.params);
case 'like_video':
return await this.deviceService.sendCommand(deviceId, 'like', step.params);
default:
return {
success: false,
error: `未知步骤类型: ${step.type}`
};
}
}
evaluateCondition(condition, context) {
// 简单的条件求值,可根据需要扩展
try {
// 例如: "context.videoInfo.likes > 100"
const expr = condition.replace(/context\\./g, 'context.');
// 实际实现需要更安全的求值器
return true; // 简化实现
} catch (error) {
console.error('条件求值错误:', error);
return false;
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
📈 下一步扩展建议
- 添加数据库:当设备超过20台时,考虑添加SQLite或PostgreSQL
- 实现任务链编辑器:基于之前提供的组件
- 添加用户认证:JWT token验证
- 完善日志系统:集成Winston进行结构化日志
- 添加监控面板:实时图表显示设备状态
这个完整框架已经包含了:
- ✅ 前后端完整项目结构
- ✅ WebSocket实时通信
- ✅ 设备状态管理
- ✅ 响应式前端界面
- ✅ 错误处理和日志
- ✅ 易于扩展的架构
你现在可以直接运行这个系统,然后在上面逐步添加你的业务逻辑。需要我详细解释某个特定部分,或者添加其他功能模块吗?
代码太多复杂了 我要慢慢啃 每行去做注释吧
1215
现在已经对系统的架构有了初步认知 经过一系列修改目前已经支持通过下面的路由发送单条指令:
socket.on('control_device', ({ deviceId, command, params }) => {)} 后面就是实现 任务调度器 和 任务执行器 两个模块了
任务调度器在收到前端的开始运行命令后决定执行哪个 任务链 任务执行器则 负责执行对应的任务链