news 2026/10/19 1:14:20

PHP+Autojs云控系统源码拆解:多设备自动化管理实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP+Autojs云控系统源码拆解:多设备自动化管理实践

去年因为项目需要,我要同时维护几十台安卓设备跑自动化任务,试了几家云控平台,要么按点位收费,要么闭源不好扩展。正好有人提到一套“PHP + Autojs”组合的开源云控系统框架源码,这个搭配第一眼确实有点违和——Autojs 平时更多被用来写个人自动化脚本,PHP 又常被吐槽性能一般。但真正把代码读完之后,我发现这个组合在中小规模设备管理场景里,反而是很务实的选择。

这篇文章不是去给你讲某个商业源码怎么部署,而是把我在拆解这类“PHP + Autojs”云控系统时总结的设计思路、通信链路、数据库结构、核心代码写法以及日常踩坑记录下来。只要你有 PHP 基础和一点 Autojs 经验,完全可以照着这份思路自己搭一套能跑通的多设备统一管理后台,把“定时巡检、批量脚本、结果上报、设备监控”这几件事做顺。如果你是刚接触安卓自动化开发,这篇文章也能帮你搞明白一台手机怎么和服务端说话、任务怎么被分发、脚本怎么在设备上按指令执行。

为什么要用“PHP + Autojs”这套组合来做云控

1.1 服务端选 PHP 的真实理由

很多做安卓自动化的团队,后端技术栈其实没有那么互联网大厂化,更多是从“某个管理系统”或“某个后台工具”演化过来的。PHP 在这个场景里最大的优势不是性能,而是上手成本低、生态齐全、部署简单。云控系统里 PHP 扮演的并不是高并发数据平面,而是控制平面——设备的注册、状态记录、任务下发、执行结果写入,这些都是低频但逻辑复杂的操作,PHP 完全接得住。

我拆源码时注意到,这套框架大量使用了 PHP 的数组与对象转换特性,接口返回 JSON 时基本上就是一段json_encode($data),非常直接。对于做这类系统的人来说,不用引入太重度的队列组件,把 MySQL 表设计好、接口写清楚,系统就能转起来。再说一点,PHP 8.x 以后性能相比旧版本提升明显,如果你现在还在用 5.x,云控这类定时轮询任务在设备规模几百台以下其实不太会构成瓶颈。我的建议是直接用 PHP 8.3,配合pdo_mysql、curl、mbstring这些扩展,开发效率和运行稳定性都够。

1.2 设备端用 Autojs 而不是原生 App 的考虑

如果你要管理一百台设备,最笨的办法是给每台设备装一个自己写的原生 Android App,但那样每改一次脚本逻辑就要重新打包、出包、装包、升级,落地成本会拖垮整个项目。Autojs 这类脚本框架的价值在于“免打包热更新”:服务器把 JavaScript 脚本内容下发到手机,本地保存后直接由脚本引擎执行。改一个界面上按钮的点击逻辑,只需要在后台改脚本内容,设备下次轮询任务时就能拉到新版本。

当然,Autojs 不是万能的。它依赖无障碍服务,部分 Android 版本上无障碍权限会被系统限制,需要手动放行或做相应适配。但比起原生开发和 APK 构建链,它至少让中小团队在“能用”和“投入产出比”之间找到了一个平衡点。实际源码里也是这样设计的:核心业务写在脚本里,后台只负责脚本的新旧版本切换和状态的记录,职责划分很清晰。

框架源码核心链路拆解:设备注册、心跳维持与任务下发

2.1 设备与服务器的通信关系

云控这个词听起来玄,但拆到最底层其实就是几个问题:设备怎么证明自己存在、设备怎么告诉服务器“我还活着”、服务器怎么把任务塞给设备、设备执行完怎么把结果交回来。源码里最常用的方案是长轮询:设备每隔几秒请求一次服务器,有没有新任务,有就带回去执行,没有就继续等着。

这套轮询机制选得很有讲究。PHP 配合长轮询非常自然,接口不需要单独维护连接,服务器也不需要对每台设备保持常驻 TCP 连接。设备请求接口后,如果当前没有任务,可以返回空数组;如果有任务,返回任务内容和脚本地址。相比 WebSocket,长轮询在“控制类、低频类”指令下发时有天然优势:服务端逻辑简单,客户端断线自动重连。对于设备数量几十台到两三百台的场景,服务器压力基本可以忽略不计。

2.2 数据库表结构设计思路

读这类源码,最先要看的永远是数据库表,因为表结构决定了一个系统的管理边界。最基本的几张表包括设备表、任务表、任务日志表和脚本版本表。

设备表至少要有:设备唯一标识、设备编号、Android 版本、Autojs 版本、最后心跳时间、在线状态、当前运行任务 ID。任务表至少要有:任务类型、任务参数(通常存 JSON)、关联脚本版本号、状态(等待中、执行中、已完成、失败)、创建时间和完成时间。脚本版本表则是记录脚本内容、版本号、灰度比例。

从实际经验看,字段不要一开始设计得太死。任务参数用 JSON 字段存是这套框架惯用的处理方式,好处是不同任务可以携带完全不同的参数结构,不用频繁改表结构。例如“点击屏幕”的任务参数可能是坐标;而“读取验证码”的任务参数可能是图片地址和识别类型。把这些放到一个 JSON 字段里,服务端解析的时候直接用 PHP 数组处理,非常方便。

2.3 脚本热更新和任务队列的取舍

很多源码在任务队列上不会选择 RabbitMQ 这类重量级组件,而是直接在 MySQL 里用状态字段来模拟队列。原因很简单,云控脚本任务并不是毫秒级金融交易,它本质上是延迟敏感度很低的操作,设备端执行一个点击脚本可能耗时 10 秒,服务端晚几秒返回任务完全无感。

我自己复刻这套逻辑时,任务队列表会加两个字段:next_run_time和retry_count。设备轮询时,只取status='waiting' AND next_run_time <= now()的任务,取到之后立刻把状态改成executing,避免多台设备同时轮询拿到同一个任务。设备执行失败回传时,再把状态改回waiting,同时增加retry_count,超过重试上限就把任务置为failed。这个方案没有引入额外中间件,但逻辑条例非常清楚。

自己动手跑通最小闭环:PHP 服务端 + Autojs 客户端

3.1 服务端接口设计与代码示例

先说接口设计。最小闭环至少需要四个接口:设备注册、心跳上报、拉取任务、回传结果。我写了一个精简版本,够你把整条链路跑通。

注册接口接收设备编号和系统版本,设备存在就直接更新最后上线时间,不存在则插入新记录。这里有个容易被忽略的点:设备编号最好由服务端注册后返回,不要依赖客户端传什么就是什么。因为后续所有授权、指令分发都以这个编号为准,一旦客户端随意伪造编号,整个后台的数据就乱了。

<?php // register.php 设备注册接口 header('Content-Type: application/json; charset=utf-8'); $device_id = $_POST['device_id'] ?? ''; $android_version = $_POST['android_version'] ?? ''; if ($device_id === '') { echo json_encode(['code' => 1, 'msg' => 'device_id required']); exit; } $pdo = new PDO('mysql:host=127.0.0.1;dbname=cloud_control;charset=utf8mb4', 'root', 'your_password'); $stmt = $pdo->prepare('SELECT id FROM devices WHERE device_id = ?'); $stmt->execute([$device_id]); $exists = $stmt->fetch(); if ($exists) { $stmt = $pdo->prepare('UPDATE devices SET last_online = NOW(), android_version = ? WHERE device_id = ?'); $stmt->execute([$android_version, $device_id]); } else { $stmt = $pdo->prepare('INSERT INTO devices (device_id, android_version, last_online) VALUES (?, ?, NOW())'); $stmt->execute([$device_id, $android_version]); } echo json_encode(['code' => 0, 'msg' => 'ok', 'device_id' => $device_id]);

任务拉取接口稍微复杂一点。设备端请求时需要携带自己的设备编号,服务端先去取一条未完成任务,然后立即把任务标记为执行中。返回内容里除了任务 ID,还需要把任务参数和脚本内容字段一起返回。这里涉及跨域,如果你是用网页版工具去调试接口,要记得加上跨域请求头,写法可以直接用header('Access-Control-Allow-Origin: *'),或者按 JSONP 方式处理,两种方案源码里都能看到。

<?php // poll_task.php 拉取任务接口 header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); $device_id = $_GET['device_id'] ?? ''; if ($device_id === '') { echo json_encode(['code' => 1, 'msg' => 'device_id required']); exit; } $pdo = new PDO('mysql:host=127.0.0.1;dbname=cloud_control;charset=utf8mb4', 'root', 'your_password'); $pdo->beginTransaction(); $stmt = $pdo->prepare('SELECT id, task_param, script_id FROM tasks WHERE status = "waiting" AND device_id = ? ORDER BY id ASC LIMIT 1 FOR UPDATE'); $stmt->execute([$device_id]); $task = $stmt->fetch(PDO::FETCH_ASSOC); if (!$task) { $pdo->commit(); echo json_encode(['code' => 0, 'msg' => 'no task', 'data' => []]); exit; } $stmt = $pdo->prepare('UPDATE tasks SET status = "executing", started_at = NOW() WHERE id = ?'); $stmt->execute([$task['id']]); $pdo->commit(); $stmt = $pdo->prepare('SELECT script_content, version FROM scripts WHERE id = ?'); $stmt->execute([$task['script_id']]); $script = $stmt->fetch(PDO::FETCH_ASSOC); echo json_encode([ 'code' => 0, 'data' => [ 'task_id' => $task['id'], 'task_param' => json_decode($task['task_param'], true), 'script_content' => $script['script_content'], 'script_version' => $script['version'], ] ]);

3.2 Autojs 客户端脚本编写

设备端的 Autojs 脚本,核心就是做三件事:启动时注册设备、定时拉取任务、执行完回传结果。下面的代码是一个可以直接跑的最小客户端。我自己把轮询间隔设成了 5 秒,太短会导致服务器日志刷得很频繁,太长又会让任务下发有延迟,你可以按实际设备数量调整。

// main.js Autojs 客户端 var server = 'http://192.168.1.100:8080'; // 改成你 PHP 服务端地址 var deviceId = 'device_001'; function httpGet(url) { var res = http.get(url); if (res.statusCode == 200) { return JSON.parse(res.body.string()); } return null; } function httpPost(url, params) { var res = http.post(url, params); if (res.statusCode == 200) { return JSON.parse(res.body.string()); } return null; } // 1. 注册设备 httpPost(server + '/register.php', { device_id: deviceId, android_version: device.getAndroidVersion() }); // 2. 定时拉取任务 setInterval(function () { var data = httpGet(server + '/poll_task.php?device_id=' + deviceId); if (data && data.data && data.data.task_id) { var taskId = data.data.task_id; try { var taskParam = data.data.task_param; var scriptContent = data.data.script_content; // 这里可以执行脚本内容,也可以直接按任务参数执行 if (taskParam.action == 'click') { click(taskParam.x, taskParam.y); } else if (taskParam.action == 'open_app') { app.launchApp(taskParam.app_name); } httpPost(server + '/report_result.php', { task_id: taskId, device_id: deviceId, status: 'success', result: '执行完成' }); } catch (e) { httpPost(server + '/report_result.php', { task_id: taskId, device_id: deviceId, status: 'failed', result: e.toString() }); } } }, 5000);

这只是一个骨架,真实场景里你大概率会需要:选中某个坐标区域点击、判断界面元素是否出现、把执行过程中截屏回传。但这些都属于 Autojs 本身的 API 用法,和云控框架关系不大,你可以按需往脚本内容里加。

3.3 用模拟器和 PHP 内置服务器做本地联调

如果手头没有大量真机,先用安卓模拟器跑通链路是完全可行的。模拟器的选择上,比较常见的思路是选那些对开发者友好、支持命令行启动和 adb 连接的方案,例如 Android Studio 自带的 AVD,或者一些轻量级模拟器。重点其实不在选哪个模拟器,而在于虚拟机或者模拟器能不能连通你本机的 PHP 服务。

如果你用的是 Android 模拟器,通常可以通过10.0.2.2访问宿主机;如果是独立的安卓虚拟机,则要把网卡模式配置成桥接或者「与宿主机共享网络」,让设备能直接访问到你电脑在局域网里的 IP。我一开始联调时,Autojs 脚本一直报超时,排查到最后发现是虚拟机网络隔离导致的,换了网卡模式之后请求就正常了。这里的要点是,PHP 侧可以先用php -S 0.0.0.0:8080起一个临时服务,省去配置 Nginx 的环节,让客户端和服务端在同一个局域网内跑通最小闭环后,再考虑正式部署。

常见问题与排查技巧实录

4.1 设备离线、心跳抖动怎么处理

设备离线是云控系统里最烦的问题。我拆源码时注意到,这类系统一般用“最后心跳时间”来判断在线状态,判断逻辑很简单:后台把每个设备最近的last_online拉出来,如果超过设定阈值(比如 15 秒),就标记为离线。问题在于,有些设备只是暂停执行长任务,或者因为省电策略休眠了,心跳间隔会突然拉长,但不代表设备本身不可控。

处理办法有两个层面。第一,客户端把心跳和任务拉取拆成两个独立循环,任务执行期间心跳照常上报,避免因为跑长任务被误判离线;第二,服务端不要把离线判断做得太激进,阈值建议设成心跳间隔的三倍以上。另外,Android 11 以及部分国产系统对后台运行限制比较严格,Autojs 的进程可能会被系统杀掉,这时候需要引导用户在系统设置里把脚本 APP 的后台运行权限和电池优化白名单加进去,这一条在你管理大量真实设备时几乎一定会遇到。

4.2 脚本执行崩溃、结果丢失的修复思路

客户端执行脚本过程中偶尔会崩溃,这是 Autojs 方案的常态,不用害怕。关键是崩溃之后的结果回传机制要设计好。我在代码里用try...catch把任何异常都捕获住,然后回传失败状态。但有些崩溃是进程级崩溃,连 catch 都没机会执行,这时候服务端就要有超时机制。

源码里常见的兜底思路是:任务表里加一个started_at字段,服务端每分钟跑一次扫描,发现status='executing'但已经超过 N 分钟没有回传结果的任务,就自动重置成waiting,或者把超时次数加一,超过上限就标记失败。这里很考验参数调整经验,任务超时时间不能设太短,否则一些真正需要跑几分钟的脚本会被反复重置;也不能设太长,否则故障任务堆积在“执行中”状态,影响后续任务下发。我个人的做法是先给每种任务类型配一个预估最长时间,然后乘以 1.5 作为超时阈值。

4.3 PHP 侧字符编码、序列化和调试工具

PHP 处理中文时最容易踩的两个坑:一个是接口返回的 JSON 里中文变成\uXXXX转义字符,另一个是写入数据库后中文变乱码。前者解决方法是json_encode($data, JSON_UNESCAPED_UNICODE),后者需要保证三处一致:数据库表字符集utf8mb4、PDO DSN 里加上charset=utf8mb4、HTTP 响应头设置application/json; charset=utf-8。这三处只要有一处不一致,就会在你的设备列表或任务日志里看到让人抓狂的乱码。

另外,PHP 在处理脚本内容序列化时,要注意别直接把转义的 JSON 字符串塞进数据库。我见过不少新手把json_encode之后的字符串再经过一次htmlspecialchars,存储到库里全是&quot;这种实体,Autojs 拿回去解析半天解析不出来。正确做法是:服务端在收到设备回传数据后,用json_decode($result, true)转成数组再处理;任务参数下发时,也是在 PHP 数组层面组装,最后一次性json_encode输出。

调试工具方面,不要只靠var_dump和print_r,在接口文件里临时输出调试信息没问题,但别忘了清理。我通常会在框架里加一个调试模式开关,开启后把每次请求的 SQL、参数、响应时间写入日志文件,关掉后生产环境就不会有泄漏风险。PHP 网站调试工具不止是日志,像 Xdebug 这种单步调试工具在排查“某个任务为什么反复执行”时的效率非常高。

4.4 这类自动化能力的使用边界

写了这么多,最后必须多说一句。PHP + Autojs 能实现的能力很强,但云控系统的使用场景一定要放在“自有设备”和“已授权设备”上。开发者的正规用途通常包括:自己手机的重复操作自动化、自动化测试、线下门店里设备巡检、个人工作效率工具。千万别把它用在未经授权的设备控制、撞库、批量注册、恶意引流这些方向上,技术本身没有善恶,但用不好容易给自己惹上大麻烦。所有自动化操作都建议留日志,谁在什么时间向哪台设备下发了什么任务,要做到可追溯。

框架源码还能往哪些方向扩展

5.1 任务结果收集后生成统计报表

最小闭环跑通后,你会发现最缺的不是功能,而是“数据怎么看”。任务执行成功后,结果字段里如果带了耗时、成功页面的关键文字、截图路径等,后台就可以做很多分析。我做过最常用的是把任务日志表里的执行结果按天汇总,用 PHP 写一个脚本在每天凌晨统计前一天的成功率、平均耗时、失败原因分布,然后把结果写入一张统计表。

这里有个取巧之处:不要把统计逻辑写到接口里,接口只负责写入明细,统计任务用 cron 跑。不然你的报表接口每次打开都会临时查大量日志,设备一多页面就卡。数据量大了以后,还可以考虑把报表接口和明细查询分开,列表页只显示聚合数据,点进去再看具体明细。

5.2 OCR 识别验证码和图片处理能力扩展

云控任务里经常遇到“识别验证码”的需求。很多框架源码会引入 OCR 库,但实现方式五花八门。我推荐的做法是:设备端只负责截屏或上传验证码图片,服务端用 PHP 调用 OCR 接口识别后,再把识别结果通过任务回传接口下发或记录下来。

PHP 做一个简单的图片上传接口并不复杂,关键是控制好上传大小和格式校验。我用过的场景里,验证码图片一般不超过 200KB,但设备截屏原图可能接近 1MB,所以要在接口里做压缩或裁剪。图片生成能力也很常用,比如给任务自动生成设备备注、统计报表导出的图片封面,PHP 的 GD 库或者 Imagick 都可以完成,这类能力放到云控后台里可以把一些人工操作进一步省掉。

5.3 适配更多设备形态:TV、模拟器、无头设备

云控系统管的不一定全是手机。安卓 TV、车载屏、收银机、无人售货机,只要跑的是 Android 系统,这套框架原则上都能管。区别在于设备端脚本里调用哪些 API,以及在注册表里标记设备类型。

我见过一个有意思的用法:把闲置的旧手机刷成“设备节点”,不配屏幕不插 SIM 卡,通过 adb 或者 Autojs 跑一些跑批任务,然后统一接到这个系统里做调度。这种“无头设备”模式的电量管理、异常重启、网络重连是更大的话题,但至少 PHP + Autojs 这套组合已经把入口打通了,你后续可以根据设备形态去做更细的适配。

我在实际拆解和复刻这类框架源码时,最大的体会是:不要被“云控”这个词吓到,拆到最后无非是心跳、任务、脚本、结果四个环节。PHP 做控制端,Autojs 做执行端,这种组合虽然不如 Golang + 原生 App 听起来“高级”,但它把开发成本、维护成本、改造成本都压到了极低。如果你也是一个人维护几十台安卓设备,这个方向绝对值得试一下。最后分享一个小技巧,设备端脚本里最好加一个“执行前检查网络”的逻辑,没有网络直接跳过本轮任务,不然每次断网重连后的第一个任务都会因为超时而失败,这个坑我调试了整整一下午才定位到。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 3:13:28

LobeChat搭配DeepSeek R1:部署团队AI推理工作台实战

简介&#xff1a;面向前端与大模型应用开发者&#xff0c;lobe-chat-deepseek r1 项目资源包将 DeepSeek R1 模型与 lobe-chat 对话框架相互集成&#xff0c;提供了一套结构完整、可直接运行并继续扩展的聊天应用工程。压缩包共包含 2000 个文件&#xff0c;整体大小 18.67MB。…

作者头像 李华
网站建设 2026/10/17 5:29:11

广电视频服务器安可迁移:开源模块选型与编译部署实战

广电行业做视频服务器的同学&#xff0c;最近一年听到最多的一个词应该就是“安可”。落到实际工作上&#xff0c;就是原来跑得很顺的转码服务器、收录服务器、流媒体分发服务器&#xff0c;都开始要考虑迁移到一套约束完全不同的国产化软硬件环境里。刚开始你可能觉得“不就是…

作者头像 李华
网站建设 2026/10/18 13:42:00

模板代码模块化:用IDEA格式化模板与Live Template固化工程规范

1. 模板代码为什么会成为工程里的隐形炸弹先说一个我自己的经历。前段时间整理一个内部项目X的工程结构&#xff0c;打开其中一个业务模块的Service实现&#xff0c;里面三百多行代码里有将近一半是这类东西&#xff1a;空指针判断、权限校验、异常包装、日志打印、参数校验、t…

作者头像 李华
网站建设 2026/10/17 5:15:40

C++后端面试算法题第十期:五道高频易错题全解析

第十期打卡来了。C后端面试算法题这个系列&#xff0c;到了这一期已经攒下不少素材。前九期把链表、二叉树、哈希、动态规划这些方向轮着刷了一遍&#xff0c;这期我没打算继续堆难题&#xff0c;反而挑了五道“高频、易错、可深挖”的题目&#xff1a;合并两个有序链表、无重复…

作者头像 李华
网站建设 2026/10/16 22:41:42

RAG最小闭环实战:从文档切分到知识库问答的完整记录

说是学习日记&#xff0c;但今天一整天基本都在跟"检索"这个词较劲。起因是上周接到一个需求&#xff1a;对方公司想把几百份内部文档变成能直接问答的知识库&#xff0c;而不是让人翻文件夹。我调研了一圈&#xff0c;决定趁周末把检索增强生成&#xff08;RAG&…

作者头像 李华
网站建设 2026/10/17 20:46:16

Spring Boot校园一卡通系统实战:从状态管理到并发控制

校园一卡通管理系统&#xff0c;可以说是校园信息化系统里最“接地气”的一类项目了。只要在学校待过&#xff0c;食堂刷卡、超市消费、门禁通行、图书馆借阅&#xff0c;背后都是这套系统在支撑。我做过的这套Spring Boot校园一卡通管理系统&#xff0c;技术上不算花哨&#x…

作者头像 李华