Java MultipartFile实现文件上传并为图片加上水印(二)

防止走丢~~欢迎大家留言收藏点赞

在上一篇我们已经实现了文件的上传,那么如果对于图片的上传,我们要给图片加上我们需要的水印信息,比如图片的上传人,上传地点等信息,此时我们为上传功能再新增水印功能。
最终上传效果如图所示
在这里插入图片描述

一、前端上传至服务端(controller层)

水印信息可以由客户端请求过来,也可以服务端自定义,因为我现在的业务是车辆图片上传,需要为上传的车辆图片增加车牌号、上传人、上传经纬度以及地址信息等,这些信息是由客户端获取以后给到服务端的,水印信息加什么东西大家可以灵活操作。

/**
	 * 文件/图片上传
	 * @param files 文件集合
	 * @param type 1储存相对路径 2储存绝对路径
	 * @return
	 * @Author Smily
	 * @DateTime 2021年2月7日
	 */
	@ResponseBody
	@RequestMapping(value = "uploadTest.html", method = RequestMethod.POST)
	public Object uploadTest(MultipartFile[] files,
						 @RequestParam(defaultValue = "") String describe,//图片描述
						 @RequestParam(defaultValue = "") String isWaterMarker,//是否增加水印 0不增加 1增加
						 @RequestParam(defaultValue = "") String waterJson,//水印文字
						 @RequestParam(defaultValue = "1") String type) //1返回图片相对路径 2返回图片绝对路径
	{
		if (files == null || (files.length == 1 && files[0].getSize() == 0)){
			return putData("图片为空");
		}
		//水印信息
		List<FileUploadWaterMarkerEntity> fileUploadWaterMarkerEntityList = new ArrayList<>();
		if ("1".equals(isWaterMarker)){
			if (StringUtils.isBlank(waterJson)){
				return putData("水印信息为空");
			}
			//水印数组的长度应该和图片数组长度相同
			fileUploadWaterMarkerEntityList = JSON.parseArray(waterJson, FileUploadWaterMarkerEntity.class);
			if (fileUploadWaterMarkerEntityList.size() != files.length){
				return putData("水印信息数量与图片数量不符");
			}
			//登录用户信息
			for (FileUploadWaterMarkerEntity fileUploadWaterMarkerEntity : fileUploadWaterMarkerEntityList){
				//Common.getLoginName()是获取用户信息的方法,这里我就不贴出来了,大家自行处理
				fileUploadWaterMarkerEntity.setUserName(Common.getLoginName());
			}
		}

		Map<String, Object> map = fileUploadService.uploadTest(files,describe,type);

		return map;

	}
二、水印实体
package com.app.model.common;

/**
 * 
 * 上传图片时需要的水印信息实体
 *
 * @author Smily
 * @date 2021年2月7日
 *
 */
public class FileUploadWaterMarkerEntity {
	//用户姓名
	private String userName;

	//车牌
	private String carNumber;

	//时间
	private String time;

	//经度
	private String longitude;

	//纬度
	private String latitude;

	//地址
	private String address;

	public String getCarNumber() {
		return carNumber;
	}

	public void setCarNumber(String carNumber) {
		this.carNumber = carNumber;
	}

	public String getTime() {
		return time;
	}

	public void setTime(String time) {
		this.time = time;
	}

	public String getLongitude() {
		return longitude;
	}

	public void setLongitude(String longitude) {
		this.longitude = longitude;
	}

	public String getLatitude() {
		return latitude;
	}

	public void setLatitude(String latitude) {
		this.latitude = latitude;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
}
三、service层
	/**
     * 文件上传
     *
     * @Author: Smily
     * @Date: 2021/2/7 14:00
     */
    Map<String, Object> uploadTest(MultipartFile[] files,String describe, String type, String isWaterMarker, List<FileUploadWaterMarkerEntity> fileUploadWaterMarkerEntityList);
四、实现文件上传

水印信息添加原理是先将原图片上传至图片服务器,再通过图片IO流读取到源图片,使用Graphics2D绘图对源图片进行处理,生成新的目标图片再存储在图片服务器上,这时我们再获取到新的图片地址就OK了

package com.app.service.common.impl;


import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.app.base.basecontroller.BaseController;
import com.app.model.common.FileUploadEntity;
import com.app.model.common.FileUploadWaterMarkerEntity;
import com.app.service.common.FileUploadService;
import com.app.util.fileupload.ImageWatermarkUtil;


/**
 * 文件/图片上传
 * @Author Smily
 * @DateTime 2021年2月7日
 */
@Service("fileUploadService")
public class FileUploadServiceImpl extends BaseController implements FileUploadService {
	
	 public static Logger logger = LoggerFactory.getLogger(FileUploadServiceImpl.class);

	@Value("${httpHead}")
	private String httpHead;
	
	@Override
	public Map<String, Object> uploadTest(MultipartFile[] files, String describe, String type,String isWaterMarker,List<FileUploadWaterMarkerEntity> fileUploadWaterMarkerEntityList) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
		List<String> urlList = new ArrayList<>();
		for (int i = 0; i < files.length; i++) {
			MultipartFile file = files[i];
			String saveDBPath = "";
			if (file != null && file.getSize() > 0) {
				String fileName = file.getOriginalFilename();
				//根据文件类型储存在不同的文件夹下
				String extension = getSuffix(fileName);//文件格式
				if ((".pjpeg").equals(extension)) {//图片扩展名=转换
					extension = ".jpeg";
				}
				if (StringUtils.isBlank(extension)){
					logger.error("上传失败,未获取到文件名");
					return putData(Boolean.FALSE, "上传失败,未获取到文件名!", null);
				}
				//文件存储目录
				String datePath = new SimpleDateFormat("yyyy/MM/dd/").format(new Date());//根据日期创建不同目录
				String folderPath = "";
				if (isImage(extension)) {
					folderPath = "image/" + datePath;
				}else {
					folderPath = "file/" + datePath;
				}
				String path = "/home/file_storage/" + folderPath;
				//文件名 普通地址 命名方式:时间-数字-描述
				String newFileName = sdf.format(new Date()) + "-" + (i + 1) + extension;
				//文件名 加了水印图片地址
				String wFileName = "w-" + sdf.format(new Date()) + "-" + describe + "-" + (i + 1) + extension;
				File targetFile = new File(path, newFileName);
				if (!targetFile.exists()) targetFile.mkdirs();
				try {
					file.transferTo(targetFile);
					//判断是否需要加水印,如果要加水印则返回加水印的图片,否则正常返回图片地址
					if ("1".equals(isWaterMarker) && isImage(extension) && fileUploadWaterMarkerEntityList.size() > 0){
						List<String> list = new ArrayList<>();
						try {
							//将水印对象转化成需要的字符串
							FileUploadWaterMarkerEntity fileUploadWaterMarkerEntity = fileUploadWaterMarkerEntityList.get(i);
							//账号信息
							if (StringUtils.isNotBlank(fileUploadWaterMarkerEntity.getUserName())){
								list.add("账号:"+ fileUploadWaterMarkerEntity.getUserName());
							}
							//车牌信息
							if (StringUtils.isNotBlank(fileUploadWaterMarkerEntity.getCarNumber())){
								list.add("车牌:"+fileUploadWaterMarkerEntity.getCarNumber());
							}
							//时间
							if (StringUtils.isNotBlank(fileUploadWaterMarkerEntity.getTime())){
								list.add("时间:"+fileUploadWaterMarkerEntity.getTime());
							}
							//经度
							if (StringUtils.isNotBlank(fileUploadWaterMarkerEntity.getLongitude())){
								list.add("经度:"+fileUploadWaterMarkerEntity.getLongitude());
							}
							//纬度
							if (StringUtils.isNotBlank(fileUploadWaterMarkerEntity.getLatitude())){
								list.add("纬度:"+fileUploadWaterMarkerEntity.getLatitude());
							}
							//地址
							if (StringUtils.isNotBlank(fileUploadWaterMarkerEntity.getAddress())){
								list.add("地址:"+fileUploadWaterMarkerEntity.getAddress());
							}
						}catch (Exception e){
							logger.error("平台上传图片时解析水印信息json串异常");
						}
						ImageWatermarkUtil.imageAddWater(list,path + newFileName,path + wFileName,null);
						newFileName = wFileName;//如果有水印图片,则数据库储存水印图片地址
					}

					//储存数据库文件名
					if ("1".equals(type)){//相对路径
						saveDBPath = "/fileserver/"+ folderPath + newFileName;
					}else if ("2".equals(type)){//绝对路径
						saveDBPath = httpHead + "/fileserver/"+ folderPath + newFileName;
					}

					urlList.add(saveDBPath);

				} catch (Exception e) {
					logger.error("文件上传失败", e);
				}
			}
		}
		return putData(Boolean.TRUE, "上传成功!", urlList);
	}


	/**
	 * 获取文件后缀
	 * @Author Smily
	 * @DateTime 2021年2月7日
	 * @param filename
	 * @return
	 */
	private String getSuffix(String filename) {
		if (StringUtils.isNotBlank(filename)){
			String suffix = "";
			int pos = filename.lastIndexOf('.');
			if (pos > 0 && pos < filename.length() - 1) {
				suffix = filename.substring(pos);
			}
			return suffix.toLowerCase();
		}
		return "";
	}

	/**
	 * 判断文件类型
	 * @param suffix
	 * @Author Smily
	 * @DateTime 2021年2月7日
	 * @return
	 */
	public static boolean isImage(String suffix) {
		suffix = suffix.toUpperCase();
		if (".PNG".equals(suffix)
				|| ".JPG".equals(suffix)
				|| ".JEPG".equals(suffix)
				|| ".JPEG".equals(suffix)
				|| ".BMP".equals(suffix)
				|| ".HEIC".equals(suffix)) {
			return true;
		}
		return false;
	}

}

五、水印操作类

这里新增水印有很多细节操作,我这里水印生成在图片的左下角,并且为了保证生成的文字可见,增加了一张透明的底色背景图,Graphics2D有很多操作,比如等比分布,旋转,对称渲染等都能够实现,后面有时间我会专门写一篇关于绘图的文章,这里我们先实现简单水印操作

package com.app.util.fileupload;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Objects;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;


/**
 * 上传的图片增加水印
 *
 * @Author Smily
 * @Date 2021/2/7
 */
public class ImageWatermarkUtil {

    public static Logger logger = LoggerFactory.getLogger(ImageWatermarkUtil.class);

    // 水印文字颜色
    private static Color color = Color.black;

    /**
     * 给图片添加水印文字
     *
     * @param logoText 水印文字
     * @param srcImgPath 源图片路径
     * @param targerPath 目标图片路径
     */
    public static void imageAddWater(java.util.List<String> logoText, String srcImgPath, String targerPath,Integer degree) {
        doImage(logoText, srcImgPath, targerPath, degree);
    }

    /**
     * 给图片添加水印
     * 在图片的左下角添加,先添加半透明图片,再添加水印文字
     *
     * @param logoText
     * @param srcImgPath
     * @param targerPath
     * @param degree
     * @param
     */
    public static void doImage(java.util.List<String> logoText, String srcImgPath, String targerPath, Integer degree) {
        OutputStream os = null;
        try {
            // 源图片
            Image srcImg = ImageIO.read (new File (srcImgPath));
            int width = srcImg.getWidth (null);// 原图宽度
            int height = srcImg.getHeight (null);// 原图高度
            //通过原图计算出字体大小
            int FONT_SIZE = width / 3600;
            Font font = new Font ("宋体", Font.BOLD, FONT_SIZE);

            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, 0, 0, width, height, null);
            //添加水印图片,增加一张底色图片,这里我把路径放在resources/images/bg_water_mark_white.png下面
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            String url = request.getSession().getServletContext().getRealPath("/") + "resources/images/bg_water_mark_white.png";
            Image srcWaterImg = ImageIO.read(new File(url));
            //水印文字的字体高度(多加一行,防止地址信息过长)
            int markHeight = FONT_SIZE * (logoText.size() + 1);
            //水印图片透明度
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,0.2f));
            int imgX = 0,imgY;//水印图片x轴原点,水印图片y轴原点
            imgY = height - markHeight - FONT_SIZE;
            //水印图片在底部添加,长度为原图片长度,宽度为字体高度再往上加一点点
            g.drawImage(srcWaterImg, imgX, imgY, width, markHeight, 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, 0.8f));

            int x,y;//水印文字x轴原点,水印文字y轴原点
            //添加多行文字水印,水印文字也在图片左下角
            for (int i = 0;i < logoText.size();i++){
                if (!logoText.get(i).contains("地址")){
                    x = 15;
                    y = (height - (markHeight - (i * FONT_SIZE)));
                    g.drawString (logoText.get(i), x, y);
                }else {
                    //如果包含地址信息,文字可能超过图片宽度,这时需要换行操作(注意:地址信息一定要在logoText数组的最后一个)
                    x = 15;
                    y = (height - (markHeight - (i * FONT_SIZE)));
                    int tempCharLen = 0;//单字符长度
                    int tempLineLen = 0;//单行字符总长度
                    StringBuffer sb =new StringBuffer();
                    for(int j = 0; j < logoText.get(i).length(); j++) {
                        char tempChar = logoText.get(i).charAt(j);
                        tempCharLen = getCharLen(tempChar, g);
                        tempLineLen += tempCharLen;
                        if(tempLineLen >= width) {
                            //长度已经满一行,进行文字叠加
                            g.drawString(sb.toString(), x, y);
                            //清空内容,重新赋值追加
                            sb.delete(0, sb.length());
                            int k = i;
                            k++;
                            y = (height - (markHeight - (k * FONT_SIZE)));
                            tempLineLen = 0;
                        }
                        sb.append(tempChar);//追加字符
                    }
                    //最后叠加余下的文字
                    g.drawString(sb.toString(), x, y);
                }
            }

            g.dispose();
            // 生成图片
            os = new FileOutputStream (targerPath);
            ImageIO.write (buffImg, "JPG", os);
            os.close();
        } catch (Exception e) {
            e.printStackTrace ();
            logger.error("图片添加水印失败");
        }
    }


    /**
     * 单个字符长度
     *
     * @Param [c, g]
     * @Author: Smily
     * @Date: 2020/9/9 14:20
     */
    public static int getCharLen(char c, Graphics2D g) {
        return g.getFontMetrics(g.getFont()).charWidth(c);
    }

}

现在己经实现了水印图片的上传,这里我把底色图片也贴出来给大家。
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值