前言
在开发的过程中,因为文件的特殊性,需要给pdf、word、excel、ppt、图片添加水印。添加水印可以在文件上传时添加,也可以在文件下载时添加。因为业务的某些原因,文件需要在浏览器预览,如果用户将文件另存为则无法添加水印,所以此文章主要介绍文件上传时添加水印。至于文件下载时添加水印功能也很简单,稍微修改即可。
一:文件上传
1.1 controller控制层
java
@ApiOperation("上传文件")
@PostMapping("/file/upload")
public String uploadFile(@RequestParam(value = "file") MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BusinessException("上传文件为空");
}
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename)) {
throw new BusinessException("上传文件名为空");
}
String filePath = obsClientHelper.upload(file, file.getOriginalFilename());
return filePath;
}
1.2 文件上传到服务器
java
/**
* 上传文件到服务器
*
* @param uploadFile 文件
* @param fileName 文件名称
* @return 文件路径
*/
@SneakyThrows
public String upload(MultipartFile uploadFile, String fileName) {
String objectKey = directory + "/" + DATE_TIME_FORMATTER.format(LocalDate.now()) + "/" + UUID.randomUUID() + "/" + fileName;
InputStream inputStream = null;
//上传文件添加水印
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String watermark = UserContext.currentUser().getUserName() + "(" + UserContext.currentUser().getUserId() + ")" + "\n" + simpleDateFormat.format(new Date());
if (fileName.endsWith("pdf")) {
inputStream = PdfWatermarkUtils.addWatermarkInputStream(uploadFile.getInputStream(), new PdfWatermarkPageEventHelper(watermark));
} else if (fileName.endsWith(ImageConstants.PICTURE_JPG) || fileName.endsWith(ImageConstants.PICTURE_PNG)) {
inputStream = ImageWatermarkUtil.imgWatermarkInputStream(watermark, uploadFile.getInputStream(), -40);
} else if (fileName.endsWith("docx") || fileName.endsWith("xlsx") || fileName.endsWith("pptx")) {
TextWaterMarkDTO waterMarkDTO = new TextWaterMarkDTO(fileName, watermark);
inputStream = OfficeWatermarkUtil.doMarkInputStream(uploadFile.getInputStream(), waterMarkDTO);
} else {
inputStream = uploadFile.getInputStream();
}
if (inputStream == null) {
throw new RuntimeException("上传文件时,文件添加水印失败");
}
try (ObsClient obsClient = new ObsClient(ak, sk, endPoint)) {
PutObjectResult putObjectResult = obsClient.putObject(bucketName, objectKey, inputStream);
if (putObjectResult.getStatusCode() != HttpServletResponse.SC_OK) {
throw new RuntimeException("文件上传失败,OBS响应码:" + putObjectResult.getStatusCode());
}
return putObjectResult.getObjectKey();
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
二:PDF添加水印
2.1 引入依赖
xml
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>7.1.11</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>layout</artifactId>
<version>7.1.11</version>
</dependency>
<!--没有该包的话,会有中文显示问题-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.11</version>
</dependency>
2.2 PdfWatermarkUtils
java
/**
* pdf加水印
* @param inputStream 输入流
* @param pdfPageEventHelper 水印
* @return InputStream
* @throws IOException
*/
public static InputStream addWatermarkInputStream(InputStream inputStream, PdfPageEventHelper pdfPageEventHelper) throws IOException {
String watermarkText = ((PdfWatermarkPageEventHelper) pdfPageEventHelper).getWatermarkText();
float fontSize = 13;
int rowSpace = 150;
int colSpace = 150;
boolean linux = SystemUtil.getOsInfo().isLinux();
String chineseFontPath = null;
if (linux) {
chineseFontPath = "/usr/share/fonts/STSONG.TTF";
} else {
chineseFontPath = SystemUtil.getUserInfo().getCurrentDir() + "\\fonts\\STSONG.TTF";
}
watermarkText = watermarkText.replace("\n", " ");
// 加载PDF文件
PDDocument document = PDDocument.load(inputStream);
document.setAllSecurityToBeRemoved(true);
// 加载水印字体
PDFont font = PDType0Font.load(document, new FileInputStream(chineseFontPath), true);
// 遍历PDF文件,在每一页加上水印
for (PDPage page : document.getPages()) {
PDPageContentStream stream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
PDExtendedGraphicsState r = new PDExtendedGraphicsState();
// 设置透明度
r.setNonStrokingAlphaConstant(0.2f);
r.setAlphaSourceFlag(true);
stream.setGraphicsStateParameters(r);
// 设置水印字体颜色
stream.setStrokingColor(Color.GRAY);
stream.beginText();
stream.setFont(font, fontSize);
stream.newLineAtOffset(0, -15);
// 获取PDF页面大小
float pageHeight = page.getMediaBox().getHeight();
float pageWidth = page.getMediaBox().getWidth();
// 根据纸张大小添加水印,30度倾斜
for (int h = 10; h < pageHeight; h = h + rowSpace) {
for (int w = -10; w < pageWidth; w = w + colSpace) {
stream.setTextMatrix(Matrix.getRotateInstance(0.3, w, h));
stream.showText(watermarkText);
}
}
// 结束渲染,关闭流
stream.endText();
stream.restoreGraphicsState();
stream.close();
}
return pdDocumentConvertorStream(document);
}
java
/**
* 将PDDocument转化为InputStream
*
* @param document document
* @return InputStream
*/
public static InputStream pdDocumentConvertorStream(PDDocument document) {
try {
//临时缓冲区
ByteArrayOutputStream out = new ByteArrayOutputStream();
document.save(out);
document.close();
return new ByteArrayInputStream(out.toByteArray());
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
三:图片添加水印
3.1 ImageWatermarkUtil
java
import com.itextpdf.text.Element;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Objects;
@Component
@Slf4j
public class ImageWatermarkUtil {
/**
* 水印透明度
*/
private static final float ALPHA = 0.5f;
/**
* 水印文字大小
*/
public static final int FONT_SIZE = 23;
/**
* 水印文字字体
*/
private static Font FONT;
/**
* 水印文字颜色
*/
private static final Color COLOR = Color.white;
/**
* 水印之间的间隔
*/
private static final int X_MOVE = 60;
/**
* 水印之间的间隔
*/
private static final int Y_MOVE = 60;
/**
* 获取文本长度。汉字为1:1,英文和数字为2:1
*/
private static int getTextLength(String text) {
int length = text.length();
for (int i = 0; i < text.length(); i++) {
String s = String.valueOf(text.charAt(i));
if (s.getBytes().length > 1) {
length++;
}
}
length = length % 2 == 0 ? length / 2 : length / 2 + 1;
return length;
}
/**
* 图片添加水印
*
* @param logoText 水印内容
* @param sourceFileStream 流文件
* @param degree 倾斜角度
* @return InputStream
*/
public static InputStream imgWatermarkInputStream(String logoText, InputStream sourceFileStream, Integer degree) {
try {
BufferedImage bufferedImage = addWatermark(logoText, sourceFileStream, degree);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "jpg", os);
return new ByteArrayInputStream(os.toByteArray());
} catch (IOException e) {
return null;
}
}
/**
* 图片添加水印
*
* @param logoText 水印内容
* @param sourceFileStream 流文件
* @param degree 倾斜角度
* @return BufferedImage
*/
public static BufferedImage addWatermark(String logoText, InputStream sourceFileStream, Integer degree) throws IOException {
//源图片
Image srcImg = ImageIO.read(sourceFileStream);
//原图宽度
int width = srcImg.getWidth(null);
//原图高度
int height = srcImg.getHeight(null);
BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null), srcImg.getHeight(null),
BufferedImage.TYPE_INT_RGB);
// 得到画笔对象
Graphics2D g = buffImg.createGraphics();
// 设置对线段的锯齿状边缘处理
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(srcImg.getScaledInstance(srcImg.getWidth(null), srcImg.getHeight(null), Image.SCALE_SMOOTH),
0, 0, null);
// 设置水印旋转
if (null != degree) {
g.rotate(Math.toRadians(degree), (double) buffImg.getWidth() / 2, (double) buffImg.getHeight() / 2);
}
// 设置水印文字颜色
g.setColor(COLOR);
// 设置水印文字Font
g.setFont(FONT);
// 设置水印文字透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));
int x = -width / 2;
int y = -height / 2;
// 字体长度
int markWidth = FONT_SIZE * getTextLength(logoText);
// 字体高度
// 循环添加水印
while (x < width * 1.5) {
y = -height;
while (y < height * 1.5) {
String[] lines = logoText.split("\n");
for (String line : lines) {
g.drawString(line, x, y);
y += FONT_SIZE + Y_MOVE;
}
}
x += markWidth + X_MOVE;
}
// 释放资源
g.dispose();
return buffImg;
}
/**
* 给图片添加水印文字、可设置水印文字的旋转角度
*
* @param logoText 水印内容
* @param sourceFileStream 输入流文件
* @param targetFileStream 输出流文件
* @param degree 倾斜角度
*/
public static void imageByText(String logoText, InputStream sourceFileStream, OutputStream targetFileStream, Integer degree) {
if (Objects.isNull(FONT)) {
FONT = new Font(FontUtil.firstZhSupportedFontName(), Font.BOLD, FONT_SIZE);
}
try {
// 生成图片
ImageIO.write(addWatermark(logoText, sourceFileStream, degree), "JPG", targetFileStream);
log.info("图片-添加水印文字成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != targetFileStream) {
targetFileStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.2 FontUtil
java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.awt.*;
import java.util.Arrays;
@Slf4j
public class FontUtil {
private static String ZH_SUP;
static {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
char randomZh = '唐';
Font[] allFonts = ge.getAllFonts();
for (Font font : allFonts) {
if(font.canDisplay(randomZh)){
ZH_SUP = font.getFontName();
break;
}
}
if(StringUtils.isEmpty(ZH_SUP)){
log.error("Zh supported font not found");
ZH_SUP = allFonts[0].getFontName();
}
}
public static String firstZhSupportedFontName(){
return ZH_SUP;
}
public static java.util.List<String> getAllFontNames(){
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] availableFontFamilyNames = ge.getAvailableFontFamilyNames();
return Arrays.asList(availableFontFamilyNames);
}
}
四:docx、xlsx、pptx添加水印
注:ppt、xls、doc由于版本新旧问题,此处先不介绍添加水印方法
4.1 TextWaterMarkDTO
java
import com.FontUtil;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class TextWaterMarkDTO{
public TextWaterMarkDTO() {
this.fontName = FontUtil.firstZhSupportedFontName();
}
public TextWaterMarkDTO(String fileName,String text) {
this.fontName = FontUtil.firstZhSupportedFontName();
this.fileSuffix = loadFileSuffix(fileName);
this.text = text;
}
String fileSuffix;
private String text;
private String fontName;
private String fontColor;
private Integer fontSize = 20;
private Integer rotation = -20;
Integer intervalHorizontal;
Integer intervalVertical;
Integer picWidth;
Integer picHeight;
Float alpha;
boolean enable;
private String loadFileSuffix(String fileName){
String realName = fileName.trim();
int index = realName.lastIndexOf(".");
if( index < 0){
return null;
}
return fileName.substring(index + 1);
}
}
4.2 doMarkInputStream
java
/**
* 添加文件水印,返回InputStream
*
* @param input 输入流
* @param waterMarkInfo waterMarkInfo
* @return InputStream
*/
public static InputStream doMarkInputStream(InputStream input, TextWaterMarkDTO waterMarkInfo) {
InputStream inputStream = null;
try {
String fileSuffix = waterMarkInfo.getFileSuffix().toLowerCase();
switch (fileSuffix) {
case "docx":
inputStream = docxWaterMarkInputStream(input, waterMarkInfo);
break;
case "xlsx":
log.info("xlsx");
inputStream = xlsxWaterMarkInputStream(input, waterMarkInfo.setPicWidth(1000).setPicHeight(2000));
break;
case "pptx":
log.info("pptx");
inputStream = pptxAddWatermarkInputStream(input, waterMarkInfo);
break;
default:
log.info("in default. fileSuffix:{}", fileSuffix);
inputStream = input;
}
return inputStream == null ? input : inputStream;
} catch (Exception e) {
log.error(ExceptionUtil.stacktraceToString(e));
return null;
} finally {
close(input);
}
}
4.3 docx
java
@SneakyThrows
private static InputStream docxWaterMarkInputStream(InputStream input, TextWaterMarkDTO waterMarkInfo) {
XWPFDocument docx = new XWPFDocument(input);
//设置默认值
String watermark = StringUtils.isBlank(waterMarkInfo.getText()) ? DEFAULT_WATERMARK : waterMarkInfo.getText();
String color = StringUtils.isBlank(waterMarkInfo.getFontColor()) ? DEFAULT_FONT_COLOR : "#" + waterMarkInfo.getFontColor();
String fontSize = (null == waterMarkInfo.getFontSize()) ? FONT_SIZE : waterMarkInfo.getFontSize() + "pt";
String rotation = (null == waterMarkInfo.getRotation()) ? STYLE_ROTATION : String.valueOf(waterMarkInfo.getRotation());
DocxUtil.makeFullWaterMarkByWordArt(docx, watermark, color, fontSize, rotation);
//将XWPFDocument转化为InputStream
return convertToInputStream(docx);
}
java
/**
* 将XWPFDocument转化为InputStream
*
* @param doc XWPFDocument
* @return InputStream
*/
public static InputStream convertToInputStream(XWPFDocument doc) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
doc.write(outputStream);
return new ByteArrayInputStream(outputStream.toByteArray());
} catch (IOException e) {
log.error(e.getMessage(), e);
return null;
}
}
4.4 xlsx
java
@SneakyThrows
private static InputStream xlsxWaterMarkInputStream(InputStream input, TextWaterMarkDTO waterMark) {
XSSFWorkbook workbook = new XSSFWorkbook(input);
Iterator<Sheet> iterator = workbook.sheetIterator();
BufferedImage image = FontImage.createWatermarkImageFillWithText(waterMark);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, "png", os);
int pictureIdx = workbook.addPicture(os.toByteArray(), Workbook.PICTURE_TYPE_PNG);
while (iterator.hasNext()) {
XSSFSheet sheet = (XSSFSheet) iterator.next();
String rID = sheet.addRelation(null, XSSFRelation.IMAGES, workbook.getAllPictures().get(pictureIdx)).getRelationship().getId();
//set background picture to sheet
sheet.getCTWorksheet().addNewPicture().setId(rID);
}
//将XSSFWorkbook转化为InputStream
return workbookConvertorStream(workbook);
}
java
/**
* 将XSSFWorkbook转化为InputStream
*
* @param workbook XSSFWorkbook
* @return InputStream
*/
public static InputStream workbookConvertorStream(XSSFWorkbook workbook) {
try {
//临时缓冲区
ByteArrayOutputStream out = new ByteArrayOutputStream();
//创建临时文件
workbook.write(out);
byte[] bookByteAry = out.toByteArray();
return new ByteArrayInputStream(bookByteAry);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
4.5 pptx
java
@SneakyThrows
private static InputStream pptxAddWatermarkInputStream(InputStream input, TextWaterMarkDTO waterMarkDto) {
XMLSlideShow slideShow = new XMLSlideShow(input);
waterMarkDto.setPicWidth(Double.valueOf(slideShow.getPageSize().width).intValue());
waterMarkDto.setPicHeight(Double.valueOf(slideShow.getPageSize().height).intValue());
BufferedImage image = FontImage.createWatermarkImageFillWithText(waterMarkDto);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, "png", os);
PictureData pictureData1 = slideShow.addPicture(os.toByteArray(), PictureData.PictureType.PNG);
for (XSLFSlide slide : slideShow.getSlides()) {
XSLFPictureShape pictureShape = slide.createPicture(pictureData1);
pictureShape.setAnchor(new Rectangle(0, 0, slideShow.getPageSize().width, slideShow.getPageSize().height));
}
return xmlSlideShowConvertorStream(slideShow);
}
java
/**
* 将XMLSlideShow转化为InputStream
*
* @param slideShow slideShow
* @return InputStream
*/
public static InputStream xmlSlideShowConvertorStream(XMLSlideShow slideShow) {
try {
//临时缓冲区
ByteArrayOutputStream out = new ByteArrayOutputStream();
//创建临时文件
slideShow.write(out);
byte[] bookByteAry = out.toByteArray();
return new ByteArrayInputStream(bookByteAry);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}