既然是o2o, 图片是不可或缺的。
存储这些图片,一般的处理方式:放到专门图片服务器或者在主机上单独划分目录去存储这些图片。数据库中的字段仅仅存放图片的地址。 关于图片的存放目录,合理的情况在系统配置表中配置存储的根目录和各个模块图片存放的相对路径, 便于应用的迁移。
处理图片的框架这里我们选择Thumbnailator为图片添加水印,为简化图片的处理,图片工具类自然是不可或缺的。
Github地址: https://github.com/coobird/thumbnailator
Thumbnailator是一个用来生成图像缩略图的 Java类库,可生成图片缩略图,支持根据一个目录批量生成缩略图,支持图片缩放,区域裁剪,水印,旋转,保持比例等等
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
思路:
1. 设置文件存储的根目录
2. 项目中使用的图片按照模块和功能,设置存储的相对路径。
3. 图片的绝对路径: 根目录+相对路径
我们首先处理商铺模块,工具类会随着项目的推进而完善。
首先定义每个商铺对应的图片,存储在对应shopId的目录下。
package com.artisan.o2o.util;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*
* @ClassName: FileUtil
*
* @Description: 文件工具类
*
* @author: Mr.Yang
*
* @date: 2018年5月18日 下午11:32:05
*/
public class FileUtil {
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
// 路径分隔符. 在 类UNIX系统上为 '/',在 windows 系统上,它为 '\'
private static String seperator = System.getProperty("file.separator");
/**
*
*
* @Title: getImgBasePath
*
* @Description: 根据不同的操作系统,获取图片的存放路径。
*
* 一般情况下都是将图片存放到专门的图片服务器或者主机上的其他目录。
* 而存放的目录路径,都是以配置项的形式存放在数据库配置表中或者配置文件中。
*
* 这里为了简单起见,我们直接将路径硬编码在代码中。
*
* 图片存储的根路径
* @return
*
* @return: String
*/
public static String getImgBasePath() {
String os = System.getProperty("os.name");
logger.debug("os.name: {}", os);
String basePath = "";
if (os.toLowerCase().startsWith("win")) {
basePath = "D:/o2o/image";
} else {
basePath = "/home/artisan/o2o/image";
}
// 根据操作系统的不同,使用当前操作系统的路径分隔符替换掉,我们写的basePath中的路径分隔符,当然了也可以在basePath赋值的时候直接使用seperator
basePath = basePath.replace("/", seperator);
logger.debug("basePath: {}", basePath);
return basePath;
}
/**
*
*
* @Title: getShopImagePath
*
* @Description: 获取特定商铺的图片的路径,根据shopId来区分。理应配置到数据库配置表中或者配置文件中。
*
* 同样的这里为了简化操作,硬编码
*
* 约定每个店铺下的图片分别存放在对应的店铺id下
*
* 图片存储的相对路径.图片最终存储的位置需要加上getImgBasePath方法返回的basePath
*
* 数据库tb_shop中的shop_img字段存储的是该相对路径,即该方法的返回值
*
* @param shopId
* @return
*
* @return: String
*/
public static String getShopImagePath(long shopId) {
String shopImgPath = "/upload/item/shopImage/" + shopId + "/";
shopImgPath = shopImgPath.replace("/", seperator);
logger.debug("shopImgPath: {}", shopImgPath);
return shopImgPath;
}
/**
*
*
* @Title: getWaterMarkFile
*
* @Description: 水印文件的位置。理应配置到数据库配置表中或者配置文件中。
*
* 同样的这里为了简化操作,硬编码
*
* @return
*
* @return: File
*/
public static File getWaterMarkFile() {
String basePath = FileUtil.getImgBasePath();
String waterMarkImg = basePath + "/watermark/watermark.png";
waterMarkImg = waterMarkImg.replace("/", seperator);
logger.debug("waterMarkImg path: {}", waterMarkImg);
return new File(waterMarkImg);
}
}
思路:
1. 围绕添加水印输出目标图片这个功能展开
2. 核心方法generateThumbnails ,创建缩略图中3个关键参数 源图片 水印图 和 目标图片
3. 围绕 源图片 水印图 和 目标图片,调用FileUtil中的方法组装目标图片的存储路径以及目标图片的名称。
4. 为了方便Service层做单元测试,详见commonsMultipartFileToFile方法的说明
package com.artisan.o2o.util;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
public class ImageUtil {
private static final Logger logger = LoggerFactory.getLogger(ImageUtil.class);
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
private static final Random random = new Random();
/**
*
*
* @Title: commonsMultipartFileToFile
*
* @Description: 众所周知,文件上传spring使用CommonsMultipartFile来接收上传过来的文件,
* 而generateThumbnails方法中的第一个入参我们已经调整为了File
* (主要是为了方便service层进行单元测试
* ,在service层无法初始化CommonsMultipartFile,只能在前端传入的时候初始化
* ,我们从底往上搭建项目,还不具备页面,因此做了适当的改造)
*
* 需要将CommonsMultipartFile转换为File
*
* @param cfile
*
* @return: File
*/
public File commonsMultipartFileToFile(CommonsMultipartFile cfile) {
File file = null;
try {
// 获取前端传递过来的文件名
file = new File(cfile.getOriginalFilename());
// 将cfile转换为file
cfile.transferTo(file);
} catch (Exception e) {
e.printStackTrace();
logger.error("commonsMultipartFileToFile failed:{}", e.getMessage());
}
return file;
}
/**
*
*
* @Title: generateThumbnails
*
* @Description: 处理缩略图,并返回绝对路径 (门面照以及商品的小图)
*
* 举例:shop的水印图标要存放在tb_shop的 shop_img字段,所以需要返回 水印图标所在的路径
*
*
*
* @param file
* 需要添加水印的文件
* @param destPath
* 添加水印后的文件的存放目录
* @return
*
* @return: String 返回相对路径的好处是,项目一旦迁移,不会影响,只需要变更basePath即可,尽可能少改动。
* 图片存储的绝对路径=basePath+该路径
*/
public static String generateThumbnails(File file, String destPath) {
// 拼接后的新文件的相对路径
String relativeAddr = null;
try {
// 1.为了防止图片的重名,不采用用户上传的文件名,系统内部采用随机命名的方式
String fileName = generateRandomFileName();
// 2.获取用户上传的文件的扩展名,用于拼接新的文件名
String fileExtensionName = getFileExtensionName(file);
// 3.校验目标目录是否存在,不存在创建目录
validateDestPath(destPath);
// 4.拼接新的文件名
relativeAddr = destPath + fileName + fileExtensionName;
logger.info("图片相对路径 {}", relativeAddr);
// 绝对路径的形式创建文件
String basePath = FileUtil.getImgBasePath();
File destFile = new File(basePath + relativeAddr);
logger.info("图片完整路径 {}", destFile.getAbsolutePath());
// 5.给源文件加水印后输出到目标文件
Thumbnails.of(file).size(500, 500).watermark(Positions.BOTTOM_RIGHT, ImageIO.read(FileUtil.getWaterMarkFile()), 0.25f).outputQuality(0.8).toFile(destFile);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("创建水印图片失败:" + e.toString());
}
return relativeAddr;
}
/**
*
*
* @Title: generateRandomFileName
*
* @Description: 系统时间+5位随机数字
*
* @param file
* @return
*
* @return: String
*/
private static String generateRandomFileName() {
String sysdate = sdf.format(new Date());
// 5位随机数 10000到99999之间 ,下面的取值[ 包括左边,不包括右边 ],满足10000到99999
int rannum = (int) (random.nextDouble() * (99999 - 10000 + 1)) + 10000;
String randomFileName = sysdate + rannum;
logger.debug("fileName: {}", randomFileName);
return randomFileName;
}
/**
*
*
* @Title: getFileExtensionName
*
* @Description: 获取文件的扩展名
*
* @param file
*
* @return: String
*/
private static String getFileExtensionName(File file) {
String fileName = file.getName();
String extension = fileName.substring(fileName.lastIndexOf("."));
logger.debug("extension: {}", extension);
return extension;
}
/**
*
*
* @Title: validateDestPath
*
* @Description:
*
* @param targetAddr
* 图片上传的相对路径
*
* @return: void
*/
private static void validateDestPath(String targetAddr) {
// 获取绝对路径
String realFileParentPath = FileUtil.getImgBasePath() + targetAddr;
// 不存在的话,逐级创建目录
File dirPath = new File(realFileParentPath);
if (!dirPath.exists()) {
dirPath.mkdirs();
}
}
/**
*
*
* @Title: main
*
* @Description: 演示thumbnail的基本用法
*
* @param args
*
* @return: void
*/
public static void main(String[] args) {
try {
// 需要加水印的图片
File souceFile = new File("D:/o2o/artisan.jpg");
// 加完水印后输出的目标图片
File destFile = new File("D:/o2o/artisan-with-watermark.jpg");
// 水印图片
File warterMarkFile = FileUtil.getWaterMarkFile();
logger.info("warterMarkFileName: {}", warterMarkFile.getName());
// 加水印
Thumbnails.of(souceFile).size(500, 500).watermark(Positions.BOTTOM_RIGHT, ImageIO.read(warterMarkFile), 0.25f).outputQuality(0.8).toFile(destFile);
logger.info("水印添加成功,带有水印的图片{}", destFile.getAbsolutePath());
generateRandomFileName();
getFileExtensionName(souceFile);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("创建缩略图失败:" + e.toString());
}
}
}
代码地址: https://github.com/yangshangwei/o2o