上个月我们部门的商户数据管理系统上线后,运营同事反馈了一个问题:截图泄密。
场景很简单——内部管理后台有1286条商户敏感信息(姓名、电话、交易流水),有人截个图发到微信群里,数据就出去了。虽然大家都知道不该这么做,但忙起来的时候谁还记得"敏感数据请勿外传"?
传统做法是后台加水印,但 JSP 老项目的方案五花八门:有人用 CSS ::after 伪元素(一调控制台就删),有人后端生成图片覆盖(占带宽、影响加载),有人在 <body> 写死文字(换用户不更新)。
我花半小时用 WorkBuddy 搭了一套模块化动态水印方案,三行代码接入任何 JSP 页面,效果拉满:
pointer-events: none)watermark-core.js 独立文件,任意 JSP 页面直接引入下面完整复盘实现过程,看完你也能直接用到自己的项目里。
动态水印本质是一个"全屏覆盖层"的问题,业界有三种主流方案:
方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
CSS 伪元素 | body::after { content: "xxx"; } | 极简,一行CSS | 控制台一秒删除;文字不可旋转/多行 |
SVG 内联 | <svg><pattern>...</pattern></svg> | 矢量清晰,CSS可控 | DOM节点可被直接移除 |
Canvas → base64背景 | JS生成Canvas → toDataURL() → background-image | 不可DOM选中;抗锯齿好;支持旋转/多行 | 需写JS,稍复杂 |
最终选 Canvas 方案,核心原因两条:
devicePixelRatio 处理 Retina 屏,文字边缘锐利打开 WorkBuddy,输入:
帮我在JSP管理页面加动态水印,要求:
1. 显示当前登录用户名+部门+时间戳
2. 文字倾斜约22度,半透明,铺满整个页面
3. 不能被控制台轻易删除(加MutationObserver防篡改)
4. 独立JS模块,各页面直接引用即可
5. 不影响页面正常点击操作
6. 支持多行文字WorkBuddy 直接给出了完整的 watermark-core.js 模块。
整个水印引擎封装在一个 IIFE(立即执行函数)里,对外暴露三个 API:
var Watermark = (function() {
// ... 内部实现 ...
return {
init: init, // 初始化水印
update: update, // 动态更新文字
destroy: destroy // 销毁水印
};
})();function createCanvasWatermark(settings) {
var canvas = document.createElement('canvas');
var dpr = window.devicePixelRatio || 1; // ① 高清屏适配
canvas.width = settings.width * dpr;
canvas.height = settings.height * dpr;
var ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr); // ② 缩放画布
ctx.font = settings.fontSize + 'px ' + settings.fontFamily;
ctx.fillStyle = settings.color;
ctx.globalAlpha = settings.opacity; // ③ 全局透明度
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var lines = settings.text.split('\n'); // ④ 支持多行文字
ctx.save();
ctx.translate(cx, cy);
ctx.rotate((settings.rotate * Math.PI) / 180); // ⑤ 旋转文字
lines.forEach(function(line, idx) {
ctx.fillText(line, 0, startY + idx * lineHeight);
});
ctx.restore();
return canvas.toDataURL('image/png'); // ⑥ 导出为base64背景图
}画布上写好文字 → 旋转 → 导出 base64 → 做成 background-image: repeat,铺满全屏。用 devicePixelRatio 乘系数保证在高分屏上文字不糊。
这是安全性的核心。普通 CSS 水印被删节点就没了,我们加一层监听:
function startObserver(settings) {
var overlay = document.getElementById('__wm_overlay__');
var parentNode = overlay.parentNode;
observer = new MutationObserver(function(mutations) {
var needRestore = false;
mutations.forEach(function(mut) {
// 检测水印节点被删除
if (mut.type === 'childList') {
var removed = Array.from(mut.removedNodes);
if (removed.some(function(n) { return n.id === '__wm_overlay__'; })) {
needRestore = true;
}
}
// 检测水印属性被篡改
if (mut.type === 'attributes' && mut.target.id === '__wm_overlay__') {
needRestore = true;
}
});
if (needRestore) {
observer.disconnect();
restoreWatermark(settings); // 自动恢复
startObserver(settings); // 重新监听
}
});
observer.observe(parentNode, { childList: true, attributes: true, ... });
}有人在控制台执行 $('#__wm_overlay__').remove() 或修改 style.display='none',MutationObserver 检测到后立即重建水印层。除非他把整个 JS 线程停掉,否则删不掉。
if (typeof window !== 'undefined' && window.WATERMARK_CONFIG) {
document.addEventListener('DOMContentLoaded', function() {
Watermark.init(window.WATERMARK_CONFIG);
});
}页面上只需定义一个 window.WATERMARK_CONFIG 对象,模块就会在 DOM 就绪后自动初始化。这就是"三行接入"的秘密。
在任意 JSP 页面底部引入即可:
jsp
<!-- ========== 1. 引入核心模块 ========== -->
<script src="/js/watermark-core.js"></script>
<!-- ========== 2. 配置水印参数 ========== -->
<script>
window.WATERMARK_CONFIG = {
text: '<%= userName %> | <%= userDept %>\n<%= nowStr %>',
fontSize: 14,
opacity: 0.07,
rotate: -22
};
</script>就这么简单。<%= userName %> 是 JSP 表达式,从 Session 里取当前登录用户,渲染到页面时已经变成了具体文字(如 张三 | 运营部)。
完整示例见附件 merchant-list.jsp——一个完整的商户列表管理页,含导航栏、统计卡片、数据表格,水印层覆盖在所有内容之上,完全不影响操作。
把水印拆成独立模块有四个好处:
项目结构:
/webapp
/js
watermark-core.js ← 水印引擎(通用)
/WEB-INF
/views
merchant-list.jsp ← 商户列表(引入水印)
order-list.jsp ← 订单列表(引入水印)
report.jsp ← 报表页(引入水印)原则:业务代码不关心水印怎么画的,水印模块不关心页面上有什么。 两者通过 window.WATERMARK_CONFIG 这个约定接口通信。未来要改渲染方式(比如从 Canvas 升级到 WebGL),只改 watermark-core.js,所有页面自动生效。
除了 Canvas 方案,这个模块还预留了另外两种渲染模式的扩展空间(代码结构已支持切换):
方案 | 适合场景 | 安全性 | 清晰度 | 性能 |
|---|---|---|---|---|
CSS 纯样式 | 内部工具、非敏感页 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
SVG 内联 | 需要无极缩放的水印 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
Canvas base64(本方案) | 管理后台、敏感数据页 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
对于商户数据管理系统这类含敏感信息的后台,Canvas + MutationObserver 是最佳平衡点。
用 WorkBuddy 生成代码的过程中,我踩了两个坑,顺手修了:
坑一:pointer-events 没生效,水印挡住了按钮点击
原因:水印 <div> 在最上层时,z-index: 9999 会导致它拦截所有鼠标事件,即使透明也点不到按钮。
解决:加水印容器的 pointer-events: none 是关键。注意这个属性会让水印本身也无法被选中,但这正是我们需要的——水印只是视觉效果,不参与交互。
坑二:高分屏水印文字模糊
原因:Canvas 默认按逻辑像素绘制,Retina 屏上一个像素点对应多个物理像素,文字边缘锯齿严重。
解决:canvas.width = width * devicePixelRatio,然后 ctx.scale(dpr, dpr) 缩放画布。这样 Canvas 内部用2倍/3倍分辨率渲染,再缩放到逻辑尺寸,文字锐利清晰。
这套水印框架可以轻松扩展:
enabled: true/false,开发环境关掉水印方便调试rotate 为 -45°,配合更小的间距,水印更密集更难去除这个 JSP 动态水印方案的完整链路:
JSP Session → 取用户信息 → 拼装水印文字 → 注入 window.WATERMARK_CONFIG
↓
watermark-core.js 读取配置
↓
Canvas 渲染 → base64 背景图
↓
MutationObserver 防篡改整件事 WorkBuddy 帮我干了:方案选型 + Canvas 绘图代码 + Observer 机制 + 模块封装。我只需要在 JSP 页面底部加三行 <script> 标签,把 Session 数据传给水印模块。
如果你也有 JSP 管理后台需要加水印,直接把附件里的 watermark-core.js 拷进项目,按上面的 JSP 集成方式写好配置就能用。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。