[Ai] feat: 花魁短剧 Next.js 站点初始提交

- 原生 Next.js App Router + 严格 TypeScript 实现花魁短剧 PC/H5 页面,按 src/features/{catalog,content,playback} 组织,保留完整 SEO(metadata、JSON-LD、canonical、robots、llms、sitemap、IndexNow)。
- 主域名通过 SITE_URL=https://huakuidj.com 强制覆盖后端 foreverUrlAddr(config/*.env、src/server/config.ts、src/server/seo/shared.ts、src/server/services/shared.ts),支持邮箱改为 support@huakuidj.com。
- 移除登录与账户体系:删除 /account 全部页面、登录/注册/充值弹窗、收藏点赞、评论、金币解锁及对应 BFF 接口;旧账户地址 GET/HEAD 统一 301 到首页(src/lib/routes.ts REMOVED_ACCOUNT_PATHS),保留游客会话、反馈、图片上传、观看上报与搜索联想。
- 播放页:评论位改为同类推荐 + 热播榜单,锁定集改为 App 观看遮罩与底部弹层,新增简介标签、分集图例与独立视觉皮肤(src/features/playback、src/styles/pages/playback.css)。
- 卡片:短剧/视频卡状态与时长移至信息行胶囊标签,封面角标移至左上角(src/components/catalog/Cards.tsx、src/styles/foundations/base.css)。
- 同步更新单元测试、浏览器冒烟脚本、路由清单与 README。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-10-16 11:33:31 +07:00
co-authored by Claude Opus 5.5
commit d2ddb1c321
423 changed files with 52152 additions and 0 deletions
+364
View File
@@ -0,0 +1,364 @@
# 花魁短剧 · Next.js / TypeScript
从 `jha-1045-hgdjnet-web` 迁移的独立 Next.js 16 项目。运行时仅需要 Node.js,不依赖 Go、原项目目录或原 Go 服务。保留内容浏览页面、原 URL、游客会话、播放器和统计交互(登录、账户与充值功能已移除,旧账户地址 301 到首页);PC/H5 界面按花魁短剧 Figma 设计更新。
## 启动
推荐 Node.js 22,最低 Node.js 20.9。
```bash
npm ci
npm run dev
```
默认开发地址为 `http://localhost:25121`,使用原项目开发 API。通过 `PORT=其他端口 npm run dev` 可以更换端口。开发及测试开发模式默认使用 Webpack 热更新;可用 `NEXT_DEV_BUNDLER=turbopack npm run dev` 选择 Turbopack。优化构建保持 Next 默认 Turbopack。
## PWA 全屏
通过 HTTPS 访问站点并安装到主屏幕后,从桌面图标启动。Android 优先采用 `fullscreen`,不支持时回退 `standalone`;iOS 使用独立窗口与透明状态栏,系统状态栏的显示由 Safari/iOS 决定。普通浏览器标签页保持浏览器本身的显示模式。
`src/app/manifest.ts` 提供安装清单,共用设置位于 `src/lib/pwa/config.ts`,安装入口为 `/`,不添加影响渠道或 canonical 的 query。全局 viewport 使用 `viewport-fit=cover`;安装窗口的页头、底部导航、弹窗与播放器按安全区布局,支持横竖屏。缺少原生 Fullscreen API 的安装窗口使用 xgplayer CSS 全屏。
`PwaProvider` 只在安全上下文注册 `/sw.js`,不弹安装提示;localhost 可用于开发验证,手机通过普通局域网 HTTP 访问时无法注册 Service Worker。Worker 源码为 TypeScript,开发启动与构建时自动编译,不拦截或离线缓存页面、接口、签名媒体和密钥;`/manifest.webmanifest` 与 `/sw.js` 绕过游客会话和 SSR Proxy,Worker 禁止 HTTP 缓存。standalone 包包含清单路由、Worker 和安装图标。
安装图标由现有花魁 SVG favicon 等比例生成,另提供保留安全留白的 maskable 图标及 180px Apple 图标。更新源图后运行:
```bash
npm run assets:pwa
npm run assets:pwa:check
npm run test:pwa
```
`test:pwa` 验证实际 React 组件、Worker 注册、模拟安全区和播放器全屏进出;安装与系统状态栏还需在真实 Android/iOS 设备验证。已安装的旧桌面入口可能保留旧清单,更新后可移除旧入口再安装。
## 本地 Charles 抓包
从 `hgdj-official-website` 移植 `dev:charles`,在 Node 启动前设置原生环境代理和 Charles 根证书,让服务端上游 API 和媒体的原生 `fetch` 请求经过 Charles。沿用普通开发的配置优先级、React 热更新和默认端口 `25121`;sitemap 读取本地后端产物,不发起远程下载。
1. 使用 Node **24.5+**(或 **22.21+**);Node 22.17 不支持此代理开关。
2. 打开 Charles,确认 HTTP Proxy 端口为 `8888`,在 Proxy → SSL Proxying Settings 中启用实际 `UPSTREAM_API_URL` 对应的域名及端口,例如 `api.example.com:443`。
3. 在 Help → SSL Proxying → Save Charles Root Certificate 中导出 PEM,保存到 `~/.charles-ca.pem`。Charles 重置根证书后需要重新导出。
```bash
nvm use 24
npm run dev:charles
# 可通过 PORT 或传递 Next 参数指定端口
npm run dev:charles -- --port 3001
```
默认代理为 `http://127.0.0.1:8888`,本机回环请求通过 `NO_PROXY` 排除,并保留既有排除项。自定义代理地址或证书:
```bash
CHARLES_PROXY_URL=http://127.0.0.1:8889 \
NODE_EXTRA_CA_CERTS="$HOME/certs/charles.pem" \
npm run dev:charles
```
代理变量只作用于本次抓包进程;普通开发仍使用 `npm run dev`。这些 Node 启动变量不能仅放进 Next 的 `.env.local` 或 `config/development.local.env`。浏览器直接发出的请求需要浏览器/系统 Charles 代理;缓存命中时不会产生新的上游请求。
Node 原生代理说明:[Node.js HTTP 文档](https://nodejs.org/download/release/v24.17.0/docs/api/http.html#built-in-proxy-support)。
## 开发、测试、生产
| 环境 | 配置文件 | 开发启动 | 构建 | 运行构建 | 构建目录 |
| --- | --- | --- | --- | --- | --- |
| 开发 | `config/development.env` | `npm run dev` | `npm run build:dev` | `npm run start:dev` | `.next-dev` |
| 测试 | `config/test.env` | `npm run dev:test` | `npm run build:test` | `npm run start:test` | `.next-test` |
| 生产 | `config/production.env` | — | `npm run build` | `npm start` | `.next` |
配置优先级:进程环境变量 > `config/<环境>.local.env` > `config/<环境>.env`。本地覆盖文件不会提交。`APP_ENV` 选择业务环境;测试环境的优化构建仍使用 Next.js 要求的 `NODE_ENV=production`。
开发 `UPSTREAM_API_URL` 使用 `http://172.104.181.99:30331`;测试服务与 API 同机,使用 `http://127.0.0.1:30331`,测试应用端口为 `30334`。本地运行测试环境且 API 不在本机时,可在 `config/test.local.env` 覆盖上游地址。正式配置使用同机 API `http://127.0.0.1:26931`、服务端口 `26935`,可由部署环境或 `config/production.local.env` 覆盖。构建不访问上游,启动时检查地址。
```dotenv
UPSTREAM_API_URL=https://your-api.example
UPSTREAM_TIMEOUT_MS=20000
IMAGE_CDN_URL=
SITEMAP_DIR=../template/pages/sitemaps
TRACKER_APP_ID=JHA-4001
TRACKER_ENCRYPTED_CONFIG=
```
这些变量仅供服务端使用。`config/info.reportSdkConfig` 优先于本地埋点密文配置。官方导航独立进程可以使用 `NAVIGATION_ONLY=true PORT=26813 npm start`,开放 `/`、`/navigation/` 和静态资源,原 `/nav/`、`/dh/` 一次 301 到 `/navigation/`。与原 Go `DHInit` 一致,导航模式不注册 robots、llms、sitemap、验证文件及业务接口,这些地址返回原站 404 页面。
图片 CDN 与 API 独立配置。上游 `config/info.config.imgCdn`、`imgAppUsCdn` 按顺序提供图片来源,`IMAGE_CDN_URL` 提供环境级备用来源。开发与测试共用 API 30331,该接口返回裸 `v3/image/...` 且 CDN 配置为空,因此两套环境均显式配置原站已验证可用的 CloudFront 图片线路;生产独立配置。修改环境配置后需重启服务。
公共图片组件统一解析相对路径,覆盖封面、横幅、背景和头像;相对路径不会随页面目录变化。图片保持原首 100 字节 XOR 协议,也兼容未加密的有效图片;校验文件头后才缓存,HTTP、证书、JSON 错误或超时会尝试配置的备用 CDN。完整外域 URL 保持原签名,只有属于已配置 CDN 的资源才允许切换相同对象路径;图片请求不向外域发送本站 Cookie/Authorization。
## 目录和渲染边界
| 目录 | 职责 |
| --- | --- |
| `src/app/` | 原生 App Router TSX 页面、Metadata 与 BFF/媒体 Route Handlers |
| `src/features/` | 短剧目录、播放、账号与内容页面组件 |
| `src/components/` | 页头、页尾、卡片、播放器、图片、弹窗、认证、支付、埋点共用组件 |
| `src/styles/` | 基础、公共组件、页面和 PWA 覆盖样式,通过 TSX 导入并由 Next 编译 |
| `src/lib/` | 类型化浏览器请求、Cookie、搜索/展示方法与埋点上下文 |
| `src/proxy.ts`、`src/server/react/` | SSR 数据、状态、响应 Cookie 和 React 数据交接 |
| `src/server/pages/` | 原路由参数、分页、页面数据组合与播放/账号场景 |
| `src/server/services/` | 上游 DTO、展示模型、公共配置和请求级复用 |
| `src/server/upstream.ts`、`auth.ts` | 上游请求、请求头/Cookie 透传、登录与游客恢复 |
| `src/server/seo/` | 原 SEO 策略、TDK、canonical、JSON-LD 和抓取文件 |
| `public/static/` | 本地 Figma 品牌/图标、分享图与私有埋点 SDK |
所有业务页面由 TSX 组件服务端渲染,交互使用 React hooks 和 Client Components。App Router 的 `generateMetadata` 输出完整首屏 SEO;JSON-LD 使用安全序列化的独立脚本。没有 HTML 模板运行时或注入整页 HTML 的适配层。
54 份业务样式统一在 `src/styles/{foundations,components,pages,overrides}`,由根 `layout.tsx` 顶层导入,播放器同时保留 npm `xgplayer` 样式。Next 输出带内容哈希的 CSS 文件,无需手工版本参数或 `/static/css` 链接。业务类名使用 `hk-` 命名空间和组件/业务名称;第三方播放器类名保留其包合同。页面样式通过零特异性的 `html[data-hk-page-styles]` 作用域隔离,基础和公共组件样式共享,PWA 安全区覆盖最后加载。`PagePresentation` 在客户端切页时同步作用域及页面 body class,同时保留 PWA、弹窗滚动锁等临时状态。
页面代码目录和公开 URL 按用户最新确认统一使用语义路径。`src/app` 使用 `dramas`、`drama`、`account` 等目录,旧有效页面 GET/HEAD 一次 301 到新地址,规则由 `src/lib/routes.ts` 管理,保留 slug 编码、原始 query 和 hash。站内链接、canonical、分页、JSON-LD、llms 和默认 App 下载入口同步新地址;同产品后端的 sitemap、canonical 与 SEO 推送生成新路径,Next 继续原字节服务后端 XML。API、媒体签名、图片 CDN、支付和外链保持各自合同。
| 场景 | 当前页面路径 | 301 兼容旧路径 |
| --- | --- | --- |
| 短剧浏览/播放 | `/dramas/`、`/drama/:slug/` | `/list/`、`/play/:slug/` |
| 剧集浏览/播放 | `/series/`、`/series/watch/:slug/` | `/series-play/:slug/` |
| 精选/最新/榜单 | `/featured/`、`/latest/`、`/rankings/` | `/popular-recommend/`、`/recently-added/`、`/rank/` |
| 专题列表/详情 | `/collections/`、`/collection/:slug/` | `/topic/`、`/topic-detail/:slug/` |
| 推荐栏目 | `/curated/dramas/:id/`、`/curated/series/:id/` | `/more-list/:id/`、`/more-series-list/:id/` |
| 移动搜索/导航/下载 | `/discover/search/`、`/navigation/`、`/app/download/` | `/search-mobile/`、`/nav/`、`/dh/`、`/download-app/` |
已移除的账户页面 `/account/**` 及旧地址 `/login/`、`/register/`、`/profile/`、`/history/`、`/favorites/`、`/purchased/`、`/setting/`、`/settings/`、`/recharge/` 的 GET/HEAD 一律 301 到首页 `/`,规则为 `src/lib/routes.ts` 的 `REMOVED_ACCOUNT_PATHS`。
分页和筛选保留原参数结构;搜索结果、标签、关于、帮助和法律声明沿用原路径。业务页面组件使用 `HomePage`、`BrowsePages`、`DiscoveryPages`、`DramaWatchPage`、`SeriesWatchPage`、`SiteNavigationPage`,服务端播放模块为 `src/server/pages/playback.ts`。公共组件封装、源码 CSS 的 TSX 导入和类名保持。
`src/app/` 页面目录按上表同名迁移,原动态参数目录保留;`nav`/`dh` 合并到同一个入口。其他代码文件的新旧对应如下:
| 所在目录 | 原文件 | 当前文件 |
| --- | --- | --- |
| `src/features/catalog/` | `IndexPage.tsx` | `HomePage.tsx` |
| `src/features/catalog/` | `ListingPages.tsx` | `BrowsePages.tsx` |
| `src/features/catalog/` | `SearchPages.tsx` | `DiscoveryPages.tsx` |
| `src/features/playback/` | `PlayPage.tsx` | `DramaWatchPage.tsx` |
| `src/features/playback/` | `SeriesPlayPage.tsx` | `SeriesWatchPage.tsx` |
| `src/features/content/` | `NavigationPage.tsx` | `SiteNavigationPage.tsx` |
| `src/server/pages/` | `play.ts` | `playback.ts` |
后台 `hkdj-web-admin` 的导航分类 URL 支持搜索、选择当前固定页面入口及直接输入具体路径或外链;历史自定义值保留,提交仍使用原 `urlValue` 字符串。完整清单源自实际路由注册,执行 `npm run routes:export -- --admin ../hkdj-web-admin` 同步本仓库 `src/lib/page-route-catalog.json`、下方对照和后台 `src/constants/hkdj-web-routes.json`;使用 `npm run routes:check -- --admin ../hkdj-web-admin` 验证一致性。动态详情、筛选和分页需要真实参数,因此只列入对照,不将未填写参数的模板作为后台可选链接。
本次路径更新涉及 Web、后台管理端和 Go 后端三个仓库。部署后端并由原 sitemap 任务重新生成 XML 后,文件内链接才会更新;已有 XML 仍按原字节返回,其中的旧页面地址通过 301 兼容。
<!-- BEGIN PAGE_ROUTE_CATALOG -->
以下清单由 `npm run routes:export` 从实际路由注册生成,包含全部页面、筛选和分页模式。含 `:参数` 的路径需要填写真实值;后台导航下拉只展示可直接访问的固定入口,详情页和外链可手动输入。
| 页面 | 原路径 | 当前路径 |
| --- | --- | --- |
| 首页 | `/` | `/` |
| 移动端搜索 | `/search-mobile/` | `/discover/search/` |
| 搜索结果 | `/search/` | `/search/` |
| 搜索结果 | `/search/:q/` | `/search/:q/` |
| 搜索结果 | `/search/:q/page/:page/` | `/search/:q/page/:page/` |
| 关于我们 | `/about/` | `/about/` |
| 帮助中心 | `/help/` | `/help/` |
| 服务条款 | `/terms/` | `/terms/` |
| 隐私政策 | `/privacy/` | `/privacy/` |
| 2257 声明 | `/2257/` | `/2257/` |
| 版权声明 | `/dmca/` | `/dmca/` |
| 下载 App | `/download-app/` | `/app/download/` |
| 回家的路 | `/home-road/` | `/return-home/` |
| 官方导航 | `/nav/`、`/dh/` | `/navigation/` |
| 短剧列表 | `/list/` | `/dramas/` |
| 短剧列表 | `/list/:theme/` | `/dramas/:theme/` |
| 短剧列表 | `/list/:theme/:updateStatus/:sortType/` | `/dramas/:theme/:updateStatus/:sortType/` |
| 短剧列表 | `/list/:theme/:updateStatus/:sortType/page/:page/` | `/dramas/:theme/:updateStatus/:sortType/page/:page/` |
| 短剧推荐栏目 | `/more-list/:configId/:timeRange/:theme/page/:page/` | `/curated/dramas/:configId/:timeRange/:theme/page/:page/` |
| 短剧推荐栏目 | `/more-list/:configId/:timeRange/:theme/` | `/curated/dramas/:configId/:timeRange/:theme/` |
| 短剧推荐栏目 | `/more-list/:configId/page/:page/` | `/curated/dramas/:configId/page/:page/` |
| 短剧推荐栏目 | `/more-list/:configId/` | `/curated/dramas/:configId/` |
| 剧集推荐栏目 | `/more-series-list/:configId/:timeRange/:theme/page/:page/` | `/curated/series/:configId/:timeRange/:theme/page/:page/` |
| 剧集推荐栏目 | `/more-series-list/:configId/:timeRange/:theme/` | `/curated/series/:configId/:timeRange/:theme/` |
| 剧集推荐栏目 | `/more-series-list/:configId/page/:page/` | `/curated/series/:configId/page/:page/` |
| 剧集推荐栏目 | `/more-series-list/:configId/` | `/curated/series/:configId/` |
| 剧集列表 | `/series/` | `/series/` |
| 剧集列表 | `/series/:media/:topic/:sortType/` | `/series/:media/:topic/:sortType/` |
| 剧集列表 | `/series/:media/:topic/:sortType/page/:page/` | `/series/:media/:topic/:sortType/page/:page/` |
| 专题列表 | `/topic/` | `/collections/` |
| 专题列表 | `/topic/page/:page/` | `/collections/page/:page/` |
| 专题列表 | `/topic/sort/:sortType/` | `/collections/sort/:sortType/` |
| 专题列表 | `/topic/sort/:sortType/page/:page/` | `/collections/sort/:sortType/page/:page/` |
| 专题详情 | `/topic-detail/:slug/sort/:sortType/page/:page/` | `/collection/:slug/sort/:sortType/page/:page/` |
| 专题详情 | `/topic-detail/:slug/sort/:sortType/` | `/collection/:slug/sort/:sortType/` |
| 专题详情 | `/topic-detail/:slug/page/:page/` | `/collection/:slug/page/:page/` |
| 专题详情 | `/topic-detail/:slug/` | `/collection/:slug/` |
| 排行榜 | `/rank/` | `/rankings/` |
| 排行榜 | `/rank/page/:page/` | `/rankings/page/:page/` |
| 排行榜 | `/rank/:sortOrder/` | `/rankings/:sortOrder/` |
| 排行榜 | `/rank/:sortOrder/page/:page/` | `/rankings/:sortOrder/page/:page/` |
| 标签详情 | `/tags/:slug/sort/:sortType/page/:page/` | `/tags/:slug/sort/:sortType/page/:page/` |
| 标签详情 | `/tags/:slug/sort/:sortType/` | `/tags/:slug/sort/:sortType/` |
| 标签详情 | `/tags/:slug/page/:page/` | `/tags/:slug/page/:page/` |
| 标签详情 | `/tags/:slug/` | `/tags/:slug/` |
| 最新更新 | `/recently-added/` | `/latest/` |
| 最新更新 | `/recently-added/page/:page/` | `/latest/page/:page/` |
| 热门推荐 | `/popular-recommend/` | `/featured/` |
| 热门推荐 | `/popular-recommend/page/:page/` | `/featured/page/:page/` |
| 热门推荐 | `/popular-recommend/:rankType/` | `/featured/:rankType/` |
| 热门推荐 | `/popular-recommend/:rankType/page/:page/` | `/featured/:rankType/page/:page/` |
| 短剧播放 | `/play/:slug/` | `/drama/:slug/` |
| 短剧播放 | `/play/:slug/:ep/` | `/drama/:slug/:ep/` |
| 短剧播放 | `/play/:slug/recommendations/page/:guessPage/` | `/drama/:slug/recommendations/page/:guessPage/` |
| 短剧播放 | `/play/:slug/:ep/recommendations/page/:guessPage/` | `/drama/:slug/:ep/recommendations/page/:guessPage/` |
| 剧集播放 | `/series-play/:slug/` | `/series/watch/:slug/` |
| 剧集播放 | `/series-play/:slug/recommendations/page/:guessPage/` | `/series/watch/:slug/recommendations/page/:guessPage/` |
| 意见反馈 | `/feedback/` | `/feedback/` |
<!-- END PAGE_ROUTE_CATALOG -->
SSR 在 Proxy 中完成上游数据、游客恢复和 Cookie 写回,再交给 React 渲染,避免首屏流式响应开始后丢失 Cookie。小数据使用压缩内部请求头,较大数据暂存于操作系统临时目录下的私有目录,绑定 URL/Cookie、权限 0700/0600、60 秒过期。适用于当前单 Node 实例或同一文件系统的 standalone 部署;若未来改为分布式 Proxy 与渲染节点,需要更换交接存储。
公共配置可短期复用;登录用户内容仅在单次请求内复用,页面响应使用 `private, no-store`。必要内容接口临时不可用返回 503、`Retry-After` 和 noindex;真实缺失的实体及越界分页仍返回 404。
播放器通过 `package.json` 管理 `xgplayer`、`xgplayer-hls`,按需加载官方模块和样式,支持 MP4/HLS、加密密钥、预告切换、章节播放、全屏、解锁、评论及播放埋点。原 DPlayer、全局 Hls 和浏览器业务 JS 已删除。加密图片、二维码分别封装为 TSX 组件,二维码使用 npm `qrcode`;占位图 Logo 透明度保持 0.2。
## SEO 保留范围
- 上游站点/导航/栏目/内容 SEO、筛选维度和分页独立 TDK。
- 原收录白名单、私有页面与章节 noindex、无效实体和越界分页真实 404。
- 永久域名优先,代理协议/Host 回退;slug canonical、尾斜杠、去查询参数。
- OG/Twitter、Google/Yandex/Bing 验证、可配置百度验证和 IndexNow 文件。
- Organization、WebSite、WebPage、Breadcrumb、ItemList、FAQ、TVSeries/TVEpisode、VideoObject 等原 JSON-LD 条件和真实元数据。
- robots.txt、llms.txt、sitemap 索引/分片、纯渠道首页 302、下载地址 302。
上游 TDK、导航、公告和 JSON-LD 中的完整旧品牌词「黄果短剧」统一显示为「花魁短剧」。永久域名、canonical、真实联系地址、普通影片名称、验证值和 SDK 配置保留原值。
抓取文件遵循原 Go 路由。普通站点的 robots/llms 读取公共配置,保留 60 秒成功缓存;刷新失败沿用最近成功配置,首次失败使用空配置。地址使用原永久域名优先、代理协议/Host 回退规则。robots 按顺序读取 `template/pages/robots.txt`、`templates/pages/robots.txt`、`app/template/pages/robots.txt`、`app/templates/pages/robots.txt`,没有可用文件时使用原内嵌文本,再回退原内置默认文本。保留原爬虫规则、行处理、已有 Host 和大小写语义,按原算法写入 Sitemap 地址。
**sitemap 由原后端生成,Next 只提供文件服务。** 开发环境的 `SITEMAP_DIR` 默认 `./template/pages/sitemaps`;测试和生产默认 `../template/pages/sitemaps`,均相对 Node 工作目录解析。测试 Node 运行于 `/server/hkdj_net/h5/next`,后端持续写入 `/server/hkdj_net/h5/template/pages/sitemaps`;正式环境同样按 `h5/next` 与 `h5/template/pages/sitemaps` 的父子目录关系读取,目录不同可通过对应 `.local.env` 或进程变量覆盖。`/sitemap.xml` 先读取该目录的 `sitemap.xml`,再读取 `./template/pages/sitemap.xml`;`/sitemaps/:name` 仅读取该目录中通过原文件名校验的分片。成功响应保留原 XML 字节、loc、priority、lastmod 和文件响应语义,不改域名、不重写新旧路径,也不从分片生成索引。缺失索引、非法或缺失分片均走原站完整 404 页面及其共享数据加载,不生成静态页面 sitemap、空 XML 或远程 API 兜底。
成功的抓取文件通过原生 Route Handler 输出,保留 `text/xml; charset=utf-8`、Last-Modified、Content-Length、Accept-Ranges、条件请求和单段/多段 Range;HEAD 不返回 body。Proxy 提前将缺失文件、非法分片和不支持的方法交给完整 TSX 404;导航模式按 Go `DHInit` 不开放这些路由。Next 应用压缩关闭以保持原 Go 文件字节长度与 Range 语义,公网代理可沿用原有传输压缩配置。
新旧服务应读取同一份持续更新的后端产物;文件来源目录不要放进前端发布时会清空的构建目录。也可通过 `SITEMAP_DIR=/绝对路径/template/pages/sitemaps` 指向后端目录,或使用保留该关系的目录挂载。公网代理仍需正确转发 `Host`、`X-Forwarded-Host` 与 `X-Forwarded-Proto`;不提供额外公网 origin 开关或 IP 限制来改变原站地址规则。
本地需要真实 XML 时,显式执行只读同步命令;页面请求、开发启动和构建不会连接 SSH:
```bash
npm run sitemap:sync
```
该命令始终按开发配置读取,优先级为进程变量 > `config/development.local.env` > `config/development.env`;已设置 `APP_ENV=test/production` 时拒绝执行,避免继承服务端目录。开发默认 SSH 来源为 `root@172.104.181.99:22889` 的 `/server/hkdj_net/h5/template/pages/sitemaps`,可覆盖 `SITEMAP_SYNC_HOST`、`SITEMAP_SYNC_PORT`、`SITEMAP_SYNC_DIR` 与本地 `SITEMAP_DIR`。需提前配置 SSH 密钥及可信 known_hosts;命令不交互输入密码,也不关闭主机校验。仅下载原索引与其引用的有效分片,校验 XML 和索引稳定性后保留原字节、域名、路径和修改时间落盘,最后替换索引;不生成 sitemap、不写远端、不删除未引用的本地文件。同步 XML 及旧本地 `data/sitemaps` 兼容文件均忽略提交;页面 trace 排除 XML,最终发布包还会定向清理 Proxy trace 带入的副本。
`SEO_INDEXNOW_KEY` 在三套环境配置中使用同一份重新生成的 Key,并与代码默认值一致;站点根路径 `/{key}.txt` 自动返回该 Key。后端推送进程的 `HKDJNET_INDEXNOW_KEY` 需使用相同值。百度可通过 `SEO_BAIDU_SITE_VERIFICATION`、`SEO_BAIDU_HTML_FILE` 配置,验证文件放入 `config/seo/`。搜索引擎实际收录取决于真实域名、后端内容和站长平台状态。
## Figma 与界面资源
设计来源:[花魁短剧 WEB / H5](https://www.figma.com/design/1vUq4wf5xOs8ZOlmt26Q3O/?node-id=0-1)。页头、页尾、徽标、favicon 和匹配图标均通过 Figma REST API 导出;资源保存在本地,运行和构建不依赖 Figma。SVG 保存 REST 返回的完整原始字节与 Figma 原始尺寸,显示尺寸由 CSS 或 HTML 的宽高属性决定。导出节点、尺寸和校验信息见 `public/static/brand/manifest.json` 与 `scripts/figma-icon-manifest.json`。没有精确对应节点的原有功能图标保留,清单记录原因。
导航、登录及 SEO 使用 `/static/brand/huakui-20261013/` 下的版本化品牌资源,路径统一在 `src/lib/brand-assets.ts` 管理,避免旧站同名静态资源和 CDN 缓存继续显示旧 logo。`assets:sync` 同步生成这些资源并写入校验清单;原路径保留兼容,PWA 图标也从新版品牌源图生成。
```bash
# 仅维护设计资源时需要本机 FIGMA_TOKEN;不将 token 写入仓库。
npm run assets:sync
# 离线检查资源完整性,不访问 Figma。
npm run assets:check
```
首页、列表、播放器和静态页面分别适配 1440px 桌面与 375px 手机设计。H5 底部导航按设计页面显示。影片、广告、余额、套餐、社交地址和二维码继续使用真实上游配置,不将设计示例数据当作业务内容。
设置页的头像、昵称、简介、隐私模式和密码使用现有上传与资料接口。通知偏好、邮箱修改及注销没有对应上游能力,保留设计位置并明确禁用,避免显示保存成功却未生效。
下载入口共用桌面下载弹窗与 H5 安装引导;iOS 支持 App Store、轻量版两个页签,Android 展示安装步骤。以下服务端变量可分别配置真实安装地址,三套环境默认均留空:
```dotenv
APP_IOS_STORE_URL=
APP_IOS_PROFILE_URL=
APP_ANDROID_APK_URL=
APP_DEEP_LINK_URL=
```
平台地址未配置时,按钮显示“前往下载页”,沿用上游 `AppAddr` 或 `/app/download/` 跳转;未配置 App 深链时隐藏打开 App 入口。链接保留原始跳转地址与导航埋点,不生成设计示例中的安装包、描述文件或 App Store 地址。
## 接口和媒体
保留原 BFF 路由、别名、参数校验、业务响应壳、上传和 CSV 导出。`Cookie`、cookie token 优先的 `Authorization`、客户端 IP、浏览器 UA、`X-User-Agent`、`X-SDK-Common` 透传;过滤 hop-by-hop 和内部控制头。多个 `Set-Cookie` 独立回传。埋点上下文按原白名单校验并保留 SDK 原字段。
公共配置隔离过期登录凭证;个人资料刷新与游客恢复写回当前请求,避免首屏重复建立会话。正式账号失效和内容允许降级游客的规则分别保留。
`/h5/m3u8/*`、`/enkey`、`/newenkey` 保留查询签名、密钥和媒体响应,媒体请求不强制携带登录凭证。`/enkey` 上游不可用时支持 `M3U8_KEY_FILE` 本地兜底;原本地密钥已复制到忽略提交的 `config/secrets/m3u8.key`。部署时提供同一文件或挂载其路径,文件不要放入 `public`。
## 验证
```bash
npm run check
npm run assets:check
npm run build:dev
npm run build:test
npm run build
npm run test:catalog
npm run test:popups
npm run test:playback
npm run test:ssr
SMOKE_URL=http://localhost:25131 npm run test:browser
SMOKE_URL=http://localhost:25131 npm run test:ui
```
浏览器检查默认使用 macOS Chrome,可通过 `CHROME_PATH` 指定。`test:catalog`、`test:popups`、`test:playback` 使用隔离上游与真实 React 组件,验证目录交互、弹层焦点/滚动、官方 xgplayer、加密 HLS、评论与埋点,不改动真实账号。`test:ssr` 使用生产构建核验 SEO、HTTP 状态、Cookie、透传和并发隔离,不访问真实业务接口。
全站界面截图默认输出 `/tmp/hkdj-figma/implementation`,浏览器基础检查输出 `/tmp/hkdj-browser-smoke`。完整真实内容回归需要原上游剧集、专题与标签数据;空环境可设置 `UI_ALLOW_EMPTY_CONTENT=1`,缺失实体仍必须返回 404 与 noindex。
原 Go 输出夹具保留为 SEO、链接、卡片、预览/可播/锁定场景的迁移参照。签名时间、票据和后端实时热度属于动态值。Next.js 会先规范化连续斜杠和 `..` 路径,此类异常 URL 与 Gin 可能存在差异,正常站内链接、canonical 和原筛选/分页规则保留。
## 独立部署
```bash
npm run build
npm run bundle
cd dist/production
UPSTREAM_API_URL=https://your-api.example node start.mjs
```
`bundle` 包含 standalone 服务、完整静态资源、对应环境公共配置,以及 robots 和站点验证文件,无需在部署机器安装依赖。后端生成的 sitemap XML(包括开发同步副本)不打入前端包;发布程序必须保留并挂载服务器持续更新的源目录。生产机可以通过进程变量或 `config/production.local.env` 提供配置;sitemap 和密钥可外部挂载。`SITEMAP_DIR` 指向的外部目录不会被构建或打包扫描、生成或覆盖。测试包使用 `npm run bundle:test`。不要在构建机器本地覆盖文件中放入需要打包的生产凭证。
Next 16.4 当前仍可能通过 Node Proxy 的 `middleware.js.nft.json` 将开发 XML 复制到原始 `.next*/standalone`,页面级 tracing 排除不能保证该目录无 XML。发布应使用 `npm run bundle` / `bundle:test` 生成的 `dist`:打包脚本只在复制后的独立目录清理 `template/pages/sitemaps`、`data/sitemaps` 内的合法 sitemap 文件及备用 `template/pages/sitemap.xml`。清理前逐层检查父目录,不接受符号链接或异常文件;只 unlink 已确认的文件,不递归删目录,也不跟随后端挂载。robots、密钥及未知文件保留。
也提供 Dockerfile,构建参数 `APP_ENV=production|test|development` 选择环境,启动时传入 `UPSTREAM_API_URL`,容器默认端口 3000。
## 测试服上传
参考 `naixin-web-nuxt` 的直连测试发布流程,入口为仓库根目录 `deploy_test.sh`:
```bash
# 只核对流程和目标,不构建、不连接服务器
DEPLOY_DRY_RUN=1 npm run deploy:test
# 检查、构建、打包、上传并更新测试服
npm run deploy:test
# 等价于 ./deploy_test.sh
```
默认目标为 `root@172.104.181.99`,SSH 端口 `22889`,上传目录 `/server/hkdj_net/h5/`。可通过 `DEPLOY_HOST`、`DEPLOY_SSH_PORT`、`DEPLOY_DIR` 临时覆盖。发布后默认检查公网 `https://hkdjneth5.remoces.com`,可通过 `DEPLOY_CHECK_URL` 覆盖为另一个 HTTP(S) 站点根地址;dry-run 仅显示该地址,不访问域名。需要本机已安装依赖、zip/unzip、SSH/SCP,并提供忽略提交的 `config/secrets/m3u8.key`。
脚本依次执行 `npm run check`、`build:test`、`bundle:test`,生成 `dist.zip` 和 `dist.zip.sha256`,连同 `deploy/up_next_h5.sh` 上传。包保留配置、页面和静态资源,排除 `node_modules` 与本地环境覆盖文件;服务器在暂存目录按 lockfile 执行 `npm ci --omit=dev --include=optional --ignore-scripts`,为 Linux 安装对应架构的运行依赖。服务器需要 Node.js **20.9+**(推荐 22)、npm、可访问 npm 包源,以及 Bash、unzip、sha256sum、curl 和 Supervisor。运行时 `dotenv` 属于正式依赖。
服务器脚本校验包后更新 `/server/hkdj_net/h5/next/`,上一版留在 `next.prev/`,通过独立 Supervisor 程序 `hkdj_next_h5` 运行 `node start.mjs`。保留服务器的 `next/config/test.local.env`、密钥和后端生成的 sitemap:运行目录内默认或自定义目录中的原 XML 优先于包内文件,第二索引 `template/pages/sitemap.xml` 和四个可选 robots 覆盖文件同样保留;新目录不存在时可迁入旧版本 `data/sitemaps`,已有空目录也视为权威,不恢复旧 XML。已配置的外置目录不复制、不清空、不覆盖,默认目录的符号链接及其运行目录内目标也保留。首次部署可读取原 Go 目录的 XML;后台持续写原目录时应配置外置 `SITEMAP_DIR`,避免只使用一次复制的快照。密钥首次可复用原目录 `config/secrets/m3u8.key` 或 `conf/m3u8.key`。后续私密配置放服务器的 `next/config/test.local.env`,不要放入上传包。启动与健康检查失败会返回非零状态,切换后的失败会尝试恢复上一版。本地包及失败时的服务器上传包留存供排查。
**测试站 Nginx 必须代理到 `127.0.0.1:30334`**(若服务器覆盖 `PORT`,使用实际端口),包括页面、`/_next/`、`/static/`、`/api/` 和媒体路径;上传脚本不修改 Nginx 配置。此前 `/etc/nginx/conf.d/hkdj_net-h5.conf` 将该域名代理到 API 端口 `30331`,导致首页和 `/healthz` 都返回 `{"code":1000,"msg":""}`,本机服务健康也无法证明域名已切流。
服务器健康检查覆盖 `/healthz`、首页及 `/enkey`;随后本地脚本通过公网域名检查 `/healthz` 必须为 HTTP 200 且 JSON `status=ok`、`environment=test`,首页必须为 HTTP 200、`text/html` 并含 Next 静态资源标记。公网检查最多重试 3 次,每次请求超时 4 秒,失败会明确提示“服务器服务已更新,但域名检查失败”,返回非零状态;检查不通过不会误报完整发布成功,也不会自动回滚已经健康的新服务。完成后仍应通过测试站域名检查 PC/H5 页面和真实媒体链路。
## 正式服上传
使用既有跳板机 `running.sh` 链路:
```bash
npm run deploy:prod -- --dry-run # 只核对配置,不构建、不连接
npm run deploy:prod -- --package-only # 检查、构建、生成运行包与 MD5,不上传
npm run deploy:prod # 构建、上传并执行正式服 running.sh
```
脚本执行 `check` → `build` → `bundle` → `bundle:linux`,生成根目录 `web_hkdj_h5.tar.gz` 和 `web_hkdj_h5.md5`(仅一行 MD5 hash)。tar 根部直接包含 `start.mjs`、`server.js`、`.next/`(含静态资源)、`public/`(含 PWA Worker/图标)、`config/production.env`、媒体密钥、必要数据及 `node_modules/`,不套 `dist/production/` 外层目录。
本机最低 Node.js 20.9、npm **10.9.2**,推荐 Node.js 22。正式包保留 Next standalone 已跟踪并裁剪的 `node_modules`;只在临时目录安装与 Linux 目标匹配的 Sharp/libvips 两个原生包,以替换 macOS 原生文件,版本、下载地址和 integrity 均取仓库 lockfile。最终输出为 `dist/production-linux-x64-glibc/`,包含裁剪后的 Next/React/Sharp 运行文件、`dotenv`、`@swc/helpers` 和已跟踪的 WASM 回退,不携带完整 Next 安装树、SWC 原生编译器、Playwright 或未跟踪的客户端依赖。包内无需执行 `npm install`,直接 `node start.mjs`;临时安装树不会进入发布包。
默认目标为 Linux **x64/glibc**;可设置 `DEPLOY_TARGET_ARCH=x64|arm64`、`DEPLOY_TARGET_LIBC=glibc|musl`。仓库 package/lock、本机依赖及 `dist/production/` 保持独立。运行包排除 `.env*`、`*.local.env`、日志及 macOS 元数据,密钥随包提供;生产私密覆盖配置需在服务器提供,并由服务器发布脚本保留。
默认上传及触发命令严格对应两种端口约定:
```bash
scp -P 22889 web_hkdj_h5.tar.gz web_hkdj_h5.md5 \
server@116.212.120.2:/home/server/web_hkdj/h5/nuxt/
ssh -p 22 server@116.212.120.2 /home/server/web_hkdj/h5/nuxt/running.sh
```
可通过 `DEPLOY_JUMP_HOST`、`DEPLOY_JUMP_DIR`、`DEPLOY_SCP_PORT`、`DEPLOY_SSH_PORT` 覆盖连接参数。跳板机脚本只能按完整路径执行,不带参数或额外 shell 命令;脚本和正式机 `up_node_h5.sh` 由既有部署链路管理。同仓库使用发布锁防止并发构建/上传覆盖同名包,构建后显示压缩包体积。任何构建、上传或远端执行失败均返回非零,本地包留存;正式脚本输出保存到 `dist/production-running.*`,即使远端返回 0,出现 `BACKOFF`、`FATAL`、`ERROR` 或 `spawn error` 也会判定失败。端口和健康检查由服务器发布脚本负责。正式站 Nginx 应将页面及 Next/BFF/媒体资源代理至 `26935`,公网域名需在发布后另行验证。
正式机 `up_node_h5.sh` 必须完整复制隐藏目录,例如 `cp -rf "$STAGING/." "$APP_DIR/"`;使用 `$STAGING/*` 会漏掉必要的 `.next/`。覆盖前保留 `.local.env`、媒体密钥、`template/pages/sitemaps` 和 `template/pages/sitemap.xml`,服务器已有 XML 优先于包内同名文件;外置 `SITEMAP_DIR` 或挂载目录不得随版本目录删除。复制后应检查 `.next/BUILD_ID` 和 `start.mjs`,启动一次并检查 Supervisor 状态,复制或启动失败须返回非零状态。该远端脚本由既有链路管理,不包含在本仓库。
正式 Next 运行目录使用 `/server/web_hkdj/h5/next/`,Supervisor 的 `directory` 与更新脚本的 `APP_DIR` 必须一致;更新脚本先创建目录,再检查 `cd` 是否成功。不要直接运行在旧 Go 的目录里:Next 会优先读取根部遗留的 `static/`,遮盖新包的 `public/static/`。`start.mjs` 检测到这种混用会拒绝启动;迁移时保留服务器的本地环境覆盖和 sitemap 数据。