提交 b0505567 authored 作者: hzh's avatar hzh

水印功能实现

上级 942fdf83
......@@ -114,6 +114,25 @@
<artifactId>hutool-json</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.8.2</version>
</dependency>
<!-- TIFF 支持 -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.8.2</version>
</dependency>
</dependencies>
</project>
......@@ -14,6 +14,8 @@ import java.io.File;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 文件处理工具类
......@@ -134,4 +136,24 @@ public class FileUtils extends FileUtil {
throw new RuntimeException("URL 下载失败:" + urlStr, e);
}
}
/**
* 在文件名前添加指定文件夹
* @param originalPath 原始文件路径
* @param folderName 要添加的文件夹名
* @return 新的文件路径
*/
public static String addFolderBeforeFileName(String originalPath, String folderName) {
Path path = Paths.get(originalPath);
// 获取文件名
String fileName = path.getFileName().toString();
// 构建新路径:父目录 + 新文件夹 + 文件名
Path newPath = path.getParent()
.resolve(folderName)
.resolve(fileName);
return newPath.toString();
}
}
package org.dromara.common.core.utils.file;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.file.watermark.WatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import org.dromara.common.core.utils.file.watermark.processor.ImageWatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.processor.PdfWatermarkProcessor;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* 水印工具类 - 支持图片和PDF添加水印
*/
@Slf4j
public class WatermarkUtil {
// 支持的文件类型处理器映射
private static final Map<String, WatermarkProcessor> PROCESSOR_MAP = new HashMap<>();
static {
// 初始化各类型文件的处理器
PROCESSOR_MAP.put("jpg", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("jpeg", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("png", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("gif", new ImageWatermarkProcessor());
PROCESSOR_MAP.put("pdf", new PdfWatermarkProcessor());
}
/**
* 为文件添加文字水印
*
* @param sourceFile 源文件
* @param targetFile 目标文件
* @param config 水印配置
* @return 是否成功添加水印
*/
public static boolean addTextWatermark(File sourceFile, File targetFile, WatermarkConfig config) {
// 获取文件扩展名
String extension = getFileExtension(sourceFile.getName()).toLowerCase();
// 获取对应的处理器
WatermarkProcessor processor = PROCESSOR_MAP.get(extension);
if (processor == null) {
log.info("不支持的文件类型: " + extension);
return false;
}
// 执行水印处理
return processor.process(sourceFile, targetFile, config);
}
/**
* 获取文件扩展名
*/
public static String getFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
}
public static void main(String[] args) {
// 配置水印参数
WatermarkConfig imageConfig = new WatermarkConfig.Builder()
.watermarkText("测试水印")
.fontName("宋体")
.fontSize(30)
.color(Color.RED)
.alpha(0.5f)
.angle(30)
.build();
// 示例:给图片添加水印
Path sourceImagePath = Paths.get("D:\\1.jpg");
Path targetImagePath = Paths.get("D:\\output.jpg");
// 验证源文件是否存在
if (!Files.exists(sourceImagePath)) {
System.err.println("源文件不存在: " + sourceImagePath);
return;
}
// 确保目标目录存在
try {
Files.createDirectories(targetImagePath.getParent());
} catch (IOException e) {
System.err.println("无法创建目标目录: " + e.getMessage());
return;
}
addTextWatermark(sourceImagePath.toFile(), targetImagePath.toFile(), imageConfig);
// 配置PDF水印参数
WatermarkConfig pdfConfig = new WatermarkConfig.Builder()
.watermarkText("Confidential")
.fontName("Helvetica")
.fontSize(30)
.color(Color.RED)
.alpha(0.3f)
.angle(45)
.build();
// 示例:给PDF添加水印
File pdfFile = new File("C:\\Users\\wenhe\\Downloads\\安盛保险-卓越个人基础版.pdf");
File pdfOutputFile = FileUtil.touch(FileUtils.addFolderBeforeFileName(pdfFile.getPath(), "watermark"));
addTextWatermark(pdfFile, pdfOutputFile, pdfConfig);
}
}
package org.dromara.common.core.utils.file.watermark;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import java.io.File;
/**
* 水印处理器接口
*/
public interface WatermarkProcessor {
boolean process(File sourceFile, File targetFile, WatermarkConfig config);
}
package org.dromara.common.core.utils.file.watermark.config;
import java.awt.*;
/**
* 水印配置类
* @author wenhe
*/
public class WatermarkConfig {
/**
* 水印文本内容
*/
private final String watermarkText;
/**
* 水印字体名称,默认为 Arial
* 支持系统安装的所有字体名称,例如:
* - "SimSun"(宋体)
* - "Microsoft YaHei"(微软雅黑)
* - "SimHei"(黑体)
*/
private final String fontName;
/**
* 水印字体大小,单位为像素,默认为 20px
*/
private final float fontSize;
/**
* 水印字体颜色,默认为黑色
*/
private final Color color;
/**
* 水印透明度,取值范围 0.0(完全透明)到 1.0(完全不透明),默认为 0.5
*/
private final float alpha;
/**
* 水印旋转角度,单位为度,默认为 0(水平)
* 正值表示顺时针旋转,负值表示逆时针旋转
*/
private final int angle;
private WatermarkConfig(Builder builder) {
this.watermarkText = builder.watermarkText;
this.fontName = builder.fontName;
this.fontSize = builder.fontSize;
this.color = builder.color;
this.alpha = builder.alpha;
this.angle = builder.angle;
}
public String getWatermarkText() {
return watermarkText;
}
public String getFontName() {
return fontName;
}
public float getFontSize() {
return fontSize;
}
public Color getColor() {
return color;
}
public float getAlpha() {
return alpha;
}
public int getAngle() {
return angle;
}
public static class Builder {
private String watermarkText;
private String fontName = "Arial";
private float fontSize = 20;
private Color color = Color.BLACK;
private float alpha = 0.5f;
private int angle = 0;
public Builder watermarkText(String watermarkText) {
this.watermarkText = watermarkText;
return this;
}
public Builder fontName(String fontName) {
this.fontName = fontName;
return this;
}
public Builder fontSize(float fontSize) {
this.fontSize = fontSize;
return this;
}
public Builder color(Color color) {
this.color = color;
return this;
}
public Builder alpha(float alpha) {
this.alpha = alpha;
return this;
}
public Builder angle(int angle) {
this.angle = angle;
return this;
}
public WatermarkConfig build() {
// 可以添加参数验证逻辑
if (watermarkText == null || watermarkText.isEmpty()) {
throw new IllegalArgumentException("水印文本不能为空");
}
if (fontSize <= 0) {
throw new IllegalArgumentException("字体大小必须大于0");
}
if (alpha < 0 || alpha > 1) {
throw new IllegalArgumentException("透明度必须在0.0到1.0之间");
}
return new WatermarkConfig(this);
}
}
}
package org.dromara.common.core.utils.file.watermark.processor;
import org.dromara.common.core.utils.file.WatermarkUtil;
import org.dromara.common.core.utils.file.watermark.WatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
/**
* 图片水印处理器
*/
public class ImageWatermarkProcessor implements WatermarkProcessor {
@Override
public boolean process(File sourceFile, File targetFile, WatermarkConfig config) {
try (InputStream is = new FileInputStream(sourceFile);
OutputStream os = new FileOutputStream(targetFile)) {
// 读取图片
BufferedImage image = ImageIO.read(is);
int width = image.getWidth();
int height = image.getHeight();
// 创建绘图上下文
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bufferedImage.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
// 设置水印字体和颜色
Font font = new Font(config.getFontName(), Font.PLAIN, (int) config.getFontSize());
g.setFont(font);
g.setColor(config.getColor());
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, config.getAlpha()));
// 设置旋转角度
if (config.getAngle() != 0) {
g.rotate(Math.toRadians(config.getAngle()), width / 2, height / 2);
}
// 计算水印文本的宽度和高度
FontMetrics metrics = g.getFontMetrics(font);
int textWidth = metrics.stringWidth(config.getWatermarkText());
int textHeight = metrics.getHeight();
// 添加水印
int xStep = textWidth * 3;
int yStep = textHeight * 3;
int xCount = (width / xStep) + 2;
int yCount = (height / yStep) + 2;
for (int x = 0; x < xCount; x++) {
for (int y = 0; y < yCount; y++) {
int xPos = x * xStep - textWidth;
int yPos = y * yStep;
g.drawString(config.getWatermarkText(), xPos, yPos);
}
}
// 保存图片
String formatName = WatermarkUtil.getFileExtension(targetFile.getName());
ImageIO.write(bufferedImage, formatName, os);
g.dispose();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
package org.dromara.common.core.utils.file.watermark.processor;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.pdf.*;
import org.dromara.common.core.utils.file.watermark.WatermarkProcessor;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* PDF水印处理器
*/
public class PdfWatermarkProcessor implements WatermarkProcessor {
public boolean process(File sourceFile, File targetFile, WatermarkConfig config) {
PdfReader reader = null;
PdfStamper stamper = null;
try {
// 移除try-with-resources中的流声明,手动管理资源
reader = new PdfReader(new FileInputStream(sourceFile));
stamper = new PdfStamper(reader, new FileOutputStream(targetFile));
// 转换颜色
BaseColor pdfColor = new BaseColor(
config.getColor().getRed(),
config.getColor().getGreen(),
config.getColor().getBlue()
);
// 获取字体
BaseFont baseFont = getBaseFont(config.getFontName());
// 设置水印透明度
PdfGState gs = new PdfGState();
gs.setFillOpacity(config.getAlpha());
// 遍历PDF的每一页
int totalPages = reader.getNumberOfPages();
for (int i = 1; i <= totalPages; i++) {
// 获取当前页的内容
PdfContentByte content = stamper.getOverContent(i);
content.setGState(gs);
content.beginText();
content.setFontAndSize(baseFont, config.getFontSize());
content.setColorFill(pdfColor);
// 设置旋转角度
content.setTextMatrix(0, 0);
// 计算水印位置和数量
com.itextpdf.text.Rectangle pageSize = reader.getPageSize(i);
float width = pageSize.getWidth();
float height = pageSize.getHeight();
// 添加多个水印
int xStep = 150;
int yStep = 100;
int xCount = (int) (width / xStep) + 1;
int yCount = (int) (height / yStep) + 1;
for (int x = 0; x < xCount; x++) {
for (int y = 0; y < yCount; y++) {
float xPos = x * xStep;
float yPos = y * yStep;
// 在指定位置添加水印
content.showTextAligned(Element.ALIGN_CENTER,
config.getWatermarkText(), xPos, yPos, config.getAngle());
}
}
content.endText();
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
// 先关闭stamper,再关闭reader(顺序很重要)
if (stamper != null) {
try {
stamper.close();
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
reader.close();
}
}
}
/**
* 获取PDF字体
*/
private BaseFont getBaseFont(String fontName) throws DocumentException, IOException {
// 默认使用Helvetica字体
BaseFont baseFont = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);
// 如果指定了其他字体,尝试使用它
if (!"Helvetica".equalsIgnoreCase(fontName)) {
try {
// 尝试查找系统字体
String fontPath = findSystemFontPath(fontName);
if (fontPath != null) {
baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}
} catch (Exception e) {
System.err.println("无法加载指定字体: " + fontName + ", 使用默认字体");
}
}
return baseFont;
}
/**
* 查找系统字体路径
*/
private String findSystemFontPath(String fontName) {
// 这里简化处理,实际应用中可能需要更复杂的字体查找逻辑
String osName = System.getProperty("os.name").toLowerCase();
String fontPath = null;
if (osName.contains("win")) {
fontPath = "C:/Windows/Fonts/" + fontName + ".ttf";
} else if (osName.contains("mac")) {
fontPath = "/Library/Fonts/" + fontName + ".ttf";
} else if (osName.contains("linux")) {
fontPath = "/usr/share/fonts/truetype/" + fontName + ".ttf";
}
// 检查文件是否存在
if (fontPath != null && new File(fontPath).exists()) {
return fontPath;
}
return null;
}
}
......@@ -29,7 +29,7 @@ public class SysDeptOssDownloadBo extends BaseEntity {
/**
* 部门文件id
*/
@NotBlank(message = "部门文件id不能为空", groups = { AddGroup.class, EditGroup.class })
@NotNull(message = "部门文件id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long deptOssId;
......
......@@ -93,5 +93,10 @@ public class SysDeptOssVo implements Serializable {
@ExcelProperty(value = "是否公开")
private Boolean open;
/**
* 创建人id
*/
private Long createBy;
}
package org.dromara.workflow.service.impl;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
......@@ -10,6 +11,8 @@ import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.core.utils.file.WatermarkUtil;
import org.dromara.common.core.utils.file.watermark.config.WatermarkConfig;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.common.mybatis.service.AbstractBaseService;
......@@ -28,6 +31,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.awt.*;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
......@@ -115,19 +120,40 @@ public class FileApproveServiceImpl extends AbstractBaseService<FileApproveVo, F
baseMapper.updateById(fa);
//审批完成
if (StringUtils.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())) {
File originFile = null;
File watermarkFile = null;
try {
//发送邮件
Map<String, Object> entity = (Map<String, Object>) WorkflowUtils.getHistoricVariableByBusinessKey(processEvent.getBusinessKey(), "entity");
String url = entity.get("fileUrl").toString();
String fileName = entity.get("fileName").toString();
MailUtils.send(fa.getEmail(), "部门文件下载", "", false, FileUtils.convert(url, fileName));
originFile = FileUtils.convert(url, fileName);
watermarkFile = FileUtil.touch(FileUtils.addFolderBeforeFileName(originFile.getPath(), "watermark"));
WatermarkConfig watermarkConfig = new WatermarkConfig.Builder()
.watermarkText(fa.getPurpose())
.fontName("宋体")
.color(Color.RED)
.alpha(0.3f)
.angle(45)
.build();
boolean result = WatermarkUtil.addTextWatermark(originFile, watermarkFile, watermarkConfig);
MailUtils.send(fa.getEmail(), "部门文件下载", "", false, result ? watermarkFile : originFile);
} catch (Exception e) {
e.printStackTrace();
log.info("邮件发送失败,邮箱{}", fa.getEmail());
} finally {
if (originFile != null) {
originFile.delete();
}
if (watermarkFile != null) {
watermarkFile.delete();
}
}
}
}
// private String
/**
* 执行办理任务监听
* 示例:也可通过 @EventListener(condition = "#processTaskEvent.key=='leave1'")进行判断
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论