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

故事是这样的

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

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

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

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

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

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

水印组件

小李想到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()
  }
})

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

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

相关推荐
郭老二8 小时前
【前端】vue3:编译步骤
前端
不在逃避q9 小时前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
夏殇之殁9 小时前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One9859 小时前
高进度算法模板速记(待完善)
java·前端·算法
陈随易9 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师10 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户0595401744611 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css
丙氨酸長鏈11 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库
kyriewen12 小时前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
IT_陈寒12 小时前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端