防止走丢~~欢迎大家留言收藏点赞
在上一篇我们已经实现了文件的上传,那么如果对于图片的上传,我们要给图片加上我们需要的水印信息,比如图片的上传人,上传地点等信息,此时我们为上传功能再新增水印功能。
最终上传效果如图所示
一、前端上传至服务端(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);
}
}
现在己经实现了水印图片的上传,这里我把底色图片也贴出来给大家。