You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

5.2 KiB

ADR 002: 分镜看板性能优化架构

状态

提议中 (Proposed)

背景

当前分镜看板 API 一次性返回所有分镜数据,当项目包含 200+ 分镜时,会导致:

  • 首次加载慢(2-5秒)
  • 数据传输量大(300-500 KB)
  • 内存占用高
  • 滚动性能差

决策

采用三层数据架构 + 虚拟滚动的方案,实现高性能的分镜看板。

1. 数据分层策略

Layer 1: 骨架数据(Skeleton API)

用途:初始加载,获取所有分镜的基础信息

接口

GET /api/v1/projects/{project_id}/storyboard-board/skeleton

响应

{
  "projectId": "xxx",
  "totalDuration": 600,
  "storyboardCount": 200,
  "tracks": [
    {
      "type": "storyboard",
      "items": [
        {
          "id": "xxx",
          "startTime": 0,
          "endTime": 3,
          "title": "分镜 1"
        }
      ]
    }
  ]
}

数据量:200 分镜 ≈ 10-20 KB

Layer 2: 时间范围详细数据(Time Range API)

用途:加载可视区域的详细数据

接口(已存在):

GET /api/v1/projects/{project_id}/storyboard-board/items/time-range
  ?startTime=0
  &endTime=60
  &trackTypes=storyboard,resource,video

响应

{
  "items": [
    {
      "id": "xxx",
      "type": "storyboard",
      "startTime": 0,
      "endTime": 3,
      "data": {
        "id": "xxx",
        "title": "分镜 1",
        "description": "...",
        "thumbnail_url": "...",
        "resources": {...}
      }
    }
  ]
}

数据量:可视区域 20 分镜 ≈ 10-15 KB

Layer 3: 单个分镜详细数据(已存在)

用途:点击分镜时加载完整信息

接口

GET /api/v1/storyboards/{storyboard_id}

2. 前端架构

数据管理

// 使用 TanStack Query 管理三层数据
const { data: skeleton } = useStoryboardBoardSkeleton(projectId);
const { data: visibleItems } = useStoryboardBoardTimeRange(projectId, timeRange);
const { data: storyboard } = useStoryboard(selectedStoryboardId);

虚拟滚动

// 使用 react-window 实现虚拟滚动
import { VariableSizeList } from 'react-window';

<VariableSizeList
  height={600}
  itemCount={skeleton.items.length}
  itemSize={getItemSize}
  width="100%"
>
  {Row}
</VariableSizeList>

时间范围计算

// 根据滚动位置计算可视时间范围
const calculateVisibleTimeRange = (scrollLeft: number, containerWidth: number, pps: number) => {
  const startTime = scrollLeft / pps;
  const endTime = (scrollLeft + containerWidth) / pps;
  const buffer = 30; // 预加载 30 秒

  return {
    startTime: Math.max(0, startTime - buffer),
    endTime: endTime + buffer
  };
};

3. 加载策略

初始加载

  1. 加载骨架数据(所有分镜基础信息)
  2. 计算总时长和布局
  3. 加载初始可视区域的详细数据

滚动加载

  1. 监听滚动事件(防抖 200ms)
  2. 计算新的可视时间范围
  3. 加载新区域的详细数据
  4. 缓存已加载的数据

预加载策略

  • 预加载相邻 30 秒的数据
  • 滚动方向预测(向右滚动时优先加载右侧)
  • 缩放时重新计算可视范围

4. 缓存策略

// TanStack Query 缓存配置
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 分钟
      cacheTime: 10 * 60 * 1000, // 10 分钟
    },
  },
});

实施计划

阶段 1:后端 API(1-2 天)

  • 新增骨架数据接口 /storyboard-board/skeleton
  • 优化时间范围接口(已存在,需测试)
  • 添加响应压缩(gzip)

阶段 2:前端基础架构(3-4 天)

  • 实现骨架数据加载
  • 实现时间范围加载
  • 实现数据缓存管理

阶段 3:虚拟滚动(3-5 天)

  • 集成 react-window
  • 实现虚拟滚动渲染
  • 优化滚动性能

阶段 4:优化和测试(2-3 天)

  • 性能测试(1000+ 分镜)
  • 内存泄漏检测
  • 用户体验优化

总计:9-14 天

性能目标

指标 当前 目标
首次加载时间 2-5s <500ms
数据传输量 300-500 KB <30 KB
内存占用 随分镜数增长 恒定 ~50 MB
滚动 FPS 30-40 60
支持分镜数 <200 1000+

优势

  1. 性能优异:首次加载快,滚动流畅
  2. 可扩展:支持大规模数据(1000+ 分镜)
  3. 用户体验好:无感知加载
  4. 架构清晰:数据分层明确,易于维护
  5. 向后兼容:保留现有 API,渐进式升级

劣势

  1. 实施复杂度高:需要前后端配合
  2. 开发周期长:预计 2-3 周
  3. 测试成本高:需要大量数据测试

替代方案

方案 A:仅移除 description 字段

  • 优点:实施快(5 分钟)
  • 缺点:数据量仅减少 40-60%,无法支持大规模数据

方案 B:分页加载

  • 优点:实施简单
  • 缺点:不适合时间轴场景,用户体验差

参考资料