Files
web_huakui_web/README.md
T

367 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 花魁短剧 · 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://127.0.0.1:20032`;测试服务与 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
```
这些变量仅供服务端使用。官方导航独立进程可以使用 `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 返回 `imgCdn`/`imgAppUsCdn`;测试 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 品牌/图标、分享图 |
所有业务页面由 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、真实联系地址、普通影片名称、验证值保留原值。
抓取文件遵循原 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 地址。
谷歌统计(GA4)由 `GOOGLE_ANALYTICS_ID` 配置,三套环境默认 `G-269F3ZFQFQ`,在根布局通过 `next/script` 于页面可交互后加载;留空即关闭。Yandex.Metrika 由 `YANDEX_METRIKA_ID` 配置,三套环境默认 `113657403`,同样在根布局加载并带 noscript 像素;留空即关闭。
所有 Telegram 联系入口(页尾、官方导航、回家的路、播放页链接、2257/DMCA、意见反馈,以及 Telegram 形式的「联系我们」客服链接)统一使用 `CONTACT_TELEGRAM_URL`,三套环境默认 `https://t.me/jbd125`,覆盖上游 `tgGroup`、`telegram`、`telegramAdd`。值无效时回退为默认账号。
## 接口和媒体
保留原 BFF 路由、别名、参数校验、业务响应壳、上传和 CSV 导出。`Cookie`、cookie token 优先的 `Authorization`、客户端 IP、浏览器 UA、`X-User-Agent` 透传;过滤 hop-by-hop 和内部控制头。多个 `Set-Cookie` 独立回传。
公共配置隔离过期登录凭证;个人资料刷新与游客恢复写回当前请求,避免首屏重复建立会话。正式账号失效和内容允许降级游客的规则分别保留。
`/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 数据。