diff --git a/.env.development b/.env.development index b199985..f498e9d 100644 --- a/.env.development +++ b/.env.development @@ -18,9 +18,9 @@ VITE_APP_TITLE=Azizi H5 (Development) VITE_API_BASE_URL=https://api.likeichat.com # 正式用户 8832199 -VITE_USER_AUTH=8C8D1E758D7B032FFB5664C4FE41C91C.djIlM0ExOTkwNjg1NTY1MzMzNTkwMDE3JTNBTElLRUklM0ExNzc5MjUxMDM2MjEyJTNBMTc3NjY1OTAzNjIxMg== +VITE_USER_AUTH=69C0D45E86F7D7BFA520B39EC75670F7.djIlM0ExOTkwNjg1NTY1MzMzNTkwMDE3JTNBTElLRUklM0ExNzgxMzM0Mjc1ODM3JTNBMTc3ODc0MjI3NTgzNw== # 正式用户 8826796 -# VITE_USER_AUTH=23116F749A21862A2B7217CDEE0FBF45.djIlM0ExOTYzNzkwMDM3NzQwNDY2MTc4JTNBTElLRUklM0ExNzc4OTAxNTg4MDA5JTNBMTc3NjMwOTU4ODAwOQ== +# VITE_USER_AUTH=EB80A174E9F927A811009B05DE982B98.djIlM0EyMDA1ODU0NTE0ODQzMTYwNTc4JTNBTElLRUklM0ExNzgwOTA0NTk5Njg1JTNBMTc3ODMxMjU5OTY4NQ== # 正式用户 10086 # VITE_USER_AUTH=E71AE3738A4FA44BD2E895CBE176AC81.djIlM0EyMDA1ODU0NTE0ODQzMTYwNTc4JTNBTElLRUklM0ExNzc4NjQwMzczOTU4JTNBMTc3NjA0ODM3Mzk1OA== diff --git a/docs/安全/前端加固-中文.md b/docs/安全/前端加固-中文.md index 22724f9..e0f3f87 100644 --- a/docs/安全/前端加固-中文.md +++ b/docs/安全/前端加固-中文.md @@ -4,7 +4,7 @@ 这份文档用于记录当前主分支已经落地的前端防扒取加固方案。 -核心目标是提高扒图、抄页面、低成本复刻的门槛,而不是宣称“前端已经绝对防住”。 +核心目标是提高扒图、抄页面、低成本复刻的门槛,而不是宣称“前端已经绝对防住”。只要页面能在客户端完整渲染,前端就无法彻底阻止抓包、截图、Hook、像素提取或人工复刻。 ## 范围 @@ -29,13 +29,13 @@ 表现: -- 公共页面仍可正常访问 -- 受保护页面在非 App 环境下会跳转到 `/#/not_app` -- 受保护页面内的接口请求在缺少 App bridge 时会被拒绝 +- 公共页面仍可正常访问。 +- 受保护页面在非 App 环境下会跳转到 `/#/not_app`。 +- 受保护页面内的接口请求在缺少 App bridge 时会被拒绝。 ### 2. 运行时防调试与防复制限制 -对受保护页面增加了一层轻量运行时限制。 +受保护页面增加了轻量运行时限制。 主要文件: @@ -45,9 +45,9 @@ 表现: -- 屏蔽常见开发者工具快捷键 -- 屏蔽右键、拖拽、复制、文本选择等交互 -- 降低长按保存图片的便利性 +- 屏蔽常见开发者工具快捷键。 +- 屏蔽右键、拖拽、复制、文本选择等交互。 +- 降低长按保存图片的便利性。 ### 3. `Activities` / `Ranking` 图片受保护地址层 @@ -59,164 +59,90 @@ - [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) - [src/utils/protectedAssetRuntime.js](/D:/programs/likei-h5/src/utils/protectedAssetRuntime.js) - [src/directives/smartImage.js](/D:/programs/likei-h5/src/directives/smartImage.js) + +表现: + +- 模板中更多使用 `likei-protected:` 形式的受保护地址。 +- 运行时再解析为真实图片地址。 +- 大部分图片最终会走缓存并转换成 blob/object URL。 + +### 4. 图片缓存和单图版本控制 + +图片缓存用于减少重复网络下载,但不再追求过大容量。 + +主要文件: + +- [src/config/imageCacheVersion.js](/D:/programs/likei-h5/src/config/imageCacheVersion.js) - [src/utils/image/imageCacheManager.js](/D:/programs/likei-h5/src/utils/image/imageCacheManager.js) -表现: +当前策略: -- `getPngUrl()` / `getWebpUrl()` 会先输出 `likei-protected:...` -- 运行时只有在真正渲染时才解析成真实地址 -- 大部分图片最终会走缓存并转换成 blob/object URL -- 即使个别页面漏了 `v-smart-img`,运行时也有兜底解析 +- 默认最多缓存 120 张图片。 +- 最大缓存体积为 80MB。 +- 普通发版不更新全局缓存版本。 +- 单张图片替换且不改名时,在 `assetVersions` 中给该图片加版本。 -### 4. `background-image` 泄露点补丁 +示例: -部分重点页面之前通过内联 `background-image: url(...)` 直接暴露真实地址,这类点位已经做了兼容收口。 - -已调整文件: - -- [src/views/Activities/LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue) -- [src/views/Activities/LuckyDollars/Season3/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season3/index.vue) -- [src/views/Activities/SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue) -- [src/views/Activities/SpringFestival/Task.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Task.vue) -- [src/views/Ranking/KingAndQueen/TopList.vue](/D:/programs/likei-h5/src/views/Ranking/KingAndQueen/TopList.vue) - -表现: - -- 写入 CSS 前先通过受保护地址辅助方法解析 -- 模板和样式字符串里直接出现真实图链的情况更少 - -### 5. 构建产物可读性降低 - -构建输出文件名从可读页面名改成了哈希命名。 - -主要文件: - -- [vite.config.js](/D:/programs/likei-h5/vite.config.js) - -表现: - -- JS chunk 文件名不再直接暴露 `TopList`、`WeeklyStar`、`Ranking` 等页面名 -- CSS 文件名也改为哈希命名 -- 静态资源文件名同样使用哈希 - -### 6. 生产包自动清理日志,并保留可切换开关 - -生产相关打包模式下,会自动清掉常见调试日志;同时保留一个方便排查 App 内问题的“保留日志”构建开关。 - -主要文件: - -- [vite.config.js](/D:/programs/likei-h5/vite.config.js) -- [package.json](/D:/programs/likei-h5/package.json) -- [scripts/build-with-version.js](/D:/programs/likei-h5/scripts/build-with-version.js) - -表现: - -- `production` 下自动移除 `console.log` -- `production` 下自动移除 `console.debug` -- `production` 下自动移除 `console.info` -- 生产相关模式下自动移除 `debugger` -- `console.warn` 和 `console.error` 保留,方便线上排查 -- Jenkins 仍可继续使用原有默认打包命令 - -常用命令: - -```bash -npm run build:prod -npm run build:prod:logs +```js +assetVersions: { + 'Activities/LuckyDollars/Season6/bg0.png': '20260514', +} ``` -### 7. 部分高风险页面改为延迟挂载主内容 DOM +### 5. 构建产物收口 -部分活动页和榜单页原来使用 `v-show="!isLoading"` 控制主内容,这会让完整 DOM 树在较早阶段就已经挂载出来,也容易和 `IntersectionObserver` 的注册时序冲突。 - -现在这几页已经改成 `v-if="!isLoading"` 再挂载主内容: - -- [src/views/Activities/LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue) -- [src/views/Activities/LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue) -- [src/views/Activities/SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue) -- [src/views/Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue) -- [src/views/Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue) - -同步补了两类兜底: - -- 对依赖 `IntersectionObserver` 的页面,在 `await nextTick()` 之后再注册观察器 -- 预加载结束逻辑仍然放在 `finally` 中,避免预加载失败时页面长期空白 - -### 8. 装饰背景堆图改为可选 canvas 渲染 - -公共背景组件现在支持可选的 canvas 渲染模式,适合处理活动页和榜单页那种“纯装饰背景层层堆图”的场景。 - -主要文件: - -- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) - -当前已接入的高风险页面: - -- [src/views/Activities/LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue) -- [src/views/Activities/LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue) -- [src/views/Activities/LuckyDollars/Season3/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season3/index.vue) -- [src/views/Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue) -- [src/views/Activities/LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue) -- [src/views/Activities/SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue) -- [src/views/Ranking/GamesKing/index.vue](/D:/programs/likei-h5/src/views/Ranking/GamesKing/index.vue) -- [src/views/Ranking/KingAndQueen/TopList.vue](/D:/programs/likei-h5/src/views/Ranking/KingAndQueen/TopList.vue) -- [src/views/Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue) -- [src/views/Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue) - -表现: - -- 多张纯装饰背景图会先合成到单个 canvas 中,而不是在 DOM 里直接挂一个个 `` -- 从 Elements 面板直接看页面背景结构会比之前更不直观 -- 受保护图片地址仍然继续走现有的运行时解析和缓存链路 - -### 9. 生产构建混淆再收紧,并补一轮明显语义类名收口 - -生产相关构建模式现在显式开启更严格的 `esbuild` 压缩参数,同时针对重点页面收了一轮最显眼的本地类名。 +生产构建使用内容 hash,减少可读文件名暴露,也提升缓存稳定性。 主要文件: - [vite.config.js](/D:/programs/likei-h5/vite.config.js) -- [src/views/Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue) -- [src/views/Activities/LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue) -- [src/views/Activities/SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue) -- [src/views/Activities/SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue) -- [src/views/Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue) -- [src/views/Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue) +- [nginx/default.conf](/D:/programs/likei-h5/nginx/default.conf) +- [nginx/default_hooka.conf](/D:/programs/likei-h5/nginx/default_hooka.conf) -### 10. `Ranking/Couple` 注释修复与背景组件说明补充 +表现: -近期对 [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) 做了一次编码与注释清理: +- JS/CSS/资源文件名使用内容 hash。 +- HTML 短缓存,静态资源长缓存。 +- 内容不变的资源在普通发版后可以继续命中缓存。 -- 清除了上一次编辑中遗留的乱码注释 -- 将核心业务注释统一收口为中文,便于后续维护 +### 6. CSS 背景受保护地址桥接 -同时补充说明两个容易混淆的背景层工具点: +部分页面必须使用 `background-image`。这类场景通过 `toCssBackgroundImage()` 把 `likei-protected:` 地址转换为 CSS 可用的 `url(...)`。 -- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 的 `useCanvas` 是显式开关,不默认全局开启 -- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 用于把 `likei-protected:` 地址还原成 CSS 可用的 `background-image: url(...)` +主要文件: -## 这套方案能挡住什么 +- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) -当前前端加固,对以下场景有一定抬门槛作用: +## Canvas 背景方案已取消 -- 普通浏览器直接打开受保护页面 -- 低门槛 DOM 抓取 -- 从模板或页面源码里直接抄图片地址 -- 从可读的构建产物文件名里反推页面用途 -- 简单右键、拖拽、长按保存 -- 依赖“页面初始 DOM 直接可见”的低成本抄结构方式 +之前 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 增加过 `useCanvas` 模式,用于把多张装饰背景绘制到单个 canvas 中,减少在 Elements 面板里直接看到背景 `` 列表的便利性。 + +经过低端机性能排查后,该方案已取消。 + +当前约定: + +- `BackgroundLayer` 仍保留 `useCanvas` 入参,兼容历史页面调用。 +- 当前实现不再生成 canvas,统一渲染为普通 ``。 +- 图片仍继续走受保护路径解析和图片缓存链路。 +- 不再把 canvas 背景作为默认防扒取方案。 + +取消原因: + +- 大尺寸 canvas 会增加内存占用和绘制成本。 +- 低端安卓 WebView 中,canvas 背景叠加弹幕、跑马灯、头像列表时容易放大卡顿。 +- canvas 只能降低从 Elements 面板直接复制背景结构的便利性,防不住抓包、运行时 Hook 或最终像素提取。 +- 页面打开速度、滑动和动画流畅度优先级高于这点有限防护收益。 ## 当前边界 这套改动依然无法彻底防住以下情况: -- 通过 Network 面板或抓包获取图片请求 -- 有经验的人持续分析前端运行时代码 -- 自定义 WebView、自动化脚本、Hook 等方式截获资源 -- 仅凭视觉观察后重新临摹布局和样式 -- 从本来就被允许渲染页面的客户端中提取 DOM、CSS、JS - -只要客户端能把页面渲染出来,客户端就一定拿到了足够还原页面的信息。 +- 通过 Network 面板或抓包获取图片请求。 +- 有经验的人持续分析前端运行时代码。 +- 自定义 WebView、自动化脚本、Hook 等方式截获资源。 +- 通过截图、录屏、最终像素提取还原视觉。 +- 人工观察后重新临摹布局和样式。 ## 推荐测试方式 @@ -237,18 +163,26 @@ npm run preview 预期结果: -- 非 App 环境下访问受保护页面会跳转到 `/#/not_app` -- 公共页面仍可正常访问 +- 非 App 环境下访问受保护页面会跳转到 `/#/not_app`。 +- 公共页面仍可正常访问。 ### 页面与图片链路验证 在接近生产环境的模式下检查: -- `Activities` / `Ranking` 页面是否仍能正常展示 -- 受保护图片是否仍能正常显示 -- 运行时 DOM 和构建产物里是否更少直接暴露真实图链 -- 本次从 `v-show` 切到 `v-if` 的页面,是否仍能在预加载结束后正常渲染 -- 依赖触底加载的页面,滚动加载是否仍然正常 +- `Activities` / `Ranking` 页面是否仍能正常展示。 +- 受保护图片是否仍能正常显示。 +- 运行时 DOM 和构建产物中是否更少直接暴露真实图链。 +- 替换同名图片时,`assetVersions` 是否能让用户看到新图。 + +### 性能验证 + +重点在低端安卓 WebView 上检查: + +- 首屏打开速度。 +- 背景图片加载是否正常。 +- 滑动是否流畅。 +- 弹幕、跑马灯等持续动画是否造成明显卡顿。 ### 构建产物验证 @@ -260,8 +194,7 @@ npm run build 预期结果: -- `dist/js` 文件名以哈希为主 -- `dist/css` 文件名以哈希为主 -- 构建产物文件名中不再直观出现页面名 -- 生产包中不再保留 `console.log`、`console.debug`、`console.info` 和 `debugger` - +- `dist/js` 文件名以 hash 为主。 +- `dist/css` 文件名以 hash 为主。 +- 构建产物文件名中不再直观出现页面名。 +- 生产包中不再保留 `console.log`、`console.debug`、`console.info` 和 `debugger`。 diff --git a/docs/安全/前端加固-中文更新.md b/docs/安全/前端加固-中文更新.md index f09a3b7..d0e155e 100644 --- a/docs/安全/前端加固-中文更新.md +++ b/docs/安全/前端加固-中文更新.md @@ -1,44 +1,48 @@ # 前端加固增量记录 -这份文档用于补充 [前端加固-中文.md](/D:/programs/likei-h5/docs/安全/前端加固-中文.md) 在最近一轮 `Activities / Ranking` 加固中的新增改动。 +这份文档用于补充 [前端加固-中文.md](/D:/programs/likei-h5/docs/安全/前端加固-中文.md) 在最近几轮 `Activities / Ranking` 加固和性能优化中的新增改动。 ## 本轮补充 -- 生产构建混淆进一步收紧,`esbuild` 在生产相关模式下显式开启了更强的标识符、语法和空白压缩,并去除了合法注释输出。相关文件在 [vite.config.js](/D:/programs/likei-h5/vite.config.js)。 -- 公共背景组件 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 新增了 `useCanvas` 模式,装饰性多层背景会优先绘制到单个 canvas,而不是在 DOM 中直接暴露多张 ``。 -- `canvas` 背景已扩展到更多高风险页面: - [LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue)、 - [LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue)、 - [LuckyDollars/Season3/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season3/index.vue)、 - [LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue)、 - [SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue)、 - [Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue)、 - [Ranking/GamesKing/index.vue](/D:/programs/likei-h5/src/views/Ranking/GamesKing/index.vue)、 - [Ranking/KingAndQueen/TopList.vue](/D:/programs/likei-h5/src/views/Ranking/KingAndQueen/TopList.vue)、 - [Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue)、 - [Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue)。 -- 第二轮语义类名收口已经继续推进,当前已覆盖: - [Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue)、 - [LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue)、 - [SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue)、 - [SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue)、 - [Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue)、 - [Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue)。 -- [SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue) 已额外做了时序加固: - 组件会在 `nextTick()` 之后再注册 `IntersectionObserver`,并在 `onUnmounted` 时执行 `disconnect()`。 +- 生产构建混淆进一步收紧,`esbuild` 在生产相关模式下显式开启了更强的标识符、语法和空白压缩,并移除了合法注释输出。相关文件在 [vite.config.js](/D:/programs/likei-h5/vite.config.js)。 +- 第二轮语义类名收口已继续推进,覆盖了 `Ranking/Couple`、`LuckyDollars/Season4`、`SpringFestival`、`Ranking/Overall`、`Ranking/WeeklyStar` 等页面。 +- [SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue) 已做时序加固:组件会在 `nextTick()` 后再注册 `IntersectionObserver`,并在 `onUnmounted` 时执行 `disconnect()`。 - [Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) 已清理上一轮遗留的乱码注释,并统一替换为中文注释。 -- [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 的 `useCanvas` 继续保持显式开启,而没有改成默认值 `true`。 -- [protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 作用已明确: - 它负责把 `likei-protected:` 地址还原为 CSS 可直接使用的 `url(...)` 字符串,主要用于必须保留 `background-image` 的内联样式场景。 +- [protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 作用已明确:负责把 `likei-protected:` 地址还原为 CSS 可直接使用的 `url(...)` 字符串,主要用于必须保留 `background-image` 的内联样式场景。 + +## Canvas 背景方案调整 + +之前 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 增加过 `useCanvas` 模式,用于把多张装饰背景绘制到单个 canvas 中,减少在 Elements 面板里直接看到背景 `` 列表的便利性。 + +经过低端机性能排查后,该方案已取消: + +- `BackgroundLayer` 仍保留 `useCanvas` 入参,兼容历史页面调用。 +- 当前实现不再根据 `useCanvas` 生成 canvas。 +- 背景图统一通过普通 `` 渲染。 +- 图片仍继续走受保护路径解析和图片缓存链路。 + +取消原因: + +- 大尺寸 canvas 会增加内存占用和绘制成本。 +- 在低端安卓 WebView 上,canvas 背景和弹幕、跑马灯、头像列表同时出现时容易放大卡顿。 +- canvas 只能降低从 DOM 面板直接复制背景结构的便利性,防不住抓包、运行时 Hook 或最终像素提取。 +- 相比这点有限收益,页面打开速度和滑动/动画流畅度优先级更高。 + +后续约定: + +- 不再把 canvas 背景作为默认防扒取方案。 +- 新活动页可以继续写 `useCanvas`,但它只是兼容字段,不再产生 canvas。 +- 防扒取主要依赖受保护路径、图片缓存 blob/object URL、构建产物混淆、资源 hash 和服务端/CDN 权限策略。 ## 当前边界 -- 这轮改动依然属于“提高门槛”,不是“彻底防住”。 -- `canvas` 能降低从 Elements 面板直接抄背景结构的便利度,但防不住抓包、运行时 Hook 或最终像素提取。 -- 类名和结构收口能降低产物可读性,但不能阻止有经验的人在页面完全渲染后继续还原布局。 +- 这轮改动仍然属于“提高门槛”,不是“彻底防住”。 +- 类名和结构收口能降低产物可读性,但不能阻止有经验的人在页面完整渲染后继续还原布局。 +- 前端无法阻止通过 Network 面板、抓包、运行时 Hook、截图或像素提取拿到最终资源和视觉结果。 ## 验证建议 - 使用 `npm run build` 验证生产包可正常构建。 -- 使用 `npm run dev:prod` 或 `npm run preview` 验证活动页、榜单页在生产态逻辑下仍能正常显示。 -- 重点检查 `SpringFestival`、`Season4`、`WeeklyStar`、`Overall` 这些近期改动较多的页面。 +- 使用 `npm run dev:prod` 或 `npm run preview` 验证活动页、榜单页在生产态逻辑下仍能正常展示。 +- 重点检查 `LuckyDollars`、`Couple`、`LesserBairam`、`GamesKing`、`WeeklyStar`、`Overall` 这些近期改动较多的页面。 +- 在低端安卓 WebView 上验证首屏打开、背景加载、滑动、弹幕和跑马灯是否流畅。 diff --git a/docs/安全/前端加固.md b/docs/安全/前端加固.md index b2aa852..222143d 100644 --- a/docs/安全/前端加固.md +++ b/docs/安全/前端加固.md @@ -144,15 +144,15 @@ Extra handling was added where needed: - pages with `IntersectionObserver` targets now register observers only after `await nextTick()` - preload completion still clears `isLoading` in `finally`, so preload failures do not leave the page permanently blank -### 8. Canvas-rendered decorative background stacks +### 8. Decorative background stacks -The shared background layer component supports a canvas rendering mode for stacked background images. +The shared background layer component previously supported a canvas rendering mode for stacked background images. This mode has now been disabled because large canvas backgrounds can increase memory and rendering pressure on low-end WebViews. Main file: - [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) -Current high-risk pages using this mode: +Pages may still pass `useCanvas`, but the prop is now kept only for compatibility: - [src/views/Activities/LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue) - [src/views/Activities/LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue) @@ -167,9 +167,9 @@ Current high-risk pages using this mode: Behavior: -- stacked decorative backgrounds are drawn into a single canvas instead of a readable `` list -- most page-level decorative background structure becomes less obvious in Elements inspection -- protected asset resolution still goes through the existing runtime/cache pipeline +- stacked decorative backgrounds are rendered as normal `` elements through `v-smart-img` +- image loading still goes through protected asset resolution and the existing runtime/cache pipeline +- canvas background rendering is no longer used as a hardening layer because the UX and memory cost is higher than the practical protection gain ### 9. Stronger production minification and selector cleanup @@ -196,7 +196,7 @@ Recent cleanup also covered: Behavior: - garbled comments in `Ranking/Couple/Ranking.vue` were cleaned up and unified into Chinese comments -- `useCanvas` remains an explicit opt-in instead of becoming a global default +- `useCanvas` remains accepted for compatibility, but `BackgroundLayer` ignores it and renders image backgrounds - `toCssBackgroundImage()` stays as the CSS bridge for protected asset URLs ## What This Helps Against @@ -263,4 +263,3 @@ Expected: - `dist/css` filenames are hash-based - no obvious page names appear in bundle filenames - production bundles no longer retain `console.log`, `console.debug`, `console.info`, or `debugger` - diff --git a/docs/活动页/竞品H5与性能优化分析.md b/docs/活动页/竞品H5与性能优化分析.md new file mode 100644 index 0000000..3626a33 --- /dev/null +++ b/docs/活动页/竞品H5与性能优化分析.md @@ -0,0 +1,345 @@ +# 竞品 H5 与本项目性能优化分析 + +## 背景 + +本次主要排查 `src/views/Activities` 和 `src/views/Ranking` 下活动页、榜单页在低端安卓 WebView 中打开和运行卡顿的问题。测试现象中,2GB 内存低端机在展示弹幕 `Barrage`、滚动文本、跑马灯等持续动画时明显掉帧,离开这些区域后流畅度恢复。 + +本次同时参考了两类竞品 H5: + +- Yalla / qrsshine 活动页: + - `activity2.yalla.live/activity/2025dist/Thursday` + - `activity2.yalla.live/activity/2025dist/sea2` + - `activity2.yalla.live/activity/2025dist/userCarnival2` + - `activity2.qrsshine.com/activity/2024dist/activityMap` + - `activity2.qrsshine.com/activity/2024dist/roomHonorSystem` +- Hawatalk 活动页: + - `cdn.hawatalk.com/activitys/hexactivityprojectv3/guild-world-peak` + - `cdn.hawatalk.com/activitys/hexactivityprojectv3/miss-pk-2026` + - `cdn.hawatalk.com/activitys/hexactivityprojectv3/dynamic-user-id` + - `cdn.hawatalk.com/activitys/hexactivityprojectv3/recharge-activity` + - `cdn.hawatalk.com/activitys/hexactivityprojectv3/tower-tycoon` + - `cdn.hawatalk.com/activitys/hexactivityprojectv3/predator-journey` + +## 竞品 H5 的主要特点 + +### 1. 活动独立包更明显 + +竞品活动页大多是独立活动目录,HTML 很薄,活动自身 JS 较小。Hawatalk 新旧活动页中,活动入口 JS 通常只有几十 KB 到八十多 KB,公共 vendor 大约两百多 KB。Yalla 部分页面 vendor 较大,但也是按活动目录独立部署。 + +这种方式的好处是: + +- 单个活动的首屏代码体积更可控。 +- 活动之间互相影响小。 +- 活动资源可以按目录独立缓存和更新。 + +### 2. 静态资源缓存策略更稳定 + +竞品普遍使用内容 hash 文件名。HTML 短缓存或不缓存,JS/CSS/图片等静态资源长缓存。这样普通发版时,只要资源内容不变,用户端就能继续命中缓存。 + +我们的项目之前在构建文件名里带 `buildTimestamp`,每次构建都会导致 JS/CSS 文件 URL 改变,哪怕内容没变也会让缓存失效。这个问题已在本次优化中处理。 + +### 3. 动画和视觉效果相对克制0 + +竞品页面也会有活动视觉、排行列表和特效,但多数页面不会在首屏同时叠加大量持续动画、canvas 背景、弹幕、跑马灯、模糊滤镜和大批图片解码。 + +本项目部分页面中,持续动画与图片层叠加较多,在高端机上影响不明显,但低端 WebView 中会明显放大。 + +## 我们项目的主要差异与问题 + +### 1. 持续动画叠加较多 + +在 `src/views/Activities` 和 `src/views/Ranking` 中,存在多类持续动画: + +- 弹幕 `Barrage` +- tab 文字滚动 +- 奖励列表跑马灯 +- 抽奖滚动动画 +- 倒计时定时器 + +其中弹幕和跑马灯最容易在低端机上触发掉帧,因为它们会持续制造合成层,并且通常伴随头像、奖品图、渐变文字和透明背景。 + +### 2. `backdrop-filter` 成本高 + +部分页面使用了 `backdrop-filter: blur(...)`,例如: + +- `src/views/Ranking/Couple/components/Barrage.vue` +- `src/views/Activities/LuckyDollars/Season5/components/Barrage.vue` +- 多个弹窗、卡片、奖励区域 + +`backdrop-filter` 在低端安卓 WebView 上成本很高。它会让浏览器每帧采样并合成背景,和弹幕/跑马灯一起出现时尤其容易卡顿。 + +### 3. canvas 背景内存压力大,已取消该方案 + +`BackgroundLayer` 曾支持 `useCanvas=true`,会先通过图片缓存加载图片,再把多张背景图绘制到一个 canvas 上。这个设计主要用于提高直接扒取 DOM 背景结构的门槛。 + +经过低端机性能排查后,该方案已取消: + +- 页面继续保留 `useCanvas` 入参,兼容历史调用。 +- 当前 `BackgroundLayer` 不再生成 canvas。 +- 背景图统一通过 `` 渲染。 +- 图片仍继续走受保护路径解析和图片缓存链路。 + +取消原因是大尺寸 canvas 会占用较高内存和 GPU 资源。如果页面同时有弹幕、跑马灯、头像列表,就容易出现明显卡顿。canvas 对防扒取只有“提高一点直接复制 DOM 的门槛”的作用,防不住抓包、运行时 Hook 或最终像素提取,因此优先保证页面流畅度。 + +### 4. 非关键图片预加载过早 + +多个页面 mounted 后立即调用 `preloadOtherImages()`。虽然缓存能减少重复网络下载,但打开页面时立即预加载非关键图片,会和首屏渲染、背景绘制、接口返回后的列表渲染、弹幕动画抢资源。 + +因此缓存不是越激进越好。缓存能减少网络重复下载,但不能减少图片解码、DOM 渲染和 GPU 合成成本。 + +### 5. 部分隐藏内容仍然挂载 + +一些页面用 `v-show` 切换大模块。`v-show` 只是隐藏,不会卸载组件。隐藏模块中的 DOM、图片、定时器、观察器仍可能占用内存和运行成本。活动页/榜单页中如果大模块较多,低端机压力会更明显。 + +## 本次已经完成的优化 + +### 1. 构建文件名改为内容 hash + +修改文件: + +- `vite.config.js` + +已移除构建文件名中的 `buildTimestamp`,改为内容 hash: + +- `js/[hash].js` +- `css/[hash].[ext]` +- `assets/[hash].[ext]` + +这样普通发版时,内容没变的 JS/CSS/图片 URL 不会改变,能继续命中浏览器/CDN 缓存。 + +### 2. Nginx 静态资源长缓存 + +修改文件: + +- `nginx/default.conf` +- `nginx/default_hooka.conf` + +静态资源长缓存范围从 JS/CSS 扩展到图片、字体、视频等: + +- `js` +- `css` +- `png` +- `jpg` +- `jpeg` +- `gif` +- `webp` +- `svg` +- `ico` +- `ttf` +- `otf` +- `woff` +- `woff2` +- `mp4` + +缓存头为: + +```nginx +Cache-Control "public, max-age=2592000, immutable" +``` + +### 3. 图片缓存容量下调并支持单图版本 + +修改文件: + +- `src/config/imageCacheVersion.js` +- `src/config/imagePaths.js` +- `src/utils/image/imageCacheManager.js` + +缓存容量从最多 200 张、200MB 调整为: + +```js +cacheLimits: { + maxItems: 120, + maxSizeMB: 80, +} +``` + +原因是低端机内存有限,过大的本地图片缓存会增加内存和存储管理压力。 + +同时新增 `assetVersions` 单图/目录版本机制: + +```js +assetVersions: { + 'Activities/LuckyDollars/Season6/bg0.png': '20260514', +} +``` + +这会让该图片 URL 自动加上 `?iv=20260514`。用户端不会继续命中旧图缓存,也不需要全量清空所有图片缓存。 + +使用建议: + +- 普通发版:不要改全局 `version`。 +- 单张图片替换且不改文件名:只在 `assetVersions` 中加或改该图片版本。 +- 大批量图片迁移、缓存污染:才更新全局 `version`。 +- `updateTime` 只作为人工记录,不参与实际缓存命中逻辑,不是每次改 `assetVersions` 都必须更新。 +- 单图版本不要下次发版立刻删除,至少保留超过静态资源最长缓存周期,当前建议不少于 30 天。 + +### 4. 图片预加载改为限流和 idle + +修改文件: + +- `src/utils/image/imagePreloader.js` + +优化点: + +- 默认并发限制为 3。 +- 去重空值和重复 URL。 +- 支持 `preloadImagesIdle()`。 +- 单张图片失败不阻塞整个预加载。 + +页面中非关键图片预加载已改为 idle: + +- `src/views/Ranking/Couple/index.vue` +- `src/views/Ranking/GamesKing/index.vue` +- `src/views/Activities/LesserBairam/index.vue` +- `src/views/Activities/SpringFestival/index.vue` +- `src/views/Activities/LuckyDollars/Season3/index.vue` +- `src/views/Activities/LuckyDollars/Season4/index.vue` +- `src/views/Activities/LuckyDollars/Season5/index.vue` + +### 5. App 先挂载,再后台检查版本 + +修改文件: + +- `src/main.js` + +之前版本检查会在 app mount 前执行,可能影响打开速度。现在先挂载页面,再通过 idle/timeout 后台检查版本。 + +### 6. 新增通用低端机性能模式 + +新增文件: + +- `src/utils/performanceMode.js` + +低端机判断不针对单一型号,而是通用条件: + +- `navigator.deviceMemory <= 2` +- 安卓低核心设备 +- 旧安卓低核心 WebView +- 用户系统开启 `prefers-reduced-motion` + +启动时会给 `html` 添加: + +- `low-performance-mode` +- `reduce-motion-mode` + +这些 class 用于全局 CSS 和组件内降级。 + +### 7. 取消 canvas 背景渲染 + +修改文件: + +- `src/components/BackgroundLayer.vue` + +当前即使页面传入 `useCanvas=true`,也会渲染为普通图片背景。这样仍然走图片缓存和受保护路径解析,但避免大 canvas 带来的额外内存和绘制成本。 + +`useCanvas` 入参暂时保留,避免大量历史页面需要同步改模板;后续新页面不再依赖它做防扒取。 + +### 8. `v-smart-img` 低端机关闭淡入动画 + +修改文件: + +- `src/directives/smartImage.js` + +低端机下关闭图片 opacity fade 动画,并设置: + +```js +el.decoding = 'async' +``` + +这样可以减少图片加载时的主线程和合成压力。 + +### 9. 全局低端机移除高成本模糊和跑马灯动画 + +修改文件: + +- `src/styles/global.css` + +低端机下统一移除: + +```css +backdrop-filter +-webkit-backdrop-filter +``` + +并暂停 `.scroll-wrapper` 这类持续跑马灯动画。系统开启减少动画时,也会暂停弹幕和滚动动画。 + +### 10. 弹幕组件专项优化 + +修改文件: + +- `src/views/Activities/LuckyDollars/Season3/components/Barrage.vue` +- `src/views/Activities/LuckyDollars/Season4/components/Barrage.vue` +- `src/views/Activities/LuckyDollars/Season5/components/Barrage.vue` +- `src/views/Ranking/Couple/components/Barrage.vue` + +优化点: + +- 低端机弹幕最大同时显示数降到每轨 1 条。 +- 低端机弹幕生成间隔至少 4500ms。 +- 页面隐藏时暂停弹幕并清空当前动画 DOM。 +- 弹幕区域离开视口时暂停并清空当前动画 DOM。 +- 去掉 `watch(..., { deep: true })`,避免深度监听大数组。 +- 弹幕文字在入轨时预计算,减少模板每次渲染重复计算。 +- 动画结束时直接从轨道移除,不再先标记 completed 再由 interval 清理。 +- Couple 弹幕在低端模式下隐藏中间徽章图,减少一张持续参与动画的图片。 + +## 后续建议 + +### 1. 优先把大模块从 `v-show` 改为按需挂载 + +活动页中排行榜、玩法、奖励、弹窗等大模块,如果不是首屏必须展示,建议逐步改成: + +- 首次进入时 `v-if` 挂载。 +- 数据和图片进入模块时再加载。 +- 离开模块后保留必要状态,释放重图片 DOM。 + +本次已按体验权衡做了选择性调整: + +- 高频按钮、轻量图片状态仍保留 `v-show`,避免点击切换时闪烁。 +- 会触发接口、预加载、定时器或 `IntersectionObserver` 的大组件改为 `v-if`。 +- 已调整 `Ranking/Couple` 的 `Ranking` / `GamePlay` 一级模块。 +- 已调整 `Ranking/Couple/GamePlay` 内的 `lottery` / `signIn` / `exchange` 子模块。 +- 已调整 `SpringFestival` 的 `Task`、`Ranking`、`RankingReward`、`RechargeReward` 模块。 + +### 2. 继续压缩活动页图片 + +建议优先处理: + +- 背景长图 +- 弹窗背景 +- 奖励图 +- 大面积 PNG + +能用 WebP 的尽量用 WebP。静态装饰图可以做多尺寸或压缩版,低端机使用轻量图。 + +### 3. 跑马灯文本尽量避免模板内测量 DOM + +例如 `isTextOverflow(text)` 这类函数,如果在模板中直接调用,并且内部创建 DOM + `getBoundingClientRect()`,容易触发布局计算。建议后续改成: + +- 数据变化后统一测量一次。 +- 缓存结果。 +- 低端机直接省略号展示,不滚动。 + +### 4. 弹幕建议抽成公共组件 + +现在 Season3/4/5 和 Couple 都有独立弹幕组件。后续可以抽一个公共 `PerformanceBarrage`,不同活动只传渲染内容和样式配置,避免同类性能修复重复做多次。 + +### 5. 真机验证顺序 + +建议在低端安卓 WebView 上按下面顺序对比: + +1. 打开 LuckyDollars Season5 首页,观察弹幕区域 FPS。 +2. 进入/离开弹幕区域,确认离开视口后不再持续掉帧。 +3. 打开 Couple 首页,观察告白墙弹幕和顶部跑马灯。 +4. 切换 Ranking / GamePlay,观察内存是否持续上涨。 +5. 冷启动和二次打开分别测试,确认缓存命中后网络变少,同时页面不卡顿。 + +## 构建验证 + +已使用项目依赖执行生产构建,构建通过。 + +仍保留两个历史警告,和本次优化无直接关系: + +- `Azizi/secondBg.png` 构建时未解析,会保留到运行时解析。 +- `src/utils/http.js` 同时被静态和动态导入,无法单独拆到动态 chunk。 diff --git a/src/components/BackgroundLayer.vue b/src/components/BackgroundLayer.vue index 70a0b86..f97c6a2 100644 --- a/src/components/BackgroundLayer.vue +++ b/src/components/BackgroundLayer.vue @@ -1,14 +1,6 @@