Skip to content

CDN 部署 ​

Vistack 的前端(web-client / web-admin)是 Vite 构建的纯静态产物,可以部署到任意 CDN / 静态托管,与后端完全解耦。事实上在线 Demo https://vistack.pages.dev 就是 Cloudflare Pages(全球边缘 CDN) 部署的。

为什么支持 CDN? ​

能力实现CDN 友好度
静态产物Vite 构建输出 build/web-client / build/web-admin,无服务端渲染依赖✅ 任意 CDN 可直接托管
API 地址编译时注入 VITE_API_BASE(默认相对路径 /api)✅ 相对路径可同域反代,无需知道后端域名
媒体地址[minio].public_endpoint 独立配置,预签名 URL 基于它生成✅ 可指向对象存储 CDN
鉴权JWT 位于请求头(Authorization: Bearer),无 Cookie 会话✅ CDN 透传即可
直播WebRTC 直连 SFU,不经前端 CDN✅ 不受影响

前置准备 ​

1. 构建前端产物 ​

bash
cd web
pnpm install
pnpm run build
# 产物:build/web-client(用户端)、build/web-admin(管理端)

2. 确定 API 地址(VITE_API_BASE) ​

前端通过 web/ui/src/api/axios.ts 读取 VITE_API_BASE(默认 /api):

ts
export const baseURL = (import.meta as any).env.VITE_API_BASE ?? '/api'
部署形态VITE_API_BASE说明
同域反代/api(默认,无需配置)CDN 域名上把 /api 反代到后端
前后端分离https://api.example.com/api/v1绝对地址,需后端开启 CORS

修改方式(任选):

bash
# 方式一:构建时注入
VITE_API_BASE=https://api.example.com/api/v1 pnpm run build

# 方式二:写进 .env 文件(如 web/.env.production)
echo "VITE_API_BASE=https://api.example.com/api/v1" > web/.env.production
pnpm run build

3. 后端配置(CORS + MinIO 公网地址) ​

CORS:前后端不同域时,conf/app.toml 的 [cors].allow_origins 需加入前端 CDN 域名:

toml
[cors]
enable = true
allow_origins = ["https://static.example.com", "https://vistack.pages.dev"]
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
allow_headers = ["Content-Type", "Authorization", "X-Requested-With"]
allow_credentials = true

MinIO 公网地址:播放 / 封面的预签名 URL 基于 [minio].public_endpoint 生成,必须可被浏览器访问:

toml
[minio]
public_endpoint = "https://media.example.com"  # 或 MinIO/CDN 公网地址
secure = true

形态一:同域反代(推荐) ​

前端与 API 同域名,CDN 节点直接返回静态文件,/api 路径回源到后端网关:

用户 ──▶ CDN 边缘节点(static.example.com)
          ├─ /api/*      ──▶ 回源后端(Traefik 网关 / Nginx)
          ├─ /admin/*    ──▶ 管理端静态(SPA 回退)
          └─ 其余        ──▶ 用户端静态(SPA 回退)

Nginx 示例:

nginx
server {
    listen 443 ssl;
    server_name static.example.com;

    # 用户端产物
    root /var/www/vistack/web-client;

    # API 反向代理到后端网关
    location /api/ {
        proxy_pass http://backend-gateway:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 管理端(独立产物目录)
    location /admin {
        alias /var/www/vistack/web-admin;
        try_files $uri $uri/ /admin/index.html;   # SPA 回退
    }

    # 用户端 SPA 回退
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源长缓存(文件名带 hash,可放心缓存)
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Vite 构建产物文件名带内容 hash(如 index-DvqF6fSJ.js),更新发布无需手动刷新缓存。

形态二:前后端分离域名 ​

前端 CDN 与 API 分属不同域名,构建时注入 API 绝对地址,后端开 CORS:

static.example.com(CDN 前端)  ──▶  api.example.com/api/v1(后端,CORS 允许 static.example.com)

Cloudflare Pages 示例 ​

  1. 创建项目:Cloudflare Dashboard → Workers & Pages → Create → Pages → 连接 Git 仓库(或上传产物);

  2. 构建配置(连接 Git 方式):

    • Build command:cd web && pnpm install && pnpm run build
    • Build output directory:build/web-client
    • Environment variables:VITE_API_BASE=https://api.example.com/api/v1
  3. SPA 回退:Cloudflare Pages 对未匹配路径默认返回 404,需添加 _redirects 文件让前端路由回退到 index.html(部署到产物根目录):

    /*  /index.html  200
  4. 管理端:单独建一个 Pages 项目(输出 build/web-admin,环境变量加 VITE_BASE=/admin/),或与用户端合并托管(见形态一 Nginx 的 /admin 配置)。

形态三:媒体加速(对象存储 CDN) ​

播放 DASH 分片与封面体积大,可前置 CDN 缓存,降低 MinIO 出口带宽:

toml
[minio]
public_endpoint = "https://media-cdn.example.com"  # 对象存储 CDN 域名
secure = true

预签名 URL 将指向 CDN 域名;CDN 首次回源 MinIO 后缓存分片。

媒体 CDN 缓存策略(DASH 分片) ​

签名 URL 与 CDN 的两个关键点 ​

预签名 URL(SigV4)形如:

https://media-cdn.example.com/vistack/dash/42/chunk-00001.m4s?X-Amz-Algorithm=...&X-Amz-Date=...&X-Amz-Signature=...&X-Amz-Expires=3600

CDN 缓存它有两个坑:

  1. 签名包含 Host:SigV4 签名校验包含请求的 Host 头。public_endpoint 必须是浏览器实际访问的域名(即 CDN 域名),且 CDN 回源 MinIO 时保持 Host 头不变(回源 HOST = public_endpoint 域名),否则 MinIO 校验签名返回 403 SignatureDoesNotMatch;
  2. 缓存 key 污染:同一分片每次生成的 X-Amz-Signature / X-Amz-Date 都不同,若 CDN 按完整 URL 做缓存 key,同一对象会被缓存成多份,命中率趋近于 0。需要让 CDN 忽略签名相关查询参数。

缓存 key 策略:忽略签名参数 ​

平台配置
CloudflareCache Rules → 自定义缓存 key:Query string → Ignore all(或只保留需要的参数),并设置 Edge TTL
阿里云 CDN缓存配置 → 过滤参数:打开"忽略 URL 参数"(过滤 ? 后全部参数)
腾讯云 CDN缓存配置 → 忽略参数:全忽略
AWS CloudFrontCache policy → Query strings:None(忽略全部)

注意

忽略参数仅用于媒体路径(如 /vistack/dash/*、/vistack/covers/*)。若同一 CDN 域名还承载带参业务(如 API),请用路径级规则只对媒体生效。

缓存 TTL 建议 ​

对象内容特征TTL 建议说明
dash/{id}/chunk-*.m4s转码产物,不可变1 年(immutable)文件名含视频 ID + 序号,重转码才会变化
dash/{id}/init-*.m4s转码产物,不可变1 年(immutable)同上
dash/{id}/manifest.mpd档位清单,重转码才变1 天码率档位列表更新后最多延迟 1 天
covers/{id}.jpg封面,可被用户更换1 天换封面后需主动刷新或等 TTL 过期

签名有效期 vs 缓存 TTL ​

  • CDN 命中缓存时不校验签名,签名过期不影响已缓存内容继续被服务;
  • 但回源时若签名已过期,MinIO 返回 403 → 前端播放失败。因此:
    • 签名有效期(X-Amz-Expires)应 ≥ CDN TTL,保证 TTL 内回源可成功;
    • 前端播放器遇到 403 时应重新请求播放地址(重新签名)再试。

一键发布脚本 ​

仓库内置 deploy/cdn-publish.sh,一条命令完成「构建前端 → 合并布局(管理端并入 /admin)→ 生成 SPA 回退规则 → 上传 CDN」:

bash
# 对象存储(S3 兼容:MinIO / OSS / COS)—— 默认
VITE_API_BASE=/api ./deploy/cdn-publish.sh s3

# Cloudflare Pages
PROVIDER=pages CF_PROJECT_NAME=vistack ./deploy/cdn-publish.sh pages

# 仅构建 + 合并布局,不上传(预检产物是否正确)
./deploy/cdn-publish.sh dry

工作流程 ​

  1. 构建:web-client(根路径)与 web-admin(VITE_BASE=/admin/,资源前缀 /admin/assets/*);

  2. 合并布局:把 web-admin 产物复制到 web-client 产物的 admin/ 子目录——单个桶 / 单个 Pages 项目同时承载两端;

  3. SPA 回退:生成 _redirects(Pages 模式):

    /admin       /admin/index.html  200
    /admin/*     /admin/index.html  200
    /*           /index.html        200
  4. 上传:

    • s3 模式:mc mirror(需先 mc alias set local <ENDPOINT> <AK> <SK>)或 aws s3 sync;
    • pages 模式:wrangler pages deploy(需安装 wrangler)。

环境变量 ​

变量默认说明
VITE_API_BASE/api前端 API 地址(同域反代保持默认;分离域名填绝对地址)
VITE_BASE/admin/管理端 base
CDN_BUCKET-s3 模式必填:桶名
CF_PROJECT_NAME-pages 模式必填:项目名

S3/OSS 静态网站

s3 模式上传后,若使用静态网站托管(非 CDN 直接回源),需额外把 Error Document / 404 规则 指向 index.html 实现 SPA 回退(脚本已提示)。

常见问题 ​

Q1:刷新 /video/123 返回 404? ​

前端是 SPA,CDN 需配置路由回退:所有未匹配路径返回 index.html(Cloudflare Pages 用 _redirects,Nginx 用 try_files)。注意 /api/* 不要回退到 index.html,应反代到后端。

Q2:接口报 CORS 错误? ​

前后端不同域时,确认:

  1. 后端 [cors].allow_origins 包含前端域名;
  2. 预检(OPTIONS)请求被放行(allow_methods 含 OPTIONS,中间件已处理)。

Q3:视频播放不出来? ​

预签名 URL 由 [minio].public_endpoint 生成——检查该地址是否公网可达,且与前端同域策略一致(不同域时确认 MinIO 侧 CORS 或改用代理播放)。

Q4:更新前端后用户看到旧版本? ​

Vite 产物文件名带 hash,/assets/ 长缓存即可自动更新;但 index.html 本身不要长缓存(默认即可),否则新版本入口文件不生效。

Q5:Docker 模式与 CDN 冲突吗? ​

不冲突。Docker 模式(api 内置托管)适合单机;CDN 模式(独立静态托管)适合多地域加速。两者可并存:api 容器仍提供 API,前端产物单独部署 CDN 并指向同一后端即可。

部署形态对比 ​

形态适合场景前端入口需要配置
Docker 内置托管单机 / 内网http://localhost无(开箱即用)
同域反代 + CDN单一域名、全国加速https://static.example.com反代规则 + 回退规则
前后端分离多环境、独立扩展https://static.example.comCORS + VITE_API_BASE
媒体 CDN高带宽播放场景同上MinIO public_endpoint + CDN 缓存

基于 MIT License 发布