# 花魁短剧 · 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` 使用 `https://webhuakuiapi.mfcg.me`;测试服务与 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 兼容。 以下清单由 `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/` | 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 地址。 所有 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 数据。