368 lines
9.6 KiB
Markdown
368 lines
9.6 KiB
Markdown
|
|
# 屏幕流监控功能实现方案
|
|||
|
|
|
|||
|
|
## 一、功能概述
|
|||
|
|
|
|||
|
|
基于 UniApp 开发的安卓 App,实现局域网内后台按需调取学习端屏幕画面的功能。
|
|||
|
|
|
|||
|
|
### 核心需求
|
|||
|
|
1. **按需启动**:后台管理端点击特定学生账号后,触发该账号绑定的安卓 App 启动屏幕捕获
|
|||
|
|
2. **实时传输**:将学习画面实时传输到后台
|
|||
|
|
3. **资源节省**:不点击时 App 不进行任何捕获和传输操作
|
|||
|
|
4. **局域网传输**:全程在局域网内传输,不依赖外网
|
|||
|
|
5. **权限兼容**:适配安卓 10+ 版本,处理好屏幕捕获权限、后台保活
|
|||
|
|
|
|||
|
|
## 二、技术方案
|
|||
|
|
|
|||
|
|
### 2.1 架构设计
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────┐ WebSocket ┌─────────────────┐
|
|||
|
|
│ 学生端 App │ ◄─────────────────────────► │ 后台管理端 │
|
|||
|
|
│ (UniApp) │ │ (RuoYi) │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ - 屏幕捕获 │ │ - 指令控制 │
|
|||
|
|
│ - WebSocket │ │ - 画面显示 │
|
|||
|
|
│ - 按需启动 │ │ - 学生管理 │
|
|||
|
|
└─────────────────┘ └─────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 技术选型
|
|||
|
|
|
|||
|
|
**前端(UniApp):**
|
|||
|
|
- 屏幕捕获:使用 `plus.screen.capture` API(安卓原生)
|
|||
|
|
- 通信协议:WebSocket(实时双向通信)
|
|||
|
|
- 图片编码:Base64(便于 WebSocket 传输)
|
|||
|
|
- 后台保活:使用 `FOREGROUND_SERVICE` 权限
|
|||
|
|
|
|||
|
|
**后端(RuoYi):**
|
|||
|
|
- WebSocket 服务器:Spring WebSocket
|
|||
|
|
- 控制接口:RESTful API
|
|||
|
|
- 画面转发:WebSocket 消息转发
|
|||
|
|
|
|||
|
|
### 2.3 数据流程
|
|||
|
|
|
|||
|
|
1. **启动监控流程**:
|
|||
|
|
```
|
|||
|
|
后台点击学生 → 调用控制接口 → 发送 WebSocket 指令 → 学生端接收指令 → 开始屏幕捕获 → 传输画面
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **停止监控流程**:
|
|||
|
|
```
|
|||
|
|
后台点击停止 → 调用控制接口 → 发送 WebSocket 指令 → 学生端接收指令 → 停止屏幕捕获
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **画面传输流程**:
|
|||
|
|
```
|
|||
|
|
学生端捕获屏幕 → Base64 编码 → WebSocket 发送 → 后台接收 → 解码显示
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 三、实现细节
|
|||
|
|
|
|||
|
|
### 3.1 学生端实现(UniApp)
|
|||
|
|
|
|||
|
|
#### 3.1.1 屏幕流捕获工具类 (`screenStream.js`)
|
|||
|
|
|
|||
|
|
**核心功能:**
|
|||
|
|
- WebSocket 连接管理
|
|||
|
|
- 接收后台指令(启动/停止)
|
|||
|
|
- 屏幕捕获和传输
|
|||
|
|
- 自动重连机制
|
|||
|
|
- 后台保活
|
|||
|
|
|
|||
|
|
**关键代码:**
|
|||
|
|
```javascript
|
|||
|
|
// 连接 WebSocket
|
|||
|
|
connect() {
|
|||
|
|
const wsUrl = this.getWebSocketUrl()
|
|||
|
|
this.ws = uni.connectSocket({
|
|||
|
|
url: wsUrl,
|
|||
|
|
header: {
|
|||
|
|
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 开始捕获
|
|||
|
|
startCapture(interval = 500) {
|
|||
|
|
this.isCapturing = true
|
|||
|
|
this.captureTimer = setInterval(() => {
|
|||
|
|
this.captureAndSend()
|
|||
|
|
}, interval)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 捕获并发送
|
|||
|
|
async captureAndSend() {
|
|||
|
|
const screenshotData = await this.captureScreen()
|
|||
|
|
this.sendMessage({
|
|||
|
|
type: 'screen_frame',
|
|||
|
|
userId: this.userId,
|
|||
|
|
data: screenshotData,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.1.2 权限配置
|
|||
|
|
|
|||
|
|
在 `manifest.json` 中添加必要权限:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"android": {
|
|||
|
|
"permissions": [
|
|||
|
|
"<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/>",
|
|||
|
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.1.3 应用初始化
|
|||
|
|
|
|||
|
|
在 `App.vue` 中初始化屏幕流服务:
|
|||
|
|
```javascript
|
|||
|
|
onShow() {
|
|||
|
|
const token = uni.getStorageSync('token')
|
|||
|
|
if (token) {
|
|||
|
|
this.initScreenStream()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 后端实现(RuoYi)
|
|||
|
|
|
|||
|
|
#### 3.2.1 WebSocket 处理器 (`ScreenStreamWebSocketHandler.java`)
|
|||
|
|
|
|||
|
|
**核心功能:**
|
|||
|
|
- 管理学生端连接
|
|||
|
|
- 管理监控端连接
|
|||
|
|
- 转发屏幕帧数据
|
|||
|
|
- 发送控制指令
|
|||
|
|
|
|||
|
|
**关键代码:**
|
|||
|
|
```java
|
|||
|
|
@ServerEndpoint("/ws/screenStream/{userId}")
|
|||
|
|
@Component
|
|||
|
|
public class ScreenStreamWebSocketHandler {
|
|||
|
|
// 学生端连接映射
|
|||
|
|
private static ConcurrentHashMap<String, ScreenStreamWebSocketHandler> studentConnections;
|
|||
|
|
|
|||
|
|
// 监控端连接映射
|
|||
|
|
private static ConcurrentHashMap<String, ScreenStreamWebSocketHandler> monitorConnections;
|
|||
|
|
|
|||
|
|
// 发送指令给学生端
|
|||
|
|
public static void sendCommandToStudent(String studentUserId, String command, Integer interval) {
|
|||
|
|
ScreenStreamWebSocketHandler handler = studentConnections.get(studentUserId);
|
|||
|
|
if (handler != null) {
|
|||
|
|
handler.sendMessage(JSON.toJSONString(cmd));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.2 控制接口 (`ScreenStreamController.java`)
|
|||
|
|
|
|||
|
|
**接口列表:**
|
|||
|
|
- `POST /study/screenStream/start` - 启动屏幕流捕获
|
|||
|
|
- `POST /study/screenStream/stop` - 停止屏幕流捕获
|
|||
|
|
- `POST /study/screenStream/checkOnline` - 检查学生是否在线
|
|||
|
|
- `POST /study/screenStream/onlineCount` - 获取在线学生数
|
|||
|
|
|
|||
|
|
#### 3.2.3 安全配置
|
|||
|
|
|
|||
|
|
在 `SecurityConfig.java` 中添加 WebSocket 路径免认证:
|
|||
|
|
```java
|
|||
|
|
.antMatchers("/ws/**").permitAll()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 后台管理端实现
|
|||
|
|
|
|||
|
|
#### 3.3.1 屏幕流监控页面 (`screenStream/index.vue`)
|
|||
|
|
|
|||
|
|
**功能特性:**
|
|||
|
|
- 学生列表展示(带在线状态)
|
|||
|
|
- 点击学生开始/停止监控
|
|||
|
|
- 实时显示屏幕画面
|
|||
|
|
- WebSocket 接收画面数据
|
|||
|
|
|
|||
|
|
**关键代码:**
|
|||
|
|
```javascript
|
|||
|
|
// 连接 WebSocket 接收画面
|
|||
|
|
connectWebSocket(studentId) {
|
|||
|
|
const wsUrl = `ws://${host}/ws/screenStream/${studentId}`
|
|||
|
|
this.ws = new WebSocket(wsUrl)
|
|||
|
|
|
|||
|
|
this.ws.onmessage = (event) => {
|
|||
|
|
const message = JSON.parse(event.data)
|
|||
|
|
if (message.type === "screen_frame") {
|
|||
|
|
this.currentScreenFrame = `data:image/jpeg;base64,${message.data}`
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动监控
|
|||
|
|
async startMonitor() {
|
|||
|
|
await startScreenStream(this.currentStudent.userId, 500)
|
|||
|
|
this.currentStudent.isMonitoring = true
|
|||
|
|
this.connectWebSocket(this.currentStudent.userId)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、部署说明
|
|||
|
|
|
|||
|
|
### 4.1 前端部署
|
|||
|
|
|
|||
|
|
1. **配置服务器地址**:
|
|||
|
|
- 修改 `frontend-uniapp/src/utils/config.js` 中的 `SERVER_HOST`
|
|||
|
|
- 确保 WebSocket 地址正确
|
|||
|
|
|
|||
|
|
2. **编译打包**:
|
|||
|
|
```bash
|
|||
|
|
cd frontend-uniapp
|
|||
|
|
npm run build:app
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **权限说明**:
|
|||
|
|
- 安卓 10+ 需要用户手动授予屏幕捕获权限
|
|||
|
|
- 首次启动时会请求相关权限
|
|||
|
|
|
|||
|
|
### 4.2 后端部署
|
|||
|
|
|
|||
|
|
1. **确保 WebSocket 支持**:
|
|||
|
|
- 检查 `pom.xml` 中已包含 `spring-boot-starter-websocket` 依赖
|
|||
|
|
|
|||
|
|
2. **配置安全策略**:
|
|||
|
|
- WebSocket 路径 `/ws/**` 已配置为免认证
|
|||
|
|
- 实际认证在 WebSocket 处理器中处理
|
|||
|
|
|
|||
|
|
3. **启动服务**:
|
|||
|
|
```bash
|
|||
|
|
cd RuoYi-Vue-redis
|
|||
|
|
mvn clean install
|
|||
|
|
java -jar ry-news-admin/target/ry-news-admin.jar
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 五、性能优化
|
|||
|
|
|
|||
|
|
### 5.1 传输优化
|
|||
|
|
|
|||
|
|
1. **图片质量调整**:
|
|||
|
|
- 默认质量 0.7(可调整)
|
|||
|
|
- 根据网络情况动态调整
|
|||
|
|
|
|||
|
|
2. **捕获频率**:
|
|||
|
|
- 默认 500ms(约 2 帧/秒)
|
|||
|
|
- 可根据需求调整
|
|||
|
|
|
|||
|
|
3. **数据压缩**:
|
|||
|
|
- 使用 JPEG 格式(比 PNG 小)
|
|||
|
|
- Base64 编码传输
|
|||
|
|
|
|||
|
|
### 5.2 资源管理
|
|||
|
|
|
|||
|
|
1. **按需启动**:
|
|||
|
|
- 只有后台点击时才启动捕获
|
|||
|
|
- 不监控时完全停止,节省资源
|
|||
|
|
|
|||
|
|
2. **连接管理**:
|
|||
|
|
- 自动重连机制
|
|||
|
|
- 连接断开时停止捕获
|
|||
|
|
|
|||
|
|
3. **后台保活**:
|
|||
|
|
- 使用前台服务权限
|
|||
|
|
- 确保应用在后台时也能运行
|
|||
|
|
|
|||
|
|
## 六、注意事项
|
|||
|
|
|
|||
|
|
### 6.1 权限问题
|
|||
|
|
|
|||
|
|
1. **屏幕捕获权限**:
|
|||
|
|
- 安卓 10+ 需要 `MediaProjection` 权限
|
|||
|
|
- 需要用户手动授权
|
|||
|
|
|
|||
|
|
2. **后台运行权限**:
|
|||
|
|
- 需要 `FOREGROUND_SERVICE` 权限
|
|||
|
|
- 在 `manifest.json` 中已配置
|
|||
|
|
|
|||
|
|
### 6.2 兼容性
|
|||
|
|
|
|||
|
|
1. **安卓版本**:
|
|||
|
|
- 最低支持:Android 5.0 (API 21)
|
|||
|
|
- 推荐:Android 10+ (API 29+)
|
|||
|
|
|
|||
|
|
2. **网络要求**:
|
|||
|
|
- 必须在同一局域网内
|
|||
|
|
- 确保防火墙允许 WebSocket 连接
|
|||
|
|
|
|||
|
|
### 6.3 安全性
|
|||
|
|
|
|||
|
|
1. **认证机制**:
|
|||
|
|
- WebSocket 连接时携带 Token
|
|||
|
|
- 后端验证用户身份
|
|||
|
|
|
|||
|
|
2. **数据加密**:
|
|||
|
|
- 建议在生产环境使用 WSS(WebSocket Secure)
|
|||
|
|
- 局域网环境可考虑使用 HTTP
|
|||
|
|
|
|||
|
|
## 七、测试建议
|
|||
|
|
|
|||
|
|
### 7.1 功能测试
|
|||
|
|
|
|||
|
|
1. **连接测试**:
|
|||
|
|
- 测试学生端 WebSocket 连接
|
|||
|
|
- 测试后台管理端连接
|
|||
|
|
|
|||
|
|
2. **指令测试**:
|
|||
|
|
- 测试启动/停止指令
|
|||
|
|
- 测试指令响应时间
|
|||
|
|
|
|||
|
|
3. **画面传输测试**:
|
|||
|
|
- 测试画面清晰度
|
|||
|
|
- 测试传输延迟
|
|||
|
|
- 测试多学生同时监控
|
|||
|
|
|
|||
|
|
### 7.2 性能测试
|
|||
|
|
|
|||
|
|
1. **资源占用**:
|
|||
|
|
- 监控 CPU 和内存占用
|
|||
|
|
- 监控网络带宽占用
|
|||
|
|
|
|||
|
|
2. **稳定性测试**:
|
|||
|
|
- 长时间运行测试
|
|||
|
|
- 网络中断恢复测试
|
|||
|
|
|
|||
|
|
## 八、后续优化方向
|
|||
|
|
|
|||
|
|
1. **WebRTC 方案**:
|
|||
|
|
- 如果 WebSocket 方案性能不足,可考虑 WebRTC
|
|||
|
|
- 需要开发原生插件支持
|
|||
|
|
|
|||
|
|
2. **流媒体服务器**:
|
|||
|
|
- 使用 RTMP/RTSP 协议
|
|||
|
|
- 需要部署流媒体服务器(如 SRS)
|
|||
|
|
|
|||
|
|
3. **画面录制**:
|
|||
|
|
- 支持录制监控画面
|
|||
|
|
- 保存为视频文件
|
|||
|
|
|
|||
|
|
4. **多画面监控**:
|
|||
|
|
- 支持同时监控多个学生
|
|||
|
|
- 画面分屏显示
|
|||
|
|
|
|||
|
|
## 九、常见问题
|
|||
|
|
|
|||
|
|
### Q1: 屏幕捕获失败?
|
|||
|
|
**A:** 检查是否授予屏幕捕获权限,安卓 10+ 需要用户手动授权。
|
|||
|
|
|
|||
|
|
### Q2: WebSocket 连接失败?
|
|||
|
|
**A:** 检查网络连接、防火墙设置,确保在同一局域网内。
|
|||
|
|
|
|||
|
|
### Q3: 画面延迟高?
|
|||
|
|
**A:** 可以调整捕获间隔(默认 500ms),或降低图片质量。
|
|||
|
|
|
|||
|
|
### Q4: 应用后台时停止捕获?
|
|||
|
|
**A:** 检查是否授予后台运行权限,确保 `FOREGROUND_SERVICE` 权限已配置。
|
|||
|
|
|
|||
|
|
### Q5: 多学生同时监控性能问题?
|
|||
|
|
**A:** 可以限制同时监控的学生数量,或使用流媒体服务器方案。
|
|||
|
|
|