前提:
图片的压缩大致有两种,一种是将图片的尺寸压缩小,另一种是尺寸不变,将压缩质量,一般对于项目我们需要第一种,即用户上传一张分辨率为3840 × 2160的图片,通过上传图片接口后上传到OSS上的图片分辨率会变成1920×1080(如3840 × 2160的图片大小为11.4M,上传后的图片大概会为1.9M),此时上传后到OSS的图片和原图质量上一致,也就是说看上去只的大小的区别,清晰度上没有任何区别,如还希望图片再小点再进行质量压缩。
整体思路:
- 用户点击上传图片按钮,调用上传接口,通过springboot的MultipartFile接口接收到文件,再将MultipartFile转化成一个文件放到项目中待使用(路径自己指定)。
- 通过File tempFile = new File(contextPath)获取到该文件(contextPath是步骤1中转存图片的文件路径),使用Thumbnails对图片进行尺寸压缩和格式转换(将图片转为jpg),然后将压缩后的图片替换步骤1中的图片。
- 再次通过File tempFile = new File(contextPath)获取压缩后的图片文件,将图片转为inputStream流上传至OSS,将项目中的图片文件删除,完成。
测试效果:
调用上传图片接口:
接口返回结果:
{
"success": true,
"status": "200",
"msg": "上传图片成功",
"data": "bookService/pictureUploadTest/b42dd004ddae4b329b91de0472b26163/原图11.4M(3840×2160.jpg"
}
OSS上压缩后的图片
项目代码:
控制器
/**
* 上传图片(通用)
*
* @param file 文件
* @param id 图书id
* @param folderSecond 设置二级图片路径(例bookUpload/)
* @return
*/
@RequestMapping(value = "pictureFile", method = RequestMethod.POST)
@ResponseBody
public JsonResult uploadPicture(MultipartFile file, String id, String folderSecond) {
try {
String pictureUrl = uploadService.uploadPicture(file, id, folderSecond);
log.info("上传后的图片地址:" + pictureUrl);
return renderSuccess("上传图片成功", pictureUrl);
} catch (ProgramException p) {
log.error("上传图片失败." + p.getMessage());
return renderError(p.getMessage());
} catch (Exception e) {
log.error("上传图片失败." + e.getMessage());
return renderError("上传图片失败");
}
}
实现类:
注意:使用Thumbnailator进行格式转换的时候如果图片格式是png,在转成jpg后会出现图片变红的bug,所以此处的处理是不处理,也就是如果图片是后缀是png的话,不进行格式转换,只进行大小压缩
@Override
public String uploadPicture(MultipartFile multipartfile, String id, String folderSecond) throws Exception {
if (multipartfile == null || id == null || folderSecond == null) {
throw new ProgramException("上传图片参数不合法");
}
if (multipartfile.getSize() > 50 * 1024 * 1024) {
throw new ProgramException("上传图片大小不能超过50M!");
}
//设置统一图片后缀名
String suffixName;
//获取图片文件名(不带扩展名的文件名)
String prefixName = getFileNameWithoutEx(multipartfile.getOriginalFilename());
//获取图片后缀名,判断如果是png的话就不进行格式转换,因为Thumbnails存在转png->jpg图片变红bug
String suffixNameOrigin = getExtensionName(multipartfile.getOriginalFilename());
if ("png".equals(suffixNameOrigin)) {
suffixName = "png";
} else {
suffixName = "jpg";
}
//图片存储文件夹
String filePath = "web/src/main/resources/";
//图片在项目中的地址(项目位置+图片名,带后缀名)
String contextPath = filePath + prefixName + "." + suffixName;
//图片在项目中的地址(项目位置+图片名,带后缀名)
File tempFile = new File(contextPath);
if (!tempFile.exists()) {
//生成图片文件
FileUtils.copyInputStreamToFile(multipartfile.getInputStream(), tempFile);
}
/*
* size(width,height) 若图片横比1920小,高比1080小,不变
* 若图片横比1920小,高比1080大,高缩小到1080,图片比例不变 若图片横比1920大,高比1080小,横缩小到1920,图片比例不变
* 若图片横比1920大,高比1080大,图片按比例缩小,横为1920或高为1080
* 图片格式转化为jpg,质量不变
*/
if (multipartfile.getSize() >= 1920 * 1080) {
if (!"png".equals(suffixName)) {
Thumbnails.of(contextPath).size(1920, 1080).outputQuality(1f).outputFormat("jpg").toFile(contextPath);
} else {
Thumbnails.of(contextPath).size(1920, 1080).outputQuality(1f).toFile(contextPath);
}
} else {
if (!"png".equals(suffixName)) {
Thumbnails.of(contextPath).outputQuality(1f).scale(1f).outputFormat("jpg").toFile(contextPath);
} else {
Thumbnails.of(contextPath).outputQuality(1f).scale(1f).toFile(contextPath);
}
}
//获取压缩后的图片
File f = new File(contextPath);
InputStream inputStream = new FileInputStream(f);
//设置三级文件夹名
String folderThird = id + "/";
//设置OSS上的二级文件目录
String folderPath = folderSecond + folderThird;
//设置图片存储在oss上的名字
String fileName = prefixName + "." + suffixName;
try {
//上传图片到OSS,返回图书路径
String resultUrl = AliyunOSSClientUtil.uploadImg2Oss(inputStream, folderPath, fileName);
//将临时文件删除
f.delete();
return resultUrl;
} catch (Exception e) {
throw new ProgramException("图片上传失败");
} finally {
inputStream.close();
}
}
/**
* 获取文件扩展名
*
* @param filename 文件名
* @return
*/
public static String getExtensionName(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length() - 1))) {
return filename.substring(dot + 1);
}
}
return filename;
}
//删除oss上的文件(该博客没有涉及到该方法,可以跳过)
@Override
public boolean deleteOssFile(String id) throws Exception {
if (id == null) {
throw new ProgramException("删除OSS文件参数不合法");
}
//设置文件位置的二级文件夹名
String folderSecond = "bookUpload/";
//设置三级文件夹名
String folderThird = id;
//设置OSS上要删除的的二级文件目录(例:client/45e233d07c664b93b7bb35331285a8d8)
String folderPath = folderSecond + folderThird;
//删除OSS上的文件(文件夹+文件)
return AliyunOSSClientUtil.deleteFile2Oss(folderPath);
}
/**
* 获取不带扩展名的文件名
*
* @param filename 文件
* @return
*/
private static String getFileNameWithoutEx(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length()))) {
return filename.substring(0, dot);
}
}
return filename;
}
自定义OSS工具类
package com.welsee.tools;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import com.welsee.exception.ProgramException;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 阿里云OSS相关java API
*
* @author lixinyu
*/
@Slf4j
public class AliyunOSSClientUtil {
//阿里云API的内或外网域名
private static String ENDPOINT;
//阿里云API的密钥Access Key ID
private static String ACCESS_KEY_ID;
//阿里云API的密钥Access Key Secret
private static String ACCESS_KEY_SECRET;
//阿里云API的bucket名称
private static String BUCKET_NAME;
//阿里云API的文件夹名称
private static String FOLDER;
//初始化属性
static {
ENDPOINT = OSSClientConstants.ENDPOINT;
ACCESS_KEY_ID = OSSClientConstants.ACCESS_KEY_ID;
ACCESS_KEY_SECRET = OSSClientConstants.ACCESS_KEY_SECRET;
BUCKET_NAME = OSSClientConstants.BUCKET_NAME;
FOLDER = OSSClientConstants.FOLDER;
}
/**
* 获取阿里云OSS客户端对象
*
* @return ossClient
*/
private static OSSClient getOSSClient() {
// log.info("实例化阿里云OSS对象===============" + ENDPOINT + "," + ACCESS_KEY_ID + "," + ACCESS_KEY_SECRET);
return new OSSClient(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
}
/**
* 通过文件名判断并获取OSS服务文件上传时文件的contentType
*
* @param fileName 文件名
* @return 文件的contentType
*/
private static String getContentType(String fileName) {
//文件的后缀名
String fileExtension = fileName.substring(fileName.lastIndexOf("."));
if (".bmp".equalsIgnoreCase(fileExtension)) {
return "image/bmp";
}
if (".gif".equalsIgnoreCase(fileExtension)) {
return "image/gif";
}
if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension) || ".png".equalsIgnoreCase(fileExtension)) {
return "image/jpeg";
}
if (".html".equalsIgnoreCase(fileExtension)) {
return "text/html";
}
if (".txt".equalsIgnoreCase(fileExtension)) {
return "text/plain";
}
if (".vsd".equalsIgnoreCase(fileExtension)) {
return "application/vnd.visio";
}
if (".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) {
return "application/vnd.ms-powerpoint";
}
if (".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) {
return "application/msword";
}
if (".xml".equalsIgnoreCase(fileExtension)) {
return "text/xml";
}
//默认返回类型
return "image/jpeg";
}
/**
* 封装上传到OSS服务器方法 如果同名文件会覆盖服务器上的
*
* @param inputStream 文件流
* @param folderPath OSS目录下的二级文件名
* @param fileName 文件名称 包括后缀名
* @return 出错返回"" ,唯一MD5数字签名
*/
public static String uploadImg2Oss(InputStream inputStream, String folderPath, String fileName) throws Exception {
String result = "";
// 创建上传Object的Metadata
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
//指定该Object被下载时的网页的缓存行为
metadata.setCacheControl("no-cache");
//指定该Object下设置Header
metadata.setHeader("Pragma", "no-cache");
//文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
//如果没有扩展名则填默认值application/octet-stream
metadata.setContentType(getContentType(fileName));
//指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
metadata.setContentDisposition("inline;filename=" + fileName);
String resultUrl = FOLDER + folderPath + fileName;
// 上传文件
PutObjectResult putResult = getOSSClient().putObject(BUCKET_NAME, resultUrl, inputStream, metadata);
// 设置文件的访问权限为公共读。
getOSSClient().setObjectAcl(BUCKET_NAME, resultUrl, CannedAccessControlList.PublicRead);
if (!"".equals(putResult.getETag())) {
result = resultUrl;
log.info("上传后的图片MD5数字唯一签名:" + putResult.getETag()); //可以用来验证上传的资源是否为同一个(暂时没用到)
log.info("上传阿里云OSS服务器成功");
} else {
log.error("上传阿里云OSS服务器异常");
}
inputStream.close();
getOSSClient().shutdown();
return result;
}
/**
* 删除oss上的文件,文件夹+文件夹里面的所的文件(该博客没有涉及到该方法,可以跳过)
*
* @param folderPath 设置OSS上要删除的的二级文件目录(例:(client/45e233d07c664b93b7bb35331285a8d8))
* @return
*/
public static boolean deleteFile2Oss(String folderPath) {
//oss项目名+图书位置(bookService/bookUpload/45e233d07c664b93b7bb35331285a8d8)
String prefix = FOLDER + folderPath;
// 列举文件。 如果不设置KeyPrefix,则列举存储空间下所有的文件。KeyPrefix,则列举包含指定前缀的文件。
ObjectListing objectListing = getOSSClient().listObjects(new ListObjectsRequest(BUCKET_NAME).withPrefix(prefix));
List<String> keys = new ArrayList<>();
List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
for (OSSObjectSummary s : sums) {
keys.add(s.getKey());
}
//如果OSS文件上有文件的话删除,没有直接跳过
if (keys.size() > 0) {
// 删除文件夹内的文件。
DeleteObjectsResult deleteObjectsResult = getOSSClient().deleteObjects(new DeleteObjectsRequest(BUCKET_NAME).withKeys(keys));
deleteObjectsResult.getDeletedObjects();
//删除文件夹
List<String> key = new ArrayList<>();
key.add(prefix);
getOSSClient().deleteObjects(new DeleteObjectsRequest(BUCKET_NAME).withKeys(key));
}
// 关闭OSSClient。
getOSSClient().shutdown();
return true;
}
/**
* 删除oss一个文件夹中的无用图片,保留一个图片(该博客没有涉及到该方法,可以跳过)
*
* @param picturePath 有用的图片路径
* @return
*/
public static boolean deletePictureUseless(String picturePath) throws Exception {
if (picturePath == null) {
throw new ProgramException("删除oss无用图片参数不合法");
}
//文件夹路径(bookService/bookUpload/0c6fd6fbac33466e8b26e73115f80edd)
String filePath = picturePath.substring(0, picturePath.lastIndexOf("/"));
//列举所有的图片
ObjectListing objectListing = getOSSClient().listObjects(new ListObjectsRequest(BUCKET_NAME).withPrefix(filePath));
List<String> keys = new ArrayList<>();
List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
for (OSSObjectSummary s : sums) {
//判断是否是图片,只删无用图片, 不删图书
if ("jpg".equals(s.getKey().substring(s.getKey().lastIndexOf(".") + 1))) {
if (!picturePath.equals(s.getKey())) {
keys.add(s.getKey());
}
}
}
log.info("要删除的图片为===============" + keys.toString());
// 删除无用图片
if (keys.size() > 0) {
DeleteObjectsResult deleteObjectsResult = getOSSClient().deleteObjects(new DeleteObjectsRequest(BUCKET_NAME).withKeys(keys));
deleteObjectsResult.getDeletedObjects();
}
return true;
}
}
OSS配置文件:
package com.welsee.tools;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* @author lixinyu
* @class:OSSClientConstants
* @descript:阿里云注册用户基本常量
* @date:2018年11月06日 下午5:52:34
*/
@Configuration
public class OSSClientConstants {
//阿里云API的外网域名
public static String ENDPOINT;
//阿里云API的密钥Access Key ID
public static String ACCESS_KEY_ID;
//阿里云API的密钥Access Key Secret
public static String ACCESS_KEY_SECRET;
//阿里云API的bucket名称
public static String BUCKET_NAME;
//阿里云API的文件夹名称
public static String FOLDER;
@Value("${OSS_ENDPOINT}")
public void setENDPOINT(String ENDPOINT) {
OSSClientConstants.ENDPOINT = ENDPOINT;
}
@Value("${OSS_ACCESS_KEY_ID}")
public void setAccessKeyId(String accessKeyId) {
OSSClientConstants.ACCESS_KEY_ID = accessKeyId;
}
@Value("${OSS_ACCESS_KEY_SECRET}")
public void setAccessKeySecret(String accessKeySecret) {
OSSClientConstants.ACCESS_KEY_SECRET = accessKeySecret;
}
@Value("${OSS_BUCKET_NAME}")
public void setBucketName(String bucketName) {
BUCKET_NAME = bucketName;
}
@Value("${OSS_FOLDER}")
public void setFOLDER(String FOLDER) {
OSSClientConstants.FOLDER = FOLDER;
}
}