Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
禁果短剧 · Next.js
项目编号 JHA-1050,应用及埋点 App ID 1050,npm 包与 /healthz 应用标识均为 jha-1050-jgdj-web。品牌名称统一为「禁果短剧」。
部署包、默认目录与 Supervisor 名已统一使用 jgdj。服务器地址和端口沿用原配置;首次发布前需准备对应目录、远端更新脚本和服务配置,并核对 Go 上游及后台 reportSdkConfig 均属于应用 1050。仓库地址登记为新项目的目标路径,本次不修改 Git remote。
Next.js App Router、React、TypeScript。保留 HTML 原稿布局和移动端导航,业务数据通过 Swagger API 动态获取。
当前视觉基准为 禁果短剧_2组.html:采用粉色主题、桌面双层品牌页头、首页主推轮播与六张横版推荐、历史悬浮面板及统一页脚。移动端沿用参考稿的菜单、搜索、双列内容和抽屉导航。栏目、剧集、广告及观看历史继续读取真实接口,HTML 内的示例剧目、账号、金额和联系方式不作为业务配置导入。
运行
已使用 Node.js 22.17.1 验证;最低要求 20.9。
nvm use 22.17.1
npm ci # 或:pnpm install
npm run lock:check
npm run dev
本地可用 npm 或 pnpm 安装依赖;发版与测试服安装以 package-lock.json + npm ci 为准。两份 lock(package-lock.json / pnpm-lock.yaml)中的运行时版本须一致,npm run lock:check(构建/打包前会自动执行)会校验已安装版本与两份 lock,避免出现 Next 小版本漂移导致 /healthz 500。变更依赖后请同时更新两份 lock(npm install 与 pnpm install)。
开发、测试和生产启动的默认地址均为 http://localhost:21030,端口或环境变量修改后需要重启 Next.js。
npm run typecheck
npm run build
npm test
npm start
现在需要运行 Next.js 服务以提供 API 代理与动态首页,不再导出静态 out/。旧的静态预览进程需要停止后用新命令启动。
环境配置
本项目使用 config/*.env 区分业务环境,启动脚本在运行 Next.js 前加载配置。
| 环境 | 配置文件 | 构建命令 | 启动命令 | 构建目录 |
|---|---|---|---|---|
| 开发 | config/development.env |
npm run build:dev |
npm run dev;构建后可用 npm run start:dev |
.next-dev/ |
| 测试 | config/test.env |
npm run build:test |
npm run start:test |
.next-test/ |
| 生产 | config/production.env |
npm run build |
npm start |
.next/ |
测试环境按生产模式构建、启动,使用 APP_ENV=test 和 NODE_ENV=production;npm run dev:test 则使用测试配置启动开发服务器。业务环境通过 APP_ENV 区分,不把测试部署误设为单元测试使用的 NODE_ENV=test。
浏览器始终访问当前站点的相对地址 /api/app/*,Next.js 服务端通过 UPSTREAM_API_URL 请求 Go 的 /api/app/*。开发、测试环境使用 http://172.104.181.99:26792,生产环境使用 http://139.162.49.252:21040(app;web 21041、h5 21042,H5 CDN d23iqbsywbvs3m.cloudfront.net)。上游地址仅由服务端读取。开发与测试默认端口 21030,生产 H5 端口 21042。公开接口缓存和 SEO 快照按解析后的上游域名隔离。修改服务器配置时,在对应目录新增 config/test.local.env 或 config/production.local.env;这些本地文件已忽略提交。优先级为:进程环境变量 > 对应 *.local.env > 对应基础配置。开发环境兼容旧 .env.local,其优先级低于 config/development.local.env;测试和生产不从旧 .env.local 加载本站业务配置。
原有 API_BASE_URL 仍作为兼容别名。在同一配置层内 UPSTREAM_API_URL 优先;较高配置层的旧变量仍可覆盖较低层的新变量,启动后统一为同一个上游地址。上游地址仅在服务端读取,不写入浏览器代码。
例如需要测试服务同时运行在另一端口,可在 config/test.local.env 写入:
PORT=21022
也可以临时运行 npm run start:test -- --port 21022。不同环境使用各自的构建产物及 SEO 快照缓存;先执行对应环境的构建命令,再执行其启动命令。UPSTREAM_API_URL 等服务端配置在启动时读取,修改后重启即可;NEXT_PUBLIC_* 在构建时写入浏览器代码,修改后应重新构建。
开发环境默认关闭公共接口缓存,测试和生产保留原有缓存上限;可用 PUBLIC_DATA_CACHE_TTL_MS 覆盖。npm run check 会先完成生产构建(含类型检查),再运行完整本地回归测试。
打包与部署
使用 Node.js 22.17.1;生产跨平台打包需要 npm >=10.9.2。两份脚本均先构建、运行回归测试,再整理 Next.js standalone、静态资源和对应环境配置。
# 预览流程,不构建、不连接服务器
bash deploy_test.sh --dry-run
bash deploy_prod.sh --dry-run
# 只生成本地压缩包,不上传
bash deploy_test.sh --package-only
bash deploy_prod.sh --package-only
测试产物为根目录的 web_jgdj_h5.tar.gz 和 web_jgdj_h5.tar.gz.sha256,上传相同文件及 up_next_h5.sh;同时刷新同名 web_jgdj_h5.md5 供本地兼容校验。包内直接包含 start.mjs、server.js、.next-test/、public/、config/test.env 和 lockfile。测试包不带本机 node_modules,使用 tar -xzf web_jgdj_h5.tar.gz 解压后,在 Linux 执行 npm ci --omit=dev --no-audit --no-fund,再执行 node start.mjs;默认端口为 21030。可通过 DEPLOY_PACKAGE_NAME=自定义名称.tar.gz 覆盖包名,校验文件随之生成。测试和生产使用同名压缩包,根目录文件对应最后一次执行的环境。
生产产物为 web_jgdj_h5.tar.gz 和纯哈希格式的 web_jgdj_h5.md5,包含 .next/ 和 standalone 裁剪依赖,默认适配 Linux x64/glibc,可直接执行 node start.mjs。可通过 DEPLOY_TARGET_ARCH=arm64、DEPLOY_TARGET_LIBC=musl 选择目标架构和 libc。打包时仅从 lockfile 安装对应 Linux Sharp/libvips,并校验 ELF 架构,不携带 macOS 原生模块。
两种包仅包含对应的基础 config/*.env,不打包 .env*、*.local.env 和 SEO 缓存。服务器的私有覆盖配置放在发布目录下对应的 config/test.local.env / config/production.local.env;修改 NEXT_PUBLIC_* 必须重新构建。本站媒体密钥由上游接口代理提供,无需额外的本地 m3u8.key 文件。
部署默认值由两份脚本开头的变量定义,并支持环境变量覆盖;当前上传配置如下。本项目包名和 Supervisor 名使用 jgdj:
| 环境 | SSH 目标 | 上传目录 | SCP / SSH 端口 |
|---|---|---|---|
| 测试 | root@172.104.181.99 |
/server/web_jgdj/h5 |
22889 / 22889 |
| 生产 | server@34.92.204.175 |
/home/server/web_jgdj/h5 |
22889 / 22889 |
直接执行脚本会使用上述地址;仅打包继续使用 --package-only。需要其他目标时可覆盖:
DEPLOY_HOST=user@test-host DEPLOY_DIR=/srv/jgdj/h5 DEPLOY_RUN_DIR=/srv/jgdj/h5 bash deploy_test.sh
DEPLOY_JUMP_HOST=user@jump-host DEPLOY_JUMP_DIR=/srv/jgdj/h5 bash deploy_prod.sh
测试当前 SSH 端口为 22889,可通过 DEPLOY_SSH_PORT 修改;它与 config/test.env 中的应用监听端口 PORT 分别配置。deploy_test.sh 上传成功后执行 ssh -p 22889 root@172.104.181.99 "cd /server/web_jgdj/h5 && bash run.sh",由服务器现有的 run.sh 负责后续更新。上传目录和执行目录当前均为 /server/web_jgdj/h5,分别可用 DEPLOY_DIR、DEPLOY_RUN_DIR 覆盖;脚本不上传或覆盖远端 run.sh。部署测试从脚本帮助信息读取当前默认执行目录,并单独验证目录覆盖及上传、执行失败时的退出行为,修改服务器目录无需同步修改测试断言。上传失败不会执行远端更新,远端命令失败会立即退出;--package-only 和 --dry-run 均不执行 SSH。可通过 DEPLOY_CHECK_URL 在远端命令成功后增加公网健康检查。
随包上传的 up_next_h5.sh 保留供服务器按需调用:该脚本校验 SHA-256、检查并解压 tar.gz、安装运行依赖、保留本地覆盖与 SEO 缓存,然后切换版本、启动服务并检查 /healthz 与首页,失败自动恢复旧版本。默认 Supervisor 程序为 jgdj_next_h5,在服务器运行该脚本时可通过 DEPLOY_PROGRAM 覆盖;使用自定义包名时需设置对应的 DEPLOY_PACKAGE_NAME。服务器 run.sh 的解包、服务管理及包名规则以该脚本自身实现为准。
生产脚本将 web_jgdj_h5.tar.gz 和 web_jgdj_h5.md5 上传到 server@34.92.204.175:/home/server/web_jgdj/h5/,SCP 与 SSH 默认端口均为 22889。上传成功后执行 ssh -p 22889 server@34.92.204.175 "cd /home/server/web_jgdj/h5 && bash rsupdate.sh"。服务器现有的 rsupdate.sh 负责解包、保留生产私有配置和重启服务;本项目不上传或覆盖该脚本。上传失败不会触发远端更新,SSH 失败或返回失败状态会停止发布。可通过 DEPLOY_JUMP_HOST、DEPLOY_JUMP_DIR、DEPLOY_SCP_PORT、DEPLOY_SSH_PORT 覆盖目标,执行目录同步跟随上传目录。
生产 SSH 命令按参考项目统一由 REMOTE_UPDATE_COMMAND 生成,预览与实际执行的命令文本一致,目录经过安全字符校验后不再额外添加单引号,兼容服务器按文本匹配的命令白名单。执行前打印远端命令,便于核对权限规则和自定义目录;完整输出继续保存在发布日志中。
npm run bundle:test / npm run bundle 只整理已有构建;npm run bundle:linux -- x64 glibc 为生产产物准备 Linux 原生依赖。同一仓库的测试、生产脚本共用打包锁,压缩包及 dist/ 已忽略提交。/healthz 是不触发游客登录、禁止缓存和索引的部署探针,不依赖上游接口。
页面路由
首页使用站点根地址,例如 http://127.0.0.1:21030,也支持 http://127.0.0.1:21030/;两者都直接显示首页。浏览器把纯域名的请求路径解析为 /,不需要跳转。短剧列表单独使用 /shorts/。
其他页面统一使用以 / 结尾的路径路由,可直接打开、刷新、在新标签页打开,并使用浏览器前进和后退。链接生成时就包含尾斜杠,不通过跳转或地址替换补齐。
- 首页
/;短剧/shorts/;专题/topics/;热播榜/rank/hot/、新剧榜/rank/new/、追剧榜/rank/follow/;应用/apps/;回家的路/home-road/。榜单 tab 使用页面链接,数据类型由路径决定;原/rank/仍可直接访问热播榜,不重定向。 - 官方入口
/navigation/:独立展示线路、官方渠道与 App 下载,不加载首页导航、播放器或账户交互;首页和业务页面不再弹出入口导航页。 - 专题、题材、标签、栏目:
/topics/:id/、/theme/:id/、/tag/:id/、/config/:id/。 - 详情
/detail/:id/;章节播放/play/:id/:chapterId/。 - 内容路由优先使用接口返回的英文或拼音
slug(字母、数字、连字符、下划线),中文或无效 slug 回退到 ID,例如/detail/city-story/、/play/city-story/90001/、/topics/urban/、/tag/urban/、/config/editors-choice/。章节段仍是章节 ID。卡片、轮播、选集、评论/列表分页、解锁后跳转和 SEO 地址使用同一规则;原 ID 地址继续直接访问。有配置的moreUrl优先级不变。 - 搜索
/search/关键词/;分页/search/关键词/page/2/。关键词通过encodeURIComponent作为单个路径段编码,支持中文和特殊字符。 - 独立导航页
/navigation/从config/info读取站名、介绍、navPageList、永久入口、TG/X 和 App 下载地址。线路按line排序;旧配置兼容dhNewUrlAddr1/2/3与最新地址字段。未配置或无效的链接不展示,不使用演示账号或首页链接兜底。 - 个人中心
/member/center/,收藏、历史、点赞、已购和设置分别为/member/favorites/、/member/history/、/member/likes/、/member/purchased/、/member/settings/。 - 帮助与平台信息
/info/:slug/;充值/info/deposit/;VIP/info/deposit/vip/。 - 所有分页使用独立路径,例如
/shorts/page/2/、/rank/new/page/2/、/topics/31/page/2/、/member/history/page/2/、/info/deposit/vip/page/2/;详情和播放页的/page/2/对应评论第 2 页。页码、上一页和下一页均为真实链接,可直接访问、刷新和前进后退。 - 排序通过
?sort=2保留;评论排序使用?commentSort=1。切换排序时链接指向第一页,翻页时保留当前条件;第一页链接使用原页面路径,不执行自动跳转或地址替换。
页面直接根据 URL 路径加载,不解析或转换旧 hash 路由。站内导航、动态卡片、播放选集、搜索和会员入口均使用路径地址;无效路径返回 404。
页面地址统一使用尾斜杠。src/proxy.ts 对有效页面的无尾斜杠地址返回 308 永久重定向,例如 /shorts → /shorts/,分页、搜索关键词和查询参数完整保留;首页 / 直接响应。Next.js 保留 trailingSlash: true 和 skipTrailingSlashRedirect: true,由 Proxy 仅处理页面,API、播放列表、密钥、静态资源地址不受影响;无效路径直接返回 404。
接口配置
顶部、侧栏和移动导航由 navigation/header.categoryList 在服务端渲染,按接口顺序显示名称、地址和二级导航。URL 导航与二级导航显示在主导航,底部类型不混入;成功返回空列表时不注入固定菜单。/hot 直接生成本站 /rank/hot/ 链接;已知系统入口的空地址按本站路由生成。导航公共数据缓存 60 秒,后台更新在缓存过期后的页面请求生效;页面高亮仍按当前路由判断。接口图标优先,缺失时沿用本站图标;外链添加 nofollow 等属性。
2026-10-26 已按 SGDJ Swagger 核对页面接口,具体映射见 页面接口对照。首页读取 modules,更多栏目使用 playlet/home-config/list 真实分页;短剧四种推荐使用 playlet/operate/list,题材使用 playlet/all/list。专题列表按区块显示,详情直接传 topicSlug 或 ID。此前移除的独立剧集入口及 /series/、/video/:id/ 路由继续返回 404。
页面 head 由本项目(JHA-1050)的 src/lib/seo-server.ts 和 src/lib/seo.ts 生成:全站名称与首页 TDK 取 config/info.seoInfo,栏目 TDK 优先使用 translation/category/list 的导航配置,具体专题、剧集使用对应接口信息。兼容字符串形式的 SEO 配置及 keyworld 等旧字段。/navigation/ 使用 seoNavInfo。
canonical 的域名优先取 config.foreverUrlAddr,其次取 newAddressInfo.foreverUrlAddr,只使用配置中的永久域名,不回退到请求 Host、API 或 CDN 域名。配置沿用 60 秒公共缓存,成功取得有效永久域名后,将公开 SEO 信息保存到 .next/cache/seo/,按 API 地址隔离;接口超时、报错、配置或永久域名暂缺时沿用最近一次有效值,服务重启也可读取已有快照。快照不包含 SDK 配置、账号或凭证;部署时需保留该缓存目录,完全没有有效配置/快照时不输出猜测的 canonical 和站点 JSON-LD。后台更新为有效永久域名后,缓存过期后的新页面请求采用新配置;同一浏览器文档沿用服务端给出的 canonical、site_name 和发布者身份。
canonical 不带查询参数,分页各自生成规范地址和有边界的 prev/next,第 1 页使用栏目地址。搜索、账户、充值和工具页按参考项目输出 noindex。服务端将 TDK、robots、Open Graph、Twitter 和图标信息直接写入 <head>,每次页面请求都返回对应页面的完整 head。本次未新增统计脚本。
/robots.txt 由 src/app/robots.ts 提供纯文本响应,不依赖账户会话或请求 Host。生产环境(APP_ENV=production)允许页面和 JS/CSS、图片、播放资源抓取,仅限制 /api/ 和 /healthz;开发、测试环境禁止全站抓取。搜索、账户、充值、工具和重复页面继续使用页面级 noindex,保留爬虫读取这些标记的权限。生产环境声明 Sitemap: https://永久域名/sitemap.xml,域名与 canonical 共用配置及最近一次有效快照;完全没有有效域名时仅省略 Sitemap 行,抓取规则继续返回。开发、测试环境不声明 Sitemap。本项只添加声明,XML 文件仍由后续 sitemap 服务提供。
公开正文与 SEO 使用同一次服务端取数:短剧、三个榜单、专题列表/详情、题材/标签、首页更多栏目、短剧详情、播放页及各自分页直接输出当前页的标题、简介、内容卡片、集数和已有评论,分页与免费集数保留原生链接。搜索结果也直接输出首屏正文,继续保持 noindex。详情/播放和栏目模板在服务端与站内导航间共用,沿用现有 HTML 样式。
请求与渲染方式:服务端先完成当前页面取数,再返回页头和完整正文;福利应用、回家的路、反馈、个人中心、收藏/历史/点赞/已购、设置及充值/VIP 页面也直接输出内容。链接和搜索使用浏览器原生页面请求,旧页面保留到新响应到达,不清空正文或插入页面加载占位。浏览器复用首屏快照绑定轮播、播放、表单和支付等交互,不再重复获取整页数据;提交成功后重新请求当前页面。
公共配置、导航、分类、页脚和标签选项使用 60 秒进程内缓存,首页使用 30 秒;同一上游地址及查询参数的并发请求合并,返回独立数据副本,缓存有数量上限。配置及首页刷新失败可沿用已成功取得的数据;错误和无效响应不写入缓存。账户、余额、套餐及付费观看权限不使用公共缓存,HTML 继续保持私有响应。可用 PUBLIC_DATA_CACHE_TTL_MS 缩短公共缓存时间,设为 0 可关闭;多实例各自维护缓存。
详情、章节、选集、评论和推荐按 URL 中的 ID 并行请求。浏览器复用首屏中的公开配置和页脚数据,下载与广告交互不再等待重复取数;SDK 配置仍单独读取,不写入 HTML。支持浏览器前进后退缓存时,公开页保留 DOM、滚动位置和交互,并在后台恢复当前会话和页面埋点;账户、播放及打开弹窗的页面仍重新请求,防止恢复已处置的支付/权限状态。
有效业务页面在 Proxy 中恢复账号或建立游客会话,将同一身份的 Cookie 传给本次服务端渲染并写回浏览器。客户端复用已准备的用户信息,避免首屏再次初始化游客。正式账号过期显示登录入口,接口异常直接返回重试状态;账户页面保持 private, no-store 和 noindex。首屏账号、账户列表、套餐、流水和播放器数据均按字段白名单传递,未授权付费视频源、服务端账号凭证及 SDK 私密配置不写入 HTML;点击金币集数仍读取实时权限、价格并由用户确认解锁。当前 Next.js 动态参数在路由入口只解码一次,中文和含空格、斜杠、百分号的搜索词与浏览器保持一致。
JSON-LD 参考 huangguo.com 的品牌节点及参考项目 seoser/jsonld.go:收录页输出 Organization、WebSite 和对应 WebPage / CollectionPage,列表输出实际内容的 ItemList,详情使用 TVSeries,播放页关联 TVEpisode 与具备真实封面、发布时间的 VideoObject。关于页使用 AboutPage,常见问题直接从页面可见问答生成 FAQPage。URL、站名与分页序号沿用现有动态 SEO 配置;不使用参考站的品牌、域名、伪造日期或评分。每份页面响应包含本页的原生 JSON-LD script,不收录页不输出旧页面的数据。测试覆盖脚本安全转义和重复节点清理。
- 文档:https://sgdj-h5.80sygtm.com/swagger/index.html
UPSTREAM_API_URL:开发、测试环境为http://172.104.181.99:26792,生产环境为http://139.162.49.252:21040;兼容旧API_BASE_URL。API_CHANNEL:可选游客、登录和绑定的默认渠道码;已有首次渠道时优先保留。- 项目编号:
JHA-1050。接口读取与业务处理位于src/lib/api/server.ts、src/lib/client/config.ts、src/lib/client/channel.ts、src/lib/client/auth.ts和src/lib/client/account.ts。
浏览器与服务端业务接口统一使用 /api/app/* 前缀。请求进入 Next.js 时,由 src/app/api/app/[...path]/route.ts 按白名单代理到配置上游的同名路径,并负责参数处理及 HttpOnly、SameSite 会话 Cookie;旧 /api/* 入口保留兼容。首页由服务端渲染,公开首页数据缓存 30 秒。公共接口不携带用户 token;Next.js 代理不向前端脚本返回 token,也不将 token 或密码写入浏览器本地存储。
部署时需核对 /api/app/* 的实际处理服务:若 Nginx 将该路径直接转发给 Go,请求会跳过 Next.js 的参数处理、Cookie 与 Authorization 转换及 token 过滤,登录、绑定、退出和受保护接口需按 Go 的鉴权约定单独验证。若该路径由 Next.js 处理,UPSTREAM_API_URL 应指向可独立访问的 Go 上游,避免请求再次回到本站。
若显式配置为 current,则使用当前请求域名(默认配置不使用此模式)。该模式依赖 Nginx 将上游的 /api/app/* 及 /h5/*、/enkey、/newenkey 等后端媒体路径转发到 Go,本站其他页面由 Next.js 处理;浏览器业务请求也使用 /api/app/*,需满足上一段的后端鉴权约定。上游请求若被再次转回 Next.js,会立即返回 502 和转发配置提示,避免递归请求。修改接口模式后重启服务即可,无须将当前域名写入前端构建产物。
POST 请求按参考项目转发,不在 Next.js 代理中比较 Origin、Host 或 Sec-Fetch-Site。CDN、反向代理修改内部域名或协议时,不再因来源不匹配提前返回 403;上游返回的鉴权及业务错误继续传回。接口白名单、请求方法限制、会话 Cookie 和代理循环检测继续生效。
请求头按 jha-1045-hgdjnet-web 的 common/request.js 与 useragent/build.go 对齐:浏览器业务请求使用 Accept: application/json、credentials: include,SDK 就绪后由其原始字段生成 X-User-Agent;JSON 设置 Content-Type,上传表单由运行时生成 boundary。Next.js 的 API、SSR 和媒体代理共用 src/lib/api/client-headers.ts,透传原生 User-Agent,优先保留客户端已有 X-User-Agent,缺失时解析 UA 中的设备、系统和浏览器信息,并读取 sid / _s_token_ Cookie 补充 SID。移除固定的 jgdj-web/1.0 和 server-public 设备头;设备 ID 仍通过业务参数和 SDK 原始字段传递。
有效客户端 IP 优先取 X-Real-IP,其次取 X-Forwarded-For 首个地址,上游只发送 X-Real-IP。公开接口仍不附带账号凭证,私有接口使用当前会话 Authorization;签名媒体和密钥使用 Accept: */*,不转发业务 Cookie 或 Authorization。合法 SDK 公共字段保持原值,短剧服务端事件上下文继续按业务路由校验后透传。共享缓存只保存公开响应数据,不保存访客请求头。
登录与游客绑定
登录流程由本项目(JHA-1050)的 src/lib/client/auth.ts 和 src/app/api/app/[...path]/route.ts 实现,保留本站登录弹窗。
- 首次访问自动建立游客会话;已有 Cookie 时先读取
auth/getInfo恢复身份、昵称和余额,接口未返回新 token 时继续使用原 token。 - 游客、登录和绑定使用同一持久设备标识,发送
devID和渠道;首次渠道取dc/ch/pc,后续访问保持不变。绑定支持inviteCode/invite/code邀请参数。服务端按参考项目发送设备头,并透传有效的客户端 IP。 - 绑定携带当前游客凭证以保留其数据;前端填写账号、密码和确认密码并同意协议,无需邮箱。上游要求的缺省邮箱由服务端生成为
账号@jgdj.local;实际已绑定邮箱仍用于找回密码。 - 绑定账号为 6–20 位字母或数字,密码为 8–64 位且包含字母和数字;登录兼容旧账号的 4 位密码及绑定允许的较长账号、密码,支持邮箱登录。
- 登录失败保留原身份;成功后更新账户并重新读取当前页面。自动游客、登录和退出响应串行处理,旧请求的失效结果不会清除客户端刚登录的身份。
- 游客请求明确返回会话失效时重建游客并重试一次;正式账号失效会清理会话并提示登录。登录、注册、找回密码自身的错误不触发游客恢复。
- 退出清理会话,并跳过紧接着一次页面初始化的自动游客登录。当前页面保持未登录,主动登录或绑定可恢复使用。
- 表单提交期间禁用重复提交;支持显示/隐藏密码,关闭和成功后均清除密码值。仅设备与渠道写本地存储,凭证保留在 HttpOnly Cookie。
已接入
- 首页轮播、公告、后台配置栏目、广告、题材及视频区块。
- 短剧运营分类、剧集视频列表、榜单、专题、题材/标签、分页和更多列表。
- 搜索、搜索联想、热搜、猜你喜欢;搜索记录单独保存在当前浏览器。
- 短剧详情、章节列表、章节权限和实际播放地址;HLS 播放、媒体代理和播放上报。
- 游客会话、登录、注册/游客绑定、找回密码、资料与头像更新。
- 个人中心统计、收藏、历史、点赞、已购列表;点赞、收藏、评论、回复、评论点赞。
- 金币/VIP 套餐、支付方式、订单创建、余额查询和逐集解锁。
- 应用列表、下载配置、固定平台说明页、反馈提交。
首页“推荐 / 最新 / 热门 / 随机”筛选项保持移除。栏目排序、列表数据、总数与观看权限由接口决定,失败时显示错误或空状态。
首页动态栏目沿用 HTML 的模块间距(桌面 48px、移动端 32px)与卡片网格,首个栏目不再复用原稿图文混排区域的选择器。首页封面统一为 3:4,图片居中裁切,避免接口方图、竖图混排时卡片高度不齐,并在图片解密加载前预留封面高度。
全站以原始 HTML 设计稿的页面结构和样式为基准:短剧、剧集、专题、题材/标签、更多列表和搜索页沿用原稿网格、标题、筛选栏和封面框;排行榜使用原稿数字角标、五列布局及窄屏断点,排名按接口分页顺序连续计算,榜单 tab 保持独立路由。搜索关键词只在已转义的标题中高亮。
专题列表与专题详情按 HTML 直接展示卡片和分页,不增加“最新专题 / 热门专题”或“编辑推荐 / 最新 / 最热”等分类选项。
搜索结果由本项目(JHA-1050)的 src/lib/client/search.ts 和 src/lib/seo-server.ts 实现:服务端准备会话后调用 playlet/search,传入 keyword/pageNum/pageSize,默认每页 25 条;展示接口总数、关键词和实际 pageSize,剧名优先使用 playName,不再本地筛选接口结果。无结果优先展示 everyoneWatching,缺少推荐时请求 playlet/operate/list?type=2;推荐不计入搜索数量。/search/ 可直接进入,空关键词跳过搜索接口,翻页使用 /search/{关键词}/page/{页码}/,搜索框、热搜、搜索历史和联想词入口均直接生成关键词路径,不通过重定向转换。标题高亮沿用 HTML 的 NFKC、大小写和空白处理;无结果/失败状态使用原稿插图,接口错误保留重试入口,不伪装为零结果。
个人中心、收藏、历史、点赞与已购页共用原稿的头像/金币余额卡片、资料入口、书架导航与卡片列表;个人中心默认展示真实观看历史,分页链接进入历史页。设置页恢复头像编辑、昵称保存和账号安全卡片,资料、头像、密码仍通过原有接口保存;登录/绑定弹窗恢复原稿输入框、眼睛图标与按钮层级。页脚与桌面导航采用新版 HTML 的排版。
“关于我们、用户协议、隐私政策、常见问题、2257、DMCA”六页固定使用 src/lib/info-pages.ts 中的原 HTML 文案与结构,不再请求富文本接口,底部入口也保持固定。每次访问均由服务端输出正文、标题和对应样式标记,无须等待首页或富文本接口。保留分节间距、折叠问答、编号段落和 mailto: 邮箱链接,反馈链接直接使用 /info/feedback/。
“关于我们”的“联系我们”沿用 HTML 分节和段落样式:商务合作读取 config.telegram(兼容 telegramAdd),用户反馈读取 config.tgGroup,空值再取 newAddressInfo;不使用邮箱或在线客服地址代替 TG。服务时间等附加说明读取 config/footer/info 的 footerInfo.contactList。这些内容随正文一起由服务端输出,未配置的项目不展示。
反馈页恢复原稿的问题选项、环境/设备面板、描述计数、上传区域和提交区。实际提交通过 feedback/add,环境信息随描述保存;图片通过现有 upload/image 上传,支持最多 3 张、每张 5MB 以内的 JPG/PNG/WebP,不展示接口尚未支持的视频上传。上传失败不清空表单,提交时阻止重复写入,离开页面后隔离迟到的结果。
详情页结构按原 HTML 调整:封面与资料卡、热度图标和标签、播放/下载与互动操作栏、三行简介展开/收起、选集横向分段和金币角标、官方链接、评论及嵌套回复。移动端复用原稿的封面、四列操作栏与五列选集布局;无下载配置时只显示播放入口,无推荐数据时不留空侧栏。内容和章节 ID 继续由接口提供,评论分页保持独立路由。
支付和解锁均由用户在页面确认后发起;创建订单不会直接把本地余额改为“支付成功”,余额通过用户信息接口刷新。
充值页按原始 HTML 设计稿展示余额与金币用途卡片、套餐面板、推荐角标、选中标记、独立支付按钮和交易明细。先选择接口套餐,再点击“立即支付”打开确认弹窗;价格、赠送金币与流水收支读取真实字段,不使用原稿的模拟金额和交易记录。桌面端四列、移动端三列套餐,交易明细继续使用独立分页路由。
充值确认弹窗按原 HTML 展示订单摘要、赠送/合计金币、金额徽标、支付渠道图标与选中圆点、“去支付”按钮和确认说明。金币数读取 baseCoin/giftCoin/totalCoin,价格按接口分单位换算;仅展示套餐配置的支付方式。切换样式按钮会同步实际下单参数,提交期间禁止重复下单,创建订单后仍需用户完成真实支付。
详情和播放页点击金币集数,直接打开原 HTML 样式的解锁弹窗,读取所选章节的最新权限、余额和价格;价格优先使用 costCoins,其次 singleCoins。确认后提交该集的 chapterId/playletId,成功后刷新余额并进入对应播放页;余额不足进入充值,会员章节进入会员套餐。读取期间禁止支付,支付期间禁止重复提交或切换目标;关闭弹窗、切换集数或离开页面会隔离迟到的读取结果。已允许观看的章节仍使用真实播放链接。
App 下载入口(顶部、悬浮图标、首页底部、详情页和 /navigation/)统一校验配置。配置返回前隐藏入口;没有有效的 HTTP(S) 下载链接时不展示。电脑端直接点击链接下载,已移除悬浮二维码面板和触发脚本;移动端保留点击下载弹窗,二维码只在实际点击时生成。
图片解密由本项目(JHA-1050)的 src/lib/client/images.ts 实现:使用相同密钥对前 100 字节做 XOR,识别 JPEG、PNG、GIF、WebP,并兼容未加密图片。封面、轮播、题材、广告、应用图标、用户/评论头像和视频 poster 统一处理;接口原图放在 data-src / data-poster,完成解密后展示 Blob URL。本地素材和下载二维码保持直接加载。移动端并发 2、桌面端并发 4,首屏主图和弹窗优先;共享请求与缓存、视口懒加载、12 秒超时及占位图回退,页面离开时释放 Blob URL。
播放逻辑由本项目(JHA-1050)的 src/lib/client/player.ts、src/lib/client/playback.ts 和 src/lib/api/media.ts 实现:绝对地址直接播放,相对地址走 /h5/m3u8/;Safari 优先原生 HLS,其余浏览器优先 hls.js,无 MSE 时再尝试原生 HLS。保留移动端内联属性,HLS 致命错误最多恢复 3 次,重试会重建播放器;切换预告、正片或离开路由时销毁旧实例。锁定章节可看配置的免费预告,正片按 canWatch 和参考项目的旧字段兼容规则判定;金币解锁需用户确认,成功后重新获取账户与章节,预告不提交正片观看上报。上一集/下一集使用接口的真实章节 ID。
H.264/AVC 与 H.265/HEVC 使用 hls.js 1.7.3 完整版解析,保留 HEVC MPEG-2 TS 支持(参考项目为 1.6.13 完整版)。多编码清单按设备能力保留 H.264、hvc1、hev1,不强制改写编码或限制为 H.264。编码不兼容时在 hls.js 与原生 HLS 之间各尝试一次,保留签名与播放位置;普通网络/密钥错误仍走原有恢复逻辑。无可用解码方式时显示编码不支持提示。H.265 的实际解码依赖浏览器、系统及设备能力,前端不提供转码或软件解码器,参见 hls.js 官方说明。
媒体和 /enkey、/newenkey 使用 Route Handler 读取运行时 UPSTREAM_API_URL,保留完整签名查询参数,不转发应用登录 Cookie 或 Authorization。密钥上游先请求无尾斜杠路径,兼容不同服务的尾斜杠规则:只允许同一域名、同一密钥路径增加或删除尾斜杠的一次跳转,签名查询、IP 和 UA 保持不变,返回值必须是 16 字节 AES 密钥。媒体支持 Range,清单和密钥均不缓存,业务错误响应不会作为视频内容返回。
年龄确认与广告弹窗
弹窗逻辑由本项目(JHA-1050)的 src/lib/client/startup.ts 实现,保留本项目的弹窗样式。
- 年龄确认使用会话记录
hg_age_ok,当前标签页会话首次进入时展示;确认后刷新、切换业务页面不再重复展示。存储不可用时仍可正常确认并继续;退出行为与参考项目一致,打开 Google。 - 大图广告使用配置接口
advitise.dialogList,按接口顺序逐条展示;先通过年龄确认,再显示已加载完成的广告,配置晚于确认返回时也能继续展示。 - 点击关闭或按 Esc 进入下一条;点击广告在新标签页打开配置链接。全部关闭后,同一会话内不再展示相同广告队列;刷新时从当前广告恢复。广告队列签名优先使用
advertiseCode,队列变化后重新展示。 - 图片加载失败、解码失败或 12 秒超时会跳过当前广告;全部失败时不记为已完成,后续页面可以重试。
- 大图广告结束后展示
advitise.nineGridList宫格弹窗,保留 HTML 的四列图标、名称、间距、模糊遮罩和底部关闭按钮;没有大图广告时,年龄确认后直接展示宫格。按配置顺序渲染,不补齐模拟数据,图片复用解密逻辑,失败项不展示;没有有效图片时不弹出。点击应用打开配置链接并关闭弹窗,关闭按钮和 Esc 均可关闭;gridAdDismissed独立记录当前宫格配置,同一会话不重复展示,配置变化后可重新展示。 - 宫格弹窗最大高度为视口的 80%,预留底部关闭按钮空间,超出的广告在宫格区域内纵向滚动;每次展示从顶部开始。
/navigation/ 保持独立入口页,不加载年龄确认和广告队列。
验证
npm test 使用独立本地模拟上游和生产构建验证代理与 SSR,不写入真实业务数据。
启动服务后可执行真实接口检查:
PREVIEW_URL=http://127.0.0.1:21030 npm run smoke:api
若游客登录服务暂时超时,可仅验证公开内容:
SMOKE_PUBLIC_ONLY=1 PREVIEW_URL=http://127.0.0.1:21030 npm run smoke:api
2026-10-21 的类型检查、生产构建和全部 258 项测试通过;真实接口检查通过游客会话、公开与个人只读接口及 29 个页面。页面检查确认账户、充值、搜索和工具页的首个响应包含完整正文。真实支付、付费解锁及浏览器视觉验收不在此次验证范围内,详情见 验证记录。
目录
src/app/api/app/[...path]/route.ts:业务代理、会话 Cookie、白名单和上游错误处理。src/app/[...path]/page.tsx与src/lib/routing.ts:页面路径入口、校验与解析。src/app/navigation/page.tsx:独立入口导航页,服务端读取线路、渠道和下载配置。src/lib/api/:接口类型、响应兼容和服务端请求。src/lib/seo-server.ts:服务端页面取数、正文模板和 SEO。src/lib/api/page-session.ts:页面会话准备及同一次请求的 Cookie 传递。src/lib/api/public-cache.ts:有上限的公共数据缓存、过期刷新及并发合并。src/lib/site-shared-data.ts:首屏公开配置和页脚数据白名单。src/lib/client/:共享页面模板及轮播、播放、登录、支付、下载等浏览器交互。public/scripts/site-runtime.js:原稿布局、移动端菜单和播放器控件。public/assets/:本地品牌、图标和空状态素材。tests/api-integration.test.mjs:SSR、鉴权隔离和代理边界测试。tests/routing.test.mjs:路径解析、搜索关键词编码、hash 地址不转换与原生页面导航测试。src/lib/client/startup.ts与tests/startup.test.mjs:年龄确认、广告队列和会话进度,以及控制器与浏览器事件适配测试。
埋点
按照 Web SDK v1.2.0 文档 、飞书埋点规范(已通过 Google Chrome 读取,包含 2026-10-17 更新)为本项目(JHA-1050)接入,官方 SDK 固定保存在 public/scripts/web-sdk-v1.2.0.js。应用 ID 默认 1050,可通过 NEXT_PUBLIC_TRACKER_APP_ID 配置,必须与当前站点密文和上游应用一致;初始化优先读取 config/info.reportSdkConfig,可选 NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG 作为同项目的密文备用配置。缺少配置或 SDK 加载失败时,业务页面继续工作。
- SDK 自动采集生命周期、网络和会话;关闭自动 PV/点击,由接入层按真实路径上报,避免重复。支持首页、榜单、分页、专题、搜索、账户、信息页和独立导航页;登录、游客恢复和退出后同步 UID,渠道沿用首次归因。
- 内容曝光以可见元素为准,关闭的弹窗、被模态弹窗遮挡的页面、后台标签页和轮播副本不计入;关闭弹窗或恢复标签页后重新观察尚未曝光的元素。短剧、专题和普通福利应用均有曝光、点击;内容无父级时
parent_item_id使用0,满足 SDK 的必填非空字符串校验。 - 广告保留接口广告代码和广告位字段,上报
adImpression/adClick;页面广告、大图弹窗、宫格弹窗按实际展示位置分别去重,避免同一广告出现在宫格时被首页曝光记录吞掉。宫格仅统计滚动区域内可见的项目。两种弹窗另按参考项目协议上报advertising的show/click/close行为;只关闭实际曝光过的项目,同一展示只记一次关闭,关闭不算广告点击。福利应用有advitiseId时使用该广告 ID 上报,没有广告关联时按普通内容统计。 - 搜索成功后上报关键词与实际总数,结果点击使用含页码的全局位置;未接推荐引擎的追踪字段保留空串。顶部、侧栏、独立入口页渠道和 App 下载入口记录导航点击。
- 短剧使用
dramaEvent/dramaStatusChange,记录展示、播放、暂停、快进/快退、完成、分享、切集和成功解锁;独立视频使用videoEvent。终止播放失败分别上报ep_failed/video_failed(播放失败),自动恢复中的中间错误和预告失败不作为正片失败上报。短剧完播同时补报ep_play,进度固定 100%,保留最后一段播放时长。播放时长按实际播放区间累计,排除缓冲和免费预告,进度为百分比,倍速乘以 100。 - 所有同源业务 API 请求携带 SDK 原始公共字段
X-SDK-Common,保留 SDK 返回的完整公共字段,包括应用、渠道、UID、数值时间戳、会话、设备、指纹和版本等;注册、登录、充值及 VIP 下单都使用同一份字段格式。字段来自WebSDK.getCommonFields(),不重新生成指纹或会话 ID,等待最多 1.5 秒。 - 点赞、收藏、评论、购买由上游服务端上报,前端传入
X-Tracker-Request-Id/X-Tracker-Event-Name/X-DC-Request-Context,包含完整公共字段和目标剧集上下文。Next.js 代理校验应用 ID、事件对应接口和字段结构后透传;游客重试复用原始快照和请求 ID,不额外从客户端发送业务成功事件。
SDK 未初始化时最多缓存 1000 条事件,配置到达后按顺序补发;配置接口的首次请求不等待 SDK 加载,避免互相等待,首次 SDK 脚本失败后在取得配置时再尝试加载。SDK 返回字段校验失败、事件不在允许列表、地址不可用或异常时,仅触发脱敏的 tracker:diagnostic 事件,不向浏览器控制台输出。自带 SDK 仅移除了三处控制台警告,保留字段校验、HTTPS 地址限制和配置解密失败兜底;网络重试和持久队列由官方 SDK 负责。
埋点回归使用 SDK 替身及项目自带官方 SDK 的真实字段校验器;浏览器验证使用同一 SDK 的事件地址参数,将签名后的测试记录发送到本机接收端,不向真实统计后台发送测试事件。生产环境还需在广告中心为当前应用的 reportSdkConfig 配置业务域名反向代理地址(SDK 的 batchUrls),由部署层转发到文档指定的生产批量上报接口,不能让客户端直连生产上报域名。代理需保留请求体、真实来源 IP 和 SDK 签名;目前未变更线上域名或 Nginx 配置。
“我的点赞”入口位于头像菜单和个人中心书架导航,路由为 /member/likes/,分页使用 /member/likes/page/N/。服务端通过当前账号请求 GET /api/app/playlet/like/list?pageNum=N&pageSize=24,列表不进入公开缓存或搜索索引;空列表显示原 HTML 的空状态和发现入口,接口失败显示重试状态。