注册
web

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

故事是这样的



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


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


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


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


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


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



2 (2).gif


水印组件



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



3.gif


原始代码


//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监听整个区域


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是什么。


image.png


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


image.png


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


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()
}
})


4.gif



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


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



作者:LonelyCorner
来源:juejin.cn/post/7362309246556356647

0 个评论

要回复文章请先登录注册