chore: 取消动态水印

This commit is contained in:
hzj 2026-04-21 18:23:45 +08:00
parent 816f4a1842
commit 073c61e6bb
5 changed files with 60 additions and 571 deletions

View File

@ -1,361 +0,0 @@
# `atu-prod` 分支前端加固迁移执行手册
## 目的
这份文档用于在后续切换到 `atu-prod` 分支后,按固定步骤复用当前分支已经落地的前端防扒取加固方案。
使用方式很简单:
- 当你切到 `atu-prod` 后,只要说明“按 `atu-prod-hardening-playbook.zh-CN.md` 执行”
- 我就按这份文档中的顺序检查、迁移、修复和验证
这份文档不是泛泛的说明,而是面向实际执行的作业手册。
## 适用前提
- `atu-prod` 分支与当前分支在业务功能上大体一致
- 主要差异集中在样式、布局、局部结构命名
- 活动页和榜单页的核心交互、接口、资源使用方式基本相同
如果后续发现 `atu-prod` 在页面结构上差异很大,这份手册仍然能作为基线,但执行时要允许局部调整。
## 迁移目标
切换到 `atu-prod` 后,需要尽量恢复当前分支已经完成的这些能力:
1. 受保护页面仅允许在 App 环境下访问
2. 运行时基础防调试、防复制、防拖拽和动态水印
3. `Activities / Ranking` 图片地址保护与运行时解析
4. `background-image` 相关真实图链泄露点收口
5. 构建产物哈希化和可读性降低
6. 生产相关模式日志清理与保留日志开关
7. `v-show``v-if` 的主内容延迟挂载改造
8. `BackgroundLayer``useCanvas` 背景合成方案
9. 明显语义类名收口
10. 已发现问题页的修复和清理
## 执行顺序
后续在 `atu-prod` 分支执行时,按下面顺序做,不要跳步。
### 第 1 步:先对照基础能力文件
优先检查这些全局能力文件是否已经存在、是否需要同步:
- [src/utils/routeGuard.js](/D:/programs/likei-h5/src/utils/routeGuard.js)
- [src/utils/http.js](/D:/programs/likei-h5/src/utils/http.js)
- [src/config/security.js](/D:/programs/likei-h5/src/config/security.js)
- [src/utils/runtimeSecurity.js](/D:/programs/likei-h5/src/utils/runtimeSecurity.js)
- [src/main.js](/D:/programs/likei-h5/src/main.js)
- [src/App.vue](/D:/programs/likei-h5/src/App.vue)
- [src/config/imagePaths.js](/D:/programs/likei-h5/src/config/imagePaths.js)
- [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)
- [src/utils/image/imageCacheManager.js](/D:/programs/likei-h5/src/utils/image/imageCacheManager.js)
- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue)
- [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)
这一步的目标是先把“全局基础设施”对齐,再进页面级迁移。
### 第 2 步:扫描 `Activities / Ranking` 页面
重点扫描这两个目录:
- [src/views/Activities](/D:/programs/likei-h5/src/views/Activities)
- [src/views/Ranking](/D:/programs/likei-h5/src/views/Ranking)
执行时重点看四类点:
1. 是否仍有大量直接 `<img :src="getPngUrl(...)" />`
2. 是否仍有 `background-image: url(...)`
3. 是否存在 `v-show="!isLoading"` 控制主内容
4. 是否存在明显语义类名、明显业务含义 chunk 入口、测试日志
### 第 3 步:优先迁移高风险页面
后续在 `atu-prod` 中优先处理这些已经确认过风险较高、且当前分支已加固的页面:
- [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/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/Activities/SpringFestival/Task.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Task.vue)
- [src/views/Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue)
- [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.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)
## 页面级迁移检查清单
后续每处理一个页面,都按这份清单检查。
### A. 图片链路
- 是否统一通过 `getPngUrl()` / `getWebpUrl()` 走受保护地址
- 是否尽量使用 `v-smart-img`
- 是否存在直接暴露真实 OSS 地址的模板位点
- 是否存在必须保留的 CSS 背景图场景
### B. 背景层
- 页面背景是否可改成 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue)
- 如果是多层纯装饰背景,是否开启 `:useCanvas="true"`
- 如果只是普通内容图,不强制改成 canvas
说明:
- `useCanvas` 不应一刀切设成默认值 `true`
- 它只针对敏感背景层启用,避免把普通页面也全部切到更重的渲染路径
### C. 主内容挂载时机
- 页面是否仍使用 `v-show="!isLoading"` 控制主内容
- 如果页面存在预加载和监听时序问题,优先改成 `v-if="!isLoading"`
- 如果页面依赖 `IntersectionObserver`,应在 `await nextTick()` 后再注册
- 如果预加载可能失败,确保在 `finally` 中放行主内容,避免整页空白
### D. 语义暴露
- 是否有过于直白的局部类名
- 是否有业务含义明显的本地变量名或测试痕迹
- 是否有无用日志、调试文案、历史残留注释
### E. 组件生命周期
- `IntersectionObserver` 是否在卸载时 `disconnect()`
- 定时器是否在卸载时清理
- 残留监听代码是否已经失效却未移除
## 已知问题与处理记录
后续迁到 `atu-prod` 时,优先留意这些已经踩过的坑。
### 1. `v-show` 会让主内容 DOM 提前暴露
现象:
- 页面还在预加载时DOM 已经被挂载
- 容易被直接从 `Elements` 面板里抄结构
- 某些触底监听也会因为节点时序问题失灵
处理方式:
- 将主内容改为 `v-if="!isLoading"`
- 对依赖监听的组件,在 `nextTick()` 后注册观察器
- 在 `finally` 中放行主内容
涉及页面:
- `LoginReward`
- `LuckyDollars/Season4`
- `SpringFestival`
- `Ranking/Couple`
- `Ranking/Overall`
### 2. `IntersectionObserver` 挂载时机不稳
现象:
- 监听节点存在,但绑定太早
- 页面结构还没稳定时就执行 `observe()`
- 页面切走后观察器残留
处理方式:
- `await nextTick()`
- 再 `observer.observe(...)`
- `onUnmounted``disconnect()`
重点页面:
- [src/views/Activities/SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue)
### 3. `Ranking/Couple/Ranking.vue` 曾有残留无效加载监听代码
现象:
- 组件里保留了已经取消的加载模块逻辑
- 相关 `ref` 节点已不存在,但监听代码还在
处理方式:
- 清理失效的 `IntersectionObserver` 逻辑
- 删除无效加载模块相关变量
- 保留当前真实在用的数据展示逻辑
### 4. `Ranking/Couple/Ranking.vue` 曾出现乱码注释
现象:
- 早期编辑和编码处理后,中文注释出现乱码
处理方式:
- 清理乱码注释
- 统一改为中文注释
- 保持逻辑不变,只修复可读性
### 5. `BackgroundLayer` 为什么不是默认 `useCanvas=true`
结论:
- 因为它是公共组件
- 默认全开会把所有背景层都切到 canvas 路径
- 会增加普通页面的图片加载、重绘和兼容风险
当前策略:
- 只在高风险的 `Activities / Ranking` 页面显式传入 `:useCanvas="true"`
### 6. `toCssBackgroundImage()` 的作用
结论:
- 它不是加密层
- 它是把 `likei-protected:` 地址还原成 CSS 可用的 `url(...)`
- 主要用于必须保留 `background-image` 的点位
相关文件:
- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js)
## 构建与验证步骤
### 基础构建
执行:
```bash
npm run build
```
预期:
- 构建成功
- 保留已知提示,但不新增本轮改动引起的报错
### 生产态验证
执行:
```bash
npm run dev:prod
```
或:
```bash
npm run build
npm run preview
```
验证点:
- 非 App 环境访问受保护页会被拦截
- 公共页可正常打开
- `Activities / Ranking` 页面的图片仍能显示
- `BackgroundLayer` 的 canvas 背景仍能正常渲染
- 触底加载和倒计时逻辑不受影响
### 日志构建验证
执行:
```bash
npm run build:prod
npm run build:prod:logs
npm run build:atu-prod
npm run build:atu-prod:logs
```
验证点:
- 默认生产相关包清理 `console.log``console.debug``console.info``debugger`
- 带 `:logs` 的命令仍保留调试日志,便于 App 内定位问题
- Jenkins 使用原有命令不需要改发布流程
## 建议的迁移方法
后续切到 `atu-prod` 后,建议按下面方式推进:
1. 先同步全局基础能力文件
2. 再扫描 `Activities / Ranking`
3. 先迁移高风险页面
4. 每迁移一页就做局部验证
5. 最后统一跑 `npm run build`
6. 再补文档同步
## 后续文档同步方法
如果后续主分支继续更新了这些安全文档,想把最新 md 同步到当前分支,不需要再手工复制。
先决条件:
- 主分支上的文档更新已经提交到 Git
- 当前已经切换到目标分支,例如 `atu_prod`
执行命令:
```bash
npm run docs:sync:security -- master
```
如果你想从远端主分支同步,也可以用:
```bash
npm run docs:sync:security -- origin/master
```
这条命令会同步这些文件:
- `docs/security/frontend-hardening.md`
- `docs/security/frontend-hardening.zh-CN.md`
- `docs/security/frontend-hardening.zh-CN.updates.md`
- `docs/security/atu-prod-hardening-playbook.zh-CN.md`
对应脚本文件:
- [scripts/sync-security-docs.js](/D:/programs/likei-h5/scripts/sync-security-docs.js)
注意:
- 如果源分支里还没有提交这些 md脚本会直接报错
- 所以推荐流程是“先在主分支提交文档,再切到目标分支执行同步命令”
## 文档同步要求
后续在 `atu-prod` 做迁移时,文档也要同步更新,至少包含:
- 本次迁移到 `atu-prod` 的实际范围
- 哪些页面已迁移
- 哪些页面因结构差异暂未迁移
- 新遇到的问题和处理方式
- 最终验证结果
建议优先更新这些文档:
- [docs/security/frontend-hardening.zh-CN.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.md)
- [docs/security/frontend-hardening.zh-CN.updates.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.updates.md)
- [docs/security/atu-prod-hardening-playbook.zh-CN.md](/D:/programs/likei-h5/docs/security/atu-prod-hardening-playbook.zh-CN.md)
## 后续对我下指令的推荐说法
后续你切到 `atu-prod` 分支后,可以直接这样说:
- 按 `atu-prod-hardening-playbook.zh-CN.md` 执行一遍
- 先按手册同步全局加固能力,再扫描 `Activities / Ranking`
- 按手册把高风险页面优先迁移,并同步更新文档
看到这类指令后,我默认会:
- 先核对分支当前状态
- 再按这份文档逐项执行
- 遇到结构差异时在不偏离目标的前提下做适配
- 最后把实际执行结果写回文档

View File

@ -2,20 +2,20 @@
## Purpose
This document records the frontend-only hardening work added to this repository to raise the cost of scraping, asset reuse, and low-effort page reconstruction.
This document records the frontend-only hardening currently applied on the main branch to raise the cost of scraping, asset reuse, and low-effort page reconstruction.
The goal is to make abuse less convenient, not to claim absolute protection.
## Scope
All changes described here are limited to the frontend project.
All changes described here stay within the frontend project.
This work does not rely on:
They do not rely on:
- backend API changes
- private OSS buckets
- signed image URLs
- server-side watermark rendering
- server-side image rewriting
## What Was Added
@ -37,7 +37,7 @@ Behavior:
### 2. Runtime anti-debug and anti-copy restrictions
Protected pages now add a lightweight runtime restriction layer in production-like modes.
Protected pages add a lightweight runtime restriction layer in production-like modes.
Main files:
@ -50,7 +50,6 @@ Behavior:
- blocks common devtools shortcuts
- blocks right-click, drag, copy, and text selection on protected pages
- reduces long-press image save convenience
- adds page-level dynamic watermark overlays
### 3. Protected asset URL layer for `Activities` and `Ranking`
@ -69,7 +68,7 @@ Behavior:
- `getPngUrl()` and `getWebpUrl()` emit `likei-protected:...`
- runtime code resolves protected URLs only when rendering is needed
- most image rendering flows now end up using cached blob/object URLs
- even pages that missed `v-smart-img` still get a runtime fallback for protected image `src`
- pages that missed `v-smart-img` still get a runtime fallback for protected image `src`
### 4. `background-image` compatibility fixes
@ -102,11 +101,9 @@ Behavior:
- CSS files also use hash-based names
- static asset filenames are hash-based
This does not stop reverse engineering, but it lowers the convenience of reading page structure from `dist/`.
### 6. Production log stripping with a troubleshooting switch
Production-like builds strip common debug output, while still supporting a keep-logs build for app-side troubleshooting.
Production-like builds strip common debug output while still supporting a keep-logs build for app-side troubleshooting.
Main files:
@ -116,9 +113,9 @@ Main files:
Behavior:
- `production` and `production-atu` builds remove `console.log`
- `production` and `production-atu` builds remove `console.debug`
- `production` and `production-atu` builds remove `console.info`
- `production` builds remove `console.log`
- `production` builds remove `console.debug`
- `production` builds remove `console.info`
- production-like builds remove `debugger`
- `console.warn` and `console.error` are preserved
- Jenkins can keep using the original default build commands
@ -128,8 +125,6 @@ Useful commands:
```bash
npm run build:prod
npm run build:prod:logs
npm run build:atu-prod
npm run build:atu-prod:logs
```
### 7. Deferred main DOM mounting for selected high-risk pages
@ -148,15 +143,10 @@ 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
- this keeps the original lazy-load behavior more stable after switching from `v-show` to `v-if`
Important limit:
This is only a weak hardening step. It reduces early DOM exposure and makes initial inspection less direct, but it does not stop scraping once the page is fully rendered on the client.
### 8. Canvas-rendered decorative background stacks
The shared background layer component now supports a canvas rendering mode for stacked background images.
The shared background layer component supports a canvas rendering mode for stacked background images.
Main file:
@ -181,11 +171,9 @@ Behavior:
- most page-level decorative background structure becomes less obvious in Elements inspection
- protected asset resolution still goes through the existing runtime/cache pipeline
This does not hide the pixels from a determined attacker, but it reduces direct DOM readability and low-effort structure copying.
### 9. Stronger production minification and selector cleanup
Production-like builds now use stricter `esbuild` minification flags, and a small set of highly readable page-local class names was reduced on sensitive pages.
Production-like builds use stricter `esbuild` minification flags, and a small set of highly readable page-local class names was reduced on sensitive pages.
Main files:
@ -197,13 +185,19 @@ Main files:
- [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)
### 10. `Ranking/Couple` comment cleanup and helper clarification
Recent cleanup also covered:
- [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue)
- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue)
- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js)
Behavior:
- production-like builds explicitly minify identifiers, syntax, and whitespace
- legal comments are removed from build output
- some obvious local selector names were replaced with shorter neutral names
- selector cleanup now also covers obvious ranking navigation and history labels on additional `Activities` and `Ranking` pages
- `SpringFestival/Ranking` now waits for `nextTick()` before observing its load sentinel and disconnects the observer on unmount
- 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
- `toCssBackgroundImage()` stays as the CSS bridge for protected asset URLs
## What This Helps Against
@ -226,11 +220,7 @@ These changes still do not fully prevent:
- rebuilding the same layout after visual inspection
- extracting DOM, CSS, and JS from any client that is allowed to render the page
Important rule:
If the client can render the page, the client must receive enough information to reproduce the page.
That is why frontend-only hardening can only increase cost, not provide absolute protection.
If the client can render the page, the client must receive enough information to reproduce it. Frontend-only hardening can only increase cost, not provide absolute protection.
## Recommended Testing
@ -249,11 +239,6 @@ npm run build
npm run preview
```
Expected:
- protected routes redirect to `/#/not_app` outside the app
- public routes still open normally
### Asset and page rendering checks
In production-like mode, verify:
@ -279,17 +264,3 @@ Expected:
- no obvious page names appear in bundle filenames
- production bundles no longer retain `console.log`, `console.debug`, `console.info`, or `debugger`
## Suggested Next Steps
If staying frontend-only:
- move more decorative shells from plain DOM/CSS into canvas rendering
- reduce semantic structure in the most sensitive event pages
- keep replacing remaining raw `background-image` and direct image flows with protected helpers
If minimal backend support becomes acceptable later:
- move sensitive OSS paths to private read
- issue short-lived signed URLs
- watermark sensitive images per user on the server side
- validate app session or device identity before returning sensitive assets

View File

@ -2,24 +2,18 @@
## 目的
这份文档用于记录当前仓库内已经落地的前端侧防扒取、防复用加固方案。
这份文档用于记录当前主分支已经落地的前端防扒取加固方案。
核心目标是提高扒图、抄页面、低成本复刻的门槛,而不是宣称“前端已经绝对防住”。
如需在后续切换到 `atu-prod` 分支后复用整套处理,请直接参考:
- [docs/security/atu-prod-hardening-playbook.zh-CN.md](/D:/programs/likei-h5/docs/security/atu-prod-hardening-playbook.zh-CN.md)
## 范围
本次加固严格限制在当前前端项目内完成。
当前方案不依赖:
本次加固严格限制在当前前端项目内完成,不依赖:
- 后端接口改造
- OSS 私有读
- 私有 OSS
- 短时签名 URL
- 服务端动态水印
- 服务端动态出图
## 已落地能力
@ -39,7 +33,7 @@
- 受保护页面在非 App 环境下会跳转到 `/#/not_app`
- 受保护页面内的接口请求在缺少 App bridge 时会被拒绝
### 2. 运行时防调试、防复制与页面水印
### 2. 运行时防调试与防复制限制
对受保护页面增加了一层轻量运行时限制。
@ -54,7 +48,6 @@
- 屏蔽常见开发者工具快捷键
- 屏蔽右键、拖拽、复制、文本选择等交互
- 降低长按保存图片的便利性
- 给受保护页面增加动态水印覆盖
### 3. `Activities` / `Ranking` 图片受保护地址层
@ -77,7 +70,7 @@
### 4. `background-image` 泄露点补丁
部分重点页面之前通过内联 `background-image: url(...)` 直接暴露真实地址,这类点位已经做了兼容收口。
部分重点页面之前通过内联 `background-image: url(...)` 直接暴露真实地址,这类点位已经做了兼容收口。
已调整文件:
@ -106,8 +99,6 @@
- CSS 文件名也改为哈希命名
- 静态资源文件名同样使用哈希
这一步不能阻止逆向,但能降低别人直接从 `dist/` 推断页面结构的便利性。
### 6. 生产包自动清理日志,并保留可切换开关
生产相关打包模式下,会自动清掉常见调试日志;同时保留一个方便排查 App 内问题的“保留日志”构建开关。
@ -120,9 +111,9 @@
表现:
- `production` `production-atu` 下自动移除 `console.log`
- `production` `production-atu` 下自动移除 `console.debug`
- `production` `production-atu` 下自动移除 `console.info`
- `production` 下自动移除 `console.log`
- `production` 下自动移除 `console.debug`
- `production` 下自动移除 `console.info`
- 生产相关模式下自动移除 `debugger`
- `console.warn``console.error` 保留,方便线上排查
- Jenkins 仍可继续使用原有默认打包命令
@ -132,8 +123,6 @@
```bash
npm run build:prod
npm run build:prod:logs
npm run build:atu-prod
npm run build:atu-prod:logs
```
### 7. 部分高风险页面改为延迟挂载主内容 DOM
@ -153,17 +142,9 @@ npm run build:atu-prod:logs
- 对依赖 `IntersectionObserver` 的页面,在 `await nextTick()` 之后再注册观察器
- 预加载结束逻辑仍然放在 `finally` 中,避免预加载失败时页面长期空白
这一步的作用要明确:
- 可以减少初始阶段 DOM 直接暴露的程度
- 可以让“页面刚进来就看 Elements 面板抄结构”变得没那么直接
- 不能阻止页面渲染完成后的继续抓取
也就是说,它属于弱加固,而不是决定性的防扒手段。
### 8. 装饰背景堆图改为可选 canvas 渲染
公共背景组件现在支持可选的 canvas 渲染模式,适合拿来处理活动页和榜单页那种“纯装饰背景层层堆图”的场景。
公共背景组件现在支持可选的 canvas 渲染模式,适合处理活动页和榜单页那种“纯装饰背景层层堆图”的场景。
主要文件:
@ -184,12 +165,10 @@ npm run build:atu-prod:logs
表现:
- 多张纯装饰背景图会先合成到单个 canvas 中,而不是在 DOM 里直接挂一 `<img>`
- 多张纯装饰背景图会先合成到单个 canvas 中,而不是在 DOM 里直接挂一个个 `<img>`
- 从 Elements 面板直接看页面背景结构会比之前更不直观
- 受保护图片地址仍然继续走现有的运行时解析和缓存链路
这一步不能阻止有经验的人继续拿到像素内容,但能明显降低“直接看 DOM 抄背景结构”的便利度。
### 9. 生产构建混淆再收紧,并补一轮明显语义类名收口
生产相关构建模式现在显式开启更严格的 `esbuild` 压缩参数,同时针对重点页面收了一轮最显眼的本地类名。
@ -204,35 +183,17 @@ npm run build:atu-prod:logs
- [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)
表现:
- 生产相关构建会显式压缩标识符、语法和空白
- 构建产物中的合法注释会被去掉
- 一部分过于直白的页面本地类名已经换成更短、更弱语义的名字
### 10. `Ranking/Couple` 注释修复与背景组件说明补充
近期对 [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) 做了一次编码与注释清理:
- 清除了上一轮编辑中遗留的乱码注释
- 将核心业务注释统一收口为中文,便于后续维护和分支迁移
- 保留当前逻辑不变,只修正文案可读性与排查体验
- 清除了上一次编辑中遗留的乱码注释
- 将核心业务注释统一收口为中文,便于后续维护
同时补充说明两个容易混淆的背景层工具点:
- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 的 `useCanvas` 是显式开关,不默认全局开启
- 原因是 `BackgroundLayer` 是公共组件,默认全开会把所有背景层都切到 canvas 路径,带来额外的图片加载、重绘和兼容风险
- 当前只在 `Activities / Ranking` 这类高风险页面显式传入 `:useCanvas=\"true\"`,把成本控制在需要加固的范围内
- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 用于把 `likei-protected:` 地址还原成 CSS 可用的 `background-image: url(...)`
- 这个方法是兼容桥接,不是额外的加密层,主要服务于仍需使用内联背景图的场景
如果后续需要在 `atu-prod` 分支做同类处理,建议优先对照这几个文件迁移:
- [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue)
- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue)
- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js)
- [docs/security/frontend-hardening.zh-CN.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.md)
- [docs/security/frontend-hardening.zh-CN.updates.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.updates.md)
## 这套方案能挡住什么
@ -247,20 +208,16 @@ npm run build:atu-prod:logs
## 当前边界
这套改动然无法彻底防住以下情况:
这套改动然无法彻底防住以下情况:
- 通过 Network 面板或抓包获取图片请求
- 有经验的人持续分析前端运行时代码
- 自定义 WebView、自动化脚本、Hook 等方式截获资源
- 仅凭视觉观察后重新临摹布局和样式
- 从一个本来就被允许渲染页面的客户端中提取 DOM、CSS、JS
一个关键原则是:
- 从本来就被允许渲染页面的客户端中提取 DOM、CSS、JS
只要客户端能把页面渲染出来,客户端就一定拿到了足够还原页面的信息。
所以前端侧能做的,本质上只有“提高成本”,而不是“绝对防住”。
## 推荐测试方式
### 受保护页面拦截
@ -308,17 +265,3 @@ npm run build
- 构建产物文件名中不再直观出现页面名
- 生产包中不再保留 `console.log``console.debug``console.info``debugger`
## 后续仍可继续推进的方向
如果继续坚持只改前端项目,可以优先考虑:
- 把更敏感的装饰层、外框层继续往 `canvas` 渲染收口
- 继续减少关键活动页中可直接阅读的结构语义
- 继续替换剩余直接 `background-image` 或直出图片链路
如果后续允许少量后端配合,优先级更高的是:
- 敏感 OSS 目录改成私有读
- 使用短时签名 URL
- 给敏感图片做按用户生成的动态水印
- 资源下发前校验 App 会话或设备身份

View File

@ -7,29 +7,28 @@
- 生产构建混淆进一步收紧,`esbuild` 在生产相关模式下显式开启了更强的标识符、语法和空白压缩,并去除了合法注释输出。相关文件在 [vite.config.js](/D:/programs/likei-h5/vite.config.js)。
- 公共背景组件 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 新增了 `useCanvas` 模式,装饰性多层背景会优先绘制到单个 canvas而不是在 DOM 中直接暴露多张 `<img>`
- `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),
[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/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()`,避免监听节点还没稳定挂载时就注册,或页面切换后残留观察器。
- [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`
这是为了避免把所有背景层都切到 canvas 渲染路径,给普通页面增加不必要的加载、绘制和重绘成本。
- [protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 作用也已明确:
组件会在 `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` 的内联样式场景。
## 当前边界
@ -42,17 +41,4 @@
- 使用 `npm run build` 验证生产包可正常构建。
- 使用 `npm run dev:prod``npm run preview` 验证活动页、榜单页在生产态逻辑下仍能正常显示。
- 重点检查 `SpringFestival``Season4``WeeklyStar``Overall` 这几类近期改动较多的页面。
## `atu-prod` 迁移建议
- 如果后续要在 `atu-prod` 分支复用同类处理,优先迁移本轮涉及的三个核心文件:
[Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue)、
[BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue)、
[protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js)。
- 同步迁移文档文件,避免分支间的实现与说明脱节:
[frontend-hardening.zh-CN.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.md)、
[frontend-hardening.zh-CN.updates.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.updates.md)。
- 后续如果主分支继续更新了这些文档,切到目标分支后可直接执行:
`npm run docs:sync:security -- master`
- 该命令依赖脚本 [scripts/sync-security-docs.js](/D:/programs/likei-h5/scripts/sync-security-docs.js),前提是主分支里的文档版本已经提交到 Git。
- 重点检查 `SpringFestival``Season4``WeeklyStar``Overall` 这些近期改动较多的页面。

View File

@ -1,47 +1,24 @@
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { computed, onMounted } from 'vue'
import { RouterView, useRoute } from 'vue-router'
import { useLangStore } from '@/stores/lang'
import { isPublicPath } from '@/config/security.js'
import { getUserAccount, getUserId } from './utils/userStore.js'
import { isDebugMode } from './utils/env.js'
const langStore = useLangStore()
const route = useRoute()
const watermarkTimestamp = ref(Date.now())
let watermarkTimer = null
const showWatermark = computed(() => !isDebugMode() && !isPublicPath(route.path))
const watermarkText = computed(() => {
const account = getUserAccount() || getUserId() || 'LIKEI'
const time = new Date(watermarkTimestamp.value).toLocaleString()
return `${account} ${time}`
})
const watermarkItems = computed(() => Array.from({ length: 12 }, (_, index) => `${index}`))
const protectedPageClass = computed(() => ({
'protected-page': !isDebugMode() && !isPublicPath(route.path),
}))
onMounted(() => {
langStore.initLanguage()
watermarkTimer = window.setInterval(() => {
watermarkTimestamp.value = Date.now()
}, 60 * 1000)
})
onBeforeUnmount(() => {
if (watermarkTimer) {
window.clearInterval(watermarkTimer)
}
})
</script>
<template>
<div id="app" :class="protectedPageClass">
<div v-if="showWatermark" class="security-watermark" aria-hidden="true">
<span v-for="item in watermarkItems" :key="item">{{ watermarkText }}</span>
</div>
<RouterView />
</div>
</template>
@ -82,31 +59,4 @@ body {
-webkit-touch-callout: none;
user-select: none;
}
.security-watermark {
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 48px 24px;
padding: 40px 16px;
opacity: 0.11;
overflow: hidden;
}
.security-watermark span {
display: flex;
align-items: center;
justify-content: center;
min-height: 96px;
color: #0f172a;
font-size: 14px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
transform: rotate(-24deg);
white-space: nowrap;
}
</style>