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

故事是这样的

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

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

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

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

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

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

水印组件

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

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

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

相关推荐
Embrace3 分钟前
NextAuth实现Google登录报错问题
前端
小海编码日记5 分钟前
Geadle,Gradle插件,Android Studio and sdk版本对应关系
前端
粤M温同学9 分钟前
Web前端基础之HTML
前端·html
love530love15 分钟前
是否需要预先安装 CUDA Toolkit?——按使用场景分级推荐及进阶说明
linux·运维·前端·人工智能·windows·后端·nlp
泯泷1 小时前
「译」为 Rust 及所有语言优化 WebAssembly
前端·后端·rust
LinXunFeng1 小时前
Flutter - GetX Helper 如何应用于旧页面
前端·flutter·开源
紫薯馍馍2 小时前
Dify创建 echarts图表 (二)dify+python后端flask实现
前端·flask·echarts·dify
梦想很大很大2 小时前
把业务逻辑写进数据库中:老办法的新思路(以 PostgreSQL 为例)
前端·后端·架构
李三岁_foucsli2 小时前
从生成器和协程的角度详解async和await,图文解析
前端·javascript
柚子8162 小时前
CSS自定义函数也来了
前端·css