文件上傳接入阿里雲OSS

目的:將文件交給阿里雲進行管理,可避免文件對本地服務器資源的佔用,阿里雲OSS還可根據讀寫偏好選擇合適的文件存儲類型服務器,文件異地備份等

一、阿里雲OSS基礎瞭解(前提)

1、存儲空間(Bucket)

用於存儲對象(Object)的容器,同一個存儲空間的內部是扁平的,沒有文件系統的目錄等概念,所有的對象都必須隸屬於某個存儲空間。存儲空間具有各種配置屬性,包括地域、訪問權限、存儲類型等。可根據實際需求,創建不同存儲空間存儲不同數據。(百度的官話)

簡而言之:Bucket就簡單理解爲C盤,D盤就可以了,可以在不同的Bucket下創建文件夾存儲文件。

2、存儲對象(Object)

是 OSS 存儲數據的基本單元,也被稱爲 OSS 的文件。對象由元信息(Object Meta)、用戶數據(Data)和文件名(Key)組成。對象由存儲空間內部唯一的 Key 來標識。對象元信息是一組鍵值對,表示了對象的一些屬性,比如最後修改時間、大小等信息,支持在元信息中存儲一些自定義的信息。對象的生命週期是從上傳成功到被刪除爲止。(還是官話)

簡而言之:就是要存儲的文件。

二、環境準備及測試

1、pom座標導入

<!-- 阿里雲OSS -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

2、阿里雲OSS API操作返回值類

package cc.mrbird.febs.finance.domain.dto;

import lombok.Data;

/**
 * @Author: sunguoqiang
 * @Description: 阿里雲上傳結果集
 * @DateTime: 2022/8/3 16:27
 **/
@Data
public class AliyunOssResult {
    /**
     * code:200成功
     * code: 400失敗
     */
    private int code;
    /**
     * 上傳成功的返回url
     */
    private String url;
    /**
     * 提示信息
     */
    private String msg;
}

3、阿里雲OSS API操作工具類(直接調用阿里雲OSS API的類)

package cc.mrbird.febs.finance.util;

import cc.mrbird.febs.finance.domain.dto.AliyunOssResult;
import cc.mrbird.febs.finance.exception.FileUploadException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.List;

/**
 * @Author: sunguoqiang
 * @Description: TODO
 * @DateTime: 2022/8/3 16:23
 **/
@Component
@Slf4j
public class AliyunOSSUtil {

    @Value("${aliyunOss.endpoint}")
    private String endpoint;
    @Value("${aliyunOss.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyunOss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliyunOss.bucketName}")
    private String bucketName;
    @Value("${aliyunOss.urlPrefix}")
    private String urlPrefix;

    private OSS ossClient;

    /**
     * 初始化OssClient
     */
    @PostConstruct
    public void generateOSS() {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        if (ossClient != null) {
            this.ossClient = ossClient;
        } else {
            log.error("OSS對象實例化失敗.");
            throw new RuntimeException("OSS對象實例化失敗.");
        }
    }

    /**
     * 判斷阿里雲bucket下是否存在filePath文件夾,不存在則創建
     *
     * @param filePath
     */
    public void isExistAndCreateFolder(String filePath) {
        if (!isExists(filePath)) {
            boolean mkdirs = createFolder(filePath);
            if (!mkdirs) {
                throw new FileUploadException("附件文件夾創建失敗!");
            }
        }
    }

    /**
     * 上傳文件,以IO流方式
     *
     * @param inputStream 輸入流
     * @param objectName  唯一objectName(在oss中的文件名字)
     */
    public AliyunOssResult upload(InputStream inputStream, String objectName) {
        AliyunOssResult aliyunOssResult = new AliyunOssResult();
        try {
            // 上傳內容到指定的存儲空間(bucketName)並保存爲指定的文件名稱(objectName)。
            PutObjectResult putObject = ossClient.putObject(bucketName, objectName, inputStream);
            // 關閉OSSClient。
            ossClient.shutdown();
            aliyunOssResult.setCode(200);
            aliyunOssResult.setUrl(urlPrefix + objectName);
            aliyunOssResult.setMsg("上傳成功");
        } catch (Exception e) {
            e.printStackTrace();
            aliyunOssResult.setCode(400);
            aliyunOssResult.setMsg("上傳失敗");
        }
        return aliyunOssResult;
    }

    /**
     * 獲取oss文件
     *
     * @param folderName
     * @return
     */
    public OSSObject get(String folderName) {
        OSSObject ossObject = ossClient.getObject(bucketName, folderName);
        return ossObject;
    }

    /**
     * 刪除OSS中的單個文件
     *
     * @param objectName 唯一objectName(在oss中的文件名字)
     */
    public void delete(String objectName) {
        try {
            ossClient.deleteObject(bucketName, objectName);
            // 關閉OSSClient。
            ossClient.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 批量刪除OSS中的文件
     *
     * @param objectNames oss中文件名list
     */
    public void delete(List<String> objectNames) {
        try {
            // 批量刪除文件。
            DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(objectNames));
            List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
            // 關閉OSSClient。
            ossClient.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取文件臨時url
     *
     * @param objectName    oss中的文件名
     * @param effectiveTime 有效時間(ms)
     */
    public String getUrl(String objectName, long effectiveTime) {
        // 設置URL過期時間
        Date expiration = new Date(new Date().getTime() + effectiveTime);
        GeneratePresignedUrlRequest generatePresignedUrlRequest;
        generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objectName);
        generatePresignedUrlRequest.setExpiration(expiration);
        URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }

    /**
     * oss拷貝文件
     *
     * @param sourcePath
     * @param targetPath
     */
    public void copyFileSourceToTarget(String sourcePath, String targetPath) throws FileNotFoundException {
        try {
            CopyObjectResult copyObjectResult = ossClient.copyObject(bucketName, sourcePath, bucketName, targetPath);
        } catch (Exception e) {
            throw new FileUploadException("文件轉移操作異常.");
        }
    }

    /**
     * 根據文件路徑獲取輸出流
     *
     * @param filePath
     * @return
     * @throws IOException
     */
    public InputStream getInputStream(String filePath) throws IOException {
        if (filePath == null || filePath.isEmpty()) {
            log.error("方法[getInputStream]參數[filePath]不能爲空.");
            return null;
        }
        OSSObject object = ossClient.getObject(bucketName, filePath);
        InputStream input = object.getObjectContent();
        byte[] bytes = toByteArray(input);
        return new ByteArrayInputStream(bytes);
    }

    /**
     * InputStream流轉byte數組
     *
     * @param input
     * @return
     * @throws IOException
     */
    private static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[input.available()];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

    /**
     * 創建文件夾
     *
     * @param folderName
     * @return
     */
    private Boolean createFolder(String folderName) {
        if (folderName == null || folderName.isEmpty()) {
            log.error("方法[createFolder]參數[folderName]不能爲空.");
            return false;
        }
        // 防止folderName文件夾因爲末尾未加【/】導致將目錄當做文件創建
        if (!folderName.substring(folderName.length() - 1).equals("/")) {
            folderName = folderName + "/";
        }
        // 創建文件夾
        try {
            ossClient.putObject(bucketName, folderName, new ByteArrayInputStream(new byte[0]));
            log.info("附件文件夾[" + folderName + "]創建成功.");
            return true;
        } catch (Exception e) {
            log.error("附件文件夾[" + folderName + "]創建失敗.");
            return false;
        }
    }

    /**
     * 判斷文件夾是否存在
     *
     * @param folderName
     * @return
     */
    private Boolean isExists(String folderName) {
        return ossClient.doesObjectExist(bucketName, folderName);
    }

    /**
     * 根據文件路徑獲取File
     *
     * @param filePath
     * @return
     * @throws IOException
     */
    public File getFile(String filePath) throws IOException {
        if (filePath == null || filePath.isEmpty()) {
            log.error("方法[getFile]參數[filePath]不能爲空.");
            return null;
        }
        File file = new File(filePath);
        InputStream inputStream = getInputStream(filePath);
        copyInputStreamToFile(inputStream, file);
        return file;
    }

    /**
     * InputStream -> File
     *
     * @param inputStream
     * @param file
     * @throws IOException
     */
    private static void copyInputStreamToFile(InputStream inputStream, File file) throws IOException {
        try (FileOutputStream outputStream = new FileOutputStream(file)) {
            int read;
            byte[] bytes = new byte[1024];
            while ((read = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, read);
            }
        }
    }


}
View Code

4、如何使用?

1、控制器

@PostMapping("/avatar")
public Result<String> updateAvatar(@RequestParam("file") MultipartFile multipartFile, String username) throws Exception {
    return Result.success(userService.updateAvatar(multipartFile, username));
}

2、service層

@Override
public String updateAvatar(MultipartFile multipartFile, String username) throws Exception {
    String avatarUrl = fileService.uploadAvatar(multipartFile);
    User user = new User();
    user.setAvatar(avatarUrl);
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(User::getUsername, username);
    this.baseMapper.update(user, queryWrapper);
    return avatarUrl;
}

第一句調用FileService的上傳頭像方法。

3、FileService上傳頭像方法

@Override
public String uploadAvatar(MultipartFile multipartFile) {
    // 設置文件上傳位置
    String currentDate = DateUtil.formatNowLocalDate("yyyyMMdd");
    String actualFileName = FileUtils.forStringFilter(System.nanoTime() + multipartFile.getOriginalFilename());
    // 預設上傳文件到阿里雲oss的返回值
    AliyunOssResult uploadResult = null;
    try {
        InputStream inputStream = new ByteArrayInputStream(multipartFile.getBytes());
        String filePath = basePath + separator + SYSTEM + separator + currentDate;
        // 判斷是否存在文件夾,不存在則創建
        aliyunOSSUtil.isExistAndCreateFolder(filePath);
        // 上傳文件
        uploadResult = aliyunOSSUtil.upload(inputStream, filePath + separator + actualFileName);
    }
    catch (IOException e) {
        log.error("附件上傳出現錯誤:{}", e.getMessage(), e);
        throw new SystemException("服務器異常");
    }
    return uploadResult.getUrl();
}

FileService中的保存文件方法都會將各種上傳的文件通過AliyunOSSUtil工具類上傳至阿里雲OSS。

三、報銷接入遇到的問題

1、如果FileService內部方法獲取的是MultipartFile類型文件需要上傳。

@Override
@Transactional(rollbackFor = Exception.class)
public FileInfoDTO uploadAttachFile(List<MultipartFile> fileList, Integer source, Integer fileType, String spNo) {
    try {
        for (int i = 0; i < fileList.size(); i++) {
            MultipartFile part = fileList.get(i);
            String originalFileName = part.getOriginalFilename();
            String actualFileName = FileUtils.forStringFilter(System.nanoTime() + part.getOriginalFilename());
            // 設置文件上傳位置
            String currentDate = DateUtil.formatNowLocalDate("yyyyMMdd");
            String filePath = basePath + separator + UPLOAD + separator + currentDate;
            // 判斷是否存在文件夾,不存在則創建
            aliyunOSSUtil.isExistAndCreateFolder(filePath);
            InputStream inputStream = new ByteArrayInputStream(part.getBytes());
            // 上傳文件
            AliyunOssResult ossResult = aliyunOSSUtil.upload(inputStream, filePath + separator + actualFileName);
            String mediaId = null;
            // 附件上傳服務器之後還會再上傳企業微信服務器(暫時忽略這一步)
            if (FileSourceEnum.isNeedUploadWeChat(source) && i < UPLOAD_FILE_NUM_LIMIT - attachFileList.size() && FileTypeEnum.isApply(fileType)) {
                mediaId = weChatManager.uploadFileWithInputStream(part);
                mediaIdList.add(mediaId);
            }
            // 存儲附件表
            this.generateAndAddAttachFile(originalFileName, currentDate + separator + actualFileName, source, fileType, part.getSize(), mediaId, spNo, attachFileIdList, ossResult.getUrl());
        }
        return fileInfoDTO;
    }
    catch (IOException e) {
        log.error("附件上傳出現錯誤:{}", e.getMessage(), e);
        throw new SystemException("服務器異常");
    }
}

由於AliyunOSSUtil中上傳方法使用的是InputStream上傳的方式,因此可將MultipartFile類型轉換成InputStream進行上傳。

2、如果需要上傳的文件是iText程序中生成的。

如果文件不是前端傳遞,而是程序中運行時生成的,而且不能將運行時生成的文件保存在服務器中。例如iText運行時生成文件需要上傳至阿里雲oss。

public String generatePreApplyPdf(String spNo) throws DocumentException, FileNotFoundException {
    Map<String, String> userMap = userService.toMap(null);
    Map<long, String> deptMap = deptService.toMap(null);
    PreApplyInfo preApplyInfo = preApplyInfoService.findBySpNo(spNo);
    Document document = new Document();
    document.setPageSize(PageSize.A4.rotate());
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    PdfWriter pdfWriter = generatePdfWriter(document, bos);
    document.open();
    fillTitle(document, preApplyInfo);
    if (FeeTypeEnum.isTravel(preApplyInfo.getFeeType())) {
        fillTravelHeader(document, userMap.get(preApplyInfo.getApplicant()), cn.hutool.core.date.DateUtil.format(preApplyInfo.getApplyTime(), DatePatternEnum.CS_D.getPattern()));
        fillTravelLines(document, preTravelDetailService.listPreTravelDetailBySpNo(spNo), preApplyInfo);
        document.add(generateBlankParagraph());
        fillTravelApprovalInfo(document, auditService.listPreApplyApprovalNodeVO(spNo), userMap);
        fillTravelRemark(document);
    } else {
        fillOtherHead(document, preApplyInfo, userMap, deptMap);
        fillOtherLines(document, preApplyInfo, preApplyLineService.listPreApplyDetailBySpNo(spNo));
        fillOtherApprovalInfo(document, auditService.listPreApplyApprovalNodeVO(spNo), userMap);
    }
    document.close();
    pdfWriter.close();
    // 阿里雲oss上傳pdf
    ByteArrayInputStream swapStream = new ByteArrayInputStream(bos.toByteArray());
    String pdfFileFolder = basePath + separator + PDF + separator;
    String pdfFileName = basePath + separator + PDF + separator + spNo + PDF_SUFFIX;
    aliyunOSSUtil.isExistAndCreateFolder(pdfFileFolder);
    AliyunOssResult aliyunOssResult = aliyunOSSUtil.upload(swapStream, pdfFileName);
    return aliyunOssResult.getCode() == 200 ? aliyunOssResult.getUrl() : "PDF文件生成失敗.";
}

第八行:generatePdfWriter方法

public PdfWriter generatePdfWriter(Document document, OutputStream out) throws FileNotFoundException, DocumentException {
    PdfWriter writer = PdfWriter.getInstance(document, out);
    writer.setPageEvent(new FootHelper());
    return writer;
}

1、該部分是將 document(程序運行時生成的pdf內容) 寫入 out(輸出流) 中。

2、當執行到22、23行時:

         document.close()
         pdfWriter.close()

3、表明PDF生成完畢,已經將document中的內容寫入out輸出流中。

4、25行 ByteArrayInputStream swapStream = new ByteArrayInputStream(bos.toByteArray())  將輸出流轉成輸入流,有了輸入流就可以調用AliyunOSSUtil工具類進行上傳文件。

3、在上述附件需要上傳兩個位置(阿里雲服務器、企業微信服務器)

阿里雲上傳可以調用工具類進行操作,上傳企業微信服務器用到RestTemplate進行操作。由於文件不在本地存儲,因此無法得到File類型文件,文件類型可能是MultipartFile類型、InputStream類型,因此RestTemplate有如下幾種上傳文件方式供參考。

參見 https://www.cnblogs.com/sun-10387834/p/16554574.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章