天天看點

Springboot使用Thumbnailator壓縮圖檔上傳到阿裡雲OSS(無損壓縮)

前提:

圖檔的壓縮大緻有兩種,一種是将圖檔的尺寸壓縮小,另一種是尺寸不變,将壓縮品質,一般對于項目我們需要第一種,即使用者上傳一張分辨率為3840 × 2160的圖檔,通過上傳圖檔接口後上傳到OSS上的圖檔分辨率會變成1920×1080(如3840 × 2160的圖檔大小為11.4M,上傳後的圖檔大概會為1.9M),此時上傳後到OSS的圖檔和原圖品質上一緻,也就是說看上去隻的大小的差別,清晰度上沒有任何差別,如還希望圖檔再小點再進行品質壓縮。

整體思路:

  1. 使用者點選上傳圖檔按鈕,調用上傳接口,通過springboot的MultipartFile接口接收到檔案,再将MultipartFile轉化成一個檔案放到項目中待使用(路徑自己指定)。
  2. 通過指定檔案路徑擷取到該檔案(contextPath是步驟1中轉存圖檔的檔案路徑),使用Thumbnails對圖檔進行尺寸壓縮和格式轉換(将圖檔轉為jpg),然後将壓縮後的圖檔替換步驟1中的圖檔。
  3. 再次通過File tempFile = new File(contextPath)擷取壓縮後的圖檔檔案,将圖檔轉為inputStream流上傳至OSS,将項目中的圖檔檔案删除,完成。

測試效果:

調用上傳圖檔接口:

Springboot使用Thumbnailator壓縮圖檔上傳到阿裡雲OSS(無損壓縮)

接口傳回結果:

{
    "success": true,
    "status": "200",
    "msg": "上傳圖檔成功",
    "data": "bookService/pictureUploadTest/b42dd004ddae4b329b91de0472b26163/原圖11.4M(3840×2160.jpg"
}
           

OSS上壓縮後的圖檔

Springboot使用Thumbnailator壓縮圖檔上傳到阿裡雲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;
    }
}