领导:你加的水印怎么还能被删掉的,扣工资!

故事是这样的

领导:小李,你加的水印怎么还能被删掉的?这可是关乎公司信息安全的大事!这种疏忽怎么能不扣工资呢?

小李:领导,请您听我解释一下!我确实按照常规的方法加了水印,可是......

领导:(打断)但是什么?难道这就是你对公司资料的保护吗?

小李:我也不明白,按理说水印是无法删除的,我会再仔细检查一下......

领导:我不能容忍这样的失误。这种安全隐患严重影响了我们的机密性。

小李焦虑地试图解释,但领导的目光如同刀剑一般锐利。他决定,这次一定要找到解决方法,否则,这将是一场职场危机......

水印组件

小李想到antd中有现成的水印组件,便去研究了一下。即使删掉了水印div,水印依然存在,因为瞬间又生成了一个相同的水印div。他一瞬间想到了解决方案,并开始了重构水印组件。

原始代码

js 复制代码
//app.vue
<template>
  <div>
    <Watermark text="前端百事通">
      <div class="content"></div>
    </Watermark>
  </div>
</template>

<script setup>
import Watermark from './components/Watermark.vue';
</script>


<style scoped>
.content{
  width: 400px;
  height: 400px;
  background-color: aquamarine;
}
</style>
//watermark.vue
<template>
  <div ref="watermarkRef" class="watermark-container">
    <slot>

    </slot>
  </div>

</template>

<script setup>
import { onMounted, ref } from 'vue';
const watermarkRef=ref(null)
const props = defineProps({
  text: {
    type: String,
    default: '前端百事通'
  },
  fontSize: {
    type: Number,
    default: 14
  },
  gap: {
    type: Number,
    default: 50
  },
  rotate: {
    type: Number,
    default: 45
  }
})
onMounted(() => {
  addWatermark()
})
const addWatermark = () => {
  const { rotate, gap, text, fontSize } = props
  const color = 'rgba(0, 0, 0, 0.3)'; // 可以从props中传入 
  const watermarkContainer = watermarkRef.value;

  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  const font=fontSize+'px DejaVu Sans Mono'
  // 设置水印文字的宽度和高度  
  const metrics = context.measureText(text);
  const canvasWidth=metrics.width+gap
  canvas.width=canvasWidth
  canvas.height=canvasWidth
  // 绘制水印文字  
  context.translate(canvas.width/2,canvas.height/2)
  context.rotate((-1 * rotate * Math.PI / 180));
  context.fillStyle = color;
  context.font=font
  context.textAlign='center'
  context.textBaseline='middle'
  context.fillText(text,0,0)
  // 将canvas转为图片  
  const url = canvas.toDataURL('image/png');
  // 创建水印元素并添加到容器中  
  const watermarkLayer = document.createElement('div');
  watermarkLayer.style.position = 'absolute';
  watermarkLayer.style.top = '0';
  watermarkLayer.style.left = '0';
  watermarkLayer.style.width = '100%';
  watermarkLayer.style.height = '100%';
  watermarkLayer.style.pointerEvents = 'none';
  watermarkLayer.style.backgroundImage = `url(${url})`;
  watermarkLayer.style.backgroundRepeat = 'repeat';
  watermarkLayer.style.zIndex = '9999';
  watermarkContainer.appendChild(watermarkLayer);
}
</script>

<style>
.watermark-container {  
  position: relative;  
  width: 100%;  
  height: 100%;  
  overflow: auto;  
}  
</style>

防篡改思路

  • 监听删除dom操作,在删除dom操作的瞬间重新生成一个相同的dom元素
  • 监听修改dom样式操作
  • 不能使用onMounted,改为watchEffect进行监听操作

使用MutationObserver监听整个区域

js 复制代码
let ob
onMounted(() => {
  ob=new MutationObserver((records)=>{
    console.log(records)
  })
  ob.observe(watermarkRef.value,{
    childList:true,
    attributes:true,
    subtree:true
  })
})
onUnmounted(()=>{
  ob.disconnect()
})

在删除水印div之后,打印一下看看records是什么。

在修改div样式之后,打印一下records

很明显,如果是删除,我们就关注removedNodes字段,如果是修改,我们就关注attributeName字段。

js 复制代码
onMounted(() => {
  ob=new MutationObserver((records)=>{
    for(let item of records){
      //监听删除
      for(let ele of item.removedNodes){
        if(ele===watermarkDiv){
          generateFlag.value=!generateFlag.value
          return
        }
      }
      //监听修改
      if(item.attributeName==='style'){
        generateFlag.value=!generateFlag.value
        return
      }
    }
  })
  ob.observe(watermarkRef.value,{
    childList:true,
    attributes:true,
    subtree:true
  })
})

watchEffect(() => {
  //generateFlag的用处是让watchEffect收集这个依赖 
  //通过改变generateFlag的值来重新调用生成水印的函数
  generateFlag.value
  if(watermarkRef.value){
    addWatermark()
  }
})

最终,小李向领导展示了新的水印组件,取得了领导的认可和赞许,保住了工资。

全剧终。 文章同步发表于前端百事通公众号,欢迎关注!

相关推荐
kyriewen6 小时前
百度用6%成本碾压硅谷?中国AI把性价比玩明白了
前端·百度·ai编程
kyriewen7 小时前
你还在手动敲命令部署?GitHub Actions 让你 push 即上线,摸鱼时间翻倍
前端·面试·github
Csvn8 小时前
Pinia 状态管理
前端
不减20斤不改头像8 小时前
手机一句话开发贪吃蛇!TRAE SOLO 移动端 AI 编程实测
前端·后端
xuankuxiaoyao9 小时前
Vue.js实践-组件基础下
前端·javascript·vue.js
一棵白菜9 小时前
Claude Code + Amazon Bedrock 使用指南
前端
大家的林语冰10 小时前
前端周刊:axios 疑遭朝鲜黑客“钓鱼“;CSS 新函数上线;npm 上线深色主题;Oxlint 兼容表;ESLint 支持 Temporal......
前端·javascript·css
哀木11 小时前
一个简单的套壳方案,就能让你的 Agent 少做重复初始化
前端
问心无愧051311 小时前
ctf show web入门27
前端
小村儿11 小时前
给 AI Agent 装上"长期记忆":Karpathy 的 LLM Wiki 思想,我做成了工具
前端·后端·ai编程