文件上传接入阿里云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的类)

文件上传接入阿里云OSS

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

 

发表评论

相关文章