915339d42a
Frontend fixes: - fix(VideoCompose): clear step dirty flag after compose success - refactor(MyWorks): play product videos directly without transcode cache - feat(CoverDesign): swap main/subtitle positions in cover preview - fix(SubtitleBurning): charge points after burn success instead of before - fix(VoiceSynthesis/VideoGeneration/SubtitleBurning/CoverDesign): mark downstream steps dirty on re-generation - fix(MyWorks): bind video event listeners after async videoUrl load - fix(CoverDesign): revoke Blob URLs on upload/unmount to prevent memory leak
677 lines
23 KiB
Markdown
677 lines
23 KiB
Markdown
# 前端系统兼容性审查报告
|
||
|
||
> 审查范围:`tauri-app/src` 全部源码 + `tauri-app/src-tauri/src` Rust 层命令
|
||
> 审查维度:跨平台(macOS/Windows)、Tauri API、媒体/音频、CSS、网络、文件系统
|
||
> 审查日期:2026-05-21
|
||
|
||
---
|
||
|
||
## 一、综述
|
||
|
||
本次审查共发现 **28 项兼容性问题**,其中:
|
||
|
||
| 级别 | 数量 | 说明 |
|
||
|------|------|------|
|
||
| 🔴 严重 | 6 | 可能导致功能失效、安全漏洞或数据损坏 |
|
||
| 🟡 中等 | 11 | 潜在风险,特定场景下会触发问题 |
|
||
| 🟢 低风险 | 11 | 建议优化,影响较小或仅存在于边缘场景 |
|
||
|
||
**关键结论**:
|
||
1. **Windows 路径处理是最大隐患**:多处 Rust 代码对 Windows 路径的反斜杠、大小写、UNC 前缀处理不完善,可能导致 FFmpeg 调用失败或安全检查被绕过。
|
||
2. **前端内存泄漏已确认 1 处**:`CoverDesign.tsx` 的 `URL.createObjectURL` 未释放。
|
||
3. **Asset Protocol 过度授权**:`tauri.conf.json` 中 `"scope": "/**"` 允许 WebView 读取整个文件系统。
|
||
4. **CSS/Web API 兼容性良好**:项目运行在 Tauri 封装的 WebView(Edge/WebKit)中,现代 CSS 特性和 Web API 支持度较高,未发现严重兼容性问题。
|
||
|
||
---
|
||
|
||
## 二、🔴 严重问题(6 项)
|
||
|
||
### 1. `URL.createObjectURL` 内存泄漏 — 背景图上传
|
||
|
||
**位置**:`tauri-app/src/pages/VideoCreation/CoverDesign.tsx:181`
|
||
|
||
```typescript
|
||
const url = URL.createObjectURL(file);
|
||
setConfig(prev => ({ ...prev, backgroundImage: url }));
|
||
```
|
||
|
||
**问题**:本地上传背景图时创建 Blob URL,但**从未调用 `URL.revokeObjectURL(url)`**。用户多次上传不同背景图时,旧的 Blob URL 会一直占用内存,直到页面刷新。
|
||
|
||
**影响**:内存泄漏,长时间使用后可能导致应用卡顿或崩溃。
|
||
|
||
**修复建议**:
|
||
```typescript
|
||
const prevUrl = config.backgroundImage;
|
||
const url = URL.createObjectURL(file);
|
||
setConfig(prev => ({ ...prev, backgroundImage: url }));
|
||
// 释放旧的 Blob URL
|
||
if (prevUrl?.startsWith('blob:')) {
|
||
URL.revokeObjectURL(prevUrl);
|
||
}
|
||
// 组件卸载时也要清理
|
||
useEffect(() => {
|
||
return () => {
|
||
if (config.backgroundImage?.startsWith('blob:')) {
|
||
URL.revokeObjectURL(config.backgroundImage);
|
||
}
|
||
};
|
||
}, []);
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Windows 敏感路径检查大小写不敏感问题
|
||
|
||
**位置**:`tauri-app/src-tauri/src/commands/file.rs:47`
|
||
|
||
```rust
|
||
let windows_denied = vec![
|
||
r"c:\windows\",
|
||
r"c:\program files\",
|
||
r"c:\program files (x86)\",
|
||
r"c:\users\all users\",
|
||
];
|
||
```
|
||
|
||
**问题**:Windows 文件系统(NTFS)是**大小写保留但大小写不敏感**的。用户传入 `C:\Windows\` 或 `C:\WINDOWS\` 会**完全绕过**上述安全检查。
|
||
|
||
**影响**:攻击者可通过大小写变体访问系统敏感目录。
|
||
|
||
**修复建议**:
|
||
```rust
|
||
let path_lower = path.to_lowercase();
|
||
let windows_denied = vec![
|
||
r"c:\windows\",
|
||
r"c:\program files\",
|
||
r"c:\program files (x86)\",
|
||
r"c:\users\all users\",
|
||
];
|
||
for denied in &windows_denied {
|
||
if path_lower.starts_with(denied) {
|
||
return Err(...);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Asset Protocol 范围过度授权
|
||
|
||
**位置**:`tauri-app/src-tauri/tauri.conf.json`
|
||
|
||
```json
|
||
"assetProtocol": {
|
||
"enable": true,
|
||
"scope": ["$APPLOCALDATA/**", "$APPDATA/**", "$APPCONFIG/**", "/**"]
|
||
}
|
||
```
|
||
|
||
**问题**:`/**` 允许 WebView 通过 `asset://` 协议读取**整个文件系统的任何文件**。这意味着前端 JavaScript 可以构造 URL 访问用户的任何本地文件(如 `asset:///etc/passwd` 或 `asset://C:/Users/xxx/Documents/`)。
|
||
|
||
**影响**:严重安全漏洞。即使需要配合路径遍历,也极大扩大了攻击面。
|
||
|
||
**修复建议**:移除 `/**`,仅保留应用数据目录:
|
||
```json
|
||
"assetProtocol": {
|
||
"enable": true,
|
||
"scope": ["$APPLOCALDATA/**", "$APPDATA/**", "$APPCONFIG/**"]
|
||
}
|
||
```
|
||
|
||
> 注:如果确有需要访问用户选择的文件,应通过 Tauri Dialog API 让用户主动选择,而非开放全局文件系统。
|
||
|
||
---
|
||
|
||
### 4. `escape_ffmpeg_path` 不支持 Windows 路径格式
|
||
|
||
**位置**:`tauri-app/src-tauri/src/ffmpeg_cmd.rs:23`
|
||
|
||
```rust
|
||
fn escape_ffmpeg_path(path: &str) -> String {
|
||
path.replace("'", "'\\''")
|
||
}
|
||
```
|
||
|
||
**问题**:该函数仅转义单引号,但**不处理 Windows 反斜杠 `\` 和盘符冒号 `:`**。在 FFmpeg 的 `ass='{}':fontsdir='{}'` filter 语法或 concat demuxer 的 `file 'path'` 格式中,Windows 路径如 `C:\Users\name\video.mp4` 中的反斜杠可能被 FFmpeg 解析为转义序列。
|
||
|
||
**影响**:Windows 用户在字幕压制、字体加载、视频合成时,FFmpeg 可能因路径解析错误而失败。
|
||
|
||
**修复建议**:
|
||
```rust
|
||
fn escape_ffmpeg_path(path: &str) -> String {
|
||
// 1. 统一使用正斜杠(FFmpeg 支持跨平台路径分隔符)
|
||
let normalized = path.replace('\\', "/");
|
||
// 2. 转义单引号(用于 FFmpeg filter 语法中的引号包裹)
|
||
normalized.replace("'", "'\\''")
|
||
}
|
||
```
|
||
|
||
> 注意:Windows 上 `C:/Users/...` 这种正斜杠路径 FFmpeg 完全支持,这是最简单的跨平台方案。
|
||
|
||
---
|
||
|
||
### 5. `canonicalize()` 在 Windows 上返回 UNC 路径导致下游问题
|
||
|
||
**位置**:多处使用 `std::fs::canonicalize`
|
||
|
||
- `tauri-app/src-tauri/src/commands/product.rs:198,258,345`
|
||
- `tauri-app/src-tauri/src/commands/project.rs:124,140`
|
||
- `tauri-app/src-tauri/src/ffmpeg_cmd.rs:46,792`
|
||
|
||
**问题**:在 Windows 上,`std::fs::canonicalize()` 返回 UNC 路径格式 `\\?\C:\Users\...`。这种路径格式:
|
||
1. **FFmpeg 某些版本不支持**,可能导致命令执行失败
|
||
2. **与 `starts_with` 比较时行为异常**,如果比较路径不是 UNC 格式
|
||
3. **序列化到 JSON 传给前端时**,前端可能无法正确理解这种路径
|
||
|
||
**影响**:Windows 上的文件校验、路径比较、FFmpeg 调用可能全部受影响。
|
||
|
||
**修复建议**:封装一个跨平台的 `normalize_path` 函数,替代 `canonicalize`:
|
||
```rust
|
||
use std::path::{Path, PathBuf};
|
||
|
||
fn normalize_path(path: &Path) -> PathBuf {
|
||
// 使用 dunce::simplified() 消除 UNC 前缀,同时保持路径有效性
|
||
dunce::simplified(path).to_path_buf()
|
||
}
|
||
```
|
||
|
||
> 需要添加 `dune` crate 依赖,这是 Rust 社区处理 UNC 路径的标准方案。
|
||
|
||
---
|
||
|
||
### 6. `atob()` 解析 JWT 存在 base64url 兼容性问题
|
||
|
||
**位置**:`tauri-app/src/api/client.ts:116`
|
||
|
||
```typescript
|
||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||
```
|
||
|
||
**问题**:JWT 使用 **base64url** 编码(将 `+` → `-`,`/` → `_`,去掉 padding `=`),而 `atob()` 是标准 **base64** 解码器。如果 JWT payload 中包含 `-`、`_` 或需要 padding 的字符,`atob()` 会抛出 `DOMException`。
|
||
|
||
**当前影响有限**:因为 `exp` 字段通常是纯数字时间戳,但理论上如果用户 ID 或其他 claim 包含这些字符就会失败。
|
||
|
||
**修复建议**:
|
||
```typescript
|
||
function base64UrlDecode(str: string): string {
|
||
// base64url → base64
|
||
let padding = '';
|
||
const padLen = 4 - (str.length % 4);
|
||
if (padLen !== 4) {
|
||
padding = '='.repeat(padLen);
|
||
}
|
||
const base64 = str.replace(/-/g, '+').replace(/_/g, '/') + padding;
|
||
return atob(base64);
|
||
}
|
||
|
||
// 使用
|
||
const payload = JSON.parse(base64UrlDecode(token.split('.')[1]));
|
||
```
|
||
|
||
---
|
||
|
||
## 三、🟡 中等问题(11 项)
|
||
|
||
### 7. `crossOrigin = 'anonymous'` 跨域图片污染 Canvas
|
||
|
||
**位置**:
|
||
- `tauri-app/src/hooks/useCoverFabric.ts:192,235`
|
||
|
||
```typescript
|
||
image.crossOrigin = 'anonymous';
|
||
image.src = imagePath;
|
||
```
|
||
|
||
**问题**:当加载远程 HTTP(S) 图片时,如果服务器未配置 `Access-Control-Allow-Origin` 响应头,Canvas 会被**污染(tainted)**。被污染的 Canvas 调用 `toDataURL()` 会抛出 `SecurityError: The canvas has been tainted by cross-origin data`。
|
||
|
||
当前代码用 try-catch 静默吞掉了错误,用户会看到空白封面,但不知道原因。
|
||
|
||
**修复建议**:捕获错误并向用户提示:
|
||
```typescript
|
||
try {
|
||
// ...加载图片...
|
||
} catch (err) {
|
||
console.error('封面图片加载失败:', err);
|
||
toast.error('封面图片加载失败,可能是跨域限制或图片链接失效');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 8. `requestAnimationFrame` + Video 字幕同步在后台标签页节流
|
||
|
||
**位置**:`tauri-app/src/hooks/useCanvasSubtitleRenderer.ts:158-168`
|
||
|
||
```typescript
|
||
const onFrame = () => {
|
||
drawFrame();
|
||
if (!video.paused) {
|
||
rafRef.current = requestAnimationFrame(onFrame);
|
||
}
|
||
};
|
||
```
|
||
|
||
**问题**:当应用窗口不在前台或标签页在后台时,浏览器会**节流 `requestAnimationFrame`**(通常降到 1fps 或完全暂停)。这会导致 Canvas 字幕与视频画面不同步。
|
||
|
||
**影响**:用户切出应用再切回时,字幕可能短暂错位。
|
||
|
||
**修复建议**:使用 `video.requestVideoFrameCallback()`(如果支持)作为更精确的同步机制,或在 `visibilitychange` 事件触发时强制重绘:
|
||
```typescript
|
||
useEffect(() => {
|
||
const onVisibilityChange = () => {
|
||
if (!document.hidden) drawFrame();
|
||
};
|
||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||
return () => document.removeEventListener('visibilitychange', onVisibilityChange);
|
||
}, [drawFrame]);
|
||
```
|
||
|
||
---
|
||
|
||
### 9. `video` 控制条高度硬编码导致字幕定位偏移
|
||
|
||
**位置**:`tauri-app/src/hooks/useCanvasSubtitleRenderer.ts:86`
|
||
|
||
```typescript
|
||
const VIDEO_CONTROLS_HEIGHT = 40;
|
||
```
|
||
|
||
**问题**:`<video controls>` 的控制条高度在不同浏览器/OS 上不同(macOS Safari 约 30px,Windows 约 40-50px,全屏模式约 0px)。硬编码 40px 会导致字幕在预览时的垂直位置与压制输出不完全一致。
|
||
|
||
**修复建议**:在视频元数据加载后动态计算:
|
||
```typescript
|
||
const video = videoRef.current;
|
||
if (video) {
|
||
const rect = video.getBoundingClientRect();
|
||
const videoRect = video.videoWidth / video.videoHeight;
|
||
// 实际视频画面高度 = 容器宽度 / 宽高比
|
||
const actualVideoHeight = rect.width / videoRect;
|
||
const controlsHeight = rect.height - actualVideoHeight;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 10. `audio.duration` 可能返回 `NaN`/`Infinity` 未处理
|
||
|
||
**位置**:
|
||
- `tauri-app/src/pages/VideoCreation/VoiceSynthesis.tsx:360-367`
|
||
- `tauri-app/src/pages/VideoGeneration/hooks/useVideoGeneration.ts:371-378`
|
||
- `tauri-app/src/pages/ContentManagement/VoiceMaterialLibrary.tsx:163-178`
|
||
|
||
```typescript
|
||
audio.onloadedmetadata = () => {
|
||
clearTimeout(timeoutId);
|
||
resolve(audio.duration);
|
||
};
|
||
```
|
||
|
||
**问题**:如果音频文件损坏、格式不支持或元数据缺失,`audio.duration` 可能返回 `NaN` 或 `Infinity`。直接 resolve 这个值会导致下游计算错误。
|
||
|
||
**修复建议**:
|
||
```typescript
|
||
audio.onloadedmetadata = () => {
|
||
clearTimeout(timeoutId);
|
||
if (!isFinite(audio.duration) || audio.duration <= 0) {
|
||
reject(new Error('音频时长无效,文件可能损坏或格式不支持'));
|
||
} else {
|
||
resolve(audio.duration);
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 11. `rename` 在 Windows 上目标已存在时失败
|
||
|
||
**位置**:
|
||
- `tauri-app/src-tauri/src/video_processing.rs:93`
|
||
- `tauri-app/src-tauri/src/commands/video_compose.rs:222`
|
||
|
||
**问题**:`std::fs::rename()` 在 Windows 上**如果目标文件已存在会直接失败**(Unix 是原子替换)。代码虽然有 copy 回退,但逻辑可能留下残留文件。
|
||
|
||
**影响**:Windows 上如果输出路径已存在(如用户重复合成),操作可能失败或留下临时文件。
|
||
|
||
**修复建议**:在 `rename` 前先删除目标文件(如果存在):
|
||
```rust
|
||
if output_path.exists() {
|
||
std::fs::remove_file(&output_path)?;
|
||
}
|
||
std::fs::rename(&temp_output, &output_path)?;
|
||
```
|
||
|
||
---
|
||
|
||
### 12. `to_str().unwrap()` 在非 UTF-8 路径上会 panic
|
||
|
||
**位置**:`tauri-app/src-tauri/src/commands/video_compose.rs:80`
|
||
|
||
```rust
|
||
concat_videos_copy(&app, list_path.to_str().unwrap(), ...)
|
||
```
|
||
|
||
**问题**:Windows 允许非 UTF-8 编码的文件路径(历史 OEM code page 文件)。`to_str()` 返回 `None`,`unwrap()` 会**直接 panic**。
|
||
|
||
**影响**:极少数 Windows 用户(使用中文 Windows 95/XP 时代遗留文件系统编码)可能导致应用崩溃。
|
||
|
||
**修复建议**:使用 `to_string_lossy()` 或 `as_os_str()` 传递路径:
|
||
```rust
|
||
// 如果需要传给 FFmpeg,使用 to_string_lossy()
|
||
let path_str = list_path.to_string_lossy();
|
||
```
|
||
|
||
---
|
||
|
||
### 13. `get_fonts_dir` 开发模式路径探测在 Windows 上可能失效
|
||
|
||
**位置**:`tauri-app/src-tauri/src/ffmpeg_cmd.rs:298-328`
|
||
|
||
```rust
|
||
cwd.join("fonts"),
|
||
parent.join("src-tauri/fonts"),
|
||
grandparent.join("tauri-app/src-tauri/fonts"),
|
||
```
|
||
|
||
**问题**:开发模式下的字体目录探测使用 `/` 路径拼接。虽然 `PathBuf::join` 会处理分隔符,但如果开发时的**当前工作目录**与预期不同(如从 IDE 以不同路径启动),探测会失败。
|
||
|
||
**影响**:开发环境下 Windows 开发者可能遇到字体加载失败。
|
||
|
||
**修复建议**:添加环境变量覆盖或更健壮的探测逻辑:
|
||
```rust
|
||
// 优先从环境变量读取
|
||
if let Ok(font_dir) = std::env::var("MEIJIAKA_FONTS_DIR") {
|
||
let p = PathBuf::from(font_dir);
|
||
if p.exists() { return Some(p); }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 14. `concat` demuxer 列表中的 Windows 反斜杠问题
|
||
|
||
**位置**:`tauri-app/src-tauri/src/commands/video_compose.rs:65`
|
||
|
||
```rust
|
||
format!("file '{}'\n", ffmpeg_cmd::escape_ffmpeg_path(path))
|
||
```
|
||
|
||
**问题**:FFmpeg concat demuxer 的列表文件格式中,`file 'path'` 语法在 Windows 上如果路径包含反斜杠,反斜杠可能被 FFmpeg 解释为转义字符。
|
||
|
||
**影响**:与问题 #4 类似,Windows 路径导致 FFmpeg 解析错误。
|
||
|
||
**修复建议**:在写入 concat 列表前统一将路径中的 `\` 替换为 `/`:
|
||
```rust
|
||
let normalized = path.replace('\\', "/");
|
||
format!("file '{}'\n", normalized)
|
||
```
|
||
|
||
---
|
||
|
||
### 15. `load_app_config` 失败时无降级配置
|
||
|
||
**位置**:`tauri-app/src/main.tsx:10-16`
|
||
|
||
```typescript
|
||
try {
|
||
const config = await loadAppConfig();
|
||
appEnvironment = config.environment;
|
||
} catch {
|
||
// 加载失败时默认为生产模式
|
||
}
|
||
```
|
||
|
||
**问题**:如果 `load_app_config`(Tauri IPC 调用)失败,应用降级为生产模式。这是合理的,但**生产模式会禁用右键菜单和 F12 DevTools**。开发者在调试时如果 IPC 调用失败,会突然失去所有调试能力,且不知道原因。
|
||
|
||
**修复建议**:在降级时输出警告日志:
|
||
```typescript
|
||
} catch (e) {
|
||
console.warn('[bootstrap] 加载应用配置失败,降级为生产模式:', e);
|
||
appEnvironment = 'production';
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 16. `window.location.reload()` 在 Tauri 中行为不确定
|
||
|
||
**位置**:`tauri-app/src/pages/Settings/Settings.tsx:178`
|
||
|
||
```typescript
|
||
setTimeout(() => { window.location.reload(); }, 500);
|
||
```
|
||
|
||
**问题**:Tauri 应用中的 `window.location.reload()` 行为与浏览器不同。在某些 Tauri 版本中可能导致:
|
||
- 白屏而非正常刷新
|
||
- WebView 进程崩溃
|
||
- 状态丢失但窗口不重新加载
|
||
|
||
**修复建议**:使用 Tauri 的 `relaunch()` 命令重启整个应用,或重新挂载 React 根组件:
|
||
```typescript
|
||
import { relaunch } from '@tauri-apps/plugin-process';
|
||
// 重启应用
|
||
await relaunch();
|
||
```
|
||
|
||
---
|
||
|
||
### 17. `Math.random()` 用于缓存清除参数(安全性)
|
||
|
||
**位置**:`tauri-app/src/api/client.ts:334`
|
||
|
||
```typescript
|
||
const cacheBuster = `_t=${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||
```
|
||
|
||
**问题**:`Math.random()` 不是加密安全的随机数生成器。虽然这里只是用于缓存清除,但如果未来用于其他安全相关场景会有风险。
|
||
|
||
**修复建议**:使用 `crypto.randomUUID()` 或 `crypto.getRandomValues()`:
|
||
```typescript
|
||
const cacheBuster = `_t=${Date.now()}_${crypto.randomUUID().slice(0, 8)}`;
|
||
```
|
||
|
||
---
|
||
|
||
## 四、🟢 低风险/建议(11 项)
|
||
|
||
### 18. `backdrop-filter` 无标准前缀回退
|
||
|
||
**位置**:`tauri-app/src/pages/VideoCreation/CoverDesign.css:444-445`
|
||
|
||
```css
|
||
backdrop-filter: blur(6px);
|
||
-webkit-backdrop-filter: blur(6px);
|
||
```
|
||
|
||
**评估**:当前代码同时有标准和 WebKit 前缀版本,在 Tauri WebView(Edge/WebKit)中支持良好。Firefox 不支持,但项目不面向 Firefox。**无需修复**。
|
||
|
||
---
|
||
|
||
### 19. `::-webkit-scrollbar` 在 Firefox 中无效
|
||
|
||
**位置**:多处(`global.css`、`CoverDesign.css` 等)
|
||
|
||
**评估**:项目运行在 Tauri WebView 中(基于系统浏览器引擎),不是 Firefox。在 Windows 上基于 WebView2(Edge),macOS 上基于 WKWebView(Safari),均支持 WebKit 滚动条样式。**无需修复**。
|
||
|
||
---
|
||
|
||
### 20. `aspect-ratio` 在旧版 Safari 中可能不支持
|
||
|
||
**位置**:多处使用 `aspect-ratio: 9 / 16`
|
||
|
||
**评估**:macOS 12+ 的 Safari 支持 `aspect-ratio`。如果目标用户可能使用较旧的 macOS 版本,可能需要 `padding-top: 177.77%` 回退。但鉴于这是 Tauri 桌面应用,可以控制最低系统版本。**建议确认 `tauri.conf.json` 中 `macOS.minimumSystemVersion` 是否要求 12.0+**。
|
||
|
||
---
|
||
|
||
### 21. `requestIdleCallback` 缺失回退不完整
|
||
|
||
**位置**:`tauri-app/src/main.tsx:75-79`
|
||
|
||
```typescript
|
||
if ('requestIdleCallback' in window) {
|
||
requestIdleCallback(showWindow, { timeout: 500 });
|
||
} else {
|
||
setTimeout(showWindow, 100);
|
||
}
|
||
```
|
||
|
||
**评估**:Tauri WebView2(Edge)和 WKWebView(Safari)均支持 `requestIdleCallback`。回退逻辑也已实现。**无需修复**。
|
||
|
||
---
|
||
|
||
### 22. `navigator.userAgent` 已被冻结
|
||
|
||
**位置**:
|
||
- `tauri-app/src/store/authStore.ts:254`
|
||
- `tauri-app/src/api/client.ts:259`
|
||
|
||
**评估**:虽然现代浏览器正在限制 `navigator.userAgent`,但 Tauri WebView 不受此限制。且当前用法仅为日志和登录信息上报,不影响功能。**无需修复**。
|
||
|
||
---
|
||
|
||
### 23. `document.fonts.check()` 参数格式兼容性
|
||
|
||
**位置**:`tauri-app/src/utils/canvasSubtitleDrawer.ts:209`
|
||
|
||
```typescript
|
||
if (document.fonts.check(`bold 16px ${fontName}`)) {
|
||
```
|
||
|
||
**评估**:`document.fonts.check()` 的参数格式在不同浏览器中实现有细微差异,但 Tauri WebView2/WKWebView 均支持此用法。**风险极低**。
|
||
|
||
---
|
||
|
||
### 24. `Date.now()` 连续调用可能冲突
|
||
|
||
**位置**:多处使用 `Date.now()` 生成文件名
|
||
|
||
**评估**:仅在极快速连续调用时(<1ms)可能冲突。当前场景下不太可能。**风险极低**。
|
||
|
||
---
|
||
|
||
### 25. `autoPlay` 视频可能被浏览器阻止
|
||
|
||
**位置**:多处 `<video autoPlay>`
|
||
|
||
**评估**:桌面应用中的 WebView 通常不受浏览器自动播放策略限制。但如果用户操作系统设置了辅助功能限制,仍可能被阻止。**建议添加 `muted` 属性作为后备**(如果需要自动播放且带声音)。
|
||
|
||
---
|
||
|
||
### 26. `file.path` 是非标准 Chromium 属性
|
||
|
||
**位置**:`tauri-app/src/pages/VideoCreation/CoverDesign.tsx:178`
|
||
|
||
```typescript
|
||
const path = (file as any).path || (file as any).webkitRelativePath || '';
|
||
```
|
||
|
||
**评估**:`File.path` 是 Chromium 的私有属性,在标准浏览器(Firefox)中不存在。但由于项目运行在 Tauri(Chromium/WebView2)中,这**当前是可行的**。但如果未来需要支持 Web 端部署,需要改用 Tauri Dialog API 获取路径。**建议添加注释说明此依赖**。
|
||
|
||
---
|
||
|
||
### 27. `storage/engine.rs` 无 Windows 文件权限设置
|
||
|
||
**位置**:`tauri-app/src-tauri/src/storage/engine.rs:161`
|
||
|
||
```rust
|
||
#[cfg(unix)]
|
||
fn set_restrictive_permissions(path: &Path) -> Result<()> {
|
||
use std::os::unix::fs::PermissionsExt;
|
||
let mut perms = std::fs::metadata(path)?.permissions();
|
||
perms.set_mode(0o600);
|
||
std::fs::set_permissions(path, perms)?;
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
**评估**:Unix 上设置了 0o600 权限,Windows 上跳过。Windows 上文件默认对用户可读可写,其他用户也可读(取决于 ACL)。虽然这不是严重安全问题(应用数据存储在用户目录),但**建议在 Windows 上设置等效的 ACL 限制**。
|
||
|
||
**修复建议**:
|
||
```rust
|
||
#[cfg(windows)]
|
||
fn set_restrictive_permissions(path: &Path) -> Result<()> {
|
||
// 使用 windows crate 或 fs_extra 设置 ACL
|
||
// 简化为仅当前用户可读写
|
||
// 这是可选优化,优先级低
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 28. `Slider.css` 和 `CoverDesign.css` 中 `appearance` 重复声明
|
||
|
||
**位置**:
|
||
- `tauri-app/src/components/Slider/Slider.css:32-33`
|
||
- `tauri-app/src/pages/VideoCreation/CoverDesign.css:79-80`
|
||
|
||
```css
|
||
appearance: none;
|
||
appearance: none;
|
||
```
|
||
|
||
**评估**:纯代码质量问题,不影响兼容性。**建议移除重复行**。
|
||
|
||
---
|
||
|
||
## 五、按维度汇总表
|
||
|
||
| 维度 | 严重 | 中等 | 低风险 | 主要文件 |
|
||
|------|------|------|--------|----------|
|
||
| **内存/资源管理** | 1 (#1) | 1 (#10) | 1 (#24) | CoverDesign.tsx, VoiceSynthesis.tsx |
|
||
| **Windows 路径** | 2 (#4, #5) | 3 (#11, #12, #14) | 1 (#27) | ffmpeg_cmd.rs, file.rs, product.rs |
|
||
| **安全检查** | 2 (#2, #3) | 0 | 0 | file.rs, tauri.conf.json |
|
||
| **Canvas/媒体** | 0 | 3 (#7, #8, #9) | 2 (#20, #25) | useCoverFabric.ts, useCanvasSubtitleRenderer.ts |
|
||
| **网络/API** | 1 (#6) | 1 (#17) | 2 (#22, #23) | client.ts |
|
||
| **Tauri 原生** | 0 | 1 (#16) | 1 (#26) | Settings.tsx, CoverDesign.tsx |
|
||
| **CSS** | 0 | 0 | 3 (#18, #19, #28) | CoverDesign.css, global.css |
|
||
| **启动/配置** | 0 | 1 (#15) | 1 (#21) | main.tsx |
|
||
| **字体加载** | 0 | 1 (#13) | 0 | ffmpeg_cmd.rs |
|
||
|
||
---
|
||
|
||
## 六、修复优先级建议
|
||
|
||
### 立即修复(影响功能/安全)
|
||
|
||
1. **#3 Asset Protocol 过度授权** — 安全漏洞,一行配置修改
|
||
2. **#2 Windows 敏感路径大小写** — 安全检查被绕过
|
||
3. **#1 URL.createObjectURL 泄漏** — 内存泄漏,用户可见
|
||
4. **#4 escape_ffmpeg_path Windows 支持** — Windows 功能失效
|
||
5. **#5 canonicalize() UNC 路径** — Windows 文件操作异常
|
||
|
||
### 本轮迭代修复(影响体验)
|
||
|
||
6. **#6 atob() base64url 兼容性** — Token 解析潜在失败
|
||
7. **#7 crossOrigin 图片污染提示** — 用户友好性
|
||
8. **#8 RAF 后台节流** — 字幕同步
|
||
9. **#10 audio.duration NaN 处理** — 音频处理健壮性
|
||
10. **#9 控制条高度硬编码** — 预览准确性
|
||
11. **#11 Windows rename 已存在** — 文件操作健壮性
|
||
|
||
### 后续排期(优化/边缘场景)
|
||
|
||
12-28. 其余低风险项
|
||
|
||
---
|
||
|
||
## 七、特别说明:Tauri 环境 vs 浏览器环境的兼容性差异
|
||
|
||
本项目同时支持两种运行模式:
|
||
|
||
| 特性 | Tauri 桌面模式 | 浏览器模式(开发调试用) |
|
||
|------|---------------|------------------------|
|
||
| `invoke()` | ✅ Tauri IPC | ❌ 会 catch 失败 |
|
||
| `convertFileSrc()` | ✅ `asset://` | ❌ 会 catch 失败 |
|
||
| `localStorage` | ✅ 可用 | ✅ 可用 |
|
||
| File 系统 API | ✅ Tauri 插件 | ❌ 不可用 |
|
||
| `__TAURI_INTERNALS__` | ✅ 存在 | ❌ 不存在 |
|
||
|
||
**当前代码对浏览器模式有降级处理**(`isTauri()` 检查 + catch 错误),这是好的实践。但以下功能在浏览器模式下完全不可用,需要评估是否影响开发调试:
|
||
|
||
- 本地视频预览(依赖 `asset://` + FFmpeg 转码)
|
||
- 文件保存/导出(依赖 Tauri Dialog)
|
||
- 项目本地持久化(依赖 Tauri IPC)
|
||
- 自动更新(依赖 Tauri Updater)
|
||
|
||
**建议**:在 `README` 或开发文档中明确列出浏览器模式的功能限制,避免开发者困惑。
|