@@ -0,0 +1,14 @@
|
||||
# Legacy development-only .env.local example. Prefer config/development.local.env.
|
||||
# Development uses this configured backend; browsers call the current site's /api/app/*.
|
||||
# Test uses this same backend; production uses http://172.104.56.28:26125.
|
||||
UPSTREAM_API_URL=http://172.104.181.99:26792
|
||||
# API_BASE_URL remains supported for existing local overrides.
|
||||
# Optional channel code passed when creating a guest account.
|
||||
API_CHANNEL=
|
||||
# Public configuration/navigation: 60s; homepage: 30s. Optional cap in milliseconds; 0 disables.
|
||||
# PUBLIC_DATA_CACHE_TTL_MS=60000
|
||||
|
||||
# Analytics application ID. Must match this site's reportSdkConfig and upstream app ID.
|
||||
NEXT_PUBLIC_TRACKER_APP_ID=1050
|
||||
# Optional same-project encrypted config fallback when config/info omits reportSdkConfig.
|
||||
NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG=
|
||||
@@ -0,0 +1,18 @@
|
||||
node_modules/
|
||||
.next/
|
||||
.next-dev/
|
||||
.next-test/
|
||||
out/
|
||||
dist/
|
||||
dist.zip
|
||||
dist.zip.sha256
|
||||
web_*_h5.tar.gz
|
||||
web_*_h5.tar.gz.sha256
|
||||
web_*_h5.md5
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
.env*
|
||||
!.env.example
|
||||
config/*.local.env
|
||||
.openai/.local/
|
||||
pnpm-lock.yaml
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"project_id": "appgprj_6ad2273b8598819198e0c9f1959d5cfa",
|
||||
"static": {
|
||||
"directory": "out"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
|
||||
## This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||||
|
||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
@@ -0,0 +1,296 @@
|
||||
# 禁果短剧 · 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。
|
||||
|
||||
```sh
|
||||
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。
|
||||
|
||||
```sh
|
||||
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` 写入:
|
||||
|
||||
```dotenv
|
||||
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、静态资源和对应环境配置。
|
||||
|
||||
```sh
|
||||
# 预览流程,不构建、不连接服务器
|
||||
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`。需要其他目标时可覆盖:
|
||||
|
||||
```sh
|
||||
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 核对页面接口,具体映射见 [页面接口对照](docs/api-contract.md)。首页读取 `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 官方说明](https://github.com/video-dev/hls.js/blob/v1.7.3/README.md)。
|
||||
|
||||
媒体和 `/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,不写入真实业务数据。
|
||||
|
||||
启动服务后可执行真实接口检查:
|
||||
|
||||
```sh
|
||||
PREVIEW_URL=http://127.0.0.1:21030 npm run smoke:api
|
||||
```
|
||||
|
||||
若游客登录服务暂时超时,可仅验证公开内容:
|
||||
|
||||
```sh
|
||||
SMOKE_PUBLIC_ONLY=1 PREVIEW_URL=http://127.0.0.1:21030 npm run smoke:api
|
||||
```
|
||||
|
||||
2026-10-21 的类型检查、生产构建和全部 258 项测试通过;真实接口检查通过游客会话、公开与个人只读接口及 29 个页面。页面检查确认账户、充值、搜索和工具页的首个响应包含完整正文。真实支付、付费解锁及浏览器视觉验收不在此次验证范围内,详情见 [验证记录](docs/verification.md)。
|
||||
|
||||
## 目录
|
||||
|
||||
- `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 文档](https://doc2.adcenteryyds.cc/web/getting-started.html) 、[飞书埋点规范](https://djprsdgo4i4n.jp.larksuite.com/wiki/X8sjwbzs2ib01Ak2TWzj9Y94pFd)(已通过 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 的空状态和发现入口,接口失败显示重试状态。
|
||||
@@ -0,0 +1,9 @@
|
||||
# Local development. Personal overrides belong in development.local.env.
|
||||
APP_ENV=development
|
||||
PORT=21030
|
||||
# Server-only Go API origin. Browser requests use the current site's /api/app/*.
|
||||
UPSTREAM_API_URL=http://172.104.181.99:26792
|
||||
API_CHANNEL=
|
||||
PUBLIC_DATA_CACHE_TTL_MS=0
|
||||
NEXT_PUBLIC_TRACKER_APP_ID=1050
|
||||
NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG=
|
||||
@@ -0,0 +1,11 @@
|
||||
# Production defaults. Server overrides belong in production.local.env.
|
||||
APP_ENV=production
|
||||
# 正式端口:app 21040 / web 21041 / h5 21042;H5 CDN:d23iqbsywbvs3m.cloudfront.net
|
||||
# 应用接口在 21040。不要写成 H5 的 21042,也不要写 127.0.0.1(H5 进程里不一定能连到本机 Go)。
|
||||
PORT=21042
|
||||
# Server-only Go API origin. Browser requests use the current site's /api/app/*.
|
||||
UPSTREAM_API_URL=http://139.162.49.252:21040
|
||||
API_CHANNEL=
|
||||
PUBLIC_DATA_CACHE_TTL_MS=60000
|
||||
NEXT_PUBLIC_TRACKER_APP_ID=1050
|
||||
NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG=
|
||||
@@ -0,0 +1,9 @@
|
||||
# Test deployment. Server overrides belong in test.local.env.
|
||||
APP_ENV=test
|
||||
PORT=21030
|
||||
# Server-only Go API origin. Browser requests use the current site's /api/app/*.
|
||||
UPSTREAM_API_URL=http://172.104.181.99:26792
|
||||
API_CHANNEL=
|
||||
PUBLIC_DATA_CACHE_TTL_MS=60000
|
||||
NEXT_PUBLIC_TRACKER_APP_ID=1050
|
||||
NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG=
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# Linux 测试服 tar.gz 更新入口;需预先配置 Supervisor 程序并手动调用。
|
||||
set -Eeuo pipefail
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
PROGRAM="${DEPLOY_PROGRAM:-jgdj_next_h5}"
|
||||
PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_jgdj_h5.tar.gz}"
|
||||
CHECKSUM_NAME="$PACKAGE_NAME.sha256"
|
||||
[[ "$PROGRAM" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || { echo 'DEPLOY_PROGRAM 格式无效' >&2; exit 1; }
|
||||
[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || { echo 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名' >&2; exit 1; }
|
||||
CURRENT="$DIR/next"
|
||||
LOCK="$DIR/.$PROGRAM.lock"
|
||||
STAGING=''; UPLOAD=''; PREVIOUS=''; FAILED=''
|
||||
MOVED_OLD=0; INSTALLED=0; STOPPED=0; WAS_RUNNING=0
|
||||
fail() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
for tool in node npm sha256sum tar supervisorctl mktemp; do command -v "$tool" >/dev/null || fail "缺少命令 $tool"; done
|
||||
node -e 'const [a,b]=process.versions.node.split(".").map(Number);if(a<20||a===20&&b<9)process.exit(1)' || fail '需要 Node.js >=20.9'
|
||||
mkdir "$LOCK" 2>/dev/null || fail "已有更新进程或残留锁:$LOCK"
|
||||
finish() {
|
||||
local result=$?
|
||||
trap - EXIT INT TERM
|
||||
if [ "$result" != 0 ] && { [ "$STOPPED" = 1 ] || [ "$INSTALLED" = 1 ] || [ "$MOVED_OLD" = 1 ]; }; then
|
||||
printf '更新失败,恢复上一版本。\n' >&2
|
||||
supervisorctl stop "$PROGRAM" >/dev/null 2>&1 || true
|
||||
if [ "$INSTALLED" = 1 ]; then
|
||||
FAILED="$DIR/next.failed.$(date +%Y%m%d%H%M%S).$$"
|
||||
mv "$CURRENT" "$FAILED" || { echo "无法保存失败版本:$CURRENT" >&2; result=1; }
|
||||
fi
|
||||
if [ "$MOVED_OLD" = 1 ]; then
|
||||
mv "$PREVIOUS" "$CURRENT" || { echo "需要手动恢复:$PREVIOUS → $CURRENT" >&2; result=1; }
|
||||
fi
|
||||
if [ "$WAS_RUNNING" = 1 ]; then supervisorctl start "$PROGRAM" || result=1; fi
|
||||
fi
|
||||
if [ -n "$STAGING" ] && [ -d "$STAGING" ]; then rm -rf "$STAGING"; fi
|
||||
if [ -n "$UPLOAD" ] && [ -d "$UPLOAD" ]; then rm -rf "$UPLOAD"; fi
|
||||
rmdir "$LOCK" 2>/dev/null || true
|
||||
exit "$result"
|
||||
}
|
||||
trap finish EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
[ ! -L "$CURRENT" ] && { [ ! -e "$CURRENT" ] || [ -d "$CURRENT" ]; } || fail 'next 必须是普通目录'
|
||||
STATUS=$(supervisorctl status "$PROGRAM" 2>/dev/null || true)
|
||||
[[ "$STATUS" == "$PROGRAM "* ]] && [[ "$STATUS" != *'ERROR'* ]] || fail "请先配置 Supervisor 程序 $PROGRAM,command 指向 node $CURRENT/start.mjs"
|
||||
if [[ "$STATUS" == *'RUNNING'* ]]; then WAS_RUNNING=1; fi
|
||||
[ -s "$DIR/$PACKAGE_NAME" ] && [ -s "$DIR/$CHECKSUM_NAME" ] || fail "缺少 $PACKAGE_NAME 或 SHA-256"
|
||||
# Freeze the uploaded pair before validation/extraction; concurrent uploads cannot replace this copy.
|
||||
UPLOAD=$(mktemp -d "$DIR/.jgdj-upload.XXXXXX")
|
||||
cp "$DIR/$PACKAGE_NAME" "$DIR/$CHECKSUM_NAME" "$UPLOAD/"
|
||||
(cd "$UPLOAD" && sha256sum -c "$CHECKSUM_NAME") || fail 'SHA-256 校验失败'
|
||||
LC_ALL=C tar -tzf "$UPLOAD/$PACKAGE_NAME" > "$UPLOAD/members" || fail 'tar.gz 压缩包损坏'
|
||||
while IFS= read -r entry; do
|
||||
entry="${entry#./}"
|
||||
case "$entry" in
|
||||
/*|../*|*/../*|*/..|node_modules|node_modules/*|*/node_modules/*|*\\*) fail "非法 tar.gz 路径:$entry" ;;
|
||||
esac
|
||||
done < "$UPLOAD/members"
|
||||
# Reject links and special entries before extraction, including hard links to outside paths.
|
||||
LC_ALL=C tar -tvzf "$UPLOAD/$PACKAGE_NAME" > "$UPLOAD/types" || fail '无法检查 tar.gz 文件类型'
|
||||
while IFS= read -r entry; do
|
||||
case "$entry" in
|
||||
-?*|d?*) ;;
|
||||
*) fail '测试包只允许普通文件和目录,不允许链接或特殊文件' ;;
|
||||
esac
|
||||
done < "$UPLOAD/types"
|
||||
STAGING=$(mktemp -d "$DIR/.jgdj-release.XXXXXX")
|
||||
tar -xzf "$UPLOAD/$PACKAGE_NAME" -C "$STAGING" --no-same-owner --no-same-permissions
|
||||
[ -z "$(find "$STAGING" -type l -print -quit)" ] || fail '测试包不允许符号链接'
|
||||
for file in start.mjs server.js .next-test/BUILD_ID config/test.env package.json package-lock.json scripts/next-env.mjs scripts/check-health.mjs; do
|
||||
[ -s "$STAGING/$file" ] || fail "包内缺少 $file"
|
||||
done
|
||||
[ -d "$STAGING/.next-test/static" ] && [ -d "$STAGING/public" ] || fail '包内缺少静态资源'
|
||||
# Install for the server's real OS/CPU, rather than shipping this Mac's node_modules.
|
||||
(cd "$STAGING" && npm ci --omit=dev --no-audit --no-fund)
|
||||
if [ -f "$CURRENT/config/test.local.env" ] && [ ! -L "$CURRENT/config/test.local.env" ]; then
|
||||
cp "$CURRENT/config/test.local.env" "$STAGING/config/test.local.env"
|
||||
chmod 600 "$STAGING/config/test.local.env"
|
||||
fi
|
||||
if [ -d "$CURRENT/.next-test/cache/seo" ] && [ ! -L "$CURRENT/.next-test/cache/seo" ]; then
|
||||
mkdir -p "$STAGING/.next-test/cache"
|
||||
cp -R "$CURRENT/.next-test/cache/seo" "$STAGING/.next-test/cache/seo"
|
||||
fi
|
||||
APP_PORT=$(cd "$STAGING" && node --input-type=module -e 'import{nextCommand}from"./scripts/next-env.mjs";console.log(nextCommand("test","start",[],{root:process.cwd()}).env.PORT)')
|
||||
# Stop only after checksum, installation and configuration validation have succeeded.
|
||||
if [ "$WAS_RUNNING" = 1 ]; then supervisorctl stop "$PROGRAM"; STOPPED=1; fi
|
||||
if [ -d "$CURRENT" ]; then
|
||||
PREVIOUS="$DIR/next.prev.$(date +%Y%m%d%H%M%S).$$"
|
||||
mv "$CURRENT" "$PREVIOUS"; MOVED_OLD=1
|
||||
fi
|
||||
mv "$STAGING" "$CURRENT"; STAGING=''; INSTALLED=1
|
||||
supervisorctl start "$PROGRAM"
|
||||
node "$CURRENT/scripts/check-health.mjs" "http://127.0.0.1:$APP_PORT" test
|
||||
supervisorctl status "$PROGRAM"
|
||||
printf '测试服更新完成:%s;上一版本:%s\n' "$CURRENT" "${PREVIOUS:-无}"
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
# 禁果短剧正式服:构建 → 测试 → Linux standalone + MD5 → 可选触发 rsupdate.sh。
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
cd "$SCRIPT_DIR"
|
||||
source "$SCRIPT_DIR/scripts/deploy-common.sh"
|
||||
JUMP_HOST="${DEPLOY_JUMP_HOST:-server@34.92.204.175}"
|
||||
# 压缩包上传完成后,在同一目录执行服务器现有的 rsupdate.sh。
|
||||
JUMP_DIR="${DEPLOY_JUMP_DIR:-/home/server/web_jgdj/h5}"
|
||||
# valid_path 限制目录为安全字符;命令文本与手动执行一致,兼容 SSH 命令白名单。
|
||||
REMOTE_UPDATE_COMMAND="cd ${JUMP_DIR%/} && bash rsupdate.sh"
|
||||
SCP_PORT="${DEPLOY_SCP_PORT:-22889}"
|
||||
SSH_PORT="${DEPLOY_SSH_PORT:-22889}"
|
||||
TARGET_ARCH="${DEPLOY_TARGET_ARCH:-x64}"
|
||||
TARGET_LIBC="${DEPLOY_TARGET_LIBC:-glibc}"
|
||||
PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_jgdj_h5.tar.gz}"
|
||||
MD5_NAME="${PACKAGE_NAME%.tar.gz}.md5"
|
||||
RELEASE="$SCRIPT_DIR/dist/production-linux-$TARGET_ARCH-$TARGET_LIBC"
|
||||
PACKAGE_ONLY=0
|
||||
for argument in "$@"; do
|
||||
case "$argument" in
|
||||
--dry-run) DEPLOY_DRY_RUN=1 ;;
|
||||
--package-only) PACKAGE_ONLY=1 ;;
|
||||
-h|--help)
|
||||
printf '用法:%s [--dry-run] [--package-only]\n' "$0"
|
||||
printf '仅打包:--package-only。仅预览:--dry-run(不构建、不联网)。\n'
|
||||
printf '上传目标:%s:%s/;上传成功后在该目录执行 bash rsupdate.sh。\n' "$JUMP_HOST" "${JUMP_DIR%/}"
|
||||
printf '可覆盖 DEPLOY_JUMP_HOST、DEPLOY_JUMP_DIR、DEPLOY_SCP_PORT(%s)、DEPLOY_SSH_PORT(%s)、DEPLOY_PACKAGE_NAME(%s)、DEPLOY_TARGET_ARCH(x64/arm64)、DEPLOY_TARGET_LIBC(glibc/musl)。\n' "$SCP_PORT" "$SSH_PORT" "$PACKAGE_NAME"
|
||||
exit 0 ;;
|
||||
*) fail "不支持的参数 $argument" ;;
|
||||
esac
|
||||
done
|
||||
case "$TARGET_ARCH" in x64|arm64) ;; *) fail 'DEPLOY_TARGET_ARCH 仅支持 x64 或 arm64' ;; esac
|
||||
case "$TARGET_LIBC" in glibc|musl) ;; *) fail 'DEPLOY_TARGET_LIBC 仅支持 glibc 或 musl' ;; esac
|
||||
[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || fail 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名'
|
||||
if [ "$PACKAGE_ONLY" = 0 ] && [ "${DEPLOY_DRY_RUN:-0}" != 1 ]; then
|
||||
valid_host "$JUMP_HOST" DEPLOY_JUMP_HOST; valid_path "$JUMP_DIR" DEPLOY_JUMP_DIR
|
||||
valid_port "$SCP_PORT" DEPLOY_SCP_PORT; valid_port "$SSH_PORT" DEPLOY_SSH_PORT
|
||||
need ssh; need scp; need tee
|
||||
fi
|
||||
check_local production
|
||||
if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then
|
||||
printf 'DRY RUN:正式环境;Linux %s/%s;不构建、不连接服务器。\n' "$TARGET_ARCH" "$TARGET_LIBC"; public_profile production
|
||||
printf ' npm run check → npm run bundle → npm run bundle:linux -- %s %s\n' "$TARGET_ARCH" "$TARGET_LIBC"
|
||||
printf ' 保留 standalone 裁剪依赖,仅安装 lockfile 中目标 Linux 的 Sharp/libvips。\n'
|
||||
printf ' 生成 %s + %s(纯 MD5 hash)。\n' "$PACKAGE_NAME" "$MD5_NAME"
|
||||
if [ "$PACKAGE_ONLY" = 1 ]; then printf ' 仅生成本地包。\n'
|
||||
else
|
||||
printf ' scp -P %s %s %s %s:%s/\n' "$SCP_PORT" "$PACKAGE_NAME" "$MD5_NAME" "$JUMP_HOST" "${JUMP_DIR%/}"
|
||||
printf ' ssh -p %s %s "%s"\n' "$SSH_PORT" "$JUMP_HOST" "$REMOTE_UPDATE_COMMAND"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
need tar
|
||||
command -v md5sum >/dev/null || command -v md5 >/dev/null || fail '缺少 md5sum 或 macOS md5'
|
||||
lock_build
|
||||
step '构建正式环境并运行回归测试'
|
||||
npm run check
|
||||
step '整理正式环境 standalone 和目标 Linux 原生依赖'
|
||||
npm run bundle
|
||||
npm run bundle:linux -- "$TARGET_ARCH" "$TARGET_LIBC"
|
||||
for file in start.mjs server.js .next/BUILD_ID config/production.env runtime-target.json; do
|
||||
[ -s "$RELEASE/$file" ] || fail "正式包缺少 $file"
|
||||
done
|
||||
for directory in node_modules .next/static public; do [ -d "$RELEASE/$directory" ] || fail "正式包缺少 $directory"; done
|
||||
step "生成 $PACKAGE_NAME"
|
||||
TEMP_PACKAGE=$(mktemp "$SCRIPT_DIR/dist/.production-archive.XXXXXX")
|
||||
if ! COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar --no-xattrs \
|
||||
--exclude='._*' --exclude='.DS_Store' --exclude='.env*' --exclude='*.local.env' --exclude='*.log' \
|
||||
-czf "$TEMP_PACKAGE" -C "$RELEASE" .; then
|
||||
rm -f "$TEMP_PACKAGE"; fail '压缩失败'
|
||||
fi
|
||||
tar -tzf "$TEMP_PACKAGE" >/dev/null
|
||||
mv -f "$TEMP_PACKAGE" "$PACKAGE_NAME"
|
||||
chmod 600 "$PACKAGE_NAME"
|
||||
if command -v md5sum >/dev/null; then md5sum "$PACKAGE_NAME" | awk '{print $1}' > "$MD5_NAME"
|
||||
else md5 -q "$PACKAGE_NAME" > "$MD5_NAME"; fi
|
||||
printf '正式包:%s/%s;校验:%s/%s\n' "$SCRIPT_DIR" "$PACKAGE_NAME" "$SCRIPT_DIR" "$MD5_NAME"
|
||||
if [ "$PACKAGE_ONLY" = 1 ]; then exit 0; fi
|
||||
step "上传到 $JUMP_HOST:$JUMP_DIR/"
|
||||
scp -P "$SCP_PORT" "$PACKAGE_NAME" "$MD5_NAME" "$JUMP_HOST:${JUMP_DIR%/}/"
|
||||
step '执行正式服 rsupdate.sh'
|
||||
printf '远端命令:%s\n' "$REMOTE_UPDATE_COMMAND"
|
||||
RUN_LOG=$(mktemp "$SCRIPT_DIR/dist/production-rsupdate.XXXXXX")
|
||||
if ! ssh -p "$SSH_PORT" "$JUMP_HOST" "$REMOTE_UPDATE_COMMAND" 2>&1 | tee "$RUN_LOG"; then fail "rsupdate.sh 执行失败,日志:$RUN_LOG"; fi
|
||||
if ! node - "$RUN_LOG" <<'NODE'
|
||||
const output=require('node:fs').readFileSync(process.argv[2],'utf8').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,'');
|
||||
if(/\b(?:BACKOFF|FATAL|ERROR)\b|\bspawn\s+error\b/i.test(output))process.exit(1);
|
||||
NODE
|
||||
then fail "远端输出包含失败状态,日志:$RUN_LOG"; fi
|
||||
printf '正式服 rsupdate.sh 执行完成;请核对远端服务状态。日志:%s\n' "$RUN_LOG"
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# 禁果短剧测试服:构建 → 测试 → tar.gz + SHA-256 → 可选上传并执行 run.sh。
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
cd "$SCRIPT_DIR"
|
||||
source "$SCRIPT_DIR/scripts/deploy-common.sh"
|
||||
HOST="${DEPLOY_HOST:-root@172.104.181.99}"
|
||||
SSH_PORT="${DEPLOY_SSH_PORT:-22889}"
|
||||
# 禁果短剧上传目录、项目包和 Supervisor 标识统一使用 jgdj。
|
||||
REMOTE_DIR="${DEPLOY_DIR:-/server/web_jgdj/h5}"
|
||||
# 远端启动脚本目录独立于上传目录,按服务器约定执行。
|
||||
RUN_DIR="${DEPLOY_RUN_DIR:-/server/web_jgdj/h5}"
|
||||
CHECK_URL="${DEPLOY_CHECK_URL:-}"
|
||||
PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_jgdj_h5.tar.gz}"
|
||||
CHECKSUM_NAME="$PACKAGE_NAME.sha256"
|
||||
PACKAGE_ONLY=0
|
||||
for argument in "$@"; do
|
||||
case "$argument" in
|
||||
--dry-run) DEPLOY_DRY_RUN=1 ;;
|
||||
--package-only) PACKAGE_ONLY=1 ;;
|
||||
-h|--help)
|
||||
printf '用法:%s [--dry-run] [--package-only]\n' "$0"
|
||||
printf '仅打包:--package-only。仅预览:--dry-run(不构建、不联网)。\n'
|
||||
printf '上传目标:%s:%s %s/。\n' "$HOST" "$SSH_PORT" "${REMOTE_DIR%/}"
|
||||
printf '上传成功后:ssh -p %s %s "cd %s && bash run.sh"\n' "$SSH_PORT" "$HOST" "$RUN_DIR"
|
||||
printf '可覆盖 DEPLOY_HOST、DEPLOY_DIR、DEPLOY_RUN_DIR(%s)、DEPLOY_SSH_PORT、DEPLOY_PACKAGE_NAME(%s)、DEPLOY_CHECK_URL。\n' "$RUN_DIR" "$PACKAGE_NAME"
|
||||
exit 0 ;;
|
||||
*) fail "不支持的参数 $argument" ;;
|
||||
esac
|
||||
done
|
||||
[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || fail 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名'
|
||||
if [ "$PACKAGE_ONLY" = 0 ] && [ "${DEPLOY_DRY_RUN:-0}" != 1 ]; then
|
||||
valid_host "$HOST" DEPLOY_HOST; valid_path "$REMOTE_DIR" DEPLOY_DIR; valid_path "$RUN_DIR" DEPLOY_RUN_DIR; valid_port "$SSH_PORT" DEPLOY_SSH_PORT
|
||||
need ssh; need scp
|
||||
fi
|
||||
check_local test
|
||||
if [ -n "$CHECK_URL" ]; then
|
||||
node --input-type=module -e 'const u=new URL(process.argv[1]);if(!["http:","https:"].includes(u.protocol)||u.username||u.password||u.search||u.hash||u.pathname!=="/")process.exit(1)' "$CHECK_URL" || fail 'DEPLOY_CHECK_URL 必须是 HTTP(S) 站点根地址'
|
||||
fi
|
||||
if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then
|
||||
printf 'DRY RUN:测试环境;不构建、不连接服务器。\n'; public_profile test
|
||||
printf ' npm run build:test → npm test → npm run bundle:test → %s + %s\n' "$PACKAGE_NAME" "$CHECKSUM_NAME"
|
||||
printf ' 包内不含 node_modules、本地环境覆盖和缓存;服务端 npm ci --omit=dev 后 node start.mjs。\n'
|
||||
if [ "$PACKAGE_ONLY" = 1 ]; then printf ' 仅生成本地包。\n'
|
||||
else
|
||||
printf ' 发布目标:%s:%s(SSH %s)。\n' "$HOST" "$REMOTE_DIR" "$SSH_PORT"
|
||||
printf ' 上传 %s、%s 和 up_next_h5.sh。\n' "$PACKAGE_NAME" "$CHECKSUM_NAME"
|
||||
printf ' 上传成功后:ssh -p %s %s "cd %s && bash run.sh"\n' "$SSH_PORT" "$HOST" "$RUN_DIR"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
need tar
|
||||
[ -s deploy/up_next_h5.sh ] || fail '缺少 deploy/up_next_h5.sh'
|
||||
bash -n deploy/up_next_h5.sh
|
||||
lock_build
|
||||
step '构建测试环境并运行回归测试'
|
||||
npm run build:test
|
||||
TEST_APP_ENV=test npm test
|
||||
step '整理 standalone 测试包'
|
||||
npm run bundle:test
|
||||
for file in start.mjs server.js .next-test/BUILD_ID config/test.env; do
|
||||
[ -s "dist/test/$file" ] || fail "测试包缺少 $file"
|
||||
done
|
||||
[ -d dist/test/.next-test/static ] && [ -d dist/test/public ] || fail '测试包缺少静态资源'
|
||||
step "生成 $PACKAGE_NAME 和 SHA-256"
|
||||
TEMP_PACKAGE=$(mktemp "$SCRIPT_DIR/dist/.test-archive.XXXXXX")
|
||||
if ! COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar --no-xattrs \
|
||||
--exclude='node_modules' --exclude='*/node_modules' --exclude='._*' --exclude='.DS_Store' \
|
||||
--exclude='*.local.env' --exclude='.env*' --exclude='*.log' \
|
||||
-czf "$TEMP_PACKAGE" -C dist/test .; then
|
||||
rm -f "$TEMP_PACKAGE"; fail '压缩失败'
|
||||
fi
|
||||
tar -tzf "$TEMP_PACKAGE" >/dev/null
|
||||
mv -f "$TEMP_PACKAGE" "$PACKAGE_NAME"
|
||||
chmod 600 "$PACKAGE_NAME"
|
||||
node --input-type=module - "$PACKAGE_NAME" "$CHECKSUM_NAME" <<'NODE'
|
||||
import {createHash} from 'node:crypto';
|
||||
import {readFileSync,writeFileSync} from 'node:fs';
|
||||
const [archive,checksum]=process.argv.slice(2);
|
||||
const bytes=readFileSync(archive);
|
||||
writeFileSync(checksum,createHash('sha256').update(bytes).digest('hex')+' '+archive+'\n');
|
||||
// The test and production commands share a package name; refresh its companion checksum too.
|
||||
writeFileSync(archive.replace(/\.tar\.gz$/,'.md5'),createHash('md5').update(bytes).digest('hex')+'\n');
|
||||
NODE
|
||||
printf '测试包:%s/%s;校验:%s/%s\n' "$SCRIPT_DIR" "$PACKAGE_NAME" "$SCRIPT_DIR" "$CHECKSUM_NAME"
|
||||
if [ "$PACKAGE_ONLY" = 1 ]; then exit 0; fi
|
||||
step "上传到 $HOST:$REMOTE_DIR/"
|
||||
ssh -p "$SSH_PORT" "$HOST" "mkdir -p '$REMOTE_DIR'"
|
||||
scp -P "$SSH_PORT" "$PACKAGE_NAME" "$CHECKSUM_NAME" deploy/up_next_h5.sh "$HOST:$REMOTE_DIR/"
|
||||
printf '测试包上传完成:%s:%s/%s\n' "$HOST" "${REMOTE_DIR%/}" "$PACKAGE_NAME"
|
||||
step "执行测试服 $RUN_DIR/run.sh"
|
||||
ssh -p "$SSH_PORT" "$HOST" "cd '$RUN_DIR' && bash run.sh"
|
||||
if [ -n "$CHECK_URL" ]; then node scripts/check-health.mjs "$CHECK_URL" test; fi
|
||||
printf '测试服 run.sh 执行完成:%s:%s/run.sh\n' "$HOST" "${RUN_DIR%/}"
|
||||
@@ -0,0 +1,64 @@
|
||||
# 禁果短剧页面接口对照
|
||||
|
||||
核对日期:2026-10-26。来源:[SGDJ Swagger](https://sgdj-h5.80sygtm.com/swagger/index.html),定义:[doc.json](https://sgdj-h5.80sygtm.com/swagger/doc.json)。本文省略 `/api/app/` 前缀。
|
||||
|
||||
运行上游由 `UPSTREAM_API_URL` 配置,兼容 `API_BASE_URL`。本地开发、测试环境使用 `http://172.104.181.99:26792`,生产环境使用 `http://139.162.49.252:21040`(H5 端口 `21042`,CDN `d23iqbsywbvs3m.cloudfront.net`);应用及埋点 ID 保持 1050。
|
||||
|
||||
| 页面 | 接口及关键参数 |
|
||||
| --- | --- |
|
||||
| 顶部、侧栏和移动导航 | `navigation/header`;沿用 `categoryList` 的名称、顺序、图标、类型与二级导航 |
|
||||
| 首页 | `playlet/home`;轮播 `homeBannerData`、公告 `announcements`、广告 `advitise`;栏目读取 `modules` |
|
||||
| 首页短剧栏目 | `modules[].moduleType=1`,标题 `title`,条目 `list`;保留 slug、介绍和顺序 |
|
||||
| 首页题材栏目 | `modules[].moduleType=2`,题材读取 `topicList`,题材卡片进入 `/theme/{slug或id}/` |
|
||||
| 首页“更多” | `playlet/home-config/list`;`slug` 或 `id`,加 `pageNum,pageSize`;读取 `config`、`list/topicList` 和真实 `totalCount`,支持后续分页 |
|
||||
| 短剧四种推荐 | `playlet/operate/list`;`type=1/2/3/4` 分别为最新、热播、独家、随机,分页使用 `pageNum,pageSize` |
|
||||
| 题材短剧 | 先由 `theme/list` 核对题材名称与身份,再读取 `playlet/all/list`;使用 `themeSlug` 或 `themeId`,支持 `sortType=1/2/3` 和 `updateStatus=0/1/2` |
|
||||
| 三个排行榜 | `playlet/hot/rank/list`;`rankType=1/2/3` 和 `pageNum,pageSize`,该接口仍在新文档中 |
|
||||
| 专题列表 | `playlet/topic/list`;`pageNum,pageSize`;返回的 `list` 是专题区块,每个区块的 `list` 才是专题卡片 |
|
||||
| 专题详情 | `playlet/topic/detail`;直接使用 `topicSlug` 或 `id`,加 `dataType=1`、`pageNum,pageSize,sortType`;不再扫描专题目录解析 slug |
|
||||
| 标签短剧 | `playlet/tag/list`;`tagSlug` 或 `tagId`,加 `pageNum,pageSize,sortType` |
|
||||
| 搜索 | `playlet/search`;`keyword,pageNum,pageSize`;无结果优先使用 `everyoneWatching`,不足时读取 `playlet/operate/list?type=2` |
|
||||
| 联想与换一批 | `playlet/search/suggest?keyword=…` 返回短剧;换一批读取 `playlet/operate/list?type=4` |
|
||||
| 短剧详情及推荐 | `playlet/detail` 使用 `slug` 或 `id`;推荐读取 `playlet/guess-like/list?playletId=…` |
|
||||
| 章节和选集 | `playlet/chapter/list` 使用 `playletSlug/playletId`;`chapter/detail` 另外传 `chapterId`,实时读取观看权限与视频源 |
|
||||
| 评论 | 详情页和播放页都评论整部短剧:`comment/list`、`comment/send` 使用短剧 `objectId` 与 `objectType=10`;列表另传 `pageNum,pageSize,sortOrder`(1 热门,2 最新)。回复传一级评论 `parentsId` 和实际回复 `replyCommentId`,一级评论两者为 0。`comment/like/set` 传 `commentId` 与布尔 `isLike`,返回状态 1 已点赞、2 未点赞 |
|
||||
| 收藏、历史、点赞、已购 | 对应 `playlet/collect/list`、`history/list`、`like/list`、`bought/list`;使用当前账号及真实分页 |
|
||||
| 账号设置 | `auth/profile`;头像通过 `upload/image` 的 multipart `file` 上传,使用返回的 `sourceNameFs` |
|
||||
| 充值、VIP | `recharge/package/list` 读取套餐和支付渠道;余额明细 `coin/trans/list` 使用 `page,pageSize,type` |
|
||||
| 福利应用、回家线路 | `applist/list`、`config/info` 与 `config/footer/info`,沿用现有配置读取 |
|
||||
|
||||
专题列表不再返回全局 totalCount:页面保留区块名称、介绍和各自卡片,使用区块 totalCount 的最大值计算共同分页边界,避免将各区块数量相加后生成不存在的页面。专题卡片和题材卡片分别使用 `/topics/` 和 `/theme/`,canonical、面包屑和 JSON-LD 同步区分。
|
||||
|
||||
首页优先使用新字段 modules;仅在响应没有该字段时兼容旧 homeConfigData 快照。明确返回空 modules 时不会恢复旧栏目。首页“更多”直接使用新分页接口,不再用首页预览条目伪造完整目录。
|
||||
|
||||
首页分类按 `modules[].style` 映射原 HTML 展示方式,兼容旧 `homeConfigData[].style`。布局仅由该字段决定,保留后台名称及条目顺序,不根据名称、位置或热度猜测。字符串会去除首尾空格并转为大写。
|
||||
|
||||
| style | 对应原稿分类 | 桌面展示 |
|
||||
| --- | --- | --- |
|
||||
| A | 热门推荐 | 竖版海报,5 列 |
|
||||
| B | 猜你喜欢 | 竖版海报,6 列 |
|
||||
| C | 最新短剧 | 左图右文,3 列;可显示后台 `label` |
|
||||
| D | 热门好剧 | 带顺序编号的竖版海报,6 列 |
|
||||
| E | 专题推荐 | 横版题材封面,4 列 |
|
||||
|
||||
A–D 用于短剧模块(`moduleType=1`),E 用于题材模块(`moduleType=2`)。缺失、未知、F–Z 预留或与内容类型不符的 style 沿用普通短剧/题材网格。
|
||||
|
||||
首页分类展示数量由 `modules[].limit` 控制,兼容旧 `homeConfigData[].limit`,所有 style(含默认网格)都使用同一规则:非负安全整数表示最多展示的条目数,`0` 显示空态;缺失或无效时展示全部有效返回条目。列表不足 limit 时按实际数量展示,保留接口顺序。`style` 仅控制卡片及列数,行数随 limit 自动增加。首页 HTML 和 JSON-LD 采用同一 limit;原始列表及“查看更多”的真实分页保持完整。
|
||||
|
||||
移动端 A、B、D 两列,C 单列图文卡,E 两列;B 在平板宽度为三列。各端均按 limit 展示全部预览条目,保留更多入口。D 的序号按列表顺序从 01 开始,手机端叠加在封面上。
|
||||
|
||||
## 写接口与请求头
|
||||
|
||||
新旧文档的登录、注册、资料、找回密码、支付、解锁、反馈和章节权限模型未发现字段变化。继续使用 `auth/login/register/resetPassword`;整剧点赞/收藏传 `id` 与布尔状态,章节操作传 `chapterId`,解锁传 `playletId,chapterId`。金币与 VIP 下单使用 `packageId,payMode`,渠道只取套餐配置;购买解锁继续由用户确认。
|
||||
|
||||
反馈按请求模型使用 1 建议、2 Bug、3 体验、4 账号、5 其他;正文和附加环境信息合计最多 500 字。上传请求不手动设置 multipart boundary。
|
||||
|
||||
原生 UA、X-User-Agent、X-Real-IP、SDK 快照与业务事件上下文沿用 JHA-1045 对齐后的转发规则。公开内容不附带账号凭证,私有接口使用 HttpOnly 会话,签名媒体不转发业务 Cookie 或 Authorization。
|
||||
|
||||
## 接口范围与验证
|
||||
|
||||
移除新文档中不存在的 `playlet/hot/list` 依赖。新文档虽然重新提供 video 系列接口,本站继续遵循此前移除独立剧集入口的要求,不恢复 `/series/`、`/video/` 或未使用的代理端点。`playlet/rank`、`playlet/today-update` 等没有对应现有页面的接口无需额外开放。
|
||||
|
||||
`tests/fixtures/sgdj-app-contract.json` 保存本次文档的端点、方法、参数、请求字段与原始定义 SHA-256。合同测试检查调用字段与代理白名单;本机模拟接口验证 SSR、专题分组、题材、更多分页、搜索、权限和支付交互,不提交真实业务写入。
|
||||
|
||||
`scripts/smoke-api.mjs` 已同步新端点和首页/专题结构,可由操作者指定 PREVIEW_URL 后执行;本次仅获取 Swagger 文档并运行本机测试,未对线上业务接口创建账号、订单、评论或解锁。
|
||||
|
After Width: | Height: | Size: 153 KiB |
@@ -0,0 +1,42 @@
|
||||
# 页面索引策略
|
||||
|
||||
调整日期:2026-10-22。规则由 `src/lib/seo.ts` 统一生成,服务端初始 HTML 与客户端更新共用。这里只控制允许索引的信号,不保证搜索引擎实际收录。
|
||||
|
||||
## 允许索引
|
||||
|
||||
- 首页、有效且非空的短剧列表、三个榜单、专题列表与详情、标签列表、作品详情。
|
||||
- 普通内容列表的有效分页继续自引用 canonical,不统一归并到第一页。
|
||||
- 关于我们、常见问题、用户协议、隐私政策、2257、DMCA。
|
||||
- `/config/{id}/` 必须有栏目自己的标题、正文简介和有效内容卡片。只有 SEO 标题/描述或空卡片不能满足条件;此规则不依赖额外接口请求,也不代表已自动判定其内容与全站其他栏目语义唯一。
|
||||
- `/play/{id}/{chapterId}/` 必须有作品名、真实正整数集数,以及当前访问身份有权观看的有效正片地址。共享预告、继承的作品简介、章节 SEO 字段不能单独使纯解锁/不可播放页开放索引。收费类型本身不作为排除条件,有权观看的金币/VIP 章节仍可索引。当前模板没有独立可见的章节正文,若以后增加此类内容,可再评估受限章节的索引范围。
|
||||
|
||||
## 不允许索引
|
||||
|
||||
| 范围 | 设置 |
|
||||
| --- | --- |
|
||||
| 内部搜索、导航入口、回家的路、福利应用 | `noindex,follow` |
|
||||
| 个人中心、收藏、历史、已购、设置、充值/VIP、反馈 | `noindex,nofollow` |
|
||||
| 非默认有效排序变体、非默认评论排序 | `noindex,follow`,沿用原有例外规则 |
|
||||
| 详情和播放页的评论分页,页码大于 1 | `noindex,follow` |
|
||||
| 接口成功确认 `totalCount=0` 的公开列表第一页 | `noindex,follow`,继续返回 200 |
|
||||
| 缺少独立栏目信息的更多列表、纯解锁或无有效正片的播放页 | `noindex,follow`,继续提供原页面 |
|
||||
|
||||
noindex 页面不输出 canonical、head 中的 prev/next 或内容 JSON-LD;正文的选集、评论、分页链接继续提供。作品详情没有评论时仍可索引,评论数为 0 不等于作品为空。
|
||||
|
||||
## 去重和结构化数据
|
||||
|
||||
- 2026-10-26 按新 Swagger 区分题材与专题:`/theme/{id或slug}/` 查询题材下短剧,`/topics/{id或slug}/` 查询专题详情,各自保留自己的 canonical;题材更新状态筛选页使用 noindex。
|
||||
- 自动生成的专题卡片与 ItemList 链接使用 `/topics/{id}/`;后台明确配置的 `moreUrl` 保留原值。
|
||||
- 数字资源 ID 的前导零在 canonical 与播放实体 URL 中归一;普通分页仍保留自己的页码。搜索关键词不进行数字归一。
|
||||
- canonical 仍仅取配置的永久域名,去除跟踪参数;`/rank/`、`/page/1/` 继续使用既有主地址。
|
||||
- 2026-10-22 起内容主地址优先使用接口的非空 slug,缺少时使用 ID。ID 地址仍直接提供内容,其 canonical 与结构化数据指向该内容的 slug 主地址;评论与操作请求继续使用真实数字 ID。
|
||||
- 可索引的付费播放页标记 `isAccessibleForFree:false` 与 `hasPart.cssSelector:.play-paywalled-content`,选择器对应真实播放器容器,不改变样式。
|
||||
- VideoObject 只在真实媒体源、有效日期与封面均存在时输出。无权观看的媒体地址不进入 SEO 数据;不编造视频日期或媒体源。
|
||||
|
||||
## 状态码与验证
|
||||
|
||||
资源缺失和已证明越界的分页继续返回 404;主内容临时故障继续返回 500。noindex 不用来替代错误状态码。本次未修改 sitemap、robots.txt 或接口访问权限。
|
||||
|
||||
`npm run build` 及全部 274 项 `npm test` 通过。验证覆盖函数级索引矩阵、客户端 head 清理、真实 Next.js 生产服务的初始 HTML、Googlebot/YandexBot 请求、普通分页、评论分页、空列表、别名与前导零、更多栏目、付费章节权限和 JSON-LD。模拟接口不提交真实业务写入;线上响应头和实际收录结果仍以部署后的验证为准。
|
||||
|
||||
规则参考:[Google canonical](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)、[Google 分页](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading)、[Google 付费内容结构化数据](https://developers.google.com/search/docs/appearance/structured-data/paywalled-content)。
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,222 @@
|
||||
# 后端 Sitemap 生成对接规则
|
||||
|
||||
核对时间:2026-10-22 22:53(Asia/Shanghai)。
|
||||
|
||||
本文根据当前工作区的前端代码(包含尚未提交的修改)整理固定页面、URL、canonical 和 noindex 规则,供后端实现 sitemap 生成。它描述现有页面行为及生成约定,不表示 sitemap、存储或 Nginx 已完成接入。
|
||||
|
||||
当前内容主地址仅优先使用符合英文/拼音字符规则的 slug;中文、已编码中文或其他无效 slug 回退到 ID。搜索关键词仍支持中文和特殊字符,但所有搜索地址均排除在 sitemap 外。
|
||||
|
||||
## 1. 域名与通用约定
|
||||
|
||||
- `D` 表示正式站点的 origin,即协议、域名和可选端口,不带末尾 `/`。
|
||||
- 域名优先取 `config.config.foreverUrlAddr`;无有效值时取 `config.newAddressInfo.foreverUrlAddr`。移除配置中的路径、查询参数和锚点,与前端 canonical 保持一致。
|
||||
- 不使用 API 域名、CDN 域名、请求 Host、临时访问域名或本地地址作为页面域名。
|
||||
- 生成任务没有有效正式域名时,不猜测域名;暂停本次发布并保留上一份成功结果。
|
||||
- 页面 URL 以 `/` 结尾;sitemap 中使用绝对地址 `D + 规范路径`。
|
||||
- sitemap 只提交正常、允许索引的页面及其 canonical 地址;别名、参数变体和重复地址不单独提交。
|
||||
- API、媒体源、静态资源和健康检查不属于本文的页面 sitemap 范围。
|
||||
|
||||
## 2. 固定页面清单
|
||||
|
||||
固定地址候选共 12 个。满足条件时,其 canonical 均为 `D + URL`。列表为空或页面异常时,按后续排除规则处理。
|
||||
|
||||
| 页面 | URL | 加入 sitemap 的条件 |
|
||||
| --- | --- | --- |
|
||||
| 首页 | `/` | 页面正常 |
|
||||
| 短剧列表 | `/shorts/` | 页面正常且列表非空 |
|
||||
| 专题列表 | `/topics/` | 页面正常且列表非空 |
|
||||
| 热播榜 | `/rank/hot/` | 页面正常且列表非空 |
|
||||
| 新剧榜 | `/rank/new/` | 页面正常且列表非空 |
|
||||
| 追剧榜 | `/rank/follow/` | 页面正常且列表非空 |
|
||||
| 关于我们 | `/info/about-us/` | 页面正常 |
|
||||
| 用户协议 | `/info/user-agreement/` | 页面正常 |
|
||||
| 隐私政策 | `/info/privacy-policy/` | 页面正常 |
|
||||
| 常见问题 | `/info/help-center/` | 页面正常 |
|
||||
| 2257 合规声明 | `/info/2257/` | 页面正常 |
|
||||
| DMCA 版权声明 | `/info/dmca/` | 页面正常 |
|
||||
|
||||
这些是页面地址,不是需要前端预建的 sitemap XML 文件。
|
||||
|
||||
## 3. 动态页面 URL 规则
|
||||
|
||||
| 类型 | 规范 URL | 加入 sitemap 的条件 |
|
||||
| --- | --- | --- |
|
||||
| 作品详情 | `/detail/{key}/` | 作品存在,页面正常且未被标记为需登录或禁止索引 |
|
||||
| 专题详情 | `/topics/{key}/` | 专题存在,页面正常且列表非空 |
|
||||
| 标签列表 | `/tag/{key}/` | 标签存在,页面正常且列表非空 |
|
||||
| 更多栏目 | `/config/{key}/` | 首页栏目存在、已启用且类型为 `1` 或 `3`,并具有自己的标题、正文简介和有效内容卡片 |
|
||||
| 单集播放 | `/play/{作品key}/{chapterId}/` | 满足下述播放页条件 |
|
||||
| 内容列表分页 | `{列表基础路径}page/{n}/` | `n >= 2`,分页真实存在且允许索引 |
|
||||
|
||||
### 3.1 内容标识
|
||||
|
||||
- `{key}` 优先使用内容接口返回的有效英文/拼音 `slug`;无有效 slug 时使用正整数 ID。前端不会自动把中文转成拼音。
|
||||
- slug 去掉首尾空格后,必须满足 `/^[a-z\d][a-z\d_-]*$/i`:以 ASCII 字母或数字开头,其余只能包含 ASCII 字母、数字、连字符 `-`、下划线 `_`。
|
||||
- 使用接口返回的规范大小写,作为一个路径段按 `encodeURIComponent` 的语义编码一次,避免重复编码;不得把编码后的中文当作英文 slug。
|
||||
- 中文、中文英文混合、内含空格、斜杠、点号、百分号、问号等不符合正则的 slug 均回退到 ID。`-city` 和 `_city` 也不符合开头规则。
|
||||
- 历史中文 slug 地址在能够匹配到真实内容时仍可读取,但 canonical 和新生成链接使用 ID;后端不应继续向 sitemap 写入这些兼容地址。
|
||||
- 纯数字路径按 ID 查询,不按 slug 查询。当前生成正则本身未单独排除纯数字 slug,因此后端应避免分配此类 slug;存量数据须核对身份,不能直接当作 slug 发布。
|
||||
- 使用数字 ID 时去除前导零;ID 必须是正整数,且处于 JavaScript 安全整数范围内。
|
||||
- `{chapterId}` 是真实章节 ID,不是集数,也不支持章节 slug。章节必须属于 URL 中的作品。
|
||||
|
||||
以作品 `id=9` 为例:
|
||||
|
||||
| 接口返回的 slug | 生成的详情路径 |
|
||||
| --- | --- |
|
||||
| `city-story` | `/detail/city-story/` |
|
||||
| `city_story-2026` | `/detail/city_story-2026/` |
|
||||
| `dushiaiqing` | `/detail/dushiaiqing/` |
|
||||
| `City-Story` | `/detail/City-Story/` |
|
||||
| `都市爱情` | `/detail/9/` |
|
||||
| `city-爱情` | `/detail/9/` |
|
||||
| `%E9%83%BD%E5%B8%82` | `/detail/9/` |
|
||||
| `city story`、`city/story`、`city.story`、空值 | `/detail/9/` |
|
||||
|
||||
以上规则同时用于作品、专题、标签和更多栏目;播放页的作品段使用同一规则,章节段仍使用章节 ID。
|
||||
|
||||
### 3.2 播放页条件
|
||||
|
||||
播放页同时满足以下条件时才可进入公共 sitemap:
|
||||
|
||||
1. 作品及章节真实存在,且两者对应。
|
||||
2. 有作品名和真实的正整数集数。
|
||||
3. 公开抓取身份有权观看当前章节。
|
||||
4. 存在有效的当前章节正片地址。
|
||||
|
||||
共享预告、继承的作品简介、章节 SEO 标题或纯解锁提示不能单独满足条件。收费类型本身不是排除依据,但不能使用管理员、VIP 或已购用户的个人权限生成公共 sitemap。
|
||||
|
||||
sitemap 提交页面 URL,不写入用户凭证或受限媒体源。
|
||||
|
||||
### 3.3 列表分页
|
||||
|
||||
- 普通内容目录当前每页 24 条,后端根据有效总数计算真实页数。
|
||||
- 更多栏目当前使用有限首页栏目快照:类型 `1` 取实际作品列表,类型 `3` 取实际专题列表,以实际条目数为 `totalCount`。SEO 层取 `pageSize = max(24, totalCount)`,因此当前只发布栏目基础地址,不生成 `/config/{key}/page/2/` 等额外分页。
|
||||
- 第一页只提交基础地址,不额外提交 `/page/1/`。
|
||||
- 第 2 页及以后保留自己的 canonical,例如 `/shorts/page/2/`,不统一归并到第一页。
|
||||
- 作品详情和播放页的 `/page/{n}/` 是评论分页;`n > 1` 时不进入 sitemap。
|
||||
- 搜索、收藏、历史、已购和充值即使有分页,也不进入 sitemap。
|
||||
|
||||
## 4. Canonical 规则
|
||||
|
||||
| 场景 | 规范化规则 |
|
||||
| --- | --- |
|
||||
| 正式域名 | 统一使用 `D`,与前端页面 canonical 的域名一致 |
|
||||
| 尾斜杠 | 页面路径以 `/` 结尾 |
|
||||
| 查询参数和锚点 | canonical 不含查询参数和锚点;sitemap 同样不提交这些变体 |
|
||||
| 第一页 | `/shorts/page/1/` 规范化为 `/shorts/`;其他支持分页的页面同理 |
|
||||
| 有效列表分页 | 第 2 页及以后自引用,保留 `/page/{n}/` |
|
||||
| 默认榜单 | `/rank/` 规范化为 `/rank/hot/`;`/rank/page/2/` 规范化为 `/rank/hot/page/2/` |
|
||||
| 专题别名 | `/theme/{key}/` 规范化为 `/topics/{规范key}/`,保留有效分页 |
|
||||
| ID 与 slug | 有符合英文/拼音字符正则的 slug 时,ID 地址指向 slug 主地址;中文或其他无效 slug 回退到规范 ID |
|
||||
| 历史中文 slug 地址 | 能匹配到资源时保留兼容读取,canonical 使用该资源的规范 ID 地址 |
|
||||
| 数字 ID 前导零 | 无 slug 时,`/detail/009/` 规范化为 `/detail/9/`;其他数字内容 ID 同理 |
|
||||
| noindex 页面 | 当前前端不输出 canonical,不进入 sitemap |
|
||||
|
||||
例如作品 `id=9`、`slug=city-story`,章节 ID 为 `90`:
|
||||
|
||||
```text
|
||||
/detail/9/ -> D/detail/city-story/
|
||||
/detail/city-story/ -> D/detail/city-story/
|
||||
/play/9/90/ -> D/play/city-story/90/
|
||||
/play/city-story/90/ -> D/play/city-story/90/
|
||||
```
|
||||
|
||||
例如专题 `id=7`、`slug=city-collection`:
|
||||
|
||||
```text
|
||||
/theme/7/ -> D/topics/city-collection/
|
||||
/topics/7/ -> D/topics/city-collection/
|
||||
/theme/7/page/2/ -> D/topics/city-collection/page/2/
|
||||
```
|
||||
|
||||
例如作品 `id=9`、`slug=都市爱情`,章节 ID 为 `90`,中文仅用于展示或兼容读取:
|
||||
|
||||
```text
|
||||
/detail/9/ -> D/detail/9/
|
||||
/detail/都市爱情/ -> D/detail/9/
|
||||
/play/9/90/ -> D/play/9/90/
|
||||
/play/都市爱情/90/ -> D/play/9/90/
|
||||
```
|
||||
|
||||
上述中文路径按可读形式展示,实际请求可使用百分号编码;sitemap 只写入右侧 ID 主地址。
|
||||
|
||||
别名页面仍可允许索引,canonical 归并不等于 noindex,也不表示已有重定向。sitemap 只提交最终 canonical 地址,并按完整地址去重。
|
||||
|
||||
`/home`、`/playlet`、`/list`、`/topic`、`/application` 等值只用于后台导航值转换,不是额外公开页面,不应据此生成 sitemap 地址。
|
||||
|
||||
## 5. 固定 Noindex 页面
|
||||
|
||||
| 页面范围 | URL | 当前 robots |
|
||||
| --- | --- | --- |
|
||||
| 搜索和搜索分页 | `/search/`、`/search/{关键词}/`、`/search/{关键词}/page/{n}/`、`/search/?q={关键词}`、`/search/page/{n}/?q={关键词}` | `noindex,follow` |
|
||||
| 独立入口 | `/navigation/` | `noindex,follow` |
|
||||
| 回家的路 | `/home-road/` | `noindex,follow` |
|
||||
| 福利应用 | `/apps/` | `noindex,follow` |
|
||||
| 个人中心 | `/member/center/`、`/info/user-center/` | `noindex,nofollow` |
|
||||
| 收藏 | `/member/favorites/`,包括分页 | `noindex,nofollow` |
|
||||
| 历史 | `/member/history/`,包括分页 | `noindex,nofollow` |
|
||||
| 已购 | `/member/purchased/`,包括分页 | `noindex,nofollow` |
|
||||
| 账号设置 | `/member/settings/`、`/info/settings/` | `noindex,nofollow` |
|
||||
| 充值及 VIP | `/info/deposit/`、`/info/deposit/vip/`,包括支持的分页 | `noindex,nofollow` |
|
||||
| 意见反馈 | `/info/feedback/` | `noindex,nofollow` |
|
||||
|
||||
以上页面及其参数变体均不进入 sitemap。
|
||||
|
||||
### 5.1 当前搜索地址的排除规则
|
||||
|
||||
- 新生成的搜索入口统一使用 `/search/{关键词}/`;关键词先去掉首尾空格,再通过 `encodeURIComponent` 整体编码为一个路径段。
|
||||
- 关键词可以包含中文、空格、斜杠、问号、百分号等字符。搜索关键词不受内容 slug 的 ASCII 正则限制,也不进行数字 ID 归一。
|
||||
- `/search/?q={关键词}` 和 `/search/page/{n}/?q={关键词}` 仍可解析;后续分页链接生成路径形式并移除 `q` 参数,不依赖重定向完成转换。
|
||||
- 路径本身已有关键词时优先使用路径关键词;只有路径没有关键词时才从 `q` 读取。
|
||||
- 不论入口形式、搜索结果数量或关键词字符,搜索页始终 `noindex,follow`,不输出 canonical,也不进入 sitemap。
|
||||
- 无关键词的 `/search/` 可以访问;无关键词且页码大于 1 时返回 404。
|
||||
|
||||
## 6. 条件性 Noindex 与异常处理
|
||||
|
||||
| 情况 | 当前前端行为 | 后端生成处理 |
|
||||
| --- | --- | --- |
|
||||
| 空公开列表 | 接口成功确认 `totalCount=0` 时,第一页 `noindex,follow`,仍返回 200 | 不提交 |
|
||||
| 详情或播放页评论分页 | `/detail/{key}/page/{n}/`、`/play/{key}/{chapterId}/page/{n}/`,`n>1` 时 `noindex,follow` | 不提交 |
|
||||
| 非默认列表排序 | 除榜单和更多栏目外,公开列表 `sort != 1` 时 noindex | 只生成无参数的默认地址 |
|
||||
| 非法列表排序 | 公开列表的 `sort` 不属于 `1、2、3` 时 noindex;未传时默认 `1` | 不生成非法变体 |
|
||||
| 非默认评论排序 | 存在 `commentSort` 且值不等于字符串 `2` 时 noindex | 不生成参数变体 |
|
||||
| 更多栏目内容不足或无效 | 已存在栏目缺少自己的标题、正文简介或有效卡片时 `noindex,follow`;找不到启用的类型 `1/3` 栏目时按资源缺失处理 | 不提交 |
|
||||
| 播放页条件不足 | 预告、纯解锁提示、无有效正片或无观看权限时 `noindex,follow` | 不提交 |
|
||||
| 明确禁止索引或需要登录 | `indexable=false` 或 `loginRequired=true` 时 noindex | 不提交 |
|
||||
| 无效路由或不存在的资源 | 返回 404,且无 canonical | 不提交 |
|
||||
| 已确认越界的分页 | 返回 404,且无 canonical | 不提交 |
|
||||
| 临时取数故障 | `failed` 或 `unavailable` 时返回 500,且无 canonical | 不将故障误判为删除;保留上次成功结果,稍后重试 |
|
||||
|
||||
补充约定:
|
||||
|
||||
- 榜单和更多栏目可以接受 `sort=2/3`,但 canonical 仍不含参数;sitemap 不重复提交这些参数地址。
|
||||
- 作品详情的评论数为 0,不等于作品内容为空,不能按空列表规则排除作品详情。
|
||||
- 不存在的内容与临时故障必须区分;无法可靠判断一批内容时,不发布因取数失败而缺项的新结果。
|
||||
|
||||
## 7. 建议的生成与发布约定
|
||||
|
||||
1. 合并本文的固定地址与后端数据库枚举的动态内容。
|
||||
2. 按公开访问时的页面状态判断是否允许索引。
|
||||
3. 按英文/拼音 slug 白名单和 ID 回退规则生成最终 canonical URL,不输出参数、锚点、别名、中文内容 slug 或非页面资源。
|
||||
4. 按完整 canonical URL 去重,只发布正常、有效的页面地址。
|
||||
5. `lastmod` 有可靠数据时使用页面内容实际更新时间;没有时省略,不统一伪造为生成时间。
|
||||
6. 生成文件保存到前端发布目录之外的持久化存储,不写入前端 `public` 或 `.next`。
|
||||
7. 完成分片生成和可访问性校验后,再切换 sitemap 索引;失败时保留上一份成功版本。
|
||||
8. 建议固定入口为 `/sitemap.xml`,分片通过 `/sitemaps/` 下的通用路径提供访问。现有入口不同时,优先保留已提交的地址。
|
||||
9. `robots.txt` 中声明正式站点的 sitemap 索引地址;前后端 URL 或索引规则变更时同步更新生成逻辑。
|
||||
|
||||
首次接入需要部署访问配置;后续内容和分片变化不应要求前端预留文件、重新构建或重启 H5。
|
||||
|
||||
## 8. 代码依据
|
||||
|
||||
- [路由解析、内容路径和分页支持](../src/lib/routing.ts)
|
||||
- [域名、canonical、noindex 和页面状态判断](../src/lib/seo.ts)
|
||||
- [服务端资源核对与 SEO 元数据输出](../src/lib/seo-server.ts)
|
||||
- [专题解析、首页栏目快照和列表分页](../src/lib/client/channel.ts)
|
||||
- [章节权限与正片地址判断](../src/lib/client/playback.ts)
|
||||
- [固定信息页](../src/lib/info-pages.ts)
|
||||
- [现有页面索引策略](indexation-policy.md)(其“非空 slug 优先”描述需结合当前代码的英文/拼音字符白名单理解)
|
||||
- [SEO 规则回归用例](../tests/seo.test.mjs)
|
||||
- [Slug 路由与 canonical 回归用例](../tests/slug-routing.test.mjs)
|
||||
- [路由与搜索参数兼容回归用例](../tests/routing.test.mjs)
|
||||
- [分页与搜索路径回归用例](../tests/pagination.test.mjs)
|
||||
@@ -0,0 +1,271 @@
|
||||
# API 对接验证
|
||||
|
||||
日期:2026-10-17。
|
||||
|
||||
## 2026-10-22 测试与生产环境配置
|
||||
|
||||
本项目增加 `config/test.env`、`config/production.env`,保留独立的开发配置。进程变量优先,其次为对应 `*.local.env`,最后为基础配置;本地覆盖文件忽略提交。接口均沿用 `https://91av-wz.80sygtm.com`,默认端口均为 21030。测试部署使用 `APP_ENV=test` 和 `NODE_ENV=production`,构建目录为 `.next-test/`;生产目录为 `.next/`。开发目录为 `.next-dev/`。SEO 快照随对应目录隔离,同 API 的两个环境也不会共用快照。
|
||||
|
||||
`npm run build:test`、`npm run check` 通过,全部 290 项测试通过。新增测试覆盖配置优先级、环境隔离、CLI 参数、错误配置、真实子进程退出状态,以及同上游的 SEO 快照隔离。另通过启动脚本分别启动两套实际 Next.js 服务,使用临时端口和本地模拟上游验证静态资源及 API 代理均返回 200,验证结束后关闭临时服务。未执行远程部署。
|
||||
|
||||
## 2026-10-22 slug 路由
|
||||
|
||||
本项目(`JHA-1050`)的内容链接、轮播、专题/标签/栏目、搜索、书架卡片、选集、评论分页及解锁后跳转统一优先 slug,缺少时用 ID。支持 slug 地址直接访问与刷新,保留原 ID 地址直接访问;业务写入及仅接受 ID 的读取继续使用真实数字 ID。专题详情按当前文档先从分页专题目录解析 ID,不向接口发送未定义的 slug 参数。
|
||||
|
||||
生产构建及全部 283 项测试通过。新增回归覆盖 URL 编码、中文及特殊字符、分页目录解析、目录异常与缺失资源、canonical/JSON-LD 一致性、客户端选集与解锁跳转,以及严格 Swagger 参数约束下的 Next.js 生产响应。当前 Swagger 的 slug 查询参数已只读核对;测试使用本地模拟接口,未提交真实付费解锁、订单或评论。
|
||||
|
||||
## 2026-10-21 跳转性能优化
|
||||
|
||||
公共数据服务增加短期进程内缓存和并发合并:配置、页脚、导航、分类及标签选项 60 秒,首页 30 秒;按上游及查询参数隔离,返回独立副本并限制条目数量。无效数据、错误响应、认证和私人业务读取不缓存,配置/首页可在刷新失败时使用最近一次有效数据。详情、章节、选集、评论和推荐在响应详情之前并行请求,仍在服务端输出完整 HTML。
|
||||
|
||||
浏览器直接复用首屏公开配置及页脚,SDK 配置不序列化。公开页从浏览器前进后退缓存恢复时保留文档,后台按当前 Cookie 恢复身份并补报页面访问;账户、播放、支付及打开弹窗的页面保留重新验证。类型检查、生产构建及全部 258 项测试通过,包括跨请求缓存、并发合并、过期、错误恢复、副本隔离、缓存边界、两个账号隔离、首屏配置白名单、并行请求屏障、后退恢复与埋点。
|
||||
|
||||
真实接口及 29 个页面检查通过。本机短期采样中,公共缓存预热后列表约 312ms、专题约 287ms、回家的路约 143ms(首字节中位数,各 3 次);首次游客会话曾超时,另一次交替对比也出现显著上游波动,不能将上述采样当作稳定延迟承诺。未提交真实订单、付费解锁、评论或反馈,未执行浏览器视觉验收。
|
||||
|
||||
## 2026-10-21 服务端直接渲染
|
||||
|
||||
请求与渲染方式:页面数据在服务端准备,页头、公开列表、搜索、详情/播放、应用目录、回家的路、反馈、个人中心及设置、充值/VIP 的正文随初始 HTML 返回。站内链接、分页和搜索使用原生文档请求,保留当前页面直到新响应到达;不再清空正文显示页面加载占位。浏览器复用首屏快照绑定交互,避免启动时重复读取整页数据。提交成功后重新请求当前页面,轮播、图片解密、实时权限、解锁和支付确认保留。
|
||||
|
||||
Proxy 为有效业务页面准备身份,并在本次请求中传递新 Cookie,供服务端读取账户内容;同一 Cookie 同时写回浏览器。客户端复用服务端用户快照,避免再次初始化游客;正式账号失效显示登录入口,服务异常直接提供重试。外部传入的内部身份头先清除,用户、列表、套餐和流水使用明确字段白名单,凭证不写入 HTML;账户响应继续禁止缓存和索引。独立 `/navigation/`、资源、无效地址和尾斜杠重定向不触发该会话流程。
|
||||
|
||||
类型检查、生产构建及全部 249 项测试通过,覆盖完整首屏正文、原生跳页保留旧文档、快照绑定不重复取数、两个账号隔离、伪造身份头、正式账号过期与退出、Cookie 传递、套餐及流水、播放器与广告/轮播清理。真实只读接口及 29 个页面检查通过,另确认真实免费与金币章节页面输出完整 HTML。没有创建真实订单、付费解锁、提交评论或反馈;未执行浏览器视觉验收。
|
||||
|
||||
## 2026-10-21 动态导航
|
||||
|
||||
顶部与侧栏共用 `navigation/header` 的服务端数据,移动抽屉继承同一菜单,底栏按同源数据生成。保留接口名称、顺序、图标和二级条目;过滤底部类型、不安全地址及已移除的独立剧集地址。首屏 HTML 已包含真实链接,原脚本不再插入固定顶部导航或覆盖接口名称。已知 API 的 `/hot` 与空系统入口直接生成本站路径,不新增跳转。
|
||||
|
||||
路由高亮由同一纯函数处理首屏及客户端导航:首页精确匹配,列表分页保持选中,排行榜覆盖三个榜单,具体专题及带查询条件的入口按对应地址匹配,顶部不借用详情页的列表高亮。接口空列表不回填静态菜单;异常只提示导航暂不可用,正文仍能加载。构建、类型检查和全部 248 项测试通过,覆盖动态顺序/名称、二级条目、外链、空列表、异常、旧剧集过滤和高亮规则。
|
||||
|
||||
## 2026-10-21 移除独立剧集
|
||||
|
||||
按后续确认移除桌面顶部、侧栏、移动端的“剧集”导航,以及独立剧集列表、视频详情和对应分页路由;旧地址无论是否带尾斜杠均直接返回 404,不请求内容接口,也不重定向。首页与 JSON-LD 不再展示旧类型的视频栏目。短剧详情、选集、金币解锁和章节播放保留。构建、类型检查与全部 242 项测试通过,包括入口清理、旧地址 404 和原有播放/解锁回归。
|
||||
|
||||
## 2026-10-21 当前 Swagger 对接
|
||||
|
||||
按 `91av-wz.80sygtm.com` 当前文档与实际只读响应核对页面接口,端点和参数映射见 [页面接口对照](api-contract.md)。修正短剧列表、专题顶层字段、题材筛选、搜索联想与空结果推荐、详情推荐及章节 SEO/统计字段;删除旧接口代理依赖。首页“更多”保留已确认的 moreUrl 优先规则,未配置时只展示真实首页栏目的数据。按用户后续要求移除独立剧集导航和路由,旧地址及分页直接返回 404;短剧章节播放保留。
|
||||
|
||||
构建、类型检查及全部 240 项接口合同与回归测试通过。真实服务验证游客身份、列表/三个榜单、专题、标签、配置、应用、套餐、个人列表和余额记录;另核对 29 个原生页面响应与实际有内容的免费/金币章节(`/play/10923/12723/`、`/play/10923/12726/`)。没有创建真实订单、付费解锁、注册账号或提交评论、反馈。未执行新的浏览器视觉验收。
|
||||
|
||||
## 2026-10-20 充值确认弹窗
|
||||
|
||||
恢复原 HTML 的支付标题/副标题、订单卡片、基础/赠送/合计金币、金额徽标、渠道图标按钮及选中圆点、确认按钮和底部说明,并复用原稿的尺寸、间距、圆角及移动布局。支付宝、微信、USDT 图标使用原始本地素材;渠道仅来自套餐配置,其他渠道保留名称和通用图标。使用隐藏表单值连接样式按钮和原下单参数,金币充值与 VIP 接口保持不变。
|
||||
|
||||
`tests/payment.test.mjs` 验证实际金币字段及金额换算、不注入演示赠送或未配置渠道、方法切换后的订单参数、重复提交拦截、VIP 接口、无效支付地址、无支付方式以及旧表单响应隔离。类型检查、生产构建和全部 152 项测试通过,仅使用本地模拟上游,没有创建真实订单或发起真实支付。
|
||||
|
||||
## 2026-10-20 详情选集解锁
|
||||
|
||||
对照参考项目 `playletser/chapter.go` 的列表权限规则和 `pages/play.js` 的解锁处理,金币集数使用按钮直接打开 `play-episode-lock`,保留原 HTML 的金币图标、余额/消耗摘要、确认和充值入口样式。弹窗先获取目标章节详情,再展示实际价格及余额;仅确认后提交解锁,成功刷新余额并跳转所选真实章节。已可观看和会员章节按最新权限处理,余额不足和登录过期分别进入充值或登录。
|
||||
|
||||
新增测试覆盖:列表权限优先级、详情页弹窗不提前跳转/扣费、最新价格覆盖列表旧价格、正确章节 ID、重复点击及确认防重、支付中禁止切换、迟到响应隔离、关闭/离开页面、余额不足、已可观看/VIP、章节信息不匹配、登录失效、失败重试,以及实际 Route Handler 的凭据和解锁参数转发。类型检查、生产构建与全部 147 项测试通过;测试仅连接本地模拟上游,未提交真实金币解锁。
|
||||
|
||||
## 2026-10-20 详情页样式调整
|
||||
|
||||
区块留白按原稿保留:横幅上下桌面端 26px、手机端 22px,官方链接至评论区 28px。无横幅数据时,选集与官方链接之间仍保留对应间距,不因条件渲染移除广告区而贴合。
|
||||
|
||||
对照原 HTML 的 `overviewMarkup`、操作栏、选集和评论 DOM 恢复结构,复用已有桌面/移动样式及原始 SVG 素材;修复 API 按钮属性与原样式选择器不一致的问题。简介支持三行截断与展开/收起,尺寸变化及离开页面时更新和清理;选集分段只横向调整滚动,保持桌面/手机切换前的章节位置。评论使用原稿的头像、正文、时间/操作行和嵌套回复容器,保留接口和独立分页路由。
|
||||
|
||||
`tests/detail.test.mjs` 验证动态字段与文本转义、图片解密入口、真实章节链接、配置官方链接和简介展开/尺寸变化/清理;播放测试补充详情页点赞接口、评论回复结构及移动端集数分段。类型检查、生产构建和全部 137 项测试通过。浏览器视觉复核仍受访问工具策略限制,未生成新的浏览器截图。
|
||||
|
||||
## 2026-10-20 登录逻辑调整
|
||||
|
||||
游客登录 403 已复现并修复:带 `Origin: http://localhost:3000` 的正常浏览器请求被本地来源校验拒绝,未到达上游。Next.js 使用监听地址 `0.0.0.0` 构造请求 URL,不能将它直接与浏览器 Origin 比较;现在使用实际 HTTP Host 和协议校验。新增真实 HTTP Host 集成测试及来源校验单元测试,覆盖 localhost、IPv4、IPv6、域名、端口、协议及伪造转发头。类型检查、生产构建及全部 131 项测试通过。
|
||||
|
||||
修复后对用户正在运行的 `http://localhost:3000/api/app/auth/guest` 进行一次正常游客握手,携带浏览器 Origin 和设备标识,真实响应为 HTTP 200 / code 200,返回用户信息及会话 Cookie。未记录或展示登录凭证,未提交正式账号注册、密码或其他业务写操作。
|
||||
|
||||
对照参考项目的 `common/auth.js`、登录/绑定弹窗及 `authser` 调整游客初始化、账号切换、绑定、失效恢复和退出。`tests/auth.test.mjs` 验证账号/邮箱和密码校验、密码确认、协议、设备持久化、首次渠道与邀请码、存储不可用、游客请求合并、登录响应顺序、失败保留原身份、旧请求隔离、正式账号过期只提示一次、手动退出,以及实际弹窗事件的防重复提交和密码清理。
|
||||
|
||||
HTTP 集成测试另验证真实 Route Handler 的登录与绑定参数、游客 Authorization、可选邮箱补全、设备头与客户端 IP、getInfo 不返回 token 时续用原凭证、正式账号初始化过期不降级、游客 GET/POST 失效最多重试一次、HTTP 401、缺少 token 的异常登录响应、Cookie 清理和退出后一次自动登录跳过。类型检查、生产构建和全部 127 项测试通过。测试只连接本地模拟上游,没有提交真实账号登录、绑定或密码重置。
|
||||
|
||||
## 真实接口
|
||||
|
||||
通过本地 Next.js 代理验证首页、配置、底部信息、短剧运营列表、热榜、专题、热搜、应用列表、充值套餐和搜索接口,均返回业务码 200。首页使用服务端请求的实际内容,首页筛选项保持移除。
|
||||
|
||||
上游 `POST /api/app/auth/guest` 在直接请求和代理请求中均出现超时(最长等待 25 秒)。无有效 token 的章节列表返回业务码 1000。真实登录态下的播放、用户资料、评论、收藏、订单和金币解锁尚未完成端到端联调;没有向真实接口提交评论、支付订单或解锁请求。
|
||||
|
||||
## 本地集成测试
|
||||
|
||||
`npm test` 启动独立模拟上游和 Next.js 生产服务,检查:
|
||||
|
||||
- 服务端渲染使用上游标题和栏目;API 文本转义;首页筛选项未恢复。
|
||||
- 首页广告列表显式返回 `null`、轮播/栏目/公告为非数组、数组夹有空项时仍返回 200;有效内容继续显示,空内容显示空状态。
|
||||
- 纯站点地址和带 `/` 的根地址均直接返回首页及 200,无 Location 响应头;首页入口指向根路径,短剧入口单独指向 `/shorts/`。
|
||||
- 游客 token 仅写 HttpOnly Cookie,不返回到前端脚本;设备头按参考格式发送。
|
||||
- 公共请求隔离过期 token;兼容二次编码的 JSON data。
|
||||
- 正式账号会话失效后,写请求不会自动降级为游客重试。
|
||||
- 接口白名单、请求方法、跨站写入拒绝。
|
||||
- 保留真实章节 ID、观看权限和单集金币数。
|
||||
- 退出登录清理会话 Cookie,不调用上游业务写接口。
|
||||
- 30 个页面路径以 `/` 结尾,直接返回 200 且没有 Location 响应头;不带尾斜杠的地址也直接返回 200;`/shorts/` 刷新返回 200;无效路径返回可见的 404;首页和业务页面链接直接包含尾斜杠。
|
||||
- `/navigation/` 直接访问与刷新返回 200,独立渲染入口线路、渠道和 App 下载链接,不加载业务页面运行脚本;首页及其余 30 个路径均不包含入口导航弹窗或隐藏页面的等待状态。
|
||||
- 顶部、悬浮工具和首页底部的下载入口在配置未知时保持隐藏;页面不包含悬浮二维码面板。独立入口页在下载地址为空或无效时不渲染下载按钮。
|
||||
|
||||
`tests/routing.test.mjs` 另检查所有路由类型、中文和含斜线的搜索词、hash 地址不转换或替换、站内链接识别,以及 History 导航的事件通知、重复地址和前进后退记录。
|
||||
|
||||
全部分页使用 `原页面路径/page/页码/`,第一页链接使用原路径;页码、上一页和下一页均为真实链接。`tests/pagination.test.mjs` 覆盖短剧、剧集、专题/题材/标签/栏目、榜单、搜索、个人收藏/历史/已购、充值和 VIP 余额明细,以及详情/播放页的评论分页,检查页码解析、首尾页、无效页码、中文搜索词、排序保留和会员接口参数。播放器测试检查评论页码与排序、真实分页链接;HTTP 集成测试检查 20 类路径的两种尾斜杠形式均直接返回 200、刷新不重定向,以及非法分页路径返回 404。本次生产构建及全部 109 项测试通过。
|
||||
|
||||
排行榜独立使用 `/rank/hot/`、`/rank/new/`、`/rank/follow/`,对应接口 `rankType=1/2/3`。`tests/rank-pages.test.mjs` 检查真实页面路由逻辑、链接与当前页高亮、标题、刷新和翻页后的榜单类型,以及加载中和请求失败时保留导航;HTTP 集成测试检查三条路径两种尾斜杠形式均直接返回 200、独立标题且没有 Location 响应头。原 `/rank/` 仍直接展示热播榜,不重定向。此次生产构建和全部 100 项测试通过。
|
||||
|
||||
`tests/download.test.mjs` 检查下载地址优先级和有效性、各下载入口随配置显示/隐藏、动态插入入口,以及桌面端直接打开链接、移动端点击弹窗与组合键保留浏览器默认操作。
|
||||
|
||||
`tests/render.test.mjs` 检查广告、首页列表及嵌套栏目中的空值和类型异常,保留有效内容顺序及 HTML 转义;弹窗测试另覆盖广告队列中的空项和非对象项。
|
||||
|
||||
`tests/images.test.mjs` 使用参考算法的固定密文验证逐字节解密、100 字节边界、MIME 识别和普通图片兼容,并检查重复请求合并与缓存、并发和优先级、失败重试、超时取消、异步切图、Blob 释放、初始/动态节点和视口懒加载。渲染与 SSR 测试另确认接口图片不直接写入 `src`,视频封面使用 `data-poster`。
|
||||
|
||||
2026-10-19 只读抽取真实首页接口的 3 张图片验证,均返回 HTTP 200,按参考密钥解密后识别为 WebP;图片 CDN 返回 `Access-Control-Allow-Origin: *`。未保存原始图片,也未提交业务写请求。
|
||||
|
||||
`tests/playback.test.mjs` 覆盖播放地址及签名保留、观看权限优先级与旧字段、正片/预告切换、Safari/Chromium/移动壳浏览器选择、HLS 恢复上限、重试重建、切换路由与异步导入竞态、拒绝自动播放后的重试、预告不计入正片记录、真实章节 ID、解锁确认与防重复提交,以及实际控件脚本的播放/重试/解锁事件。HTTP 集成测试覆盖媒体清单、密钥端点两种尾斜杠形式、原始 AES 字节、凭据隔离、Range 和错误状态。
|
||||
|
||||
H.264/H.265 兼容测试另覆盖 MSE/原生 HLS 编码错误回退、签名与播放位置保留、重复错误及重试次数限制、网络错误区别处理、MP4 不误走 HLS,以及回退过程中的路由取消。`tests/codec-manifest.test.mjs` 运行真实 hls.js 1.7.3 完整版的清单解析和 LevelController,在模拟设备能力下验证 H.264 回退、`hvc1`/`hev1` 保留、HEVC 单编码清单接受/拒绝;测试不访问网络,不代表真实浏览器或硬件的解码实播结果。
|
||||
|
||||
2026-10-19 只读验证真实免费预告:清单返回 200,保留 `id/t/ticket` 参数后密钥代理返回 200 和 16 字节 AES 密钥,首片成功通过 AES-128-CBC 解密,输出首字节为 MPEG-TS 同步字节 `0x47`。不带签名参数的密钥请求会被上游拒绝。本次未保存密钥/媒体,也未提交真实观看上报、解锁或订单请求;真实账号付费章节和浏览器实播仍需联调。
|
||||
|
||||
`tests/startup.test.mjs` 对照参考项目的实际脚本规则验证:会话内首次年龄确认、确认早于配置返回、完整广告队列、刷新恢复进度、相同队列不重复弹出、广告码变化后重置、无效图片与失败图片跳过、全部失败后可重试、存储不可用、过期异步结果隔离,以及图片加载超时和取消。DOM 事件适配测试覆盖年龄弹窗禁止 Esc 关闭、广告 Esc/按钮切换下一条、广告链接与展示/点击事件,以及退出行为。
|
||||
|
||||
浏览器访问工具受 URL 策略限制,本次未进行浏览器视觉复核。目录中的桌面、手机截图属于原 HTML 迁移阶段的布局基线,不是当前 API 数据截图。
|
||||
|
||||
## 2026-10-22 部署脚本与本地发布包
|
||||
|
||||
- `bash deploy_test.sh --package-only` 完成测试环境构建、294 项回归测试和 zip/SHA-256 生成;`bash deploy_prod.sh --package-only` 完成生产构建、299 项回归测试和 Linux x64/glibc 包/MD5 生成。构建包含 TypeScript 检查。
|
||||
- 两份脚本的 `--dry-run` 通过命令替身验证,不执行构建、SSH 或 SCP;缺失/非法部署目标在构建前拒绝。测试和生产共用构建锁。
|
||||
- 发布包测试覆盖基础配置隔离、本地覆盖/缓存/旧 dist 排除、符号链接目标保护。Next.js 自动追踪漏带的 Turbopack Route Handler 运行文件已显式包含。
|
||||
- 最终 `dist.zip`(29.87 MiB)解压到临时目录,使用本机裁剪依赖启动 `node start.mjs`;本地模拟 API 下首页、`/healthz`、Next 静态资源和 public 文件均返回 200,健康检查未触发上游请求。测试 zip 按约定不含依赖,真实 Linux 服务器需执行 `npm ci --omit=dev`。
|
||||
- 最终 `web_jgdj_h5.tar.gz`(44.94 MiB)通过内容、MD5 和目标平台元信息检查;目标 Sharp/libvips 使用 lockfile 固定版本,并通过 Linux x64 ELF 校验,不含 macOS 原生模块。本机未运行 Linux 生产服务。
|
||||
- `deploy/up_next_h5.sh` 用临时目录和命令替身验证校验失败、安装失败不停止旧服务;健康检查失败恢复旧版本;成功更新保留测试私有覆盖和公开 SEO 缓存。新增部署测试共 8 项通过,已包含在最新生产环境的 299 项回归中。
|
||||
- 本次仅本地构建和打包,未执行任何真实 SSH/SCP、Supervisor 更新或远端 `running.sh`。部署前需配置本站目标地址,测试 Supervisor 程序及生产跳板机脚本由运维预先配置。
|
||||
|
||||
部署配置统一采用本站标识:测试 `root@172.104.181.99:22889` → `/server/web_jgdj/h5`;生产 `server@116.212.120.2` → `/home/server/web_jgdj/h5/nuxt`,SCP 22889 / SSH 22。包名 `web_jgdj_h5.tar.gz`、校验文件和 Supervisor `jgdj_next_h5` 使用本站标识;生产仍只通过完整路径调用原有 `running.sh`。部署测试验证当前配置、环境变量覆盖、无效目标提前失败及 dry-run 不连接远端;本次未执行实际上传或部署。
|
||||
|
||||
### 部署配置变化后的测试修复
|
||||
|
||||
已复现 `tests/deploy.test.mjs:72` 的目录断言失败:测试仍固定期望旧项目的上传目录,实际测试脚本已配置 `/server/web_jgdj/h5`。修复后不再将主机、路径、端口或 API 域名作为测试不变量,保留对参数覆盖、命令与帮助输出一致、非法目标提前失败、dry-run 不构建/不连接远端的验证。测试服更新的模拟 Supervisor 使用固定测试服务,避免调用者的 `DEPLOY_PROGRAM` 污染测试。两份脚本的帮助内容统一从当前变量生成,修复当时测试 SSH 配置 21030、帮助文案却写 22889 的不一致;保留修复时的上传路径和端口。
|
||||
|
||||
`bash deploy_test.sh --package-only` 已完成测试环境构建、全部 299 项测试和 `dist.zip` 打包;压缩内容及 SHA-256 通过校验。另在设置自定义 `DEPLOY_PROGRAM`、`DEPLOY_DIR`、`DEPLOY_SSH_PORT` 后运行部署专项测试,8 项通过。当前 SSH 端口和应用监听端口分别配置,未连接真实服务器验证 SSH 端口或执行上传/部署。
|
||||
|
||||
### 测试部署改用 tar.gz
|
||||
|
||||
`deploy_test.sh` 现在生成并上传 `web_jgdj_h5.tar.gz`、`web_jgdj_h5.tar.gz.sha256` 和 `up_next_h5.sh`,包名可通过 `DEPLOY_PACKAGE_NAME` 覆盖;同时刷新对应 MD5 文件供本地兼容校验。测试包继续排除依赖、本地覆盖、缓存和 macOS 附加文件,包内使用 `.next-test` 与 `config/test.env`。保留当前上传目录和端口,以及用户注释的远端自动更新命令;实际脚本上传完成后结束。
|
||||
|
||||
服务器辅助脚本同步使用 tar.gz:先校验 SHA-256、检查成员路径与类型,再解压、安装依赖和切换版本;损坏包、路径越界、链接及特殊文件在停止旧服务前拒绝。测试覆盖真实 tar.gz 上传参数、校验值、自定义包名、自动更新保持禁用,以及安装/健康检查失败的回滚。
|
||||
|
||||
完整本地构建和 304 项测试通过;测试部署专项 13 项通过。生成的测试 tar.gz 为 29.78 MiB、414 个文件,内容、SHA-256 和 MD5 均通过校验。解压后使用本机裁剪依赖和本地模拟 API 验证启动、首页、健康检查、Next 静态资源与 public 文件均返回 200。未执行真实上传或服务器更新。
|
||||
|
||||
## 2026-10-22 埋点检查与补全
|
||||
|
||||
通过项目自带的官方 SDK v1.2.0 复现了内容事件因空 `parent_item_id` 被拒收的问题;此前测试替身未校验字段,所以没有发现。现在回归测试调用真实 SDK 的具名事件方法,仅使用其初始化前缓存和字段校验,不向外发送测试数据。只读获取当前业务接口配置并在本机解析后,确认页面、内容、搜索、广告、弹窗行为、播放事件均在事件允许列表内;未修改服务端配置或打印密文、签名密钥。
|
||||
|
||||
| 范围 | 事件与检查结果 |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| 页面、分页、后退恢复 | `appPageView/pageClick`,保留真实路径及原有访问去重规则 |
|
||||
| 导航与下载 | `navigation`,补齐独立入口页渠道、App 下载入口选择器 |
|
||||
| 首页及列表内容 | `appPageitemExposure/appPageitemClick`,无父级用字符串 `0`,通过真实 SDK 校验 |
|
||||
| 专题、福利应用 | 补齐专题卡片内容元数据;关联 `advitiseId` 的应用按广告上报,其他应用按内容并携带分类父级 |
|
||||
| 大图广告 | `adImpression/adClick`,保留原展示、点击触发;独立展示位置及延迟 SDK 队列防止漏报 |
|
||||
| 宫格广告 | 与首页分开去重,滚动后补报可见项;关闭、遮挡或后台标签页不记曝光,恢复后重新观察 |
|
||||
| 弹窗行为 | 补齐参考项目的 `advertising`:show/click/close,使用真实广告 ID;关闭不记 adClick,重复关闭不重报 |
|
||||
| 搜索 | `keywordSearch/keywordClick`,保留真实总数、首轮搜索及分页后的全局位置 |
|
||||
| 播放、切集、分享、失败 | `dramaEvent/dramaStatusChange`,全部现有行为的载荷通过真实 SDK 校验,预告不作为正片统计 |
|
||||
| 登录、游客、注册、充值、VIP | 沿用 SDK 原始公共字段透传及 UID 同步,成功业务事件由服务端负责 |
|
||||
| 点赞、收藏、评论、购买解锁 | 保留 BFF 校验后的事件上下文和请求 ID 透传,前端不重复发送服务端成功事件 |
|
||||
| 加载、排队、错误 | 配置首读跳过 SDK 等待;脚本失败重试加载;扩大初始化前队列并报告溢出;SDK 拒收和发送失败有脱敏诊断 |
|
||||
|
||||
浏览器使用真实页面及未修改的官方 SDK,测试专用包装通过 SDK 支持的单次事件地址参数把请求送入本机接收端,避开官方域名解析器对配置地址的 HTTPS 要求。已验证大图广告展示/点击、宫格展示/点击、页面访问、内容曝光,以及原生页面跳转前的内容点击均实际发出带 `event_sig` 的网络记录。UI 点击和弹窗滚动经浏览器执行,未向正式统计后台发测试事件。未验证正式部署的反向代理和统计后台入库结果。
|
||||
|
||||
最终验证:测试环境构建(含 TypeScript)及全部 323 项回归测试通过。最后一轮本机浏览器验证收到 78 条带签名的 SDK 事件,其中 `advertising` 为 show 21、click 2、close 21;大图广告的三种行为均到达,宫格关闭只覆盖实际曝光的广告,关闭未增加 `ad_click`。另外一轮已确认原生页面跳转前的 `app_pageitem_click` 实际到达,内容父级字段为字符串 `0`。所有网络验证仅使用本地模拟业务数据和本地测试签名配置。
|
||||
|
||||
## 2026-10-26 禁果短剧基础信息迁移
|
||||
|
||||
项目编号统一为 `JHA-1050`,应用及埋点 App ID 为 `1050`,npm 包和健康检查应用名为 `jha-1050-jgdj-web`。开发、测试、生产基础配置与测试数据同步更新。站名、页面标题、下载提示、协议品牌名称和 SEO 默认值统一为「禁果短剧」;Logo、侧栏缩略标识、App 下载图标、浏览器/桌面图标及默认分享图均已更新,并核对实际渲染及素材字节数登记。
|
||||
|
||||
发布包、默认目录和 Supervisor 使用 `jgdj` 标识,服务器地址与端口保持原配置。侧栏和页脚从后台配置生成官方联系方式,未配置或不安全的链接不输出;版权通知入口改为本站意见反馈,移除参考站邮箱。新增集成测试覆盖首屏联系方式的有效、缺失和不安全配置。
|
||||
|
||||
验证结果:生产构建、`npm run typecheck`、全部 **374 项**回归测试和 `git diff --check` 通过。首次测试因沙箱禁止本机端口监听而取消集成用例,获得本地测试所需权限后完整重跑,374 项通过、0 失败、0 取消。未执行部署、推送或 Git remote 切换;上线前需核对新仓库地址、服务器目录/更新脚本/Supervisor,以及应用 `1050` 对应的 Go 上游、后台 SEO 和 `reportSdkConfig`。
|
||||
|
||||
## 2026-10-26 按禁果短剧 HTML 调整页面
|
||||
|
||||
以用户提供的 `禁果短剧_2组.html` 为视觉参考,提取其样式并保留原级联顺序,导入实际使用的品牌、页头背景、空状态、导航和年龄提示素材。桌面改为双层品牌页头;移动端按参考稿保留菜单、搜索及账户工具,正文使用独立主推轮播和六张横版推荐,模块副标题、热搜标签、排行榜角标、页脚及弹窗使用同一套粉色主题。真实 API 的动态栏目、路径、分页、账户、支付及下载流程继续使用现有实现。
|
||||
|
||||
首页横版推荐从启用的真实栏目中去重选取,数量不足时使用有效轮播数据;轮播切换只替换主推区,避免重复替换横版推荐或生成额外曝光。桌面历史面板在悬停或键盘聚焦时读取当前账户的历史接口,支持错误/空状态,关闭及身份变化后忽略过期返回,不持久化私人记录。
|
||||
|
||||
生产构建、TypeScript 检查、全部 **376 项**回归测试及 JavaScript 语法检查通过。已用本机模拟接口在浏览器对照桌面、390px 手机和 800px 平板布局,并验证轮播、历史面板的键盘进入和真实接口数据展示。预览中的剧目及账号均为本地测试数据;未执行线上部署或向外部服务发送测试事件。
|
||||
|
||||
## 2026-10-26 宫格弹窗限高与独立滚动
|
||||
|
||||
按参考项目的宫格弹窗规则,将 `#startup-grid` 最大高度限制为 `80vh / 80dvh`,仅广告列表区域纵向滚动,列表最大高度为视口 80% 减去 60px,预留底部关闭按钮及间距。隐藏滚动条并限制滚动向页面传递;原有四列宫格、手机间距和广告交互继续使用现有实现。
|
||||
|
||||
使用真实弹窗结构和当前全局样式,分别在桌面及 390 × 650 手机视口下放入 48 条本地测试数据,验证滚动可到达最后一排,关闭按钮始终可见。弹窗与埋点相关 32 项测试全部通过,`git diff --check` 通过。
|
||||
|
||||
## 2026-10-26 请求代理移除来源拦截
|
||||
|
||||
按 `jha-1047-91dj-web` 移除 Next.js Route Handler 对 POST 的 `isSameOriginRequest` 校验及不再使用的工具和单元测试。该逻辑原用于防止跨站 POST,运行于服务端代理;当 CDN / 反向代理使浏览器 Origin 与内部 Host 或协议不同时,可能在请求到达 Go 之前返回「请求来源不允许」。本次调整覆盖 `/api/*` 与兼容的 `/api/app/*` 两个入口,保留接口白名单、请求方法限制、HttpOnly / SameSite Cookie、会话失效处理及代理循环检测,上游鉴权错误继续传回。此前记录中的本地来源检查及跨站写入拒绝已由本次规则替代。
|
||||
|
||||
集成用例覆盖 localhost、IP、CDN HTTPS、反代内部域名与协议变化、`Origin: null` 及跨站标记,验证请求确实到达本机模拟上游、成功响应不泄漏 token,并检查上游 HTTP 403 仍返回 403。所有业务请求只发送至本机模拟服务。生产构建(含 TypeScript)、全部 **374 项**测试及 `git diff --check` 通过。
|
||||
|
||||
## 2026-10-26 生产部署脚本对齐参考项目
|
||||
|
||||
`deploy_prod.sh` 与 `jha-1047-91dj-web` 同步,生产远端命令统一为 `REMOTE_UPDATE_COMMAND`,预览和实际 SSH 执行使用相同文本。目录经 `valid_path` 校验后生成 `cd /目录 && bash rsupdate.sh`,移除原实际执行时额外的目录单引号,兼容按命令文本配置的 SSH 白名单;执行前打印远端命令,保留发布日志及失败状态检测。本站品牌、`web_jgdj` 包名和目录、主机与端口沿用当前配置。
|
||||
|
||||
脚本语法检查、默认 dry-run、26 项部署专项测试及 `git diff --check` 通过。测试通过本机命令替身验证上传先于更新、白名单命令匹配、权限拒绝、SSH 失败、远端错误输出、curl 401、自定义目录及仅打包行为;未执行真实上传或生产部署。
|
||||
|
||||
## 2026-10-26 请求头与 UA 对齐 JHA-1045
|
||||
|
||||
对照 `jha-1045-hgdjnet-web/app/static/js/common/request.js`、`pub/common/useragent/build.go`、`app/service/upstream/client.go` 和媒体代理实现,统一浏览器 Accept / Cookie 携带方式、SDK X-User-Agent 生成,以及 API、SSR、媒体和密钥的 UA / IP / SID 转发。原生浏览器 UA 按原值透传,客户端已有 X-User-Agent 优先;缺少时使用参考 Go 的解析规则,补充设备品牌/型号、系统名称/版本、浏览器 BuildID 与 SID。移除固定站点 UA 及虚构的 server-public 设备标识,公开 SSR 在缓存未命中时也使用本次访客的请求信息。应用 1050、接口地址、业务设备参数、会话恢复、公开数据凭证隔离及事件上下文校验沿用当前项目。
|
||||
|
||||
直接抽取并运行参考项目的 Go UA 生成函数,生成 12 组样例,覆盖 iPhone / iPad、Android 手机/平板及品牌、Windows / Edge、macOS / Safari、Linux / Firefox、Opera、IE、爬虫和空 UA;TypeScript 输出与 Go 输出逐字一致,样例保存在 `tests/fixtures/request-user-agents.json`。HTTP 集成测试验证两种 API 路径的请求头及 SDK 字段、完整 SSR 请求链路、媒体与密钥转发和凭证隔离。生产构建(含 TypeScript)、全部 **394 项**回归测试及 `git diff --check` 通过;网络验证仅连接本机模拟上游,未执行真实登录、支付或部署。
|
||||
|
||||
## 2026-10-26 对齐 SGDJ Swagger
|
||||
|
||||
只读获取用户指定的 `https://sgdj-h5.80sygtm.com/swagger/doc.json`,与旧文档核对 70 个应用接口及响应模型;生成 `tests/fixtures/sgdj-app-contract.json`,记录端点、方法、查询和请求字段及原始文档 SHA-256。首页读取 modules,短剧和题材模块分别映射现有页面组件,题材进入自己的 theme 路径;只有未提供 modules 字段时兼容旧首页快照,显式空模块不恢复旧数据。
|
||||
|
||||
首页更多使用 home-config/list 的 config、list / topicList、totalCount 和分页字段。短剧四类推荐及搜索换一批使用 operate/list 的 type 参数,移除文档中不存在的 hot/list。专题列表按分组显示,使用各组计数的最大值确定共同分页边界;专题详情直接传 topicSlug 或 ID,题材使用 theme/list 与 all/list 的 themeId / themeSlug、排序和更新状态参数。题材与专题各自保留主地址,正文链接、canonical、面包屑和 JSON-LD 同步,筛选变体继续 noindex。新文档没有要求改变的登录、支付、解锁和章节权限字段沿用原有处理,不恢复此前移除的独立视频路由。
|
||||
|
||||
生产构建(含 TypeScript)、全部 **399 项**回归测试、smoke 脚本语法及 `git diff --check` 通过。新增 HTTP 场景验证新首页模块、首页更多第二页的实际数据、专题分组和分页上限、直接 slug 请求没有目录扫描、题材更新状态筛选及 SEO。文档抓取之外,所有业务验证仅连接本机模拟 API;未执行正式账号注册、订单、评论、解锁或部署。应用 1050、服务端上游配置和已对齐的请求头规则继续使用当前项目设置。
|
||||
|
||||
## 2026-10-26 本地与测试上游切换 SGDJ 域名
|
||||
|
||||
开发、测试基础配置及服务端非生产默认地址统一改为 `https://sgdj-h5.80sygtm.com`,同步本地配置示例、项目说明及配置断言。生产上游继续为 `http://172.104.56.28:26125`。已核对本机没有旧地址的私有环境覆盖或进程覆盖;实际启动配置解析结果与目标地址一致,测试部署 dry-run 也显示新地址。
|
||||
|
||||
环境、API 配置及部署专项共 **42 项**测试、`git diff --check` 通过。没有执行实际上传或部署;已运行的本地进程需要重启,测试服需发布新配置并重启后生效。
|
||||
|
||||
## 2026-10-26 本地与测试上游域名更正为 JGDJ
|
||||
|
||||
按用户更正,开发、测试基础配置和非生产默认上游改为 `https://jgdj-h5.80sygtm.com`,同步配置示例、当前文档及测试断言;Swagger 来源链接继续保留实际核对过的 SGDJ 文档地址。确认本机有效配置与测试部署 dry-run 均采用新 JGDJ 地址,生产配置保持原值。环境及 API 配置 **16 项**测试和 `git diff --check` 通过;本次未上传或部署,运行中的服务需更新配置并重启后生效。
|
||||
|
||||
## 2026-10-26 HTML 页面样式核对
|
||||
|
||||
对照 `禁果短剧_2组.html` 的最终样式,用当前页面渲染函数和本地模拟内容检查首页、短剧列表、榜单、专题及详情、题材、标签、更多栏目、搜索、个人中心及四种个人列表、账号设置、金币充值、短剧详情、播放、应用、反馈和六种信息页面,并补查回家入口及独立导航页。桌面和 390px 手机预览分别加载原稿 CSS 与项目 CSS,核对关键组件的字号、字重、行高、内外边距、颜色、圆角和宽度;手机预览同时检查横向溢出。
|
||||
|
||||
- 榜单链接恢复原稿 9px 圆角、106px 最小宽度、16px 字号和 13px/24px 内边距;600px 以下按原稿等宽分配,保留真实榜单链接和当前页状态。
|
||||
- 列表卡片、专题卡片及搜索关键词样式同时兼容语义化的 `h2`、`h3`,恢复原稿标题大小、字重、截断和间距。
|
||||
- 个人中心、历史、收藏、点赞、已购和设置页按实际组件匹配原稿容器样式,去掉失效页面标识带来的额外面板和内边距;个人列表导航恢复原稿文字、间距和焦点样式。
|
||||
- 详情及播放页点赞、收藏样式兼容当前业务属性,恢复按钮固定宽度和选中背景;手机端保留原稿自适应宽度。
|
||||
- 通用 API 表单样式仅用于新增的简介和安全字段,避免覆盖原稿昵称输入框及反馈表单背景、边框和圆角。
|
||||
|
||||
浏览器核对的 26 类桌面页面关键样式指标与原稿一致;28 类手机页面(含回家入口和独立导航页)未出现横向溢出,涉及修复的关键组件指标一致。使用模拟内容核对布局,未执行真实付款、解锁、评论或账户修改。生产构建、TypeScript 检查和全部 403 项回归测试通过。
|
||||
|
||||
## 2026-10-26 首页分类按 style 渲染
|
||||
|
||||
首页模块保留接口 `style` 和 `label`,按 A–E 映射原稿分类布局:A 五列双排海报、B 六列海报、C 三列双排图文、D 六列编号海报、E 四列双排横版题材。A/B/C/D/E 首页预览上限分别为 10/6/6/6/8;分类名称和后台顺序保持原值,D 序号依次从 01 开始。缺失、预留或类型不兼容的 style 保留默认网格。首页 HTML 与 JSON-LD 使用同一预览限制,“查看更多”仍读取完整列表及真实分页。
|
||||
|
||||
使用本地模拟内容,将原稿最终 CSS 与项目 CSS 分别加载到 1440px、900px、390px、320px 的浏览器预览中,核对 A–E 的列数、可见数量、间距和封面比例;修正 B 在平板宽度的间距,并检查手机 C 图文卡和 D 封面编号。四种宽度均无横向溢出。
|
||||
|
||||
生产构建及全部 **409 项**回归测试通过,覆盖按 style 渲染的初始 SSR、预览上限、名称与顺序无关性、空数据、未知 style、内容转义、真实链接、结构化数据及更多分页。最后的平板间距修正后再次通过生产构建;`git diff --check` 通过。
|
||||
|
||||
## 2026-10-27 首页分类数量由 limit 控制
|
||||
|
||||
分类模块透传接口 `limit`,替代此前由 A–E 固定的预览上限。`style` 继续决定卡片布局及响应式列数,分类条目按 `limit` 取接口顺序中的前 N 项;数量不足时按实际有效条目数展示。`limit=0` 显示空态,缺失或无效时展示全部有效返回条目。新 `modules` 和旧 `homeConfigData`、短剧和题材、预留或默认 style 均使用相同规则,HTML 与 JSON-LD 同步;更多页继续使用真实总数和分页。
|
||||
|
||||
移除 B 样式在平板和手机端按固定序号隐藏条目的规则。在 1440px、900px、390px 浏览器模拟预览中,A/B/C/D/E 分别配置 limit=3/9/8/7/5,各尺寸均显示对应数量,无横向溢出。
|
||||
|
||||
生产构建(含 TypeScript)及全部 **411 项**回归测试通过,覆盖动态数量、新旧结构、0 值、缺失/异常 limit、列表不足、接口顺序、结构化数据、SSR 及更多页真实分页;`git diff --check` 通过。
|
||||
|
||||
## 2026-10-27 本地与测试上游切换 IP 地址
|
||||
|
||||
开发、测试基础配置及非生产默认上游改为 `http://172.104.181.99:26792`,同步配置示例、当前文档和配置断言。实际启动配置解析及测试部署 dry-run 均采用该地址,兼容变量 `API_BASE_URL` 同步解析;应用端口保持 `21030`,生产上游保持 `http://172.104.56.28:26125`。
|
||||
|
||||
环境及 API 配置 **16 项**测试和 `git diff --check` 通过。本次未上传或部署;运行中的本地服务需重启,测试服需发布新配置并重启后生效。
|
||||
|
||||
## 2026-10-27 业务接口统一 `/api/app/` 前缀
|
||||
|
||||
参考 `jha-1051-cgdj2-web`,浏览器业务请求及 API 冒烟脚本统一使用 `/api/app/*`,与已有 SSR 上游路径保持一致;旧 `/api/*` 处理器保留兼容。同步环境配置注释和部署说明,明确请求直达 Go 时会跳过 Next.js 的会话 Cookie、参数转换及 token 过滤。冒烟脚本同时按参考项目改用空登录参数校验,移除已失效的跨站 POST 拒绝断言。
|
||||
|
||||
生产构建(含 TypeScript)及全部 **411 项**回归测试通过,覆盖浏览器查询参数、JSON 和 FormData 请求、会话与旧路径兼容,并验证 `current` 模式下 Go 直达与 Next.js 会话处理的差别;`git diff --check` 通过。集成测试使用本机模拟上游,本次未部署或调用真实业务写接口。
|
||||
@@ -0,0 +1,7 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/types/root-params.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const appEnvironment=process.env.APP_ENV||(process.env.NODE_ENV==='development'?'development':'production');
|
||||
if(!['development','test','production'].includes(appEnvironment))throw new Error('Invalid APP_ENV');
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
outputFileTracingExcludes: {
|
||||
'/*': ['./dist/**/*', './tests/**/*', './.next/cache/**/*', './.next-test/cache/**/*', './.next-dev/cache/**/*'],
|
||||
},
|
||||
// Turbopack's route runtime is external; keep it in relocatable standalone releases.
|
||||
outputFileTracingIncludes: {
|
||||
'/*': ['./node_modules/next/dist/compiled/next-server/*runtime.prod.js'],
|
||||
},
|
||||
distDir: appEnvironment==='development'?'.next-dev':appEnvironment==='test'?'.next-test':'.next',
|
||||
trailingSlash: true,
|
||||
// Page redirects live in src/proxy.ts; API and media URLs keep their exact paths.
|
||||
skipTrailingSlashRedirect: true,
|
||||
// Serve the complete page metadata in <head> to browsers and crawlers alike.
|
||||
htmlLimitedBots: /.*/,
|
||||
images: { unoptimized: true },
|
||||
poweredByHeader: false,
|
||||
turbopack: { root: process.cwd() },
|
||||
async headers() {
|
||||
const xml=[{key:'Content-Type',value:'application/xml; charset=utf-8'}]
|
||||
const xsl=[{key:'Content-Type',value:'text/xsl; charset=utf-8'}]
|
||||
return [
|
||||
{source:'/sitemap.xml',headers:xml},
|
||||
{source:'/sitemaps/:file',headers:xml},
|
||||
{source:'/static/sitemapindex.xsl',headers:xsl},
|
||||
{source:'/static/sitemap.xsl',headers:xsl},
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "jha-1050-jgdj-web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "node scripts/next-env.mjs development dev",
|
||||
"dev:test": "node scripts/next-env.mjs test dev",
|
||||
"build": "node scripts/next-env.mjs production build",
|
||||
"build:dev": "node scripts/next-env.mjs development build",
|
||||
"build:test": "node scripts/next-env.mjs test build",
|
||||
"start": "node scripts/next-env.mjs production start",
|
||||
"start:dev": "node scripts/next-env.mjs development start",
|
||||
"start:test": "node scripts/next-env.mjs test start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lock:check": "node scripts/check-lock-sync.mjs",
|
||||
"test": "node --test tests/*.test.mjs",
|
||||
"check": "npm run lock:check && npm run build && npm test",
|
||||
"bundle": "npm run lock:check && node scripts/package-standalone.mjs production",
|
||||
"bundle:test": "npm run lock:check && node scripts/package-standalone.mjs test",
|
||||
"bundle:linux": "npm run lock:check && node scripts/prepare-production-runtime.mjs",
|
||||
"deploy:test": "bash deploy_test.sh",
|
||||
"deploy:prod": "bash deploy_prod.sh",
|
||||
"smoke:api": "node scripts/smoke-api.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "17.2.3",
|
||||
"hls.js": "1.7.3",
|
||||
"next": "16.4.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"typescript": "^5.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
---
|
||||
项目名称: 禁果短剧web
|
||||
一级部门: 北斗
|
||||
二级部门: 瑞升
|
||||
|
||||
DMP编码: JHA-1050
|
||||
|
||||
项目类型:
|
||||
PC
|
||||
# 可选值:
|
||||
# H5 / APP / PC / 服务 / 组件 / 工具 / AI / 中间件
|
||||
|
||||
开发语言:
|
||||
Node
|
||||
# 可选值:
|
||||
# GO / Java / PHP / Node
|
||||
|
||||
项目负责人: DN1425 # 员工编号, 多个用逗号隔开,这里的负责是这套代码的负责人
|
||||
|
||||
运维负责人: DN9460, DN9651, DN4792 # 负责部署、监控、K8S、发布、证书等
|
||||
|
||||
产品负责人: YY1166 # 项目产品经理
|
||||
|
||||
项目成员: DN7208, DN2185, DN8431
|
||||
|
||||
项目状态:
|
||||
维护中
|
||||
# 可选值:
|
||||
# 开发中 / 维护中 / 已停止 / 已归档
|
||||
|
||||
项目级别:
|
||||
中
|
||||
# 可选值:
|
||||
# 高 / 中 / 低 , 北斗体系 s和a为 高 b为中 c d为低
|
||||
|
||||
核心分支:
|
||||
main
|
||||
# 即部署时候使用的分支,可选值:
|
||||
# master / main / dev / release
|
||||
|
||||
代码仓库:
|
||||
- ssh://git@bd-git-sh.jsyyds.com:8082/ruisheng/jha-1021-91jhs/frontend/jha-1050-jgdj-web.git
|
||||
# 按新项目标识登记的目标地址;远端仓库创建及本地 origin 切换需另行确认。
|
||||
|
||||
最后审计人:
|
||||
|
||||
最后审计时间:
|
||||
|
||||
告警群:
|
||||
|
||||
备注: Next.js 网站与 API 代理服务;业务数据由独立 Go 后端提供。
|
||||
---
|
||||
|
||||
# 1. 项目说明
|
||||
|
||||
禁果短剧 Web 项目,DMP 编码 **JHA-1050**,应用及埋点 App ID 为 **1050**。采用 Next.js App Router、React、TypeScript,实现 PC 和移动端响应式页面,布局依据提供的 HTML 原稿还原。
|
||||
|
||||
本文按当前仓库代码整理,更新日期为 **2026-10-26**;该日期不代表安全审计或线上验收时间。人员信息沿用原登记信息,部署目标以当前脚本默认值为准。
|
||||
|
||||
## 1.1 功能范围
|
||||
|
||||
- 首页动态栏目、轮播、顶部及侧栏导航、广告与宫格广告弹窗。
|
||||
- 短剧列表、排行榜、专题、题材/标签、栏目更多页、搜索及独立分页。
|
||||
- 短剧详情、章节播放、HLS 播放、图片解密、点赞、收藏、评论与分享。
|
||||
- 游客/账号登录、个人中心、观看历史、我的收藏、我的点赞、已购剧集、账号设置。
|
||||
- 金币充值、VIP 套餐、章节解锁、反馈、App 下载、福利应用、回家的路和独立导航页。
|
||||
- 服务端 SEO 元数据、JSON-LD、robots.txt 与广告/页面/播放等埋点。
|
||||
|
||||
本仓库负责网站页面、浏览器交互和服务端 API 代理,不包含 Go 业务服务、管理后台或数据库部署代码。独立“剧集”入口及 `/series/`、`/video/*` 页面已移除。
|
||||
|
||||
## 1.2 主要目录与路由
|
||||
|
||||
| 位置 | 职责 |
|
||||
| ----------------------------------------------- | ------------------------------------------------ |
|
||||
| `src/app/` | App Router 页面、API/媒体代理、robots 和健康检查 |
|
||||
| `src/components/site/` | 首页、详情、播放、导航等页面组件 |
|
||||
| `src/lib/api/` | 上游配置、接口白名单、会话、公共缓存和媒体请求 |
|
||||
| `src/lib/client/` | 登录、账户、播放、解锁、广告、图片及埋点交互 |
|
||||
| `src/lib/routing.ts` | 路由解析、slug/ID、分页地址与导航匹配 |
|
||||
| `src/lib/seo*.ts`、`src/lib/structured-data.ts` | TDK、canonical、索引策略、SEO 快照和 JSON-LD |
|
||||
| `src/proxy.ts` | 页面尾斜杠规范化和首屏会话准备 |
|
||||
| `config/`、`scripts/`、`deploy/` | 环境配置、启动/打包脚本及辅助更新脚本 |
|
||||
| `public/`、`tests/`、`docs/` | 静态素材、回归测试、接口与收录规则说明 |
|
||||
|
||||
| 页面 | 主要路径 |
|
||||
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| 首页 / 短剧 | `/`、`/shorts/` |
|
||||
| 三个排行榜 | `/rank/hot/`、`/rank/new/`、`/rank/follow/` |
|
||||
| 专题 / 标签 / 更多栏目 | `/topics/`、`/topics/{key}/`、`/tag/{key}/`、`/config/{key}/` |
|
||||
| 详情 / 播放 | `/detail/{key}/`、`/play/{key}/{chapterId}/` |
|
||||
| 搜索 | `/search/{keyword}/` |
|
||||
| 个人中心及书架 | `/member/center/`、`/member/history/`、`/member/favorites/`、`/member/likes/`、`/member/purchased/` |
|
||||
| 充值 / VIP | `/info/deposit/`、`/info/deposit/vip/` |
|
||||
| 官方入口 / 回家的路 / 福利应用 | `/navigation/`、`/home-road/`、`/apps/` |
|
||||
|
||||
内容地址优先使用有效英文/拼音 slug,无效或缺失时使用 ID;播放页章节段使用真实章节 ID。搜索词作为单个路径段编码。支持分页的页面使用 `/page/{N}/`,第一页使用基础路径;详情和播放页的分页对应评论。有效页面无尾斜杠访问返回 308,API、媒体和静态资源保留原路径。确认不存在的资源或越界分页返回 404,上游业务服务异常与资源不存在分别处理。
|
||||
|
||||
# 2. 基础设施与中间件
|
||||
|
||||
## 2.1 运行依赖与请求链路
|
||||
|
||||
- Node.js 最低 **20.9.0**,推荐 **22.17.1**;生产跨平台打包要求 **npm >=10.9.2**。依赖版本以 [package-lock.json](package-lock.json) 为准,使用 `npm ci` 安装。
|
||||
- Next.js 16、React 19、TypeScript 5.9;主要运行库包括 `hls.js`、`qrcode`、`dotenv`。
|
||||
- 网站需要运行 Node.js 服务,使用 Next.js `standalone` 产物,不是静态 `out/` 部署。
|
||||
- 页面在服务端取数并输出当前页面正文与 head;浏览器复用首屏数据绑定交互,页面链接和分页使用原生路径访问。
|
||||
- 浏览器与服务端业务接口统一使用 `/api/app/*`。请求进入 Next.js 后按白名单转发到独立 Go 上游的同名路径,旧 `/api/*` 入口保留兼容。`UPSTREAM_API_URL` 仅服务端使用,`API_BASE_URL` 是兼容别名。
|
||||
- `/h5/m3u8/*`、`/enkey`、`/newenkey` 由媒体代理处理,保留签名查询、客户端相关请求信息;媒体和密钥不缓存,不转发应用登录 Cookie 或 Authorization。
|
||||
|
||||
登录会话通过 HttpOnly Cookie 传递;SSR 启动数据按字段白名单输出,不向浏览器暴露后端登录凭证。公共接口和账户、余额、订单、付费权限数据采用不同缓存策略。
|
||||
|
||||
本仓库不直接连接 MongoDB、Redis 或 Elasticsearch。Go 后端是否使用这些中间件及其拓扑,由后端/运维文档确认;本仓库没有 Telegram Bot 部署通知实现。
|
||||
|
||||
## 2.2 环境配置
|
||||
|
||||
| 环境 | 配置文件 | Go API 上游 | 网站端口 | 构建目录 |
|
||||
| ---- | ------------------------ | ----------------------------- | -------- | ------------- |
|
||||
| 开发 | `config/development.env` | `http://172.104.181.99:26792` | `21030` | `.next-dev/` |
|
||||
| 测试 | `config/test.env` | `http://172.104.181.99:26792` | `21030` | `.next-test/` |
|
||||
| 生产 | `config/production.env` | `http://139.162.49.252:21040` | `21042` | `.next/` |
|
||||
|
||||
配置由 [scripts/next-env.mjs](scripts/next-env.mjs) 加载,优先级为:进程环境变量 > `config/{环境}.local.env` > `config/{环境}.env`。开发环境兼容旧 `.env.local`,优先级低于 `config/development.local.env`;测试和生产不从旧 `.env.local` 加载本站业务配置。
|
||||
|
||||
`APP_ENV` 区分业务环境;测试构建/启动仍使用 `NODE_ENV=production`,`dev` 命令使用 `NODE_ENV=development`。服务端配置修改后需重启,`NEXT_PUBLIC_*` 修改后需重新构建。私有覆盖文件已忽略提交和打包。
|
||||
|
||||
## 2.3 缓存、SEO 与外部配置
|
||||
|
||||
- 公共配置、页脚、导航、分类和标签选项默认缓存 60 秒,首页缓存 30 秒,进程内最多 128 条并合并并发请求;开发配置关闭缓存。`PUBLIC_DATA_CACHE_TTL_MS=0` 可关闭,正数仅缩短既有缓存上限。
|
||||
- 公共缓存按上游地址隔离,各实例独立;配置和首页刷新失败可沿用已成功的数据。账户及付费权限不进入公共缓存。
|
||||
- canonical 和站点身份取后台配置的永久域名,优先 `config.foreverUrlAddr`,其次 `newAddressInfo.foreverUrlAddr`。有效 SEO 配置保存在对应构建目录的 `cache/seo/`,异常时复用最近一次有效快照;不以 API IP 或访问 Host 作为正式域名。
|
||||
- 生产 `/robots.txt` 允许抓取公开资源,限制 `/api/` 和 `/healthz`;开发、测试禁止全站抓取。账户、充值、搜索、工具等页面另有页面级 `noindex` 规则。
|
||||
- 生产 robots 在取得有效永久域名后声明 `https://{永久域名}/sitemap.xml` 形式的绝对 URL。**本仓库没有 sitemap XML 生成或读取路由**;现有 XSL 和响应头配置不等于已提供 XML,需由后端/反向代理另行接入。规则见 [后端 Sitemap 对接约定](docs/sitemap-backend-contract.md)。
|
||||
- 导航、官方入口、商务 TG、用户反馈 TG 群、广告和下载地址由接口动态配置;未配置有效 App 下载链接时隐藏入口。
|
||||
- 埋点使用 App ID `1050`,优先读取接口 `reportSdkConfig`,构建配置 `NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG` 为兜底;配置及上报链路需与后端保持一致。
|
||||
|
||||
# 3. 运维部署
|
||||
|
||||
## 3.1 开发、验证和启动
|
||||
|
||||
```sh
|
||||
nvm use 22.17.1
|
||||
npm ci
|
||||
npm run dev # 开发配置,默认 http://localhost:21030
|
||||
npm run typecheck # 类型检查
|
||||
npm test # 本地回归测试
|
||||
npm run check # 生产构建 + 回归测试
|
||||
```
|
||||
|
||||
| 环境 | 构建 | 启动 |
|
||||
| ---- | -------------------- | ----------------------------------------------------- |
|
||||
| 开发 | `npm run build:dev` | `npm run dev`;已有构建用 `npm run start:dev` |
|
||||
| 测试 | `npm run build:test` | `npm run start:test`;开发调试可用 `npm run dev:test` |
|
||||
| 生产 | `npm run build` | `npm start` |
|
||||
|
||||
启动前确认 `21030` 未被本项目的旧进程或其他服务占用。不同环境须使用对应构建产物,不能互换构建目录。
|
||||
|
||||
## 3.2 打包与发布目标
|
||||
|
||||
```sh
|
||||
bash deploy_test.sh --dry-run # 仅预览,不构建、不联网
|
||||
bash deploy_prod.sh --dry-run
|
||||
bash deploy_test.sh --package-only # 仅本地构建、测试、打包
|
||||
bash deploy_prod.sh --package-only
|
||||
```
|
||||
|
||||
| 环境 | 默认 SSH 目标 | 上传及执行目录 | SCP / SSH 端口 | 上传后执行 |
|
||||
| ---- | ---------------------- | -------------------------- | --------------- | ------------------ |
|
||||
| 测试 | `root@172.104.181.99` | `/server/web_jgdj/h5` | `22889 / 22889` | `bash run.sh` |
|
||||
| 生产 | `server@34.92.204.175` | `/home/server/web_jgdj/h5` | `22889 / 22889` | `bash rsupdate.sh` |
|
||||
|
||||
直接执行 `bash deploy_test.sh` 或 `bash deploy_prod.sh` 会构建、测试、打包、上传并执行远端脚本。生产上传主机与生产 API 主机不同,不要混用。
|
||||
|
||||
- **测试脚本**:[deploy_test.sh](deploy_test.sh)。执行 `build:test`、测试、`bundle:test`,生成 `web_jgdj_h5.tar.gz` 和 `web_jgdj_h5.tar.gz.sha256`,上传这两份文件及 `deploy/up_next_h5.sh`;同时刷新本地兼容 MD5 文件。测试包不含 `node_modules`,Linux 解包后需 `npm ci --omit=dev --no-audit --no-fund`,再运行 `node start.mjs`。
|
||||
- **生产脚本**:[deploy_prod.sh](deploy_prod.sh)。执行 `check`、`bundle`、`bundle:linux`,生成 `web_jgdj_h5.tar.gz` 和纯哈希格式 `web_jgdj_h5.md5`,两份文件一并上传。包内含 standalone 裁剪依赖,默认 Linux x64/glibc,可运行 `node start.mjs`;打包时安装并校验目标平台 Sharp/libvips。
|
||||
- 两种包只带对应环境的基础配置,不包含私有 `*.local.env`、`.env*` 和 SEO 缓存。服务器覆盖配置放在实际运行目录的 `config/` 下。
|
||||
- 测试与生产使用**同名压缩包**,后一次打包会覆盖根目录旧产物;发布前核对环境和校验文件。`dist/`、压缩包和校验文件均已忽略提交,两份脚本共用 `dist/.deploy.lock` 防止并发打包。
|
||||
|
||||
测试目标可通过 `DEPLOY_HOST`、`DEPLOY_DIR`、`DEPLOY_RUN_DIR`、`DEPLOY_SSH_PORT` 覆盖;上传目录和执行目录分别配置。生产使用 `DEPLOY_JUMP_HOST`、`DEPLOY_JUMP_DIR`、`DEPLOY_SCP_PORT`、`DEPLOY_SSH_PORT`,执行目录跟随上传目录。两者支持 `DEPLOY_PACKAGE_NAME`;生产另支持 `DEPLOY_TARGET_ARCH=x64|arm64`、`DEPLOY_TARGET_LIBC=glibc|musl`。
|
||||
|
||||
上传失败不会执行远端更新;远端命令失败会使发布退出。生产脚本还检查远端输出中的失败状态,并将执行日志写入 `dist/production-rsupdate.*`。
|
||||
|
||||
## 3.3 服务管理、检查和回退
|
||||
|
||||
远端现有 `run.sh`、`rsupdate.sh` 不在本仓库,发布脚本不会覆盖它们。服务启动方式、私有配置保留、日志位置和线上回退步骤,需由运维结合服务器脚本确认。
|
||||
|
||||
仓库提供的 [deploy/up_next_h5.sh](deploy/up_next_h5.sh) 是测试环境辅助更新脚本,默认 Supervisor 程序为 `jgdj_next_h5`,可通过 `DEPLOY_PROGRAM` 覆盖。它支持 SHA-256 校验、解包、安装依赖、保留本地配置和 SEO 缓存、切换版本、检查和失败回退;是否由服务器 `run.sh` 调用,应核对远端实现。
|
||||
|
||||
`/healthz` 返回进程状态、应用名和环境,不请求 Go 后端。发布后可执行:
|
||||
|
||||
```sh
|
||||
node scripts/check-health.mjs http://127.0.0.1:21030 production
|
||||
# 测试环境将最后一个参数改为 test
|
||||
PREVIEW_URL=http://127.0.0.1:21030 npm run smoke:api
|
||||
```
|
||||
|
||||
健康检查同时检查 `/healthz` 和首页;测试发布可设置 `DEPLOY_CHECK_URL` 自动执行。API 冒烟检查可能创建临时游客会话,使用 `SMOKE_PUBLIC_ONLY=1` 可仅检查公共部分。支付、解锁、媒体播放及埋点仍需对应业务验收,不能仅以进程存活判定全部功能正常。
|
||||
|
||||
# 4. 风险说明
|
||||
|
||||
| 风险点 | 影响及处理要求 |
|
||||
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 上游及反向代理配置错误 | SSR、登录或媒体可能失败;区分 H5(开发/测试 `21030`、生产 `21042`)、API(生产 `21040`)和 SSH `22889`。默认将本站 `/api/app/*` 交给 Next.js,并配置独立 Go 上游。若 Nginx 直接转发 Go(包括可选 `current` 模式),需单独验证 Go 鉴权及会话,避免跳过 Next.js Cookie 处理或造成上游回环。 |
|
||||
| 播放签名、IP、UA 或密钥异常 | 可能出现播放 502;保留签名查询和客户端信息,检查真实上游响应,AES 密钥必须为 16 字节。H.265 解码受设备及浏览器限制,前端不提供转码。 |
|
||||
| 个人数据误入共享缓存 | 可能造成身份或权限混用;新增接口需延续公开数据与账户、订单、付费权限隔离,首屏输出遵守字段白名单。 |
|
||||
| 发布包平台或环境不匹配 | 生产需匹配 Linux 架构/libc,测试包需安装运行依赖;同名产物发布前核对配置和校验值。 |
|
||||
| SEO 配置或 sitemap 未接入 | 测试环境禁止抓取;生产需核对 `APP_ENV`、永久域名、robots、页面索引标记及实际 sitemap 响应,不能只确认 Sitemap 声明。 |
|
||||
| 服务器更新脚本未交接 | 本地脚本成功不等于业务验收通过;运维应提供远端脚本、服务管理、日志、监控告警及回退说明。 |
|
||||
|
||||
# 5. 历史事故(可选)
|
||||
|
||||
仓库内未提供经确认的生产事故台账。负责人可按“时间、环境、影响范围、原因、处置及预防措施”补充;开发调试错误不直接登记为生产事故。
|
||||
|
||||
# 6. 交接说明(可选)
|
||||
|
||||
- **应用标识**:DMP 为 `JHA-1050`;三套环境的 `NEXT_PUBLIC_TRACKER_APP_ID` 均为 `1050`,对接后台与 SDK 时保持一致。
|
||||
- **标识迁移**:包名与健康检查应用名为 `jha-1050-jgdj-web`,发布包为 `web_jgdj_h5.tar.gz`,Supervisor 默认为 `jgdj_next_h5`。服务器地址和端口沿用现有配置;发布前需准备新的 `web_jgdj/h5` 目录、更新脚本及对应 Supervisor 配置,并确认 Go 上游和 `reportSdkConfig` 属于应用 `1050`。
|
||||
- **新增页面**:复用集中路由、slug/ID、分页和 SEO 规则,补充有效/缺失/异常状态处理。导航与下载入口优先消费动态配置,不写死演示链接。
|
||||
- **个人列表**:我的点赞入口为 `/member/likes/`,上游接口为 `GET /api/app/playlet/like/list`,与收藏、历史、已购共同保持私有响应及禁止索引。
|
||||
- **配置交接**:确认实际运行目录的私有覆盖、上游连通性、正式永久域名、TG 联系方式、广告、下载及埋点配置。新版本切换需保留服务器私有配置和对应 SEO 快照。
|
||||
- **发布交接**:确认 SSH 权限、远端 `run.sh`/`rsupdate.sh`、服务管理名称、日志与回退方法;本文记录的是仓库默认值,不替代服务器实际配置验收。
|
||||
- **待补登记**:最后审计人、最后审计时间、告警群,以及运维确认后的监控和事故台账;不将本次文档整理视为已完成审计。
|
||||
- **参考资料**:[运行与部署说明](README.md)、[页面接口对照](docs/api-contract.md)、[索引策略](docs/indexation-policy.md)、[Sitemap 对接约定](docs/sitemap-backend-contract.md)。历史核对记录以其日期为界,当前配置和行为以代码为准。
|
||||
@@ -0,0 +1 @@
|
||||
6d41643810d540ce924edb982c3d3171
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="37.0911" height="37.0911" viewBox="0 0 37.0911 37.0911" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Star 7" d="M16.3279 0.979548C17.5179 -0.326515 19.5732 -0.326516 20.7632 0.979547L21.3914 1.66906C22.1808 2.53547 23.4024 2.86281 24.5193 2.50717L25.4081 2.22414C27.0916 1.68803 28.8716 2.71569 29.2491 4.44175L29.4483 5.35299C29.6988 6.49801 30.5931 7.39234 31.7381 7.64276L32.6494 7.84205C34.3754 8.21954 35.4031 9.99949 34.867 11.6831L34.5839 12.5719C34.2283 13.6887 34.5556 14.9104 35.422 15.6997L36.1116 16.3279C37.4176 17.5179 37.4176 19.5732 36.1116 20.7632L35.422 21.3914C34.5556 22.1807 34.2283 23.4024 34.5839 24.5193L34.867 25.408C35.4031 27.0916 34.3754 28.8716 32.6494 29.2491L31.7381 29.4483C30.5931 29.6988 29.6988 30.5931 29.4483 31.7381L29.2491 32.6494C28.8716 34.3754 27.0916 35.4031 25.4081 34.867L24.5193 34.5839C23.4024 34.2283 22.1808 34.5556 21.3914 35.422L20.7632 36.1116C19.5732 37.4176 17.5179 37.4176 16.3279 36.1116L15.6997 35.422C14.9104 34.5556 13.6887 34.2283 12.5719 34.5839L11.6831 34.867C9.9995 35.4031 8.21954 34.3754 7.84205 32.6494L7.64276 31.7381C7.39234 30.5931 6.49801 29.6988 5.35299 29.4483L4.44175 29.2491C2.71569 28.8716 1.68803 27.0916 2.22414 25.4081L2.50717 24.5193C2.86281 23.4024 2.53547 22.1808 1.66906 21.3914L0.979548 20.7632C-0.326515 19.5732 -0.326517 17.5179 0.979547 16.3279L1.66906 15.6997C2.53547 14.9104 2.86281 13.6887 2.50717 12.5719L2.22414 11.6831C1.68803 9.9995 2.71569 8.21954 4.44175 7.84205L5.35299 7.64276C6.49801 7.39234 7.39234 6.49801 7.64276 5.35299L7.84205 4.44175C8.21954 2.71569 9.99949 1.68803 11.6831 2.22414L12.5719 2.50717C13.6887 2.86281 14.9104 2.53547 15.6997 1.66906L16.3279 0.979548Z" fill="#FF0095"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame">
|
||||
<path id="Vector" d="M42.0004 13.0039V33.3979C42.0048 33.7924 41.9292 34.1836 41.7783 34.548C41.6273 34.9125 41.404 35.2425 41.122 35.5183C40.8476 35.8023 40.5178 36.0267 40.153 36.1779C39.7883 36.329 39.3963 36.4035 39.0016 36.3967H28.7956C28.8034 36.8916 28.9061 37.3804 29.098 37.8367C29.2742 38.2906 29.4737 38.7352 29.6956 39.1687C29.8516 39.4161 29.9545 39.6931 29.998 39.9823C29.9951 40.3003 29.8676 40.6045 29.6427 40.8294C29.4178 41.0543 29.1136 41.1819 28.7956 41.1847H19.2016C18.8835 41.1819 18.5793 41.0543 18.3544 40.8294C18.1296 40.6045 18.002 40.3003 17.9992 39.9823C18.0395 39.6884 18.1439 39.407 18.3052 39.1579C18.528 38.7311 18.7275 38.2924 18.9028 37.8439C19.0975 37.381 19.2002 36.8845 19.2052 36.3823H9.00276C8.60816 36.3878 8.2166 36.3127 7.85201 36.1617C7.48743 36.0106 7.15748 35.7868 6.88236 35.5039C6.59954 35.2315 6.37542 34.9042 6.22374 34.542C6.07206 34.1799 5.99605 33.7905 6.00036 33.3979V13.0039C5.99488 12.6093 6.06996 12.2178 6.22099 11.8532C6.37203 11.4886 6.59584 11.1586 6.87876 10.8835C7.15207 10.5987 7.48101 10.3731 7.84515 10.2208C8.20929 10.0684 8.60086 9.99258 8.99556 9.99791H38.9944C39.7913 9.99886 40.5553 10.3159 41.1189 10.8794C41.6824 11.4429 41.9994 12.207 42.0004 13.0039ZM39.5956 28.6063V13.0039C39.5956 12.9255 39.58 12.8479 39.5497 12.7756C39.5194 12.7032 39.4751 12.6377 39.4192 12.5827C39.3641 12.5259 39.2981 12.4809 39.225 12.4506C39.1519 12.4202 39.0735 12.4052 38.9944 12.4063H9.00276C8.92435 12.4063 8.84673 12.4219 8.77441 12.4521C8.70209 12.4824 8.63653 12.5268 8.58156 12.5827C8.52516 12.6373 8.48045 12.7028 8.45013 12.7752C8.4198 12.8476 8.4045 12.9254 8.40516 13.0039V28.6027C8.40464 28.6817 8.41998 28.76 8.45028 28.833C8.48059 28.906 8.52522 28.9721 8.58156 29.0275C8.63616 29.0839 8.70165 29.1286 8.77405 29.1589C8.84645 29.1893 8.92426 29.2046 9.00276 29.2039H38.998C39.077 29.2044 39.1553 29.1891 39.2282 29.1588C39.3012 29.1285 39.3674 29.0838 39.4228 29.0275C39.479 28.9736 39.5237 28.9087 39.5541 28.8369C39.5844 28.7651 39.5997 28.6879 39.5992 28.6099L39.5956 28.6063Z" fill="#EEEEEE"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 241 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" aria-hidden="true"><path d="M512 204.8C269.06624 204.8 40.96 358.68672 40.96 512s228.10624 307.2 471.04 307.2 471.04-153.88672 471.04-307.2-228.10624-307.2-471.04-307.2z" fill="#A3A3AD"/><path d="M512 337.92a174.08 174.08 0 1 0 0 348.16 174.08 174.08 0 0 0 0-348.16z m0 61.44a112.64 112.64 0 1 1 0 225.28 112.64 112.64 0 0 1 0-225.28z" fill="#232326"/></svg>
|
||||
|
After Width: | Height: | Size: 422 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="禁果短剧"><title>禁果短剧</title><path d="M65 31C67 18 77 9 90 10C89 23 79 31 65 31Z" fill="#FCD02B"/><path d="M60 34C45 24 22 34 18 55C14 77 25 105 41 114C49 119 57 114 64 114C72 114 80 119 88 114C102 106 111 86 111 67C111 44 94 29 77 31C71 32 67 35 64 35Z" fill="#FCD02B"/><path d="M54 53C52 52 49 54 49 57V88C49 91 52 93 54 91L79 75C82 73 82 71 79 69Z" fill="#131414"/></svg>
|
||||
|
After Width: | Height: | Size: 497 B |
@@ -0,0 +1 @@
|
||||
<svg class="svg_quick_icon svg_icon_app" width="22" height="22" viewBox="-2 -2 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path stroke="currentColor" d="M1.5 4.5C1.5 2.84315 2.84315 1.5 4.5 1.5V1.5C6.15685 1.5 7.5 2.84315 7.5 4.5V7.47015C7.5 7.48664 7.48664 7.5 7.47015 7.5H4.5C2.84315 7.5 1.5 6.15685 1.5 4.5V4.5Z" stroke-width="1.2"/><path stroke="currentColor" d="M10.5 4.5C10.5 2.84315 11.8431 1.5 13.5 1.5V1.5C15.1569 1.5 16.5 2.84315 16.5 4.5V4.5C16.5 6.15685 15.1569 7.5 13.5 7.5H10.5299C10.5134 7.5 10.5 7.48664 10.5 7.47015V4.5Z" stroke-width="1.2"/><path stroke="currentColor" d="M1.5 13.5C1.5 11.8431 2.84315 10.5 4.5 10.5H7.47015C7.48664 10.5 7.5 10.5134 7.5 10.5299V13.5C7.5 15.1569 6.15685 16.5 4.5 16.5V16.5C2.84315 16.5 1.5 15.1569 1.5 13.5V13.5Z" stroke-width="1.2"/><path stroke="currentColor" d="M10.5 10.5299C10.5 10.5134 10.5134 10.5 10.5299 10.5H13.5C15.1569 10.5 16.5 11.8431 16.5 13.5V13.5C16.5 15.1569 15.1569 16.5 13.5 16.5V16.5C11.8431 16.5 10.5 15.1569 10.5 13.5V10.5299Z" stroke-width="1.2"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="关闭">
|
||||
<circle id="图标" cx="18" cy="18" r="14" stroke="white" stroke-width="2"/>
|
||||
<g id="x">
|
||||
<path id="Vector 434" d="M23.0903 12.9082L12.908 23.0905" stroke="white" stroke-width="2.25" stroke-linecap="round"/>
|
||||
<path id="Vector 435" d="M23.0922 23.0893L12.9098 12.907" stroke="white" stroke-width="2.25" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 583 B |
@@ -0,0 +1,10 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="18+ · 年龄标识" clip-path="url(#clip0_0_4)">
|
||||
<path id="Vector" d="M46.6861 36.6866C46.6861 31.0846 48.5533 29.4371 53.1661 29.4371C57.7789 29.4371 59.6461 31.0846 59.6461 36.6866C59.6461 42.0687 57.7789 43.9359 53.1661 43.9359C48.5533 44.0458 46.6861 42.1786 46.6861 36.6866ZM53.1661 51.7346C47.7844 51.7346 45.9174 54.1511 45.9174 59.4234C45.9174 64.6955 47.7844 66.7826 53.1661 66.7826C58.5479 66.7826 60.4149 64.6955 60.4149 59.4234C60.4149 54.1511 58.4381 51.7346 53.1661 51.7346ZM88.5316 43.7163V39.762C88.5316 36.7964 88.0923 35.3685 85.5661 35.3685H83.04C80.5139 35.3685 80.0746 36.9062 80.0746 39.762V43.7163H76.1207C73.1553 43.7163 71.7276 44.1556 71.7276 46.6819V49.3181C71.7276 51.8444 73.2651 52.2837 76.1207 52.2837H80.1844V56.238C80.1844 59.2036 80.6237 60.6315 83.1498 60.6315H85.676C88.2021 60.6315 88.6414 59.0938 88.6414 56.238V52.2837H94.2426C95.3411 52.2837 95.8902 52.833 96 53.9313V66.6728C96 69.7482 95.1214 72.0549 92.9247 74.142L74.2535 92.8147C72.057 95.0114 69.6407 96 66.6753 96H29.333C26.3676 96 23.9512 95.0114 21.7546 92.8147L3.08348 74.142C0.886867 71.9451 -0.101607 69.7482 0.0082236 66.6728V29.7665C0.0082236 26.6911 0.996697 24.0549 3.30313 21.7482L22.194 2.52632C24.1709 0.549199 26.5872 0 29.333 0H66.5654C69.3112 0 71.7275 0.549199 73.7044 2.52632L92.7051 21.7482C94.9018 24.0549 96 26.6911 96 29.7665V41.7391C96 42.9473 95.4509 43.6065 94.2426 43.6065H88.5316V43.7163ZM31.0902 25.5926C31.0902 23.1761 29.5526 22.627 26.697 22.627H19.1187C16.1533 22.627 14.7255 23.1761 14.7255 25.5926V28.3386C14.7255 30.7551 16.2631 31.3043 19.1187 31.3043H20.876C21.9744 31.3043 22.4137 31.8536 22.4137 33.6109V69.6384C22.4137 72.604 22.9628 74.0321 25.3791 74.0321H28.2346C30.6509 74.0321 31.2 72.4942 31.2 69.6384V25.5926H31.0902ZM68.9818 60.6315C68.9818 54.3707 68.5425 49.6476 62.9411 47.4509C67.6637 44.0458 67.6637 40.5308 67.6637 37.675V34.8192C67.1146 25.373 64.0393 21.7482 53.0563 21.7482C42.0733 21.7482 38.9981 25.373 38.4488 34.8192V37.675C38.4488 40.5308 38.5588 44.0458 43.1716 47.4509C37.5702 49.6476 37.1309 54.3707 37.1309 60.6315C37.1309 69.858 40.4258 74.4714 52.9465 74.6911C65.6868 74.4714 68.9818 69.858 68.9818 60.6315Z" fill="#FFC000"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_0_4">
|
||||
<rect width="96" height="96" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4" d="m12 3.6 2.72 5.53 6.08.891-4.4 4.302 1.038 6.077L12 17.53 6.562 20.4 7.6 14.323l-4.4-4.302 6.08-.892z"/></svg>
|
||||
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 147 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic_gf">
|
||||
<path id="Vector" d="M11.6304 16.2755L11.6268 16.2883L11.6322 16.2928L11.6304 16.2755ZM27.1194 10.7593C26.095 10.1861 24.6487 7.80189 24.6651 7.08054C24.7351 4.03674 22.6893 3.59109 21.6658 3.67464C19.3962 3.85929 17.9716 3.17889 16.8937 2.03994C15.8158 0.900085 14.8489 0.949285 14.8489 0.949285C14.0793 0.949285 13.4626 1.23219 12.6675 2.17554C11.4213 3.65379 9.44815 3.70659 8.8495 3.67374C8.2519 3.64194 6.8455 3.51729 5.98675 4.21959C5.128 4.92189 5.10625 6.13899 4.8961 7.48989C4.68595 8.83989 3.2833 10.1725 2.5783 10.7593C1.8733 11.3459 1.46035 11.8045 1.35115 12.394C1.24195 12.9842 1.04905 13.1917 2.16985 15.1192C3.4234 17.2751 2.7748 18.8051 2.5792 20.2961C2.3836 21.788 3.27415 22.7741 5.17 23.2934C7.06585 23.8129 8.29015 25.7669 8.4421 26.1544C8.59405 26.542 9.5719 28.0348 10.4871 28.1984C11.6751 28.4104 12.4429 27.5816 13.2142 27.3806C13.9848 27.1796 15.6777 27.0031 16.6228 27.5171C19.5348 29.0999 20.7637 27.4798 21.1221 26.4274C21.5077 25.294 22.9605 23.6837 24.5307 23.2936C28.0239 22.4267 26.9331 19.3666 26.8485 19.07C26.7639 18.7735 26.5092 16.2455 27.3943 15.2549C29.2491 13.1762 28.1437 11.3323 27.1194 10.7593ZM17.758 16.2791L17.7534 16.2628V16.2829L17.7589 16.2793C18.5085 18.7745 18.6741 20.9132 18.6741 20.9132L14.7006 18.4981C14.6985 18.4991 14.6964 18.5 14.6943 18.5008L14.6806 18.4963C12.7402 19.838 10.7145 20.9132 10.7145 20.9132C10.7145 20.9132 10.8792 18.7801 11.6269 16.2884C9.7948 14.914 8.3266 12.9536 8.3266 12.9536L12.7039 12.5561L14.6943 8.17794L16.6846 12.5552L21.0619 12.9536C21.0619 12.9533 19.5919 14.9027 17.758 16.2791Z" fill="#EEEEEE"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 129 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M4 10h4v11H4a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1Zm6-6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v17h-4Zm7 10h3a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-4v-6a1 1 0 0 1 1-1Z"/></svg>
|
||||
|
After Width: | Height: | Size: 234 B |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="78" height="78" viewBox="0 0 78 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="_图层_1" clip-path="url(#clip0_0_12)">
|
||||
|
||||
<path id="Vector" d="M78 39.0005C78 60.5399 60.5389 78.001 38.9995 78.001C17.4601 78.001 0 60.5389 0 39.0005C0 17.462 17.4611 0 39.0005 0C60.5399 0 78.001 17.4611 78.001 39.0005H78ZM53.0315 53.1697L51.3565 53.306C51.9924 51.7855 52.5434 50.3603 53.369 48.9274L64.235 59.803C75.0647 46.8044 73.9426 27.5901 62.0833 15.7947C50.2726 4.04781 31.1537 2.97532 18.1923 13.7631L27.8895 23.446L27.8676 32.3053L13.7622 18.198C2.95912 31.1708 4.04781 50.3193 15.8404 62.1319C27.6607 73.9722 46.8864 75.0304 59.8096 64.2273L53.0496 57.495L53.0315 53.1707V53.1697Z" fill="#FFC000"/>
|
||||
<path id="Vector_2" d="M50.5109 50.8522C48.8455 49.815 47.5928 48.364 46.4173 46.853L46.4707 49.9151L42.4706 49.917L42.5812 46.8473C41.7298 48.037 40.7174 49.1438 39.4876 50.1553L38.1558 47.9264L37.3226 49.447C36.4065 48.7072 35.6829 47.8006 34.9679 46.8663C34.9365 47.9331 34.9565 48.8617 35.0413 49.9141H31.0784L31.1499 46.8387L30.4244 47.7711C29.5635 48.9722 28.5244 50.0009 27.2879 50.8646C26.7789 49.5395 26.2393 48.3278 25.5491 47.1142C27.165 46.1027 28.6312 44.9692 29.7351 43.36C28.5377 43.3019 27.3785 43.3505 26.2155 43.4515L26.2069 39.1816C27.9209 39.358 29.4796 39.3561 31.1622 39.317L31.0431 37.3017L35.0165 37.2988L34.9593 39.3265C36.1329 39.3551 37.172 39.3294 38.3312 39.2093V43.4144L36.1272 43.3457C36.8831 44.3305 37.784 45.0531 38.7993 45.7595C39.7984 45.0493 40.5954 44.2952 41.3838 43.3257L39.0062 43.4372L38.9976 39.1807C40.2188 39.3132 41.3495 39.358 42.5754 39.3313L42.4744 37.3017L46.4736 37.2969L46.4202 39.3284C48.2506 39.3494 49.9561 39.3618 51.8131 39.1835L51.816 43.4535C50.4852 43.3391 49.1963 43.3038 47.8283 43.3591C49.0866 44.9645 50.6434 46.0408 52.4347 47.0599C51.7474 48.2592 51.0972 49.4108 50.5128 50.8512L50.5109 50.8522Z" fill="#FFC000"/>
|
||||
<path id="Vector_3" d="M51.4194 30.8057C49.7368 34.619 44.648 35.3998 41.1235 34.2796C38.3951 33.413 36.6801 31.1146 36.5342 28.3051C36.4084 25.8723 37.5562 23.6682 39.8251 22.4899C37.6191 20.9369 37.07 19.0045 37.4618 16.4248C37.8069 14.3466 39.1711 12.6802 41.1817 11.9728C44.892 10.6677 50.0857 11.5896 51.1287 16.0473C51.6263 18.4583 51.2326 21.0151 48.7844 22.2267C52.0076 23.8274 52.7503 27.7894 51.4194 30.8057ZM45.4211 20.3792C46.5212 20.1475 47.0885 19.2791 47.1295 18.2266C47.1676 17.2437 46.9321 16.2227 46.0455 15.6745C45.0341 15.0482 43.6413 15.0482 42.6079 15.6326C41.8071 16.0854 41.563 16.9749 41.5249 17.8376C41.4715 19.0512 42.1293 20.0827 43.3305 20.3716C43.9854 20.5289 44.7166 20.5279 45.4211 20.3802V20.3792ZM47.3945 29.1164C47.7272 28.2012 47.7262 27.204 47.4183 26.2822C46.8578 24.6034 45.0808 24.2335 43.4973 24.4585C41.9815 24.673 41.0168 25.8684 40.9596 27.3814C40.9138 28.6026 41.3333 29.7914 42.4172 30.433C43.9482 31.3386 46.6557 31.1479 47.3945 29.1174V29.1164Z" fill="#FFC000"/>
|
||||
<path id="Vector_4" d="M44.5421 59.2586L41.3933 59.2224L41.358 64.1005C41.3495 65.3256 40.4743 66.1435 39.3008 66.3227C37.703 66.5668 36.1005 66.5201 34.4865 66.4219C34.3044 65.173 34.0289 64.0624 33.5885 62.9213C32.1451 64.4485 30.535 65.5544 28.6626 66.5468C27.8618 65.1339 26.959 63.9032 25.7826 62.7401L27.8208 61.9346C29.1584 61.2587 30.3634 60.4827 31.3958 59.2453C29.82 59.1833 28.3147 59.2691 26.6654 59.353L26.6616 55.0869L30.1565 55.2203H47.7625C48.997 55.2194 50.0962 55.165 51.3107 55.0945L51.3012 59.3587L46.8978 59.2357C47.8016 60.15 48.8188 60.7506 49.9084 61.3569L52.2174 62.3655C51.1001 63.6449 50.265 64.9995 49.4918 66.5191C47.1552 65.2541 45.157 63.6134 43.4353 61.6515L42.6069 60.6953L44.5421 59.2596V59.2586ZM37.0347 61.7992L37.0309 59.2233L33.5675 59.2538L35.5418 60.4846L34.2186 62.2816C34.9937 62.3131 37.0357 62.8622 37.0347 61.7992Z" fill="#FFC000"/>
|
||||
<path id="Vector_5" d="M29.6226 34.3444L29.616 17.5516C28.3881 18.055 27.2527 18.3486 25.9571 18.5021L25.9504 14.869C27.9143 14.3752 29.6341 13.4095 30.9697 11.8403L34.089 11.8355L34.0909 34.3225L29.6236 34.3444H29.6226Z" fill="#FFC000"/>
|
||||
<path id="Vector_6" d="M48.6405 50.5271H29.3319V54.307H48.6405V50.5271Z" fill="#FFC000"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_0_12">
|
||||
<rect width="78" height="78" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 413 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" fill="none">
|
||||
<!-- Figma Subtract 复合路径:Safari 必须 evenodd,否则 nonzero 会把外形抵消成空白 -->
|
||||
<path fill="#EEEEEE" fill-rule="evenodd" clip-rule="evenodd" d="M24.6006 4.80005C25.0733 4.80005 25.5417 4.89355 25.9785 5.07446C26.4152 5.25538 26.8122 5.5205 27.1465 5.85474C27.4807 6.18899 27.7459 6.58602 27.9268 7.02271C28.1076 7.45928 28.2011 7.92713 28.2012 8.39966V21.5999C28.2012 22.5546 27.8216 23.4706 27.1465 24.1458C26.4714 24.8209 25.5554 25.2004 24.6006 25.2004H5.40039C4.44584 25.2003 3.53049 24.8207 2.85547 24.1458C2.18034 23.4706 1.80078 22.5546 1.80078 21.5999V8.39966C1.80088 7.44502 2.18043 6.52978 2.85547 5.85474C3.53051 5.1797 4.44575 4.80015 5.40039 4.80005H24.6006ZM4.7998 6.90747C3.86035 6.90759 3.54966 8.16535 4.37988 8.60278L14.6133 13.9963C14.743 14.0647 14.8876 14.1001 15.0342 14.0999C15.1808 14.0996 15.3255 14.0641 15.4551 13.9954L25.6221 8.59497C26.4501 8.15517 26.1376 6.90003 25.1992 6.90063L4.7998 6.90747Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 85 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 18C0 8.05888 8.05888 0 18 0C27.9411 0 36 8.05888 36 18C36 27.9411 27.9411 36 18 36C8.05888 36 0 27.9411 0 18Z" fill="#15BA11"/>
|
||||
<g clip-path="url(#clip0_40635_137444)">
|
||||
<path d="M16.3758 22.4796C16.2358 22.4811 16.0979 22.4391 15.9755 22.3577C15.8531 22.2763 15.7504 22.1583 15.6773 22.0152L15.6243 21.8844L11.4385 15.252C11.4133 15.1928 11.3997 15.1276 11.3985 15.0612C11.4017 14.9375 11.4455 14.8203 11.5205 14.7353C11.5956 14.6503 11.6956 14.6044 11.7988 14.6076C11.8888 14.6076 11.9719 14.6412 12.0389 14.6976L16.6199 17.9772C16.7781 18.093 16.9556 18.1656 17.1398 18.1896C17.324 18.2137 17.5104 18.1887 17.6856 18.1164L27.8162 10.8C25.6408 7.896 22.0605 6 18.0078 6C11.3755 6 6 11.076 6 17.3352C6 20.7504 7.61705 23.8248 10.1487 25.902C10.2509 25.9826 10.3349 26.092 10.3936 26.2207C10.4522 26.3494 10.4836 26.4934 10.4849 26.64C10.4824 26.7387 10.4683 26.8365 10.4429 26.9304L9.90154 29.2152C9.87552 29.3232 9.8365 29.4348 9.8365 29.5464C9.8365 29.7984 10.0166 30 10.2368 30C10.3228 30 10.3939 29.964 10.4669 29.916L13.0966 28.1976C13.2947 28.068 13.5029 27.9876 13.734 27.9876C13.8571 27.9876 13.9742 28.0092 14.0873 28.0464C15.3131 28.446 16.6369 28.6692 18.0068 28.6692C24.6391 28.668 30.0156 23.592 30.0156 17.3352C30.0156 15.438 29.5183 13.6524 28.6467 12.0828L16.823 22.3248L16.736 22.3812C16.6237 22.4463 16.5005 22.4799 16.3758 22.4796Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_40635_137444">
|
||||
<rect width="24" height="24" fill="white" transform="translate(6 6)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 90 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="5" height="3.5" viewBox="0 0 5 3.5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Vector 438" d="M0 3H5C4 3 2.5 1.5 2.5 0" stroke="#EC2083"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Iconly/UntitledUI/chevron-right">
|
||||
<path id="Icon" d="M6.66667 4.66667L10 8L6.66667 11.3333" stroke="white" stroke-opacity="0.6" stroke-width="0.888889" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 368 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Frame 2147231187">
|
||||
<g clip-path="url(#clip0_0_4)">
|
||||
<circle id="Ellipse 14975" cx="12" cy="12" r="12" fill="url(#paint0_linear_0_4)"/>
|
||||
<g id="Ellipse 14976" opacity="0.3" filter="url(#filter0_f_0_4)">
|
||||
<circle cx="12" cy="22" r="9" fill="white"/>
|
||||
</g>
|
||||
<path id="Vector 5628" d="M8.14286 11L6 13L11.1429 18L18 7.5L16.2857 6L10.2857 12.5L8.14286 11Z" fill="#2D251E"/>
|
||||
</g>
|
||||
<rect x="1" y="1" width="22" height="22" rx="11" stroke="#2D251E" stroke-width="2"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_0_4" x="-1" y="9" width="26" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_0_4"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_0_4" x1="12" y1="0" x2="12" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FEBF8B"/>
|
||||
<stop offset="1" stop-color="#FFB27D"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_0_4">
|
||||
<rect width="24" height="24" rx="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 259 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><rect x="3" y="3" width="8" height="8" rx="2"/><rect x="13" y="3" width="8" height="8" rx="2"/><rect x="3" y="13" width="8" height="8" rx="2"/><rect x="13" y="13" width="8" height="8" rx="2"/></svg>
|
||||
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 134 KiB |
@@ -0,0 +1 @@
|
||||
<svg class="svg_quick_icon svg_icon_history" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22" fill="none"><circle cx="11.0001" cy="10.9999" r="6.96667" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/><path d="M14.8704 11.7741H11V7.12964" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 403 B |
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ic_js">
|
||||
<path id="Vector" d="M9.99883 34.0138C8.90965 36.1763 8.08688 39.9081 8.08688 39.9081C8.08688 39.9081 11.8202 39.0866 13.9848 37.9976L15.9714 39.9832C15.7319 40.3943 15.4481 40.7891 15.0954 41.1426C12.8655 43.3713 2.00002 45.9907 2.00002 45.9907C2.00002 45.9907 4.62246 35.1335 6.85331 32.9047C7.20586 32.5515 7.59831 32.2659 8.01227 32.0284L9.99883 34.0138ZM29.5866 40.2725L30.9337 31.1604L39.4984 22.6L39.4537 22.5555C41.5337 20.2934 49.0528 10.7301 44.6297 3.37559C37.2728 -1.04319 27.7045 6.47094 25.4396 8.5498L25.418 8.52733L16.8534 17.0874L7.7373 18.4348L6.30882 19.8615L11.0752 24.6263L7.81342 26.1235C6.76324 27.1723 6.77956 28.8941 7.85226 29.9668L18.0501 40.1591C19.1219 41.2295 20.8453 41.2468 21.8955 40.1979L23.3927 36.9372L28.1582 41.7001L29.5866 40.2725ZM34.1989 21.1082C33.1478 22.1586 31.4244 22.1399 30.3526 21.0683L26.9533 17.671C25.8796 16.5979 25.8624 14.8766 26.9135 13.8266L30.2439 10.498C31.2952 9.44734 33.0167 9.46472 34.0905 10.5375L37.4898 13.935C38.5616 15.0063 38.5806 16.7289 37.5294 17.7795L34.1989 21.1082ZM17.963 31.5096L16.5056 30.0541L22.4529 24.1099C22.8462 23.7173 23.4927 23.723 23.8944 24.1245C24.2963 24.5262 24.3029 25.1734 23.9101 25.5655L17.963 31.5096Z" fill="#2ECC71"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,36 @@
|
||||
<svg width="135" height="40" viewBox="0 0 135 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_42558_135748)">
|
||||
<path d="M-0.0055542 19.984C-0.0055542 8.93831 8.94875 -0.0159912 19.9944 -0.0159912V-0.0159912C31.0401 -0.0159912 39.9944 8.93831 39.9944 19.984V19.984C39.9944 31.0297 31.0401 39.984 19.9944 39.984V39.984C8.94875 39.984 -0.0055542 31.0297 -0.0055542 19.984V19.984Z" fill="url(#paint0_linear_42558_135748)"/>
|
||||
<path d="M34.9795 20.2336C34.9651 20.0651 34.9542 19.8931 34.9421 19.7241L34.9358 19.682C34.9231 19.3903 34.8054 18.875 34.7423 18.5923L34.7292 18.5587C34.4771 17.3099 33.6857 16.01 32.8351 15.082C32.3404 14.5425 31.7519 14.0335 31.1963 13.5545C28.6032 11.2953 25.6656 9.45862 22.4934 8.11303C20.9669 7.48079 19.8656 7.11202 18.2159 7.00627C17.9724 7.00179 17.7289 7 17.4849 7C16.8459 7.0242 16.1103 7.1053 15.4776 7.24913C13.6755 7.65913 11.9186 8.63281 10.6423 9.9645C10.0998 10.5309 9.58342 11.199 9.1816 11.8684C7.52878 14.621 6.8857 18.1034 7.01603 21.279C7.1662 24.9353 8.31483 28.9425 11.0969 31.4867C12.9806 33.1943 15.4668 34.0945 18.0148 33.9919C19.8372 33.9149 21.2992 33.3825 22.9624 32.6719C26.0962 31.3379 28.9825 29.4919 31.502 27.2102C32.5704 26.2366 33.5341 25.3189 34.1786 23.9961L34.3454 23.6377C34.7653 22.6107 34.8731 22.0161 34.9772 20.9107C34.9849 20.8892 34.9849 20.8829 34.9948 20.8417C34.9975 20.7014 35.0102 20.3568 34.98 20.2336H34.9795ZM27.4253 21.1329C27.1637 21.568 25.6385 22.8831 25.165 23.2062C24.9445 23.3984 24.6067 23.6466 24.3654 23.8169C22.9061 24.843 21.3331 25.6993 19.6775 26.3692C19.2631 26.539 18.6475 26.7823 18.2065 26.8697C17.8764 26.936 17.5368 26.9423 17.2044 26.8885C15.9268 26.6689 15.2612 25.7163 14.8133 24.6006C14.0616 22.7276 13.9349 20.4922 14.2853 18.5184C14.3926 17.7759 14.6889 16.9174 15.0118 16.2376C15.1935 15.9168 15.3505 15.6368 15.589 15.3513C16.2461 14.5681 17.2946 14.2688 18.2872 14.4543C18.7224 14.5354 19.1661 14.7294 19.5765 14.9006C20.5173 15.2881 21.4318 15.7353 22.3148 16.2403C22.4745 16.3304 22.6594 16.4357 22.8104 16.5401C24.1485 17.3404 25.3882 18.3024 26.5553 19.3307C26.8151 19.5597 27.1105 19.7819 27.3242 20.0548C27.4117 20.1664 27.4776 20.2869 27.5127 20.4245C27.5736 20.662 27.5533 20.9192 27.4257 21.1316L27.4253 21.1329Z" fill="white"/>
|
||||
<g filter="url(#filter0_d_42558_135748)">
|
||||
<path d="M20.5 28.7835C25.0621 28.7835 28.7604 25.0852 28.7604 20.5232C28.7604 15.9611 25.0621 12.2628 20.5 12.2628C15.938 12.2628 12.2397 15.9611 12.2397 20.5232C12.2397 25.0852 15.938 28.7835 20.5 28.7835Z" fill="#0F0F0F"/>
|
||||
<path d="M16.6111 24.3571V17.1369L17.0821 17.7465L15.2763 18.6624L14.9065 17.212L17.3334 15.9523H18.2493V24.3542H16.6082L16.6111 24.3571ZM15.126 24.9783V23.4527H19.7777V24.9783H15.126Z" fill="white"/>
|
||||
<path d="M23.5916 25.016C23.0224 25.016 22.5052 24.9004 22.0343 24.6664C21.5633 24.4323 21.1877 24.1203 20.9103 23.7245C20.6301 23.3315 20.4914 22.8924 20.4914 22.4099C20.4914 22.1209 20.5318 21.8551 20.6127 21.6095C20.6936 21.364 20.8121 21.1444 20.9681 20.9479C21.1241 20.7543 21.3148 20.5781 21.5402 20.4192C21.7656 20.2632 22.0198 20.1245 22.3087 20.006L22.2076 20.3614C21.988 20.2689 21.7858 20.1591 21.6038 20.032C21.4217 19.9049 21.2657 19.7575 21.1328 19.5929C20.9999 19.4282 20.9017 19.249 20.8352 19.0583C20.7688 18.8677 20.7341 18.6654 20.7341 18.4545C20.7341 17.998 20.8641 17.5848 21.1212 17.2208C21.3784 16.8567 21.7251 16.5649 22.1585 16.3511C22.5919 16.1344 23.0686 16.0275 23.5944 16.0275C24.1203 16.0275 24.6115 16.1344 25.0448 16.3511C25.4782 16.5678 25.8221 16.8567 26.0821 17.2208C26.3392 17.5848 26.4692 17.9951 26.4692 18.4545C26.4692 18.6654 26.4346 18.8677 26.3681 19.0583C26.3017 19.249 26.2063 19.4224 26.0821 19.5784C25.9578 19.7344 25.8047 19.8789 25.6169 20.0118C25.4291 20.1447 25.2153 20.2603 24.9697 20.3614L24.9062 20.0551C25.1778 20.1476 25.4233 20.2718 25.6429 20.4249C25.8625 20.5781 26.0532 20.7543 26.215 20.9595C26.3768 21.1617 26.501 21.3871 26.5906 21.6327C26.6802 21.8782 26.7235 22.1383 26.7235 22.407C26.7235 22.8981 26.5848 23.3402 26.3046 23.736C26.0243 24.129 25.6487 24.441 25.1806 24.6693C24.7126 24.8975 24.1839 25.0131 23.5973 25.0131L23.5916 25.016ZM23.5916 23.5945C23.8805 23.5945 24.1376 23.5396 24.3659 23.4298C24.5941 23.32 24.7733 23.1726 24.9062 22.9848C25.0362 22.7999 25.1026 22.5832 25.1026 22.3376C25.1026 22.0747 25.0362 21.8436 24.9062 21.6442C24.7733 21.4449 24.5941 21.2888 24.3659 21.1733C24.1376 21.0577 23.8776 21.0028 23.5916 21.0028C23.3055 21.0028 23.0599 21.0606 22.8346 21.1733C22.6092 21.2888 22.4301 21.4449 22.2943 21.6442C22.1585 21.8436 22.092 22.0747 22.092 22.3376C22.092 22.5746 22.1585 22.7884 22.2943 22.9733C22.4301 23.1611 22.6092 23.3113 22.8346 23.424C23.0599 23.5396 23.3113 23.5945 23.5916 23.5945ZM23.5916 19.6015C23.8458 19.6015 24.0712 19.5553 24.2705 19.4628C24.4699 19.3704 24.623 19.2433 24.7357 19.0815C24.8455 18.9197 24.9004 18.7347 24.9004 18.5209C24.9004 18.3071 24.8455 18.1078 24.7357 17.9431C24.6259 17.7784 24.4699 17.6455 24.2705 17.5502C24.0712 17.4519 23.8458 17.4028 23.5916 17.4028C23.3373 17.4028 23.1235 17.4519 22.9241 17.5502C22.7248 17.6484 22.5688 17.7784 22.459 17.9431C22.3492 18.1078 22.2943 18.3014 22.2943 18.5209C22.2943 18.7405 22.3492 18.9197 22.459 19.0815C22.5688 19.2433 22.7248 19.3704 22.9241 19.4628C23.1235 19.5553 23.346 19.6015 23.5916 19.6015Z" fill="white"/>
|
||||
<path d="M20.5 11.5C15.5363 11.5 11.5 15.5363 11.5 20.5C11.5 25.4637 15.5363 29.5 20.5 29.5C25.4637 29.5 29.5 25.4637 29.5 20.5C29.5 15.5363 25.4637 11.5 20.5 11.5ZM28.0583 20.5C28.0583 22.0284 27.5989 23.4528 26.8159 24.6432L16.7266 13.9588C17.839 13.3144 19.1276 12.9417 20.5029 12.9417C24.6692 12.9417 28.0583 16.3308 28.0583 20.4971V20.5ZM20.5 28.0554C16.3337 28.0554 12.9446 24.6663 12.9446 20.5C12.9446 18.2319 13.9501 16.195 15.5392 14.8082L25.9 25.7787C24.5276 27.1828 22.6149 28.0554 20.5029 28.0554H20.5Z" fill="#DB0015"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M46.0458 23.3007H66.637V25.8768H57.8285V30.431H54.8542V25.8768H46.0458V23.3007ZM46 19.0685C46.6406 17.443 47.0219 15.7103 47.144 13.8702H49.5692C49.4929 15.787 49.1878 17.5197 48.654 19.0685H46ZM46.3889 10.6501H50.3013V9.5H53.2298V10.6501H56.0211V13.2492H53.2298V13.6402L55.1746 14.5832V17.4354L53.2298 16.4923V19.0455H50.3013V13.2492H46.3889V10.6501ZM47.0296 30.431C47.9142 29.1889 48.5701 27.9239 48.9972 26.6358H52.0859C51.7045 27.8625 51.1249 29.1276 50.347 30.431H47.0296ZM47.8761 19.7815H64.8067V22.3346H47.8761V19.7815ZM55.1517 18.7004C55.777 17.1824 56.1584 15.5723 56.2956 13.8702H58.7208C58.5988 15.5723 58.2709 17.1824 57.737 18.7004H55.1517ZM56.6617 10.6501H59.4987V9.5H62.4272V10.6501H66.2938V13.2492H62.29V19.0455H59.4987V13.2492H56.6617V10.6501ZM63.6856 26.6358C64.1127 27.9239 64.7686 29.1889 65.6532 30.431H62.3357C61.5578 29.1276 60.9782 27.8625 60.5969 26.6358H63.6856ZM65.5388 13.8702C65.6608 15.7103 66.0422 17.443 66.6828 19.0685H64.0288C63.4949 17.5197 63.1899 15.787 63.1136 13.8702H65.5388Z" fill="white"/>
|
||||
<path d="M88.0748 20.1725H80.6848V21.5296H89.5162V24.3817H85.1234C85.9318 25.9458 87.3961 26.9885 89.5162 27.5099V30.5C85.4284 29.5033 82.988 27.4639 82.1949 24.3817H80.6848V30.431H77.7563V24.3817H76.2463C75.4531 27.4639 73.0127 29.5033 68.9249 30.5V27.5099C71.0451 26.9885 72.5093 25.9458 73.3177 24.3817H68.9249V21.5296H77.7563V20.1725H70.3663V9.96002H88.0748V20.1725ZM77.7563 17.6424V16.1703H73.2949V17.6424H77.7563ZM77.7563 13.9392V12.4901H73.2949V13.9392H77.7563ZM85.1463 17.6424V16.1703H80.6848V17.6424H85.1463ZM85.1463 13.9392V12.4901H80.6848V13.9392H85.1463Z" fill="white"/>
|
||||
<path d="M111.434 21.9436H101.688V13.8242H111.434V21.9436ZM92.2617 9.73001H95.236L95.0072 11.2481H100.132V14.1922H97.5697V17.9414H100.452V20.8855H97.4553L97.3866 21.8976L100.247 23.7607V27.2568L97.0206 25.1637C96.7155 27.2492 96.3189 29.0049 95.8308 30.431H92.2388C92.8642 29.2503 93.3599 27.8165 93.726 26.1298C94.092 24.4277 94.3361 22.6796 94.4581 20.8855H91.8041V17.9414H94.5954V14.1922H91.5753L92.2617 9.73001ZM100.544 27.4639H103.175L102.283 23.3927H104.983L105.875 27.4639H107.179L108.437 23.1627H111.137L109.879 27.4639H112.578V30.224H100.544V27.4639ZM100.933 9.96002H112.212V12.4901H100.933V9.96002ZM108.689 19.4595V16.3083H104.433V19.4595H108.689Z" fill="white"/>
|
||||
<path d="M135 9.73001V30.431H129.074V27.6709H132.14V9.73001H135ZM114.294 30.454C114.767 29.1659 115.065 27.9009 115.187 26.6588C115.324 25.4168 115.393 23.8834 115.393 22.0586V9.86802H127.45V16.5153H123.995V18.2404H127.702V20.9545H123.995V22.8406H127.107V30.431H118.824V22.8406H121.433V20.9545H118.001V21.9206C118.001 23.7913 117.947 25.3861 117.841 26.7048C117.749 28.0082 117.536 29.2579 117.2 30.454H114.294ZM121.433 18.2404V16.5153H118.001V18.2404H121.433ZM124.773 14.0312V12.3521H118.001V14.0312H124.773ZM124.59 27.9009V25.3708H121.341V27.9009H124.59ZM128.319 10.9951H130.996V25.6008H128.319V10.9951Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_42558_135748" x="10.4177" y="10.4177" width="20.1647" height="20.1647" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="0.541168"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_42558_135748"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_42558_135748" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_42558_135748" x1="30.5469" y1="9.57031" x2="5.54686" y2="33.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#89FFEB"/>
|
||||
<stop offset="0.5" stop-color="#FFA53E"/>
|
||||
<stop offset="1" stop-color="#FD6119"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_42558_135748">
|
||||
<rect width="40" height="40" rx="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 107 KiB |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200" fill="#D68B13"><path d="M959.069091 480.116364a124.974545 124.974545 0 0 0-93.090909-40.96 122.88 122.88 0 0 0-79.825455 29.323636 123.345455 123.345455 0 0 0-79.825454-29.323636 125.44 125.44 0 0 0-93.090909 40.96 169.192727 169.192727 0 0 0-24.436364 184.32 360.261818 360.261818 0 0 0 167.098182 165.701818 67.258182 67.258182 0 0 0 30.487273 6.749091 64 64 0 0 0 31.883636-7.68 402.385455 402.385455 0 0 0 162.909091-162.909091 167.098182 167.098182 0 0 0-22.109091-186.181818z"/><path d="M536.436364 695.156364c-32.349091-71.68-42.356364-185.018182 35.84-269.265455a181.992727 181.992727 0 0 1 134.516363-59.810909A178.269091 178.269091 0 0 1 814.545455 403.083636c-4.887273-104.029091-72.145455-229.236364-197.352728-272.989091a235.287273 235.287273 0 0 0-190.836363 14.429091 246.690909 246.690909 0 0 0-110.312728 122.647273 235.054545 235.054545 0 0 0-160.116363 23.272727 249.716364 249.716364 0 0 0-121.018182 153.6c-41.658182 146.618182 36.072727 271.825455 116.363636 334.661819 179.2 141.963636 404.48 129.396364 424.029091 128a127.767273 127.767273 0 0 0 57.483637-16.29091 138.007273 138.007273 0 0 0 34.90909-27.694545 481.047273 481.047273 0 0 1-131.258181-167.563636z m-252.974546-53.76c-13.730909-74.705455-24.901818-86.109091-97.047273-100.538182 72.145455-14.429091 83.316364-25.832727 97.047273-100.538182 13.963636 74.705455 25.134545 86.109091 97.047273 100.538182-71.912727 14.429091-83.083636 25.832727-97.047273 100.538182z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 428 KiB |
|
After Width: | Height: | Size: 521 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><g fill="none" fill-rule="evenodd"><path fill="#00D1D1" fill-rule="nonzero" d="M18 7.28a2.72 2.72 0 0 1 2.715 2.554l.005.166v8a2.72 2.72 0 0 1-2.554 2.715L18 20.72H6a2.72 2.72 0 0 1-2.715-2.554L3.28 18v-8a2.72 2.72 0 0 1 2.554-2.715L6 7.28zM9.4 11v6a.8.8 0 0 0 1.25.661l4.4-3a.8.8 0 0 0 0-1.322l-4.4-3A.8.8 0 0 0 9.4 11m1.6 1.513 2.18 1.486L11 15.486z"/><path stroke="#00D1D1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.4" d="m16 4-4 4-4-4"/></g></svg>
|
||||
|
After Width: | Height: | Size: 535 B |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 418 KiB |
@@ -0,0 +1,32 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_42558_134113)">
|
||||
<path d="M-0.0055542 19.984C-0.0055542 8.93831 8.94875 -0.0159912 19.9944 -0.0159912V-0.0159912C31.0401 -0.0159912 39.9944 8.93831 39.9944 19.984V19.984C39.9944 31.0297 31.0401 39.984 19.9944 39.984V39.984C8.94875 39.984 -0.0055542 31.0297 -0.0055542 19.984V19.984Z" fill="url(#paint0_linear_42558_134113)"/>
|
||||
<path d="M34.9795 20.2336C34.9651 20.0651 34.9542 19.8931 34.9421 19.7241L34.9358 19.682C34.9231 19.3903 34.8054 18.875 34.7423 18.5923L34.7292 18.5587C34.4771 17.3099 33.6857 16.01 32.8351 15.082C32.3404 14.5425 31.7519 14.0335 31.1963 13.5545C28.6032 11.2953 25.6656 9.45862 22.4934 8.11303C20.9669 7.48079 19.8656 7.11202 18.2159 7.00627C17.9724 7.00179 17.7289 7 17.4849 7C16.8459 7.0242 16.1103 7.1053 15.4776 7.24913C13.6755 7.65913 11.9186 8.63281 10.6423 9.9645C10.0998 10.5309 9.58342 11.199 9.1816 11.8684C7.52878 14.621 6.8857 18.1034 7.01603 21.279C7.1662 24.9353 8.31483 28.9425 11.0969 31.4867C12.9806 33.1943 15.4668 34.0945 18.0148 33.9919C19.8372 33.9149 21.2992 33.3825 22.9624 32.6719C26.0962 31.3379 28.9825 29.4919 31.502 27.2102C32.5704 26.2366 33.5341 25.3189 34.1786 23.9961L34.3454 23.6377C34.7653 22.6107 34.8731 22.0161 34.9772 20.9107C34.9849 20.8892 34.9849 20.8829 34.9948 20.8417C34.9975 20.7014 35.0102 20.3568 34.98 20.2336H34.9795ZM27.4253 21.1329C27.1637 21.568 25.6385 22.8831 25.165 23.2062C24.9445 23.3984 24.6067 23.6466 24.3654 23.8169C22.9061 24.843 21.3331 25.6993 19.6775 26.3692C19.2631 26.539 18.6475 26.7823 18.2065 26.8697C17.8764 26.936 17.5368 26.9423 17.2044 26.8885C15.9268 26.6689 15.2612 25.7163 14.8133 24.6006C14.0616 22.7276 13.9349 20.4922 14.2853 18.5184C14.3926 17.7759 14.6889 16.9174 15.0118 16.2376C15.1935 15.9168 15.3505 15.6368 15.589 15.3513C16.2461 14.5681 17.2946 14.2688 18.2872 14.4543C18.7224 14.5354 19.1661 14.7294 19.5765 14.9006C20.5173 15.2881 21.4318 15.7353 22.3148 16.2403C22.4745 16.3304 22.6594 16.4357 22.8104 16.5401C24.1485 17.3404 25.3882 18.3024 26.5553 19.3307C26.8151 19.5597 27.1105 19.7819 27.3242 20.0548C27.4117 20.1664 27.4776 20.2869 27.5127 20.4245C27.5736 20.662 27.5533 20.9192 27.4257 21.1316L27.4253 21.1329Z" fill="white"/>
|
||||
<g filter="url(#filter0_d_42558_134113)">
|
||||
<path d="M20.5 28.7835C25.0621 28.7835 28.7604 25.0852 28.7604 20.5232C28.7604 15.9611 25.0621 12.2628 20.5 12.2628C15.938 12.2628 12.2397 15.9611 12.2397 20.5232C12.2397 25.0852 15.938 28.7835 20.5 28.7835Z" fill="#0F0F0F"/>
|
||||
<path d="M16.6111 24.3571V17.1369L17.0821 17.7465L15.2763 18.6624L14.9065 17.212L17.3334 15.9523H18.2493V24.3542H16.6082L16.6111 24.3571ZM15.126 24.9783V23.4527H19.7777V24.9783H15.126Z" fill="white"/>
|
||||
<path d="M23.5916 25.016C23.0224 25.016 22.5052 24.9004 22.0343 24.6664C21.5633 24.4323 21.1877 24.1203 20.9103 23.7245C20.6301 23.3315 20.4914 22.8924 20.4914 22.4099C20.4914 22.1209 20.5318 21.8551 20.6127 21.6095C20.6936 21.364 20.8121 21.1444 20.9681 20.9479C21.1241 20.7543 21.3148 20.5781 21.5402 20.4192C21.7656 20.2632 22.0198 20.1245 22.3087 20.006L22.2076 20.3614C21.988 20.2689 21.7858 20.1591 21.6038 20.032C21.4217 19.9049 21.2657 19.7575 21.1328 19.5929C20.9999 19.4282 20.9017 19.249 20.8352 19.0583C20.7688 18.8677 20.7341 18.6654 20.7341 18.4545C20.7341 17.998 20.8641 17.5848 21.1212 17.2208C21.3784 16.8567 21.7251 16.5649 22.1585 16.3511C22.5919 16.1344 23.0686 16.0275 23.5944 16.0275C24.1203 16.0275 24.6115 16.1344 25.0448 16.3511C25.4782 16.5678 25.8221 16.8567 26.0821 17.2208C26.3392 17.5848 26.4692 17.9951 26.4692 18.4545C26.4692 18.6654 26.4346 18.8677 26.3681 19.0583C26.3017 19.249 26.2063 19.4224 26.0821 19.5784C25.9578 19.7344 25.8047 19.8789 25.6169 20.0118C25.4291 20.1447 25.2153 20.2603 24.9697 20.3614L24.9062 20.0551C25.1778 20.1476 25.4233 20.2718 25.6429 20.4249C25.8625 20.5781 26.0532 20.7543 26.215 20.9595C26.3768 21.1617 26.501 21.3871 26.5906 21.6327C26.6802 21.8782 26.7235 22.1383 26.7235 22.407C26.7235 22.8981 26.5848 23.3402 26.3046 23.736C26.0243 24.129 25.6487 24.441 25.1806 24.6693C24.7126 24.8975 24.1839 25.0131 23.5973 25.0131L23.5916 25.016ZM23.5916 23.5945C23.8805 23.5945 24.1376 23.5396 24.3659 23.4298C24.5941 23.32 24.7733 23.1726 24.9062 22.9848C25.0362 22.7999 25.1026 22.5832 25.1026 22.3376C25.1026 22.0747 25.0362 21.8436 24.9062 21.6442C24.7733 21.4449 24.5941 21.2888 24.3659 21.1733C24.1376 21.0577 23.8776 21.0028 23.5916 21.0028C23.3055 21.0028 23.0599 21.0606 22.8346 21.1733C22.6092 21.2888 22.4301 21.4449 22.2943 21.6442C22.1585 21.8436 22.092 22.0747 22.092 22.3376C22.092 22.5746 22.1585 22.7884 22.2943 22.9733C22.4301 23.1611 22.6092 23.3113 22.8346 23.424C23.0599 23.5396 23.3113 23.5945 23.5916 23.5945ZM23.5916 19.6015C23.8458 19.6015 24.0712 19.5553 24.2705 19.4628C24.4699 19.3704 24.623 19.2433 24.7357 19.0815C24.8455 18.9197 24.9004 18.7347 24.9004 18.5209C24.9004 18.3071 24.8455 18.1078 24.7357 17.9431C24.6259 17.7784 24.4699 17.6455 24.2705 17.5502C24.0712 17.4519 23.8458 17.4028 23.5916 17.4028C23.3373 17.4028 23.1235 17.4519 22.9241 17.5502C22.7248 17.6484 22.5688 17.7784 22.459 17.9431C22.3492 18.1078 22.2943 18.3014 22.2943 18.5209C22.2943 18.7405 22.3492 18.9197 22.459 19.0815C22.5688 19.2433 22.7248 19.3704 22.9241 19.4628C23.1235 19.5553 23.346 19.6015 23.5916 19.6015Z" fill="white"/>
|
||||
<path d="M20.5 11.5C15.5363 11.5 11.5 15.5363 11.5 20.5C11.5 25.4637 15.5363 29.5 20.5 29.5C25.4637 29.5 29.5 25.4637 29.5 20.5C29.5 15.5363 25.4637 11.5 20.5 11.5ZM28.0583 20.5C28.0583 22.0284 27.5989 23.4528 26.8159 24.6432L16.7266 13.9588C17.839 13.3144 19.1276 12.9417 20.5029 12.9417C24.6692 12.9417 28.0583 16.3308 28.0583 20.4971V20.5ZM20.5 28.0554C16.3337 28.0554 12.9446 24.6663 12.9446 20.5C12.9446 18.2319 13.9501 16.195 15.5392 14.8082L25.9 25.7787C24.5276 27.1828 22.6149 28.0554 20.5029 28.0554H20.5Z" fill="#DB0015"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_42558_134113" x="10.4177" y="10.4177" width="20.1647" height="20.1647" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="0.541168"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_42558_134113"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_42558_134113" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_42558_134113" x1="30.5469" y1="9.57031" x2="5.54686" y2="33.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#89FFEB"/>
|
||||
<stop offset="0.5" stop-color="#FFA53E"/>
|
||||
<stop offset="1" stop-color="#FD6119"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_42558_134113">
|
||||
<rect width="40" height="40" rx="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 381 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 18C0 8.05888 8.05888 0 18 0C27.9411 0 36 8.05888 36 18C36 27.9411 27.9411 36 18 36C8.05888 36 0 27.9411 0 18Z" fill="#335AE8"/>
|
||||
<path d="M21.6004 20.5992C23.3828 17.45 24.0303 14.4566 24.0303 14.4566H18.0355V12.2516H25.1652V11.3074H18.0355V8H14.7957V11.3074H8.31341V12.2516H14.7957V14.4566H9.28587V15.4008H20.4679C20.4679 15.559 20.4679 15.559 20.3054 15.7147C20.3054 16.8172 19.4955 18.3918 18.848 19.6524C10.5833 16.5033 8.15345 18.3918 7.50345 18.7082C1.99366 22.4877 7.17845 27.2115 7.98841 27.0557C13.8232 28.3164 17.548 25.9533 20.1429 22.9598C20.3054 23.118 20.4679 23.118 20.6279 23.118C22.4103 24.0623 31 28 31 28V23.2762C29.7025 23.2762 25.0053 21.7017 21.6004 20.5992ZM17.388 22.0156C13.3357 27.0557 8.47591 25.4812 7.66595 25.1647C5.72102 24.6926 5.07356 21.2295 7.50345 20.1271C11.5558 18.8664 15.1207 20.2853 17.713 21.5434C17.5505 21.8574 17.388 22.0156 17.388 22.0156Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1016 B |
|
After Width: | Height: | Size: 812 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 158 KiB |