# node-video-v3 技术文档

> 基于 FFmpeg 的 Node.js 图片合成视频服务，支持多图转场、背景音乐、多云存储。

---

## 目录

- [1. 项目概述](#1-项目概述)
- [2. 技术栈](#2-技术栈)
- [3. 项目结构](#3-项目结构)
- [4. 系统架构](#4-系统架构)
- [5. 运行机制](#5-运行机制)
- [6. 核心模块详解](#6-核心模块详解)
- [7. 配置说明](#7-配置说明)
- [8. 对外 API 接口](#8-对外-api-接口)
- [9. 部署运维](#9-部署运维)
- [10. 开发指南](#10-开发指南)

---

## 1. 项目概述

node-video-v3 是一个 **视频合成工作节点服务**，运行于服务器后台，以"拉取-处理-上报"模式持续从业务网关获取视频制作任务，将多张图片合成为带转场特效和背景音乐的 MP4 视频，最终上传至对象存储并回调结果。

**核心能力：**

| 能力           | 说明                                               |
| -------------- | -------------------------------------------------- |
| 图片合成       | 多张图片按固定时长序列化，支持 6 种转场效果        |
| Ken Burns 效果 | zoom-in 缓慢推近镜头                               |
| 背景音乐       | 内置音频库 + 自定义音乐下载                        |
| 多云存储       | 阿里云 OSS / 腾讯云 COS / 火山引擎 TOS / 京东云 S3 |
| 资源自适应     | 按 CPU 核数控制并发，内存/负载阈值保护             |
| 进程守护       | PM2 自动重启 + 文件监听热更新                      |

---

## 2. 技术栈

| 分类        | 技术 / 依赖         | 版本     | 用途                         |
| ----------- | ------------------- | -------- | ---------------------------- |
| 运行时      | Node.js             | ≥ 16     | JavaScript 服务端运行时      |
| 视频引擎    | simple-ffmpegjs     | latest   | FFmpeg 高级封装，clips → MP4 |
| 多云 SDK    | ali-oss             | ^6.18.0  | 阿里云 OSS                   |
|             | cos-nodejs-sdk-v5   | ^2.14.4  | 腾讯云 COS                   |
|             | @volcengine/tos-sdk | ^2.9.1   | 火山引擎 TOS                 |
|             | aws-sdk             | 2.1356.0 | 京东云 S3 兼容               |
| HTTP 客户端 | axios               | ^1.6.0   | 任务拉取 / 结果上报          |
| 文件操作    | fs-extra            | ^11.3.2  | 流式文件读写                 |
| 进程管理    | PM2                 | -        | 守护进程、日志、热重载       |
| 系统依赖    | FFmpeg              | -        | 视频编码底层引擎             |

---

## 3. 项目结构

```
node-video-v3/
├── src/
│   ├── main.js                      # 应用入口，启动两个定时器
│   ├── conf/
│   │   ├── config.js                # 配置加载器（读取 Zk JSON）
│   │   └── Zk.node-video-v2.json    # 运行时配置（密钥、IDC、网关等）
│   ├── services/
│   │   └── videoMaker.js            # 视频合成核心服务（图片→MP4）
│   ├── timer/
│   │   ├── makeVideoTimer.js        # 任务轮询 + 合成编排 + 结果上报
│   │   └── taskManager.js           # 内存任务表 + 心跳管理
│   └── tool/
│       ├── commonTool.js            # 通用工具（下载、加密、系统检测、格式化）
│       └── ossTool.js               # 多云对象存储统一抽象层
├── audio/                           # 内置背景音乐音频库（21 个 mp3）
├── pm2.json                         # PM2 进程配置
├── package.json                     # 依赖与脚本
└── .npmrc                           # npm 配置
```

---

## 4. 系统架构

### 4.1 整体架构图

```
┌─────────────────────────────────────────────────────────────┐
│                        业务网关 (yktGateway)                    │
│              get_video_create_task_info                       │
│              apiReportSuccessUrl (回调)                        │
└──────────▲───────────────────────────────────┬────────────────┘
           │ ② 拉取任务                  ⑥ 上报结果 │
           │                                       │
┌──────────┴───────────────────────────────────────┴────────────┐
│                    node-video-v3 (工作节点)                     │
│                                                               │
│  ┌──────────────┐    ┌──────────────────────────────────┐     │
│  │  main.js     │    │        MakeVideoTimer             │     │
│  │              │    │                                   │     │
│  │ 定时器A(2s)  │───▶│  start()                          │     │
│  │ 轮询任务     │    │   ├─ checkAllowGetTask() 资源检查  │     │
│  │              │    │   ├─ getCreateVideoTask() 拉取    │     │
│  │ 定时器B(10s) │    │   ├─ doMakeVideo()    合成编排    │     │
│  │ 心跳清理     │    │   │   ├─ downloadImages()        │     │
│  └──────┬───────┘    │   │   ├─ VideoMaker.buildVideo() │     │
│         │            │   │   ├─ ossTool.uploadFile()    │     │
│         ▼            │   └─ reportMakeVideoResult()     │     │
│  ┌──────────────┐    └──────────────────────────────────┘     │
│  │ TaskManager  │              │                               │
│  │ taskMap{}    │              │ heartbeat                     │
│  │ 心跳时间戳   │◀─────────────┘                               │
│  └──────────────┘                                              │
└───────────────────────────┬───────────────────────────────────┘
                            │ ⑤ 上传
                ┌───────────▼───────────┐
                │   对象存储 (OSS/COS)   │
                │   make_video/{app}/   │
                │   {date}/{file}.mp4   │
                └───────────────────────┘
```

### 4.2 模块依赖关系

```
main.js
  ├── TaskManager ──▶ commonTool
  └── MakeVideoTimer ──▶ commonTool
                      ├─ ossTool ──▶ commonTool, config
                      ├─ VideoMaker ──▶ commonTool, simple-ffmpegjs
                      ├─ axios
                      └─ config
```

### 4.3 设计模式

| 模式        | 应用位置                     | 说明                          |
| ----------- | ---------------------------- | ----------------------------- |
| 单例模式    | `ossTool.js` `getInstance()` | 全局唯一 OSS 客户端           |
| 策略模式    | `ossTool.js` `uploadFile()`  | 按 `idc` 选择不同云厂商策略   |
| 观察者/轮询 | `main.js` 两个 `setInterval` | 定时拉取任务 + 心跳守护       |
| 模板方法    | `MakeVideoTimer.start()`     | 固定流程：拉取→合成→上报→清理 |

---

## 5. 运行机制

### 5.1 双定时器模型

程序启动后运行两个独立的定时器：

```
┌─────────────────────────────────────────────────┐
│                main.js bootstrap()                │
│                                                  │
│  ┌─────────────────────┐  ┌────────────────────┐ │
│  │  定时器 A (2 秒)     │  │  定时器 B (10 秒)   │ │
│  │                     │  │                    │ │
│  │  new MakeVideoTimer │  │  遍历 taskMap      │ │
│  │  await start()      │  │  检查心跳时间戳     │ │
│  │                     │  │  >120s → 删除任务   │ │
│  │  ◀ 任务执行主循环 ▶  │  │                    │ │
│  └─────────────────────┘  └────────────────────┘ │
└─────────────────────────────────────────────────┘
```

- **定时器 A（核心循环）**：每 2 秒尝试拉取一个新任务并处理。`start()` 是 `async`，会等待整个合成流程完成才返回。
- **定时器 B（心跳守护）**：每 10 秒扫描所有任务，清除心跳超时（> 120 秒）的"僵尸"任务。

### 5.2 单任务完整生命周期

```
   开始
    │
    ▼
┌─ checkAllowGetTask() ──────────────────────────┐
│  内存 > 阈值? ──── 是 ──▶ 抛出 "内存不足"        │
│  负载 > 阈值? ──── 是 ──▶ 抛出 "负载过高"        │
│  任务数 >= CPU核数? ── 是 ──▶ 抛出 "已达最大数量" │
│  全部通过 ──▶ 继续                              │
└────────────────────────────────────────────────┘
    │
    ▼
┌─ getCreateVideoTask() ─────────────────────────┐
│  POST → yktGateway/get_video_create_task_info  │
│  参数: hostname, maxTaskNum, timestamp, sign   │
│  返回: { taskId, videoConfig, apiReportSuccessUrl } │
│  无任务 ──▶ return (结束)                       │
└────────────────────────────────────────────────┘
    │
    ▼
  taskManager.addTask(taskId)  ◀── 注册任务到内存表
    │
    ▼
┌─ doMakeVideo() ────────────────────────────────┐
│                                                 │
│  1. 校验 appName (必须存在)                     │
│  2. 校验 imageUrlList (必须非空)                │
│  3. 解析参数: videoEffect, musicCode, 尺寸等    │
│  4. 生成 saveFilename                           │
│  5. 下载自定义音乐 (如有 musicPath)              │
│                                                 │
│  ┌─ VideoMaker 流程 ─────────────────────────┐  │
│  │                                           │  │
│  │  downloadImages() ── 并行下载所有图片      │  │
│  │       │                                   │  │
│  │       ▼                                   │  │
│  │  heartbeat(taskId) ◀── 更新心跳            │  │
│  │       │                                   │  │
│  │       ▼                                   │  │
│  │  buildVideo() ── simple-ffmpegjs 合成      │  │
│  │   ├ 构建 clips[] (图片+转场+音乐)          │  │
│  │   ├ project.load(clips)                   │  │
│  │   └ project.export(outputPath)            │  │
│  │       │                                   │  │
│  │       ▼                                   │  │
│  │  heartbeat(taskId) ◀── 更新心跳            │  │
│  │       │                                   │  │
│  │       ▼                                   │  │
│  │  getMp4FilePath() ── 校验文件生成成功       │  │
│  └───────────────────────────────────────────┘  │
│                                                 │
│  ossTool.uploadFile(ossPath, mp4File)           │
│   ├ 成功 ──▶ successResult { ossUrl, ... }      │
│   └ 失败 ──▶ failResult                         │
│                                                 │
│  finally: videoMaker.cleanup() + 删除临时音频    │
└─────────────────────────────────────────────────┘
    │
    ▼
┌─ reportMakeVideoResult() ──────────────────────┐
│  POST → apiReportSuccessUrl                    │
│  参数: taskId, status, hostname,               │
│        ossUrl, internaOssUrl, timeStat, reason │
└────────────────────────────────────────────────┘
    │
    ▼
  finally: taskManager.deleteTask(taskId)  ◀── 注销任务
    │
   结束
```

### 5.3 视频合成参数

| 参数           | 默认值 | 说明                                   |
| -------------- | ------ | -------------------------------------- |
| 每张图独显时长 | 2.0 秒 | 单张图片完全展示时间                   |
| 转场重叠时长   | 1.0 秒 | 与下一张重叠的转场时间                 |
| FPS            | 30     | 帧率                                   |
| 预设           | slow   | FFmpeg 编码预设（质量优先）            |
| 音频音量       | 0.5    | 背景音乐音量（50%）                    |
| 音频码率       | 256k   | 音频比特率                             |
| CRF            | 18     | libx264/libx265 质量因子（越低越清晰） |
| 视频码率       | 8M     | 非 x264 编码器的高码率                 |

> **总时长公式**：`≈ 2 × N + 1 秒`（N = 图片张数）

### 5.4 转场效果映射

| videoEffect     | 转场类型                       | 额外效果           |
| --------------- | ------------------------------ | ------------------ |
| `sceneFade`     | fade（淡入淡出）               | -                  |
| `sceneZoom`     | fade（淡入淡出）               | Ken Burns: zoom-in |
| `sceneSlide`    | slideleft / slideright（交替） | -                  |
| `sceneShutters` | dissolve（溶解）               | -                  |
| `sceneRotate`   | circleopen（圆形展开）         | -                  |
| `sceneSkew`     | wipeleft（擦除）               | -                  |

---

## 6. 核心模块详解

### 6.1 main.js — 应用入口

启动入口，实例化 `TaskManager` 并运行两个定时器：

- **定时器 A**：每 2 秒创建 `MakeVideoTimer` 实例执行 `start()`
- **定时器 B**：每 10 秒扫描任务表，清理心跳超 120 秒的僵尸任务

> 注意：定时器 A 中每次都 `new MakeVideoTimer(taskManager)`，但 `start()` 内部会 `await`，因此同一时刻只可能有一个任务在执行。

### 6.2 TaskManager — 任务管理器

**文件**: `src/timer/taskManager.js`

内存级任务注册表，结构为 `{ taskId: heartbeatTimestamp }`。

| 方法                 | 说明                     |
| -------------------- | ------------------------ |
| `addTask(taskId)`    | 注册任务，重复注册抛异常 |
| `heartbeat(taskId)`  | 更新心跳时间戳           |
| `deleteTask(taskId)` | 移除任务                 |
| `has(taskId)`        | 判断任务是否存在         |
| `count()`            | 当前任务数量             |

> 配合 main.js 的定时器 B 实现"僵尸任务"自动回收：心跳超过 120 秒未更新即清除。

### 6.3 MakeVideoTimer — 任务编排器

**文件**: `src/timer/makeVideoTimer.js`

完整的任务生命周期管理，是整个服务的**编排核心**。

#### 关键方法

| 方法                      | 职责                                                    |
| ------------------------- | ------------------------------------------------------- |
| `start()`                 | 入口：拉取 → 合成 → 上报，含 try/catch/finally 完整收尾 |
| `checkAllowGetTask()`     | 资源准入检查（内存、负载、并发数）                      |
| `getCreateVideoTask()`    | 向网关 POST 拉取任务                                    |
| `doMakeVideo()`           | 编排视频合成全流程                                      |
| `reportMakeVideoResult()` | 向业务系统上报结果                                      |

#### 资源准入检查逻辑

```
内存检查: 可用率 < 10% 且绝对值 < 500MB → 拒绝接单
负载检查: loadavg > CPU核数 × 2 → 拒绝接单  (Windows 跳过)
并发检查: 当前任务数 >= CPU核数 → 拒绝接单
```

#### musicCode 白名单机制

只有特定 `appName`（`mspdd`、`mspddsp`）才能使用内置音频编码，其他应用会被强制清空 `musicCode`，仅支持自定义 `musicPath`。

### 6.4 VideoMaker — 视频合成服务

**文件**: `src/services/videoMaker.js`

封装 `simple-ffmpegjs`，将图片 + 音乐转化为 MP4。

#### 工作流程

1. **初始化路径**：在 `os.tmpdir()/video/{date}/` 下创建工作目录
2. **下载图片**：并行下载所有图片到临时目录
3. **构建 clips**：每张图片 → `{ type:'image', url, duration, imageFit:'cover', transition, kenBurns }`
4. **添加音频**：内置音乐 `audio/{musicCode}.mp3` 或自定义路径，`{ type:'music', volume:0.5, loop:true }`
5. **渲染导出**：`project.load(clips)` → `project.export(options)`

#### 编码器自适应

```javascript
// 从配置读取编码器，兼容无 libx264 的环境
videoEncoder = config.videoEncoder || "libx264";

if (encoder == "libx264" || encoder == "libx265") {
  crf = 18; // CRF 质量控制
} else {
  videoBitrate = "8M"; // 高码率保证质量
}
```

#### 文件命名规则

```
saveFilename = {goodsId}_{videoEffect}_{musicCode}_{timestamp}
mp4 文件    = {saveFilename}.mp4
oss 路径    = make_video/{appName}/{date}/{saveFilename}.mp4
```

### 6.5 OssTool — 多云存储抽象层

**文件**: `src/tool/ossTool.js`

单例模式，根据 `config.idc` 自动选择云厂商 SDK。

#### 支持的云厂商

| idc 值           | 云厂商       | SDK                 | 客户端    |
| ---------------- | ------------ | ------------------- | --------- |
| `ALIYUN`（默认） | 阿里云 OSS   | ali-oss             | OSS       |
| `TENCENT`        | 腾讯云 COS   | cos-nodejs-sdk-v5   | COS       |
| `VOLC`           | 火山引擎 TOS | @volcengine/tos-sdk | TosClient |
| `jcloud`         | 京东云       | aws-sdk (S3 兼容)   | AWS.S3    |

#### 上传统一接口

```javascript
ossTool.uploadFile(ossPath, localFile);
// 返回: { result:'success', name, url, internalUrl }
```

所有厂商上传后均返回统一的 `url`（公网）和 `internalUrl`（内网）。

### 6.6 CommonTool — 通用工具集

**文件**: `src/tool/commonTool.js`

| 方法                          | 功能                                                  |
| ----------------------------- | ----------------------------------------------------- |
| `downloadImage(url)`          | 下载图片到临时目录，返回本地路径                      |
| `downloadAudio(url)`          | 下载音频到临时目录（有缓存）                          |
| `getApiCryptoInfo()`          | 生成 API 签名（MD5: `timestamp + token + timestamp`） |
| `checkSystemMemIsAvailable()` | 检查系统可用内存（跨平台）                            |
| `checkSystemLoadavg()`        | 检查系统负载（Linux 读取 `/proc/loadavg`）            |
| `getMaxCreateTaskCount()`     | 返回 CPU 核数作为最大并发                             |
| `dateFormat(fmt)`             | 日期格式化                                            |
| `successResult / failResult`  | 统一返回结构封装                                      |

#### API 签名算法

```
plaintext = timestamp + jxGroupToken + timestamp
sign      = MD5(plaintext)
```

请求时携带 `timestamp` 和 `sign` 供服务端校验。

---

## 7. 配置说明

### 7.1 配置加载机制

**文件**: `src/conf/config.js`

```javascript
// 生产环境路径: /alidata/s/Zk.node-video-v2.json  (外部配置中心)
// 本地开发路径: src/conf/Zk.node-video-v2.json
// 生产环境自动覆盖 rootDir → /alidata/www/node-video-v3
```

配置在模块加载时同步读取（`fs.readFileSync`），进程启动后固定。PM2 的 `watch` 配置会在配置文件变更时自动重启。

### 7.2 配置项说明

**文件**: `src/conf/Zk.node-video-v2.json`

| 配置项             | 类型   | 说明                                 |
| ------------------ | ------ | ------------------------------------ |
| `yktGateway`       | string | 业务网关地址（拉取任务 + 签名校验）  |
| `jxGroupToken`     | string | API 签名令牌                         |
| `jxGroupTokenList` | array  | 签名令牌列表（备用）                 |
| `syncTaskCnt`      | number | 同步任务数（遗留配置）               |
| `ffmpegPath`       | string | FFmpeg 二进制路径                    |
| `rootDir`          | string | 项目根目录（用于定位 audio 目录）    |
| `idc`              | string | 数据中心标识，决定 OSS 厂商          |
| `videoEncoder`     | string | 视频编码器（`libx264` / `mpeg4` 等） |
| `ALIYUN`           | object | 阿里云 OSS 配置                      |
| `TENCENT`          | object | 腾讯云 COS 配置                      |
| `VOLC`             | object | 火山引擎 TOS 配置                    |
| `JCLOUD`           | object | 京东云 S3 配置                       |

### 7.3 各云厂商配置结构

每个厂商配置块包含以下字段（`ACCESS_KEY` / `SECRET_KEY` 需替换为真实值）：

```json
{
  "ACCESS_KEY": "your_access_key",
  "SECRET_KEY": "your_secret_key",
  "BUCKET": "bucket_name",
  "OSS_REGION": "region",
  "ENDPOINT": "https://endpoint",
  "OSS_DOMAIN": "https://public.domain/",
  "OSS_INTERNAL_DOMAIN": "https://internal.domain/"
}
```

---

## 8. 对外 API 接口

本服务 **不提供 HTTP 服务端**，作为客户端主动调用以下接口：

### 8.1 拉取任务

```
POST {yktGateway}/api/common/canvas_video_node_gateway/get_video_create_task_info
Content-Type: application/x-www-form-urlencoded

hostname={hostname}&maxTaskNum={cpuCount}&timestamp={ts}&sign={md5}
```

**响应：**

```json
{
  "result": "success",
  "data": {
    "taskInfo": {
      "taskId": "unique_task_id",
      "videoConfig": {
        "appName": "mspdd",
        "imageUrlList": "[\"https://...\"]",
        "videoEffect": "sceneFade",
        "musicCode": "bhp1",
        "musicPath": "",
        "videoSeconds": 9,
        "width": 800,
        "height": 800,
        "goodsId": 12345
      },
      "apiReportSuccessUrl": "https://.../report"
    }
  }
}
```

### 8.2 上报结果

```
POST {apiReportSuccessUrl}
Content-Type: application/x-www-form-urlencoded

taskId={id}&status={finish|fail}&hostname={name}
&ossUrl={url}&internaOssUrl={url}&timeStat={json}&reason={text}
```

---

## 9. 部署运维

### 9.1 环境要求

- **Node.js** ≥ 16
- **FFmpeg** 已安装（含 libx264 编码器或备用编码器）
- **PM2** 全局安装

### 9.2 PM2 配置解读

**文件**: `pm2.json`

| 配置项                   | 值                | 说明             |
| ------------------------ | ----------------- | ---------------- |
| `exec_mode`              | fork              | 单进程模式       |
| `instances`              | 1                 | 单实例           |
| `max_memory_restart`     | 512M              | 内存超限自动重启 |
| `autorestart`            | true              | 崩溃自动重启     |
| `watch`                  | `src/` + 配置文件 | 文件变更自动重启 |
| `watch_options.interval` | 3000ms            | 轮询间隔         |

### 9.3 部署步骤

```bash
# 1. 安装依赖
npm install

# 2. 配置密钥
# 编辑 src/conf/Zk.node-video-v2.json，填入真实的 ACCESS_KEY / SECRET_KEY / jxGroupToken
# 生产环境将配置放到 /alidata/s/Zk.node-video-v2.json

# 3. 本地运行
npm start

# 4. 生产部署 (PM2)
pm2 start pm2.json
pm2 save
pm2 startup    # 开机自启

# 5. 常用命令
pm2 logs node-video-v3          # 查看日志
pm2 restart node-video-v3       # 重启
pm2 monit                       # 监控面板
```

### 9.4 日志路径（生产）

```
输出日志: /alidata/log/node-video-v3-biz/node.log
错误日志: /alidata/log/node-video-v3-biz/node-error.log
```

### 9.5 临时文件路径

| 内容       | 路径                                                |
| ---------- | --------------------------------------------------- |
| 下载的图片 | `{tmpdir}/node-video/images/{timestamp}_{filename}` |
| 自定义音乐 | `{tmpdir}/node-video/custom_music/{filename}`       |
| 合成的视频 | `{tmpdir}/video/{date}/{saveFilename}.mp4`          |

> 任务完成后自动清理图片和视频；自定义音乐有缓存（存在则跳过下载）。

---

## 10. 开发指南

### 10.1 本地开发

```bash
# 1. 安装 FFmpeg
# Windows: 下载 ffmpeg.exe 并加入 PATH
# Linux:   apt-get install ffmpeg

# 2. 安装依赖
npm install

# 3. 修改配置
# 编辑 src/conf/Zk.node-video-v2.json:
#   - idc 设为你的云厂商
#   - 填入对应的 ACCESS_KEY / SECRET_KEY
#   - 设置 yktGateway 和 jxGroupToken

# 4. 启动
npm start
```

### 10.2 添加新的转场效果

1. 在 `videoMaker.js` 的 `TRANSITION_MAP` 中添加映射：

```javascript
const TRANSITION_MAP = {
  // ...existing
  sceneYourEffect: { type: "fadegrays", duration: TRANSITION_DURATION },
};
```

2. （可选）在 `KEN_BURNS_MAP` 中添加 Ken Burns 效果。

3. 参考 [simple-ffmpegjs xfade](https://trac.ffmpeg.org/wiki/Xfade) 支持的转场类型。

### 10.3 添加新的云存储厂商

1. 在 `Zk.node-video-v2.json` 中添加厂商配置块。
2. 在 `ossTool.js` 构造函数中添加分支：

```javascript
} else if (this.idc === 'YOUR_IDC') {
  const yourConfig = config.YOUR_IDC || {};
  this.yourConfig = yourConfig;
  this.ossClient = new YourSDK({ /* credentials */ });
}
```

3. 在 `uploadFile()` 和 `deleteFile()` 中添加对应分支。
4. 返回统一的 `{ result, name, url, internalUrl }` 结构。

### 10.4 调整视频质量参数

修改 `videoMaker.js` 中 `buildVideo()` 的 `exportOptions`：

```javascript
let exportOptions = {
  outputPath: this.mp4FilePath,
  videoCodec: videoEncoder,
  preset: "slow", // ← 改为 'medium' 或 'fast' 提速
  audioBitrate: "256k", // ← 降低为 '128k' 减小体积
  crf: 18, // ← 提高(如 23)降低质量但减小体积
};
```

### 10.5 关键注意事项

| 注意点         | 说明                                                          |
| -------------- | ------------------------------------------------------------- |
| **内存管理**   | 每个任务完成后必须调用 `cleanup()` 清理临时文件，否则磁盘会满 |
| **编码器兼容** | 部分环境 FFmpeg 无 libx264，配置 `videoEncoder: "mpeg4"` 降级 |
| **并发控制**   | 最大并发 = CPU 核数，视频编码是 CPU 密集型任务                |
| **HTTPS**      | 所有外部请求关闭了证书校验（`rejectUnauthorized: false`）     |
| **配置热更新** | PM2 watch 配置文件变更，修改后会自动重启服务                  |
| **音频白名单** | 仅 `mspdd`、`mspddsp` 应用可使用内置音频库                    |

---

_文档生成时间：2026-08-07 | 项目版本：1.0.0_
