前言
在开发的过程中,因为文件的特殊性,需要给pdf、word、excel、ppt、图片添加水印。添加水印可以在文件上传时添加,也可以在文件下载时添加。因为业务的某些原因,文件需要在浏览器预览,如果用户将文件另存为则无法添加水印,所以此文章主要介绍文件上传时添加水印。至于文件下载时添加水印功能也很简单,稍微修改即可。
一:文件上传
1.1 controller控制层
@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 文件上传到服务器
/*** 上传文件到服务器** @param uploadFile 文件* @param fileName 文件名称* @return 文件路径*/@SneakyThrowspublic 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 引入依赖
<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
/*** 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);}
/*** 将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
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);// 设置水印文字Fontg.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
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
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
/*** 添加文件水印,返回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
@SneakyThrowsprivate 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转化为InputStreamreturn convertToInputStream(docx);}
/*** 将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
@SneakyThrowsprivate 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 sheetsheet.getCTWorksheet().addNewPicture().setId(rID);}//将XSSFWorkbook转化为InputStreamreturn workbookConvertorStream(workbook);}
/*** 将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
@SneakyThrowsprivate 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);}
/*** 将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;}}