v2 platform runtime (docs 22 Phase 2/4/7 + 17): Project/Environment + Config + Storage + Observability
- Project/Environment model with default environments (dev/staging/prod) - Config Runtime: cascade resolution (platform→org→project→env), secrets via vault - Storage Runtime: local provider, sha256 dedup, integrity check, file list/upload/delete - Observability: execution query, per-action summary (success rate, avg, p95), health - Realtime SSE: workspace event stream endpoint (GET /api/v1/v2/realtime/events) - Phase 1 acceptance: core.health.check, core.user.create, core.user.list - Actions: core.project.*, core.config.*, core.file.*, core.user.* (62 total)
This commit is contained in:
@@ -0,0 +1,761 @@
|
||||
# 动态 FPS 截图推送模块
|
||||
|
||||
## 1. 目标
|
||||
|
||||
现有 Browser SSE 已经支持 Screenshot 推送,本次只增加**动态 Screenshot 推送频率**。
|
||||
|
||||
核心规则:
|
||||
|
||||
- 无操作时:每 **2 秒** 推送一张 Screenshot
|
||||
- 用户发生浏览器操作后:进入高频模式
|
||||
- 高频模式:每 **500ms** 推送一张 Screenshot
|
||||
- 高频模式持续 **5 秒**
|
||||
- 5 秒的计算基于**最后一次浏览器操作**
|
||||
- 新操作发生时,只延长高频模式时间
|
||||
- 操作本身**不立即触发 Screenshot**
|
||||
- 不使用真正的视频流
|
||||
- 不使用 WebRTC
|
||||
- 不使用 VNC
|
||||
- Screenshot 仍然是独立 JPEG 图片
|
||||
- Screenshot 仍然通过现有 SSE 推送
|
||||
|
||||
---
|
||||
|
||||
# 2. 配置
|
||||
|
||||
默认配置:
|
||||
|
||||
```ts
|
||||
const screenshotConfig = {
|
||||
quality: 30,
|
||||
|
||||
normalInterval: 2000,
|
||||
|
||||
activeInterval: 500,
|
||||
|
||||
activeDuration: 5000
|
||||
}
|
||||
```
|
||||
|
||||
参数:
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|---|---:|---|
|
||||
| `quality` | `30` | JPEG 质量 |
|
||||
| `normalInterval` | `2000` | 普通模式截图间隔,单位 ms |
|
||||
| `activeInterval` | `500` | 活跃模式截图间隔,单位 ms |
|
||||
| `activeDuration` | `5000` | 最后一次操作后保持活跃模式的时间,单位 ms |
|
||||
|
||||
所有时间参数统一使用 `ms`。
|
||||
|
||||
---
|
||||
|
||||
# 3. Screenshot Mode
|
||||
|
||||
只有两种状态:
|
||||
|
||||
```ts
|
||||
type ScreenshotMode = 'normal' | 'active'
|
||||
```
|
||||
|
||||
### Normal
|
||||
|
||||
```text
|
||||
interval = 2000ms
|
||||
```
|
||||
|
||||
即:
|
||||
|
||||
```text
|
||||
Screenshot
|
||||
↓
|
||||
等待 2 秒
|
||||
↓
|
||||
Screenshot
|
||||
↓
|
||||
等待 2 秒
|
||||
↓
|
||||
...
|
||||
```
|
||||
|
||||
### Active
|
||||
|
||||
```text
|
||||
interval = 500ms
|
||||
```
|
||||
|
||||
即:
|
||||
|
||||
```text
|
||||
Screenshot
|
||||
↓
|
||||
等待 500ms
|
||||
↓
|
||||
Screenshot
|
||||
↓
|
||||
等待 500ms
|
||||
↓
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 4. 状态
|
||||
|
||||
Scheduler 至少维护:
|
||||
|
||||
```ts
|
||||
interface ScreenshotSchedulerState {
|
||||
mode: 'normal' | 'active'
|
||||
|
||||
lastActivityAt: number
|
||||
|
||||
activeUntil: number
|
||||
|
||||
running: boolean
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
### `mode`
|
||||
|
||||
当前截图模式:
|
||||
|
||||
```text
|
||||
normal
|
||||
active
|
||||
```
|
||||
|
||||
### `lastActivityAt`
|
||||
|
||||
最后一次成功 Browser Action 的时间。
|
||||
|
||||
### `activeUntil`
|
||||
|
||||
Active Mode 的结束时间。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
当前时间:10:00:00
|
||||
|
||||
用户操作
|
||||
↓
|
||||
activeUntil = 10:00:05
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. 操作触发 Active Mode
|
||||
|
||||
任何**成功的浏览器操作**完成后调用:
|
||||
|
||||
```ts
|
||||
notifyActivity()
|
||||
```
|
||||
|
||||
实现:
|
||||
|
||||
```ts
|
||||
function notifyActivity() {
|
||||
const now = Date.now()
|
||||
|
||||
lastActivityAt = now
|
||||
activeUntil = now + activeDuration
|
||||
}
|
||||
```
|
||||
|
||||
同时:
|
||||
|
||||
```ts
|
||||
mode = 'active'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 6. 操作不立即 Screenshot
|
||||
|
||||
这是必须遵守的规则。
|
||||
|
||||
错误:
|
||||
|
||||
```text
|
||||
Browser Action
|
||||
↓
|
||||
Screenshot
|
||||
↓
|
||||
SSE
|
||||
```
|
||||
|
||||
正确:
|
||||
|
||||
```text
|
||||
Browser Action
|
||||
↓
|
||||
Action 执行成功
|
||||
↓
|
||||
notifyActivity()
|
||||
↓
|
||||
切换 Active Mode
|
||||
↓
|
||||
Scheduler 按 500ms 推送 Screenshot
|
||||
```
|
||||
|
||||
也就是说:
|
||||
|
||||
> **Browser Action 只修改 Screenshot Scheduler 状态,不主动调用 Screenshot。**
|
||||
|
||||
---
|
||||
|
||||
# 7. Active Mode 生命周期
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
00:00
|
||||
用户 click
|
||||
↓
|
||||
Active Until = 00:05
|
||||
|
||||
00:00.5 Screenshot
|
||||
00:01.0 Screenshot
|
||||
00:01.5 Screenshot
|
||||
00:02.0 Screenshot
|
||||
...
|
||||
00:05.0
|
||||
↓
|
||||
恢复 Normal
|
||||
```
|
||||
|
||||
恢复后:
|
||||
|
||||
```text
|
||||
每 2000ms Screenshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. 连续操作
|
||||
|
||||
如果 Active Mode 中再次发生操作:
|
||||
|
||||
```text
|
||||
00:00 click
|
||||
↓
|
||||
activeUntil = 00:05
|
||||
|
||||
00:02 fill
|
||||
↓
|
||||
activeUntil = 00:07
|
||||
|
||||
00:04 scroll
|
||||
↓
|
||||
activeUntil = 00:09
|
||||
```
|
||||
|
||||
因此:
|
||||
|
||||
> **Active Mode 的 5 秒永远从最后一次操作重新计算。**
|
||||
|
||||
最终:
|
||||
|
||||
```text
|
||||
00:04 最后一次操作
|
||||
↓
|
||||
保持 Active 到 00:09
|
||||
↓
|
||||
恢复 Normal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 9. 多次操作不会创建多个 Timer
|
||||
|
||||
禁止:
|
||||
|
||||
```ts
|
||||
setInterval(...)
|
||||
setInterval(...)
|
||||
setInterval(...)
|
||||
```
|
||||
|
||||
也禁止每次操作创建一个新的 Screenshot Loop。
|
||||
|
||||
整个 Browser Session 只允许存在:
|
||||
|
||||
```text
|
||||
1 个 Screenshot Scheduler
|
||||
```
|
||||
|
||||
所有操作统一调用:
|
||||
|
||||
```ts
|
||||
notifyActivity()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 10. Scheduler
|
||||
|
||||
推荐采用单一循环。
|
||||
|
||||
示例:
|
||||
|
||||
```ts
|
||||
class ScreenshotScheduler {
|
||||
private running = false
|
||||
|
||||
private mode: 'normal' | 'active' = 'normal'
|
||||
|
||||
private activeUntil = 0
|
||||
|
||||
async start() {
|
||||
if (this.running) {
|
||||
return
|
||||
}
|
||||
|
||||
this.running = true
|
||||
|
||||
while (this.running) {
|
||||
const now = Date.now()
|
||||
|
||||
this.mode =
|
||||
now < this.activeUntil
|
||||
? 'active'
|
||||
: 'normal'
|
||||
|
||||
await this.capture()
|
||||
|
||||
const interval =
|
||||
this.mode === 'active'
|
||||
? this.config.activeInterval
|
||||
: this.config.normalInterval
|
||||
|
||||
await sleep(interval)
|
||||
}
|
||||
}
|
||||
|
||||
notifyActivity() {
|
||||
const now = Date.now()
|
||||
|
||||
this.mode = 'active'
|
||||
|
||||
this.activeUntil =
|
||||
now + this.config.activeDuration
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.running = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实际代码可以根据项目结构调整,但必须保持相同的行为。
|
||||
|
||||
---
|
||||
|
||||
# 11. Screenshot 执行顺序
|
||||
|
||||
Scheduler 每次执行:
|
||||
|
||||
```text
|
||||
检查当前 Mode
|
||||
↓
|
||||
Screenshot
|
||||
↓
|
||||
SSE 推送
|
||||
↓
|
||||
根据当前 Mode 等待
|
||||
↓
|
||||
进入下一轮
|
||||
```
|
||||
|
||||
例如 Normal:
|
||||
|
||||
```text
|
||||
Screenshot
|
||||
↓
|
||||
wait 2000ms
|
||||
↓
|
||||
Screenshot
|
||||
↓
|
||||
wait 2000ms
|
||||
```
|
||||
|
||||
Active:
|
||||
|
||||
```text
|
||||
Screenshot
|
||||
↓
|
||||
wait 500ms
|
||||
↓
|
||||
Screenshot
|
||||
↓
|
||||
wait 500ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 12. Screenshot 不允许并发
|
||||
|
||||
必须保证同一 Browser Session:
|
||||
|
||||
```text
|
||||
同时最多 1 个 Screenshot
|
||||
```
|
||||
|
||||
禁止:
|
||||
|
||||
```text
|
||||
Screenshot A
|
||||
↓
|
||||
还没完成
|
||||
Screenshot B
|
||||
↓
|
||||
还没完成
|
||||
Screenshot C
|
||||
```
|
||||
|
||||
正确:
|
||||
|
||||
```text
|
||||
Screenshot A
|
||||
↓
|
||||
完成
|
||||
↓
|
||||
等待 interval
|
||||
↓
|
||||
Screenshot B
|
||||
```
|
||||
|
||||
原因:
|
||||
|
||||
- 避免 CPU 突增
|
||||
- 避免内存占用增加
|
||||
- 避免多个 Screenshot 同时进入 SSE
|
||||
- 避免页面状态竞争
|
||||
|
||||
---
|
||||
|
||||
# 13. Browser Action 与 Scheduler 的关系
|
||||
|
||||
Browser Controller 在 Action 成功后:
|
||||
|
||||
```ts
|
||||
await executeAction(action)
|
||||
|
||||
scheduler.notifyActivity()
|
||||
```
|
||||
|
||||
不要:
|
||||
|
||||
```ts
|
||||
await executeAction(action)
|
||||
|
||||
await screenshot()
|
||||
```
|
||||
|
||||
也不要:
|
||||
|
||||
```ts
|
||||
scheduler.start()
|
||||
```
|
||||
|
||||
Browser Session 创建时只启动一次 Scheduler。
|
||||
|
||||
---
|
||||
|
||||
# 14. Action 失败
|
||||
|
||||
Action 失败:
|
||||
|
||||
```text
|
||||
Browser Action
|
||||
↓
|
||||
失败
|
||||
↓
|
||||
不调用 notifyActivity()
|
||||
```
|
||||
|
||||
因此不会进入 Active Mode。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
click element
|
||||
↓
|
||||
Element Not Found
|
||||
↓
|
||||
Action Failed
|
||||
↓
|
||||
保持 Normal Mode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 15. Navigation Action
|
||||
|
||||
以下操作成功后同样进入 Active Mode:
|
||||
|
||||
```text
|
||||
goto
|
||||
reload
|
||||
back
|
||||
forward
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
goto
|
||||
↓
|
||||
页面加载完成
|
||||
↓
|
||||
notifyActivity()
|
||||
↓
|
||||
Active Mode
|
||||
↓
|
||||
500ms Screenshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 16. Batch Action
|
||||
|
||||
Batch Action 只调用一次:
|
||||
|
||||
```ts
|
||||
scheduler.notifyActivity()
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
Action 1
|
||||
↓
|
||||
Action 2
|
||||
↓
|
||||
Action 3
|
||||
↓
|
||||
全部成功
|
||||
↓
|
||||
notifyActivity()
|
||||
```
|
||||
|
||||
不要:
|
||||
|
||||
```text
|
||||
Action 1 → notifyActivity()
|
||||
Action 2 → notifyActivity()
|
||||
Action 3 → notifyActivity()
|
||||
```
|
||||
|
||||
Batch 本身只需要最终刷新一次 Active Mode 时间。
|
||||
|
||||
---
|
||||
|
||||
# 17. SSE
|
||||
|
||||
动态 FPS 不改变现有 SSE API。
|
||||
|
||||
仍然:
|
||||
|
||||
```http
|
||||
GET /profiles/:profileId/browser/events
|
||||
```
|
||||
|
||||
Screenshot Event 保持现有格式。
|
||||
|
||||
Scheduler 只决定:
|
||||
|
||||
> **什么时候生成下一张 Screenshot。**
|
||||
|
||||
SSE 只负责:
|
||||
|
||||
> **把 Screenshot 推送给前端。**
|
||||
|
||||
两者职责保持分离。
|
||||
|
||||
---
|
||||
|
||||
# 18. SSE State
|
||||
|
||||
建议在 `state` event 中增加:
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "active",
|
||||
"normalInterval": 2000,
|
||||
"activeInterval": 500,
|
||||
"activeUntil": 1780000000000
|
||||
}
|
||||
```
|
||||
|
||||
前端可以知道当前 Screenshot Scheduler 状态。
|
||||
|
||||
如果现有 `state` 已经定义完成,也可以不修改协议,仅在服务端内部维护状态。
|
||||
|
||||
---
|
||||
|
||||
# 19. SSE Client 数量
|
||||
|
||||
如果当前没有 SSE Client:
|
||||
|
||||
```text
|
||||
SSE Client = 0
|
||||
```
|
||||
|
||||
可以暂停 Screenshot Scheduler。
|
||||
|
||||
重新建立 SSE:
|
||||
|
||||
```text
|
||||
SSE Connected
|
||||
↓
|
||||
启动 Screenshot Scheduler
|
||||
↓
|
||||
发送当前 Screenshot
|
||||
↓
|
||||
进入 Normal Mode
|
||||
```
|
||||
|
||||
如果当前项目已经有 Scheduler 生命周期管理,则只增加动态 interval,不修改现有连接管理。
|
||||
|
||||
---
|
||||
|
||||
# 20. Active Mode 不等于视频流
|
||||
|
||||
必须保持:
|
||||
|
||||
```text
|
||||
Active Mode
|
||||
↓
|
||||
每 500ms
|
||||
↓
|
||||
生成一张 JPEG
|
||||
```
|
||||
|
||||
而不是:
|
||||
|
||||
```text
|
||||
Active Mode
|
||||
↓
|
||||
视频编码
|
||||
↓
|
||||
持续视频流
|
||||
```
|
||||
|
||||
所以本功能仍然是:
|
||||
|
||||
> **Adaptive Screenshot Streaming**
|
||||
|
||||
而不是 Live View / Video Streaming。
|
||||
|
||||
---
|
||||
|
||||
# 21. 推荐默认参数
|
||||
|
||||
第一版固定默认:
|
||||
|
||||
```ts
|
||||
{
|
||||
quality: 30,
|
||||
normalInterval: 2000,
|
||||
activeInterval: 500,
|
||||
activeDuration: 5000
|
||||
}
|
||||
```
|
||||
|
||||
即:
|
||||
|
||||
```text
|
||||
无操作:
|
||||
2 秒 / 张
|
||||
|
||||
操作后:
|
||||
500ms / 张
|
||||
|
||||
最后一次操作后:
|
||||
5 秒恢复正常
|
||||
|
||||
JPEG:
|
||||
quality 30
|
||||
```
|
||||
|
||||
后续如果需要,再将这些参数开放为系统配置。
|
||||
|
||||
---
|
||||
|
||||
# 22. 核心流程
|
||||
|
||||
```text
|
||||
Browser Action
|
||||
│
|
||||
▼
|
||||
Playwright
|
||||
│
|
||||
操作成功
|
||||
│
|
||||
▼
|
||||
notifyActivity()
|
||||
│
|
||||
▼
|
||||
Active Mode
|
||||
interval=500ms
|
||||
│
|
||||
▼
|
||||
Screenshot
|
||||
│
|
||||
▼
|
||||
SSE
|
||||
│
|
||||
▼
|
||||
Console
|
||||
│
|
||||
│
|
||||
500ms 后
|
||||
│
|
||||
▼
|
||||
Screenshot
|
||||
│
|
||||
...
|
||||
│
|
||||
最后操作 + 5 秒
|
||||
│
|
||||
▼
|
||||
Normal Mode
|
||||
interval=2000ms
|
||||
│
|
||||
▼
|
||||
Screenshot
|
||||
│
|
||||
2 秒后
|
||||
│
|
||||
▼
|
||||
Screenshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 23. 验收标准
|
||||
|
||||
- [ ] Normal Mode 默认 2000ms
|
||||
- [ ] Active Mode 默认 500ms
|
||||
- [ ] Active Duration 默认 5000ms
|
||||
- [ ] Browser Action 成功后进入 Active Mode
|
||||
- [ ] Browser Action 不立即触发 Screenshot
|
||||
- [ ] Action 失败不进入 Active Mode
|
||||
- [ ] Active Mode 从最后一次操作重新计时
|
||||
- [ ] 连续操作不会创建多个 Timer
|
||||
- [ ] 每个 Browser Session 只有一个 Screenshot Scheduler
|
||||
- [ ] Screenshot 不允许并发执行
|
||||
- [ ] Active Mode 结束后自动恢复 Normal Mode
|
||||
- [ ] SSE 接口保持不变
|
||||
- [ ] Screenshot 仍然使用 JPEG
|
||||
- [ ] Screenshot quality 默认 30
|
||||
- [ ] 不引入 WebSocket
|
||||
- [ ] 不引入 WebRTC
|
||||
- [ ] 不引入 VNC
|
||||
- [ ] 不引入 CDP
|
||||
- [ ] 不实现视频流
|
||||
Reference in New Issue
Block a user