前提:
圖檔的壓縮大緻有兩種,一種是将圖檔的尺寸壓縮小,另一種是尺寸不變,将壓縮品質,一般對于項目我們需要第一種,即使用者上傳一張分辨率為3840 × 2160的圖檔,通過上傳圖檔接口後上傳到OSS上的圖檔分辨率會變成1920×1080(如3840 × 2160的圖檔大小為11.4M,上傳後的圖檔大概會為1.9M),此時上傳後到OSS的圖檔和原圖品質上一緻,也就是說看上去隻的大小的差別,清晰度上沒有任何差別,如還希望圖檔再小點再進行品質壓縮。
整體思路:
- 使用者點選上傳圖檔按鈕,調用上傳接口,通過springboot的MultipartFile接口接收到檔案,再将MultipartFile轉化成一個檔案放到項目中待使用(路徑自己指定)。
- 通過指定檔案路徑擷取到該檔案(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 = null;
//上傳時從項目中拿到的圖檔
File f = null;
InputStream inputStream = null;
try {
//圖檔在項目中的位址(項目位置+圖檔名,帶字尾名)
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,品質不變
*/
BufferedImage image = ImageIO.read(multipartfile.getInputStream());
if (image.getHeight() > 1080 || image.getWidth() > 1920) {
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);
}
}
//擷取壓縮後的圖檔
f = new File(contextPath);
inputStream = new FileInputStream(f);
//設定三級檔案夾名
String folderThird = id + "/";
//設定OSS上的二級檔案目錄
String folderPath = folderSecond + folderThird;
//設定圖檔存儲在oss上的名字
String fileName = prefixName + "." + suffixName;
//上傳圖檔到OSS,傳回圖書路徑
String resultUrl = AliyunOSSClientUtil.uploadImg2Oss(inputStream, folderPath, fileName);
return resultUrl;
} catch (Exception e) {
throw new ProgramException("圖檔上傳失敗");
} finally {
//将臨時檔案删除
tempFile.delete();
f.delete();
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;
}
}