From 8c88943a06f7b6a9ade18cac9a915d91dab11e40 Mon Sep 17 00:00:00 2001 From: iye <1713042409@qq.com> Date: Wed, 13 May 2026 14:37:46 +0800 Subject: [PATCH] feat(tos): point all static assets to volcano TOS bucket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 资源已上传到 https://cyberstar.tos-cn-shanghai.volces.com/cyber-star/ 代码改动: - 新增 src/lib/tos.ts 提供 tosUrl(path) 工具,读 NEXT_PUBLIC_TOS_DOMAIN - mock-data.ts: portrait/gallery 切到 .webp, videoUrl 走 TOS, 全部通过 tosUrl() - page.tsx Hero PV 走 tosUrl("videos/hero-pv.mp4") - next.config.ts 把火山 TOS 域名(沪/京)+ 火山 CDN 加进 images.remotePatterns 白名单 - .env.example 更新 NEXT_PUBLIC_TOS_DOMAIN 示例为实际桶域名 体积影响 (与之前打包给运维的 cyber-star-assets.tar.gz 一致): - 立绘 5MB png → 100-300KB webp (-95%) - 单人 solo 5-10MB mp4 → 1-3MB (-70%) - Hero PV 45MB → 12MB (-70%) Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 2 +- next.config.ts | 8 ++++++++ src/app/page.tsx | 3 ++- src/lib/mock-data.ts | 14 +++++++++----- src/lib/tos.ts | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 src/lib/tos.ts diff --git a/.env.example b/.env.example index b75a79b..099a81d 100644 --- a/.env.example +++ b/.env.example @@ -19,7 +19,7 @@ TOS_REGION="cn-beijing" TOS_BUCKET="cyber-star" TOS_ACCESS_KEY="CHANGE_ME" TOS_SECRET_KEY="CHANGE_ME" -NEXT_PUBLIC_TOS_DOMAIN="https://cyber-star.tos-cn-beijing.volces.com" +NEXT_PUBLIC_TOS_DOMAIN="https://cyberstar.tos-cn-shanghai.volces.com/cyber-star" # ── Auth.js 鉴权 ── # 用 `openssl rand -base64 32` 生成 diff --git a/next.config.ts b/next.config.ts index 3020ec5..42227ca 100644 --- a/next.config.ts +++ b/next.config.ts @@ -5,6 +5,14 @@ const nextConfig: NextConfig = { devIndicators: false, // 容器化部署:产出精简的 standalone 包(node server.js 启动) output: "standalone", + // next/image 远程域名白名单:火山 TOS 桶 + 后续 CDN 域名 + images: { + remotePatterns: [ + { protocol: "https", hostname: "*.tos-cn-shanghai.volces.com" }, + { protocol: "https", hostname: "*.tos-cn-beijing.volces.com" }, + { protocol: "https", hostname: "*.volccdn.com" }, + ], + }, }; export default nextConfig; diff --git a/src/app/page.tsx b/src/app/page.tsx index db875bc..20780a4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -11,6 +11,7 @@ import { getActivityEndTime, sortArtists, type SortKey } from "@/lib/mock-data"; import { useVoteStore } from "@/lib/store"; import { useVoteAction } from "@/hooks/useVoteAction"; import { cn } from "@/lib/cn"; +import { tosUrl } from "@/lib/tos"; export default function Home() { const artists = useVoteStore((s) => s.artists); @@ -70,7 +71,7 @@ export default function Home() { scrollMarginTop: "80px", }} > - + {/* Top12 出道位 · 作为第二个 snap 点:滚动结束后自然落到这里,标题贴近顶部 */} diff --git a/src/lib/mock-data.ts b/src/lib/mock-data.ts index 83ad92e..863945b 100644 --- a/src/lib/mock-data.ts +++ b/src/lib/mock-data.ts @@ -1,11 +1,12 @@ import type { Artist } from "@/types/artist"; import { ARTIST_SEEDS } from "./artist-bios"; +import { tosUrl } from "./tos"; /** * 真实艺人数据装配层。 * 所有人物字段(姓名 / 性别 / 年龄 / 身高 / 性格 / 技能 / 赛道 / 口头禅 / * 座右铭 / 长简介)来自《36 位虚拟艺人人物小传.docx》。 - * 立绘 / 三视图 / 氛围图 / solo 视频 来自 public/portraits/ 和 public/videos/artists/。 + * 立绘 / 三视图 / 氛围图 / solo 视频 走火山 TOS 桶(webp + mp4 已压缩到 1/10)。 * * 票数 / 排名是运行时计算(store 投票后会更新)。除此之外不再有任何虚构数据。 */ @@ -18,9 +19,12 @@ const MISSING_ATMOSPHERE_3: ReadonlySet = new Set(["036"]); /** 画廊 = 三张氛围图(1/2/3)。不包含三视图,因为长宽比与卡片不一致。 */ function buildGallery(no: string): string[] { - const items = [`/portraits/${no}.png`, `/portraits/${no}-2.png`]; + const items = [ + tosUrl(`portraits/${no}.webp`), + tosUrl(`portraits/${no}-2.webp`), + ]; if (!MISSING_ATMOSPHERE_3.has(no)) { - items.push(`/portraits/${no}-3.png`); + items.push(tosUrl(`portraits/${no}-3.webp`)); } return items; } @@ -35,12 +39,12 @@ function buildArtists(): Artist[] { age: seed.age, gender: seed.gender, bio: seed.bio, - portrait: `/portraits/${seed.no}.png`, + portrait: tosUrl(`portraits/${seed.no}.webp`), avatar: "", gallery: buildGallery(seed.no), videoUrl: MISSING_VIDEO.has(seed.no) ? undefined - : `/videos/artists/${seed.no}.mp4`, + : tosUrl(`videos/artists/${seed.no}.mp4`), // 不设置 poster,由播放器运行时 seek 到 0.001s 渲染首帧作为封面 videoPoster: "", tags: seed.tags, diff --git a/src/lib/tos.ts b/src/lib/tos.ts new file mode 100644 index 0000000..5409b46 --- /dev/null +++ b/src/lib/tos.ts @@ -0,0 +1,18 @@ +/** + * TOS 资源 URL 拼接工具 + * + * 用法: + * tosUrl("portraits/001.webp") + * → https://cyberstar.tos-cn-shanghai.volces.com/cyber-star/portraits/001.webp + * + * 环境变量 NEXT_PUBLIC_TOS_DOMAIN 配置: + * .env.local / .env.production → 完整的桶 + 路径前缀 (含 scheme, 不含末尾 /) + * 未设置时 fallback 到相对路径 (/path/...), 适合本地用 public/ 静态文件托管的场景。 + */ +const TOS_BASE = (process.env.NEXT_PUBLIC_TOS_DOMAIN ?? "").replace(/\/+$/, ""); + +export function tosUrl(path: string): string { + const clean = path.replace(/^\/+/, ""); + if (!TOS_BASE) return `/${clean}`; + return `${TOS_BASE}/${clean}`; +}