基于 Python+Flask+Vue3完成 iOS Web 端投屏互动控制平台
·
一个纯浏览器的 iOS 真机远程镜像 / 控制 / 运维平台。无需安装任何客户端,即可在 Windows / macOS / Linux 上运行。已适配 iOS 26。
文章目录
一、项目概述
WebAppFlaskauto-iOS 是一款基于 Python + Flask + Flask-SocketIO 后端、Vue 3 + Vite 前端的 Web 端 iOS 投屏互动控制平台。启动浏览器即可使用:
- 发现已连接到本机的 iPhone 真机
- 实时镜像设备屏幕(MJPEG 主路 + 可选 WebRTC 低延迟)
- 远程触控 / 滑动 / 文本输入 / 截图
- 浏览设备文件、安装卸载应用、运行 UI 自动化定位
- 查看实时系统日志、设备硬件信息
底层链路:
- pymobiledevice3(纯 Python)负责设备发现、usbmux 端口转发、WDA HTTP 通信
- WebDriverAgent (WDA) 负责实际的控制指令下发
- go-ios(可选,仅 iOS 17+)走用户态 RSD 隧道拉起 WDA,免管理员权限
✨ 重点适配:iOS 17+ 起 RSD 隧道在 Windows 上通需常要管理员权限,本项目通过内置的 go-ios 用户态隧道彻底绕开;已在 iOS 26.5 真机验证可用。

二、使用指南
1、环境准备
| 组件 | 版本要求 |
|---|---|
| Python | 3.10 及以上 |
| Node.js | 18 及以上(构建前端用) |
| iTunes / Apple Mobile Device Support | Windows 用户必装(提供 usbmux 驱动) |
| 浏览器 | Chrome / Edge / Safari 最新版 |
iOS 设备侧:
- iOS 17+ 需要在「设置 → 隐私与安全 → 开发者模式」打开开发者模式
- 首次插线信任本机
2、安装启动
# 1. 克隆仓库
git clone https://github.com/zhoujun94511/WebAppFlaskauto-iOS.git
cd WebAppFlaskauto-iOS
# 2. 创建 Python 虚拟环境
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# 3. 安装依赖
pip install -r requirements.txt
# 4. 复制环境变量模板(默认值即可启动)
cp .env.example .env
# 5. 安装并构建前端
cd frontend && npm install && npm run build && cd ..
# 6. 一键启动(自动拉起后端 :5001 + Vite 开发服务 :5173)
python start_dev.py
打开浏览器访问 http://localhost:5001/ 即可。默认超级管理员账号:
- 用户名:
superadmin - 密码:
superadmin123
3、连接 iPhone 并开始操作
- 用数据线插上 iPhone,在 iOS 端弹窗中信任本机
- 登录后,左侧导航点「设备」即可看到列表
- 点击某台设备的「连接」按钮,等待约 10-20 秒(首次启动 WDA 较慢)
- 连接成功后切到「单屏」模式,屏幕画面即时呈现,光环代表"在线 streaming"
- 鼠标点击 / 拖动 = 触控 / 滑动;右侧面板可切换控制 / 应用 / 文件 / 日志 / 自动化

三、功能特性

1、远程镜像与控制
- MJPEG 主路 + 截图轮询兜底:跨平台、稳定可靠
- 可选 WebRTC(aiortc):H264 优先、VP8 兜底,码率可调,宿主侧 libx264 软编
- 触控全覆盖:tap / swipe / long-press / double-tap / 文本输入 / 截图
- 系统按键:Home / 锁屏 / 音量 ± / 方向 D-pad(合成 WDA swipe)
- 无障碍快捷开关:辅助触控、旁白(VoiceOver)、缩放(经 go-ios)
2、单设备视图 / 多设备网格双模式
- 单设备模式:左侧设备信息卡 + 中央真机投屏 + 右侧可弹出的控制抽屉(控制 / 自动化 / 应用 / 文件 / 日志)
- 多设备网格:自动按可用宽度排版,每台设备进网格时自动降到 70% 分辨率减负
- 设备 streaming 中卡片自带双层蓝色光环(外层卡片描边 + 内层屏幕区域 pulsing 双线),视觉与 Android 端一致
3、设备占用(预约)机制
真机稀缺,多人争抢是日常痛点。本项目内置完整的预约系统:
- 任意普通用户可对未被占用的设备发起占用,自选时长(默认 30 分钟)
- 占用期间仅占用者可控制;其它用户只读可看不可点
- 三种释放路径统一:本人取消 / 管理员强制 / 到期清扫线程自动回收
- 数据库
device_id主键 + UNIQUE 约束解决并发抢占(败者得 IntegrityError 而非半成功)
4、UI 自动化定位
- 支持四种定位方式:accessibility id / predicate / xpath / class / name
- 内置 find / tap / type 三类动作
- 当前前台应用实时显示(bundleId + 进程名)
- 页面源码(XML)格式化展示 — 一键拉取整页 XML,自动缩进、可折叠
5、文件浏览器
- 可展开 / 收起的目录树
- 点击文件直接下载到本机
- 图片 / 音视频 / PDF / 文本类支持应用内预览(弹层)
- 拖拽或选取文件即可上传到设备目录(目标目录与当前展开节点联动)
6、应用管理
- 列举已安装应用(含 BundleId、版本、显示名)
- 启动 / 结束应用
- 安装
.ipa包(拖拽到面板或文件选择) - 卸载应用(带二次确认)
7、账号体系与权限
三级角色:
| 角色 | 权限 |
|---|---|
| 超级管理员(super_admin) | 全权限;管理所有用户的角色、邮箱、密码、启停状态 |
| 管理员(admin) | 管理普通用户、强制释放设备占用 |
| 普通用户(user) | 自助注册、自助占用 / 释放设备、控制已占用的设备 |
- 服务端会话(cookie + 内存)+ 登录闸门:未登录访问任何
/api/*都返回 401 - 登录失败限流:连续失败 5 次 60 秒锁定
- 管理面板(仅管理员可见):建号 / 改邮箱 / 重置密码 / 改角色 / 启停 / 删除

8、实时系统日志
- 后端用 go-ios syslog 拉设备实时日志,通过 SSE 推到前端
- 客户端支持正则 / 进程 / 级别三重过滤
- 手动启停按钮 —— 不看就停,避免空闲时长连接占用资源
9、国际化与多主题
- 三语:
zh-CN简体中文 /zh-TW繁體中文 /enEnglish - 切换语言时自动同步:
<html lang>+document.title(浏览器标签页)+ 所有界面文案 - 浅 / 深双主题 + 自动跟随系统
10、环境需求与常见问题
| 问题 | 排查方向 |
|---|---|
| 设备列表为空 | 检查 iTunes 是否装、数据线是否原装、设备是否信任本机 |
| 连接超时 / WDA 起不来 | 开发者模式有没开;首次启动 WDA 耗时较长(10-20 秒) |
Windows 报 wintun.dll 缺失 |
本仓库 resources/wintun/<arch>/ 已内置;go-ios 会自动加载 |
| WebRTC 看不到画面 | 检查 .env 里 IOS_ENABLE_WEBRTC=1;浏览器需要支持 H264 |
| 多人同时连接断流 | 看是否两个用户都拿了占用 — 设备占用机制限定同时仅一人可控 |
四、项目框架代码
1、项目框架
WebAppFlaskauto-iOS/
├── app.py # Flask + Socket.IO 入口;蓝图注册、登录闸门、SPA 托管
├── start_dev.py # 跨平台一键启动器(后端 5001 + Vite 5173)
├── requirements.txt
│
├── api/ # HTTP / Socket.IO 路由(薄适配层)
│ ├── auth_api.py # 登录 / 注册 / 会话 / 用户管理
│ ├── reservations_api.py # 设备占用 REST
│ ├── devices_api.py # 设备列表 / 连接 / 断开 / WDA 状态
│ ├── control_api.py # tap / swipe / input / 截图 / 启动 / 弹窗 / 剪贴板
│ ├── stream_api.py # 画面流启停 / 质量 / 分辨率
│ ├── files_api.py # 文件树 / 下载 / 上传 / 内联预览
│ ├── apps_api.py # 应用列举 / 安装 / 卸载
│ └── automation_api.py # WDA 选择器 find/tap/type
│
├── services/ # 业务逻辑层
│ ├── app_db.py # SQLite 连接、建表、预置账号
│ ├── auth_service.py # 口令哈希、会话令牌、角色
│ ├── reservation_service.py # 设备占用:claim / release / sweep
│ ├── device_info.py # lockdown 信息聚合
│ ├── stream_bridge.py # 画面帧 → Socket.IO
│ └── webrtc_bridge.py # aiortc 管线(JpegVideoTrack + 码率调优)
│
├── ios/ # iOS 平台适配
│ ├── ios_adapter.py # 统一门面(发现 / 连接 / 控制 / 流)
│ ├── go_ios.py # go-ios 封装(隧道 / runwda / fsync / apps)
│ ├── tunnel_manager.py # 用户态 RSD 隧道生命周期
│ ├── port_forward.py # usbmux 端口转发
│ ├── device_marketing.py # ProductType → 营销名映射(iPhone 15 Pro Max 等)
│ └── screen_provider/ # wda_mjpeg / wda_screenshot 插件
│
├── frontend/ # Vue 3 + Vite 前端
│ └── src/
│ ├── components/ # LoginView / DeviceStage / DeviceMatrix / ...
│ ├── composables/ # useAuth / useDevices / useWebRTC / useUiI18n ...
│ └── locales/ # zh-CN / zh-TW / en 三语 JSON
│
├── resources/
│ ├── executable/<os>/ # 内置 go-ios 二进制
│ └── wintun/<arch>/ # Windows 隧道所需 wintun.dll
│
└── tests/ # pytest 白盒测试
2、核心代码片段
(1)跨平台一键启动器
# start_dev.py(简化版)
import os, sys, signal, subprocess
def start_backend():
return subprocess.Popen(
[sys.executable, "app.py"],
env={**os.environ, "PORT": "5001"},
)
def start_frontend():
return subprocess.Popen(
["npm", "run", "dev"],
cwd="frontend",
shell=(os.name == "nt"),
)
if __name__ == "__main__":
backend = start_backend()
frontend = start_frontend()
print("[start_dev] Ctrl+C to stop both.")
try:
backend.wait()
finally:
frontend.terminate()
(2)IOSAdapter:连接 / 断开核心流程
# ios/ios_adapter.py(节选)
class IOSAdapter:
def connect(self, udid: str) -> None:
# 1) 起用户态隧道(iOS 17+ 才需要,免管理员)
self.tunnel_manager.ensure(udid)
# 2) 通过 go-ios runwda 拉起 WebDriverAgent
self.wda_launcher.start(udid)
# 3) usbmux 端口转发到 WDA HTTP(8100)和 MJPEG(9100)
self.port_forward.start(udid, 18100, 8100)
self.port_forward.start(f"{udid}#mjpeg", 19100, 9100)
# 4) HTTP probe,确认 WDA 起来了
self.wda_controller.wait_ready(udid)
def disconnect(self, udid: str) -> None:
# 关 WebRTC peer 在前,撕端口转发在后 —— 避免读线程拿到 ECONNRESET
with suppress(Exception):
self.webrtc.stop_device(udid)
self.stop_stream(udid)
# 关 WDA 进程 + 控制器
self.wda_launcher.stop(udid)
# 最后撕端口转发
self.port_forward.stop_forward(udid)
self.port_forward.stop_forward(f"{udid}#mjpeg")
(3)ProductType 友好名映射(节选)
# ios/device_marketing.py(节选)
MARKETING_NAMES: dict[str, str] = {
# iPhone 17 series (2025)
"iPhone18,1": "iPhone 17 Pro",
"iPhone18,2": "iPhone 17 Pro Max",
"iPhone18,3": "iPhone 17",
"iPhone18,4": "iPhone Air",
"iPhone18,5": "iPhone 17e",
# iPhone 16 series (2024)
"iPhone17,1": "iPhone 16 Pro",
"iPhone17,2": "iPhone 16 Pro Max",
# ... 一直覆盖到 2007 年原代 iPhone(63 条)
"iPhone1,1": "iPhone",
}
def marketing_name(product_type: str) -> str:
"""ProductType → 营销名;未知则原样返回 identifier。"""
return MARKETING_NAMES.get(product_type, product_type)
(4)项目架构时序图
浏览器 (Vue3 SPA)
│ HTTP /api/* Socket.IO(设备/画面/控制/日志) WebRTC(可选, 视频+控制 DataChannel)
▼ ▼ ▼
Flask + Flask-SocketIO (async_mode="threading")
│ api/ → services/ → ios/ios_adapter.py
▼
IOSAdapter
├─ DeviceManager pymobiledevice3:设备发现 / lockdown 信息 ┐ Flask 原生主路径
├─ PortForward pymobiledevice3 usbmux:本地端口 → WDA 8100/9100 │ (无需隧道)
├─ WDAController WebDriverAgent HTTP:tap/swipe/text/截图/应用/弹窗 ┘
├─ GoIOS + Tunnel go-ios:用户态 RSD 隧道(免管理员) + `runwda` ← iOS 17+ 拉起
├─ ScreenProvider wda_mjpeg(主) / wda_screenshot(兜底)
└─ StreamBridge 画面帧 → Socket.IO room=udid
WebRTCBridge → aiortc(JpegVideoTrack, 宿主侧 libx264)
▼
SQLite (账号 / 会话 / 设备占用)
五、结语
本项目的初衷是把 iOS 真机变成可调度、可审计、零客户端的共享资源。和市面常见的 Appium / tidevice 方案相比,主要区别在:
- 直接用 HTTP 与 WDA 通信 —— 不装 Appium Server,启动链路短;
- pymobiledevice3 + go-ios 双引擎互补 —— iOS 17+ 免管理员模式可真实运行;
- 业务层完备 —— 账号、占用、审计、面板一体,开箱即用;
后续规划:
- 流媒体输出适配
- 与 Android 姊妹项目 WebAppFlaskscrcpy 合并为统一跨平台运维台
如果觉得有用欢迎 Star ⭐:https://github.com/zhoujun94511/WebAppFlaskauto-iOS
有问题、想法或合作意向,欢迎 Issue 或评论区交流。
更多推荐
所有评论(0)