gongxiangp2/docs/网络流量逻辑完整梳理.md

16 KiB
Raw Blame History

网络流量逻辑完整梳理

📋 目录

  1. 网络架构概览
  2. 公网/内网访问逻辑
  3. 首页预览文件流量逻辑
  4. 预览文件访问流量逻辑
  5. 下载源文件流量逻辑
  6. 当前问题分析
  7. 优化方案建议

网络架构概览

服务器配置

  • 服务器内网IP: 192.168.58.20
  • 应用端口: 4320 (内网) / 3000 (Docker容器内)
  • MinIO内网地址: 192.168.58.20:9000
  • MinIO公网域名: s3.yydd.ai (通过FRP穿透,解析到 103.118.245.6)
  • 应用公网域名: 3d.yydd.ai (通过FRP穿透)

存储位置

文件类型 存储位置 访问方式
预览图 MinIO S3 https://s3.yydd.ai/gongxiangpan/由壹点AI/...
视频预览 (720P) MinIO S3 https://s3.yydd.ai/gongxiangpan/由壹点AI/video-preview-720p-...
视频片段 (10秒) MinIO S3 https://s3.yydd.ai/gongxiangpan/由壹点AI/video-clip-10s-...
视频封面 MinIO S3 https://s3.yydd.ai/gongxiangpan/由壹点AI/video-cover-...
视频源文件 本地 uploads/ /uploads/video-xxx.mp4
音频源文件 本地 uploads/ /uploads/audio-source-xxx.mp3
其他源文件 本地 uploads/ /uploads/material-xxx.zip

关键环境变量

S3_ENDPOINT=https://s3.yydd.ai          # 服务器访问S3的地址(当前是公网域名)
S3_PUBLIC_DOMAIN=https://s3.yydd.ai/gongxiangpan  # 返回给前端的S3公网域名
SERVER_IP=192.168.58.20                 # 服务器内网IP
MINIO_API_PORT=9000                      # MinIO内网端口

公网/内网访问逻辑

1. 内网检测机制

客户端检测(app/page.tsx)

  • 检测时机: 页面加载时,如果访问的是公网域名(3d.yydd.ai)
  • 检测方法:
    1. HTTP环境: 直接尝试访问 http://192.168.58.20:4320/api/network-status
      • 如果成功且返回的 hostname 是 192.168.58.20,判定为内网,自动跳转到内网地址
    2. HTTPS环境: 只通过当前域名的 /api/network-status 检测
      • 检查返回的 clientIp 是否为内网IP段
      • 如果是内网IP,跳转到 http://192.168.58.20:4320

服务端检测(lib/network-detection.ts)

  • 函数: detectIsIntranet(request: NextRequest)
  • 检测逻辑:
    1. 从请求头获取 x-forwarded-for 或 x-real-ip 作为 clientIp
    2. 从请求头获取 host 作为 hostname
    3. 判断 clientIp 或 hostname 是否为内网IP段(10.x.x.x, 172.16-31.x.x, 192.168.x.x)
    4. 任一为内网IP即判定为内网用户

2. URL重写逻辑(lib/url-rewrite.ts)

rewriteStorageUrlForClient(url, request)

  • 作用: 将数据库中的内网URL(如 http://localhost:9000/...)替换为可访问的地址
  • 逻辑:
    • 内网用户: 使用 http://192.168.58.20:9000(内网MinIO地址)
    • 外网用户: 使用 https://s3.yydd.ai/gongxiangpan(公网域名)
    • 已为公网域名: 直接返回,不做修改

rewriteMaterialStorageUrlsForClient(material, request)

  • 作用: 批量重写素材对象中的所有存储URL字段
  • 重写字段:
    • previewUrl
    • videoCoverUrl
    • videoPreview540Url
    • videoPreviewClipUrl
    • videoSourceUrl
    • displayImages[]

proxyHttpUrl(url)

  • 作用: 将S3 URL转换为代理URL(/api/proxy-media?url=...)
  • 当前状态: 未在素材API中使用,仅用于系统设置中的LOGO URL

首页预览文件流量逻辑

1. 数据获取流程

客户端 (浏览器)
  ↓ GET /api/materials?pageSize=20&materialType=...
服务端 (app/api/materials/route.ts)
  ↓ 查询数据库
  ↓ rewriteMaterialStorageUrlsForClient(material, request)
  ↓ 根据用户网络位置重写URL
  ↓ 返回素材列表(包含重写后的URL)
客户端
  ↓ 渲染 MaterialCard 组件

2. URL重写位置

文件: app/api/materials/route.ts:764

return rewriteMaterialStorageUrlsForClient(normalizeMaterial(formatted), request);

重写逻辑:

  • 内网用户: http://localhost:9000/... → http://192.168.58.20:9000/...
  • 外网用户: http://localhost:9000/... → https://s3.yydd.ai/gongxiangpan/...
  • 已为公网域名: 保持不变

3. 前端使用方式

文件: components/home/MaterialCard.tsx

图片预览

// 直接使用 previewUrl(已重写)
<img src={material.previewUrl} />

视频预览

// 直接使用 videoPreviewClipUrl 或 videoPreview540Url(已重写)
<video src={material.videoPreviewClipUrl || material.videoPreview540Url} />

音频预览

// 新素材: /uploads/audio-source-xxx.mp3(相对路径,不走代理)
// 老素材: https://s3.yydd.ai/...(S3 URL,直接访问)
const audioUrl = zipFileName.startsWith('http') 
  ? zipFileName 
  : `/uploads/${zipFileName}`

4. 当前流量路径

内网用户

浏览器 → 内网地址 (192.168.58.20:4320)
  → 应用服务器
  → 数据库查询
  → URL重写为内网MinIO地址 (192.168.58.20:9000)
  → 浏览器直接访问内网MinIO ✅ (内网直连,速度快)

外网用户

浏览器 → 公网域名 (3d.yydd.ai) → FRP穿透
  → 应用服务器
  → 数据库查询
  → URL重写为公网S3域名 (s3.yydd.ai)
  → 浏览器直接访问 s3.yydd.ai → FRP穿透 ❌ (带宽受限,速度慢)

预览文件访问流量逻辑

1. 图片预览(previewUrl, displayImages)

当前实现

  • 内网用户: 直接访问 http://192.168.58.20:9000/...(内网直连)
  • 外网用户: 直接访问 https://s3.yydd.ai/...(通过FRP穿透)

流量路径

外网用户:
  浏览器 → s3.yydd.ai (DNS解析到 103.118.245.6)
    → FRP服务器 (103.118.245.6)
    → FRP穿透到内网 (192.168.58.20:9000)
    → MinIO服务器
    → 返回文件
    → FRP穿透回外网
    → 浏览器

2. 视频预览(videoPreviewClipUrl, videoPreview540Url)

当前实现

  • 与图片预览相同:直接使用S3 URL,未通过代理
  • 问题: 外网用户通过FRP访问,10秒片段(可能5-10MB)加载很慢

流量路径

外网用户:
  浏览器 → s3.yydd.ai → FRP穿透 → MinIO
    ❌ 大文件(5-10MB)通过FRP,带宽受限,加载慢

3. 音频预览(zipFileName)

新素材(存储在本地)

浏览器 → /uploads/audio-source-xxx.mp3
  → Next.js静态文件服务
  → 本地文件系统
  → 返回文件
  • 内网用户: ✅ 内网直连,速度快
  • 外网用户: ❌ 通过FRP穿透应用服务器,速度慢

老素材(存储在S3)

浏览器 → s3.yydd.ai → FRP穿透 → MinIO
  ❌ 通过FRP,速度慢

下载源文件流量逻辑

1. 下载API流程

文件: app/api/materials/download/route.ts

本地文件(新素材)

// 视频源文件、音频源文件、压缩包
zipFilePath = "/uploads/video-xxx.mp4"
  → 服务器读取本地文件
  → 流式传输给客户端

流量路径:

外网用户:
  浏览器 → /api/materials/download?id=xxx
    → FRP穿透到应用服务器
    → 服务器读取本地文件
    → 流式传输
    → FRP穿透回外网
    → 浏览器下载

S3文件(老素材)

// 老素材的源文件可能存储在S3
zipFileName = "https://s3.yydd.ai/..."
  → 服务器fetch S3 URL
  → 流式传输给客户端

流量路径:

外网用户:
  浏览器 → /api/materials/download?id=xxx
    → FRP穿透到应用服务器
    → 服务器fetch s3.yydd.ai
      → 服务器通过FRP访问S3(双重FRP!)
    → 流式传输
    → FRP穿透回外网
    → 浏览器下载
  ❌ 双重FRP穿透,速度极慢

2. 产品全案下载

特殊处理: 从S3获取资产文件(主图/SKU图/详情图)

// 服务器端fetch S3 URL
const response = await fetch(fileUrl); // fileUrl是S3 URL

流量路径:

外网用户:
  浏览器 → /api/materials/download?id=xxx&type=all
    → FRP穿透到应用服务器
    → 服务器fetch多个S3文件
      → 每个文件都通过FRP访问S3
    → 打包成ZIP
    → 流式传输
    → FRP穿透回外网
  ❌ 多个文件,每个都双重FRP,非常慢

当前问题分析

⚠️ 核心问题:服务器访问自己的资源也走FRP穿透

关键理解:

  • ✅ 存储桶/系统/数据库都在同一台机器上(192.168.58.20)
  • ✅ 理论上所有流量只需要走一次FRP穿透(外网用户访问时)
  • ❌ 但当前配置导致服务器访问自己的MinIO也走FRP(浪费且慢)

问题1: 服务器访问自己的MinIO走FRP(最严重)

当前配置:

S3_ENDPOINT=https://s3.yydd.ai  # 公网域名,通过FRP穿透

实际流量路径:

服务器 (192.168.58.20)
  → 访问 s3.yydd.ai
  → DNS解析到 103.118.245.6 (FRP服务器)
  → FRP穿透回 192.168.58.20:9000 (MinIO)
  → 返回数据
  → FRP穿透回 103.118.245.6
  → 服务器接收

问题: 服务器访问同一台机器上的MinIO,却要走FRP穿透,完全浪费!

影响场景:

  1. 上传文件到S3: 服务器 → FRP → 自己的MinIO(浪费)
  2. 下载API从S3获取文件: 浏览器 → FRP → 服务器 → FRP → 自己的MinIO → FRP → 服务器 → FRP → 浏览器(双重FRP!)
  3. 产品全案下载: 服务器fetch多个S3文件,每个都走FRP

问题2: 外网用户访问视频/音频预览慢

根本原因:

  1. 视频预览文件(10秒片段,5-10MB)存储在S3
  2. 外网用户直接访问 s3.yydd.ai,通过FRP穿透
  3. FRP带宽受限,大文件加载慢

流量路径:

外网用户浏览器
  → s3.yydd.ai (DNS解析到 103.118.245.6)
  → FRP穿透到 192.168.58.20:9000 (MinIO)
  → 返回文件
  → FRP穿透回外网
  → 浏览器

这是正常的,只需要一次FRP,但大文件通过FRP带宽受限。

影响:

  • 首页视频卡片悬停播放卡顿
  • 音频预览加载失败或超时

问题3: 音频文件访问路径不一致

新素材: 存储在本地,通过 /uploads/... 访问

  • 内网用户: ✅ 快(内网直连)
  • 外网用户: ✅ 只需一次FRP(浏览器 → FRP → 应用服务器 → 本地文件)

老素材: 存储在S3,通过 https://s3.yydd.ai/... 访问

  • 所有用户: ✅ 只需一次FRP(浏览器 → FRP → MinIO)

问题总结

场景 当前流量路径 应该的流量路径 问题
外网用户访问预览图 浏览器 → FRP → MinIO 浏览器 → FRP → MinIO ✅ 正常(一次FRP)
外网用户访问视频预览 浏览器 → FRP → MinIO 浏览器 → FRP → MinIO ⚠️ 正常但慢(大文件)
外网用户下载本地文件 浏览器 → FRP → 应用服务器 → 本地文件 浏览器 → FRP → 应用服务器 → 本地文件 ✅ 正常(一次FRP)
服务器上传到S3 服务器 → FRP → 自己的MinIO 服务器 → 内网直连MinIO ❌ 浪费!应该内网直连
下载API从S3获取 浏览器 → FRP → 服务器 → FRP → 自己的MinIO 浏览器 → FRP → 服务器 → 内网直连MinIO ❌ 双重FRP!应该内网直连

优化方案建议

方案A: 服务器使用内网地址访问MinIO + 外网用户通过代理

1. 修改服务器S3配置

# docker-compose.yml
S3_ENDPOINT=http://192.168.58.20:9000  # 改为内网地址

2. 视频/音频URL通过代理访问

// app/api/materials/route.ts
// 在返回素材前,将视频/音频URL转换为代理URL
if (material.videoPreviewClipUrl) {
  material.videoPreviewClipUrl = `/api/proxy-media?url=${encodeURIComponent(material.videoPreviewClipUrl)}`
}

3. 优化代理API性能

  • 添加HTTP缓存头
  • 优化流式传输
  • 支持Range请求(视频播放必需)

优点:

  • ✅ 服务器到MinIO内网直连,速度快
  • ✅ 外网用户通过代理,服务器从内网获取资源后转发
  • ✅ 减少FRP带宽占用

缺点:

  • ⚠️ 增加服务器负载(需要转发视频/音频流量)
  • ⚠️ 需要修改代码

方案B: 仅优化加载策略(不改架构)

1. 视频懒加载

  • 只在视频进入视口时加载
  • 优先使用更小的预览片段(videoPreviewClipUrl 优先于 videoPreview540Url)

2. 优化浏览器缓存

  • 添加适当的缓存头
  • 使用Service Worker缓存

优点:

  • ✅ 不改架构,风险低
  • ✅ 实现简单

缺点:

  • ⚠️ 提升有限,不能根本解决问题

方案C: 混合方案(推荐)

1. 服务器使用内网地址访问MinIO

S3_ENDPOINT=http://192.168.58.20:9000

2. 外网用户:视频/音频通过代理,图片直接访问

  • 视频/音频:通过 /api/proxy-media 代理
  • 图片:直接访问S3(文件小,影响不大)

3. 内网用户:全部直接访问内网MinIO

  • 不通过代理,直接访问 http://192.168.58.20:9000

优点:

  • ✅ 兼顾速度和服务器负载
  • ✅ 内网用户不受影响
  • ✅ 外网用户体验提升明显

总结

当前状态

  • ✅ 内网用户:体验良好(内网直连)
  • ❌ 外网用户:视频/音频预览加载慢(FRP带宽受限)
  • ❌ 服务器访问S3也走FRP(效率低)

推荐方案

方案C(混合方案):

  1. 服务器使用内网地址访问MinIO
  2. 外网用户的视频/音频通过代理访问
  3. 内网用户直接访问内网MinIO

实施步骤

  1. 修改 S3_ENDPOINT 为内网地址
  2. 在素材API中,检测用户网络位置
  3. 外网用户的视频/音频URL转换为代理URL
  4. 优化代理API性能(缓存、流式传输)
  5. 测试验证

💡 关键理解总结

你的理解 ✅ 基本正确!

核心观点:

"因为存储桶/系统/数据库都是在同一台机器上的,所以所有流量只需要走一次穿透就行(老素材除外)?而现在的一些素材都是服务器走穿透访问的服务器本身的资源(老素材除外)?"

答案:

  • ✅ 前半句正确:理论上,外网用户访问时只需要一次FRP穿透
  • ✅ 后半句正确:当前配置下,服务器访问自己的MinIO也走FRP穿透,这是完全浪费的

详细说明

✅ 应该只需要一次FRP的场景(新素材)

场景 应该的流量路径 FRP次数
外网用户访问预览图 浏览器 → FRP → MinIO 1次 ✅
外网用户访问视频预览 浏览器 → FRP → MinIO 1次 ✅
外网用户下载本地文件 浏览器 → FRP → 应用服务器 → 本地文件 1次 ✅
服务器上传到S3 服务器 → 内网直连 → MinIO 0次 ✅
下载API从S3获取 浏览器 → FRP → 服务器 → 内网直连 → MinIO 1次 ✅

❌ 当前实际发生的浪费场景

场景 当前流量路径 FRP次数 问题
服务器上传到S3 服务器 → FRP → 自己的MinIO 1次 ❌ 浪费!应该内网直连
下载API从S3获取 浏览器 → FRP → 服务器 → FRP → 自己的MinIO 2次 ❌ 双重FRP!应该内网直连
产品全案下载 浏览器 → FRP → 服务器 → FRP×N → MinIO 1+N次 ❌ 每个文件都双重FRP

优化后的理想状态

修改 S3_ENDPOINT=http://192.168.58.20:9000 后:

场景 优化后流量路径 FRP次数
服务器上传到S3 服务器 → 内网直连 → MinIO 0次 ✅
下载API从S3获取 浏览器 → FRP → 服务器 → 内网直连 → MinIO 1次 ✅
产品全案下载 浏览器 → FRP → 服务器 → 内网直连×N → MinIO 1次 ✅

结论:

  • ✅ 所有新素材的流量都只需要一次FRP穿透(外网用户访问时)
  • ✅ 服务器访问自己的资源应该不走FRP(内网直连)
  • ✅ 老素材存储在外部S3,需要走FRP是正常的

文档版本: v1.1
最后更新: 2026-01-09
维护者: AI Assistant