一、上傳文件基本實現(xiàn)
- 1、前端效果圖展示,這里使用
element-ui plus
來展示樣式效果
-
2、基礎(chǔ)代碼如下
<template> <div > <el-upload ref="uploadRef" class="upload-demo" :limit="1" :on-change="handleExceed" :auto-upload="false" > <template #trigger> <el-button type="primary">選擇文件</el-button> </template> <el-button style="margin-left: 30px" type="success" @click="submitUpload"> 上傳 </el-button> </el-upload> </div> </template> <script setup> import { ref } from 'vue'; import axios from 'axios'; const fileRef = ref(null); const handleExceed = (files) => { console.log(files); fileRef.value = files; }; const submitUpload = () => { console.log('開始上傳文件', JSON.stringify(fileRef.value)); const formData = new FormData(); formData.append('file', fileRef.value.raw); axios.post('http://localhost:9002/file/upload', formData).then((res) => { console.log(fileRef.value, '??'); console.log('上傳成功'); }); }; </script> <style lang="scss" scoped></style>
-
3、定義后端接口,并且處理好跨域(關(guān)于跨域處理,自己百度處理)
package com.course.file.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController public class UploadController { private static final Logger LOG = LoggerFactory.getLogger(UploadController.class); @PostMapping("/upload") public String uploadApi(@RequestParam MultipartFile file) { this.LOG.info("上傳文件開始"); this.LOG.info(file.getOriginalFilename()); this.LOG.info(String.valueOf(file.getSize())); return "上傳成功"; } }
-
4、保存文件到本地文件
package com.course.file.controller; import com.course.file.utils.UuidUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @RestController public class UploadController { private static final Logger LOG = LoggerFactory.getLogger(UploadController.class); @PostMapping("/upload") public String uploadApi(@RequestParam MultipartFile file) throws IOException { this.LOG.info("上傳文件開始"); this.LOG.info(file.getOriginalFilename()); this.LOG.info(String.valueOf(file.getSize())); // 保存文件到本地 String fileName = file.getOriginalFilename(); String key = UuidUtil.getShortUuid(); // 需要先本地創(chuàng)建一個file文件夾 String fullPath = "E:/file/" + key + "-" + fileName; File dest = new File(fullPath); file.transferTo(dest); return "上傳成功"; } }
二、配置靜態(tài)目錄
-
1、在
FileApplication.java
旁邊添加一個SpringMvcConfig.java
的文件package com.course.file; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class SpringMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/f/**").addResourceLocations("file:E:/file/"); } // http://localhost:9002/file/f/FdXMQJdF-xx.png }
-
2、直接瀏覽器上直接訪問上面的地址
-
3、上傳地址返回給前端
@RestController public class UploadController { private static final Logger LOG = LoggerFactory.getLogger(UploadController.class); @PostMapping("/upload") public String uploadApi(@RequestParam MultipartFile file) throws IOException { this.LOG.info("上傳文件開始"); this.LOG.info(file.getOriginalFilename()); this.LOG.info(String.valueOf(file.getSize())); // 保存文件到本地 String fileName = file.getOriginalFilename(); String key = UuidUtil.getShortUuid(); // 需要先本地創(chuàng)建一個file文件夾 String fullPath = "E:/file/" + key + "-" + fileName; File dest = new File(fullPath); file.transferTo(dest); return "http://localhost:9002/file/f/" + key + "-" + fileName; } }
-
4、將上面幾個固定的配置寫到配置文件中
file.path=E:/file/ file.domain=http://localhost:9002/file/ # 修改上傳文件大小(不設(shè)置大文件可能上傳失敗) spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB
-
5、在控制器中使用
public class UploadController { private static final Logger LOG = LoggerFactory.getLogger(UploadController.class); @Value("${file.path}") private String FILE_PATH; @Value("${file.domain}") private String FILE_DOMAIN; }
三、斷點續(xù)傳
-
1、主要原理
- 前端將大文件根據(jù)文件大小來切割成小片段,使用遞歸的方式調(diào)用后端接口,將文件上傳到服務(wù)器端
- 服務(wù)器端接收到前端上傳片段,存儲到服務(wù)器上
- 等前端最后一個上傳完成后,將全部的文件合并成一個文件
- 合并完成后,返回一個
url
地址,將之前的分片上傳的文件刪除
-
2、手動演示前端分段上傳
const submitUpload = () => { console.log('開始上傳文件', JSON.stringify(fileRef.value)); const formData = new FormData(); const file = fileRef.value.raw; // 文件分配 let shardSize = 5 * 1024 * 1024; // 以5MB為一個分片 let shardIndex = 0; // 分片索引 let start = shardIndex * shardSize; // 開始位置 let end = Math.min(file.size, start + shardSize); // 結(jié)束位置 let fileShard = file.slice(start, end); // 每次上傳的分片數(shù)據(jù) formData.append('file', fileShard); axios.post('http://localhost:9002/file/upload', formData).then((res) => { console.log(fileRef.value, '??'); console.log('上傳成功'); }); };
-
3、第二次的時候?qū)?code>shardIndex改為
1
-
4、查看本地文件夾下的文件
-
5、手動創(chuàng)建一個接口來嘗試合并文件
@GetMapping("merge") public String merge() throws FileNotFoundException { // 最終合成后的視頻文件名稱 File newFile = new File(FILE_PATH + "test.mp4"); FileOutputStream outputStream = new FileOutputStream(newFile, true); FileInputStream fileInputStream = null; byte[] bytes = new byte[5 * 1024 * 1024]; int len; try { // 讀取第一段 fileInputStream = new FileInputStream(new File(FILE_PATH + "/pN0EoOny-blob")); while ((len = fileInputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, len); } // 讀取第二段 fileInputStream = new FileInputStream(new File(FILE_PATH + "/f5oeIEDW-blob")); while ((len = fileInputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, len); } // 讀取第三段 fileInputStream = new FileInputStream(new File(FILE_PATH + "/qsm8n03q-blob")); while ((len = fileInputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, len); } } catch (IOException e) { LOG.error("合并分片失敗", e); } finally { try { if (fileInputStream != null) { fileInputStream.close(); } outputStream.close(); LOG.info("IO流關(guān)閉"); } catch (IOException e) { LOG.error("IO流關(guān)閉", e); } } return "合并視頻成功"; }
四、使用數(shù)據(jù)庫來實現(xiàn)分片上傳
-
1、數(shù)據(jù)表字段
在數(shù)據(jù)庫中涉及
key
只跟文件有關(guān),跟上傳多少片沒關(guān)系的,當已經(jīng)上傳的分片數(shù)和分片總數(shù)一樣的時候就合并文件drop table if exists `file`; create table `file` ( `id` int(11) not null PRIMARY key auto_increment comment '主鍵id', `path` varchar(100) not null comment '相對路徑', `name` varchar(100) comment '文件名', `suffix` varchar(10) comment '后綴', `size` int(11) comment '大小|字節(jié)B', `shard_index` int(11) DEFAULT 0 COMMENT '已上傳分片', `shard_size` int(11) DEFAULT 0 COMMENT '分片大小', `shard_total` int(11) DEFAULT 0 COMMENT '分片總數(shù)', `key` VARCHAR(100) DEFAULT NULL COMMENT '文件標識', `created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '創(chuàng)建時間', `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新時間', `deleted_at` timestamp(6) NULL DEFAULT NULL COMMENT '軟刪除時間' ) engine=innodb default charset=utf8mb4 comment='文件';
-
2、在
pom.xml
文件中添加mybatis-plus
的依賴包<!-- 配置連接到數(shù)據(jù)庫 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <!-- mybatis plus 依賴包 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!-- mybatis 代碼生成器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.2.0</version> </dependency>
-
3、
application.properties
添加配置# mysql數(shù)據(jù)庫鏈接 spring.datasource.url=jdbc:mysql://localhost:3306/beego?characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456 # 配置mybatis-plus # 開啟下劃線轉(zhuǎn)駝峰 mybatis-plus.configuration.map-underscore-to-camel-case=true mybatis-plus.configuration.auto-mapping-behavior=full mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # mapping的路徑 mybatis-plus.mapper-locations=classpath*:mapper/**/*Mapper.xml #主鍵類型 AUTO:"數(shù)據(jù)庫ID自增", INPUT:"用戶輸入ID", ID_WORKER:"全局唯一ID (數(shù)字類型唯一ID)", UUID:"全局唯一ID UUID"; mybatis-plus.global-config.db-config.id-type=AUTO # 邏輯刪除(軟刪除) mybatis-plus.global-config.db-config.logic-delete-value=NOW() mybatis-plus.global-config.db-config.logic-not-delete-value=NULL
-
4、使用模板生成器生成代碼
-
5、前端使用遞歸的方式來分片上傳文件
<script setup> import { ref } from 'vue'; import { genFileId } from 'element-plus'; import axios from 'axios'; import md5 from 'js-md5'; const fileRef = ref(null); const handleExceed = (files) => { console.log(files); fileRef.value = files; }; const updateFile = (shardIndex) => { const formData = new FormData(); const file = fileRef.value.raw; // 文件分配 let shardSize = 5 * 1024 * 1024; // 以5MB為一個分片 // let shardIndex = 0; // 分片索引 let start = (shardIndex - 1) * shardSize; // 開始位置 let end = Math.min(file.size, start + shardSize); // 結(jié)束位置 let fileShard = file.slice(start, end); // 每次上傳的分片數(shù)據(jù) // 前端多上傳參數(shù) let size = file.size; let shardTotal = Math.ceil(size / shardSize); // 總片數(shù) const suffix = file.name.substring(file.name.lastIndexOf('.') + 1); formData.append('shard', fileShard); formData.append('shardIndex', shardIndex); formData.append('shardSize', shardSize); formData.append('shardTotal', shardTotal); formData.append('name', file.name); formData.append('size', size); formData.append('suffix', suffix); formData.append('key', md5(`${file.name}_${file.size}_${file.type}`)); axios.post('http://localhost:9002/file/upload1', formData).then((res) => { // 判斷如果當前的shardIndex < shardTotal的時候遞歸上傳 if (shardIndex < shardTotal) { updateFile(++shardIndex); } else { console.log('上傳成功'); } }); }; const submitUpload = () => { console.log('開始上傳文件', JSON.stringify(fileRef.value)); // 開始上傳文件 updateFile(1); }; </script>
-
6、后端對文件上傳處理,存儲到本地和入庫操作
package com.course.file.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.course.file.model.FileEntity; import com.course.file.service.IFileService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @RestController public class FileController { @Autowired private IFileService fileService; private static final Logger LOG = LoggerFactory.getLogger(FileController.class); @Value("${file.path}") private String FILE_PATH; @Value("${file.domain}") private String FILE_DOMAIN; @PostMapping("upload1") public String upload1( @RequestParam MultipartFile shard, Integer shardIndex, Integer shardSize, Integer shardTotal, String name, String suffix, Integer size, String key ) throws IOException { this.LOG.info("開始上傳文件"); System.out.println("當前分片:" + shardIndex); System.out.println("當前分片大小:" + shardSize); System.out.println("當前分片總數(shù):" + shardTotal); System.out.println("文件名稱:" + name); System.out.println("文件后綴名:" + suffix); System.out.println("文件大小:" + size); System.out.println("文件唯一的key:" + key); // 文件保存到本地目錄下 String localPath = this.FILE_PATH + key + "." + suffix + "." + shardIndex; File dest = new File(localPath); shard.transferTo(dest); LOG.info(dest.getAbsolutePath()); // 數(shù)據(jù)入庫操作 LambdaQueryWrapper<FileEntity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(FileEntity::getFileKey, key); FileEntity fileEntity = new FileEntity(); if (this.fileService.getOne(queryWrapper) != null) { // 說明不是第一次上傳,需要更新當前上傳的分片數(shù)量 fileEntity.setShardIndex(shardIndex); if (this.fileService.update(fileEntity, queryWrapper)) { return "上傳成功"; } else { return "上傳失敗"; } } else { // 第一次上傳創(chuàng)建 String path = this.FILE_PATH + key + "." + suffix; fileEntity.setFileKey(key); fileEntity.setName(name); fileEntity.setPath(path); fileEntity.setShardIndex(shardIndex); fileEntity.setSuffix(suffix); fileEntity.setShardSize(shardSize); fileEntity.setShardTotal(shardTotal); fileEntity.setSize(size); if (this.fileService.save(fileEntity)) { return "上傳成功"; } else { return "上傳失敗"; } } } }
-
7、查看本地目錄是否生成分片文件
-
8、對本地分片文件合并操作,當
shardIndex=shardTotal
的時候進行合并操作@RestController public class FileController { @Autowired private IFileService fileService; private static final Logger LOG = LoggerFactory.getLogger(FileController.class); @Value("${file.path}") private String FILE_PATH; @Value("${file.domain}") private String FILE_DOMAIN; @PostMapping("upload1") public String upload1( @RequestParam MultipartFile shard, Integer shardIndex, Integer shardSize, Integer shardTotal, String name, String suffix, Integer size, String key ) throws IOException { this.LOG.info("開始上傳文件"); System.out.println("當前分片:" + shardIndex); System.out.println("當前分片大小:" + shardSize); System.out.println("當前分片總數(shù):" + shardTotal); System.out.println("文件名稱:" + name); System.out.println("文件后綴名:" + suffix); System.out.println("文件大小:" + size); System.out.println("文件唯一的key:" + key); // 文件保存到本地目錄下 String localPath = this.FILE_PATH + key + "." + suffix + "." + shardIndex; File dest = new File(localPath); shard.transferTo(dest); LOG.info(dest.getAbsolutePath()); // 合并文件操作 if (Objects.equals(shardIndex, shardTotal)) { this.merge(key, suffix, shardTotal); } // 數(shù)據(jù)入庫操作 LambdaQueryWrapper<FileEntity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(FileEntity::getFileKey, key); FileEntity fileEntity = new FileEntity(); if (this.fileService.getOne(queryWrapper) != null) { // 說明不是第一次上傳,需要更新當前上傳的分片數(shù)量 fileEntity.setShardIndex(shardIndex); if (this.fileService.update(fileEntity, queryWrapper)) { return "上傳成功"; } else { return "上傳失敗"; } } else { // 第一次上傳創(chuàng)建 String path = this.FILE_PATH + key + "." + suffix; fileEntity.setFileKey(key); fileEntity.setName(name); fileEntity.setPath(path); fileEntity.setShardIndex(shardIndex); fileEntity.setSuffix(suffix); fileEntity.setShardSize(shardSize); fileEntity.setShardTotal(shardTotal); fileEntity.setSize(size); if (this.fileService.save(fileEntity)) { return "上傳成功"; } else { return "上傳失敗"; } } } /** * 對上傳的文件片段合并操作 * @param key * @param suffix * @param shardTotal * @throws FileNotFoundException */ private void merge(String key, String suffix, Integer shardTotal) throws FileNotFoundException { this.LOG.info("=====開始合并切片操作====="); String path = this.FILE_PATH + key + "." + suffix; File newFile = new File(path); FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加寫入 FileInputStream fileInputStream = null;//分片文件 byte[] byt = new byte[10 * 1024 * 1024]; int len; try { for (int i = 0; i < shardTotal; i++) { fileInputStream = new FileInputStream(new File(this.FILE_PATH + key + "." + suffix + "." + (i + 1))); while ((len = fileInputStream.read(byt)) != -1) { outputStream.write(byt, 0, len); } } } catch (Exception e) { this.LOG.error("分片合并異常", e); } finally { try { if (fileInputStream != null) { fileInputStream.close(); } outputStream.close(); LOG.info("IO流關(guān)閉"); } catch (Exception e) { LOG.error("IO流關(guān)閉", e); } } this.LOG.info("=====結(jié)束合并切片操作====="); } }
-
9、當合并后可以對之前的分片進行刪除操作,避免占用更的磁盤空間
private void merge(String key, String suffix, Integer shardTotal) throws FileNotFoundException, InterruptedException { // ... this.LOG.info("=====結(jié)束合并切片操作====="); System.gc(); Thread.sleep(1000); // 刪除分片 LOG.info("刪除分片開始"); for (int i = 0; i < shardTotal; i++) { String filePath = path + "." + (i + 1); File file = new File(filePath); boolean result = file.delete(); this.LOG.info("刪除{},{}", filePath, result ? "成功" : "失敗"); } LOG.info("刪除分片結(jié)束"); }
五、前端使用BS64提交數(shù)據(jù)
-
1、使用
bs64
提交后端可以直接定義一個字符串接收數(shù)據(jù),將接收到的數(shù)據(jù)轉(zhuǎn)換為圖片 -
2、前端將上傳的文件轉(zhuǎn)換為
bs64
// 使用BS64上傳 const bs64UploadHandler = () => { const file = fileRef.value.raw; let fileReader = new FileReader(); fileReader.onload = function (e) { const base64 = e.target.result; console.log('base64', base64); const suffix = file.name.substring(file.name.lastIndexOf('.') + 1); axios .post('http://localhost:9002/file/bs64Upload', { fileName: md5(`${file.name}_${file.size}_${file.type}`) + '.' + suffix, fileBs64: base64, }) .then((res) => { console.log(res); }); }; fileReader.readAsDataURL(file); };
-
3、后端定義一個方法,將字符串轉(zhuǎn)換為
MultipartFile
數(shù)據(jù)類型package com.course.file.utils; import org.springframework.web.multipart.MultipartFile; import sun.misc.BASE64Decoder; import java.io.*; public class Base64ToMultipartFile implements MultipartFile { private final byte[] imgContent; private final String header; public Base64ToMultipartFile(byte[] imgContent, String header) { this.imgContent = imgContent; this.header = header.split(";")[0]; } @Override public String getName() { // TODO - implementation depends on your requirements return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1]; } @Override public String getOriginalFilename() { // TODO - implementation depends on your requirements return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1]; } @Override public String getContentType() { // TODO - implementation depends on your requirements return header.split(":")[1]; } @Override public boolean isEmpty() { return imgContent == null || imgContent.length == 0; } @Override public long getSize() { return imgContent.length; } @Override public byte[] getBytes() throws IOException { return imgContent; } @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(imgContent); } @Override public void transferTo(File dest) throws IOException, IllegalStateException { new FileOutputStream(dest).write(imgContent); } public static MultipartFile base64ToMultipart(String base64) { try { String[] baseStrs = base64.split(","); BASE64Decoder decoder = new BASE64Decoder(); byte[] b = new byte[0]; b = decoder.decodeBuffer(baseStrs[1]); for(int i = 0; i < b.length; ++i) { if (b[i] < 0) { b[i] += 256; } } return new Base64ToMultipartFile(b, baseStrs[0]); } catch (IOException e) { e.printStackTrace(); return null; } } }
-
4、直接保存文件,這里就不做分片上傳
@PostMapping("bs64Upload") public String bs64Upload(@RequestBody FileDTO req) throws IOException { // 1.將上傳的bs64轉(zhuǎn)為圖片 String bs64 = req.getFileBs64(); MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(bs64); // 圖片保存到本地 String localPath = this.FILE_PATH + req.getFileName() ; File dest = new File(localPath); shard.transferTo(dest); LOG.info(dest.getAbsolutePath()); return this.FILE_DOMAIN + req.getFileName(); }
六、斷點續(xù)傳
-
1、斷點續(xù)傳主要原諒,前端在點擊上傳按鈕的時候先調(diào)用后端一個接口,判斷之前是否有上傳過記錄,如果有就返回之前上傳的分片
shardIndex
,前端就繼續(xù)以這個分片來上傳,如果沒有就返回0
表示從0
開始上傳 -
2、后端定義一個接口根據(jù)
key
來查詢數(shù)據(jù)庫是否已經(jīng)有上傳過記錄@GetMapping("{key}") public Integer getShardIndexByKeyApi(@PathVariable String key) { LambdaQueryWrapper<FileEntity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(FileEntity::getFileKey, key).select(FileEntity::getShardIndex); FileEntity fileEntity = this.fileService.getOne(queryWrapper); return fileEntity.getShardIndex(); }
-
3、前端在使用上傳前先調(diào)用上面的接口
const submitUpload = () => { console.log('開始上傳文件', JSON.stringify(fileRef.value)); const file = fileRef.value.raw; const key = md5(`${file.name}_${file.size}_${file.type}`); axios.get(`http://localhost:9002/file/${key}`).then((response) => { if (response.data > 0) { // 歷史上傳 updateFile(response.data + 1); } else { // 首次上傳 updateFile(1); } }); };
七、秒傳功能
- 1、當前端使用
MD5
加密后提交的名字在數(shù)據(jù)庫已經(jīng)存在,則直接拼接url
返回就可以,不需要再次上傳
八、上傳到阿里OSS
-
1、配置依賴包文章來源:http://www.zghlxwxcb.cn/news/detail-742964.html
<!-- 阿里云oss存儲 --> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.10.2</version> </dependency>
-
2、封裝方法使用文章來源地址http://www.zghlxwxcb.cn/news/detail-742964.html
import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.ObjectMetadata; import com.aliyun.oss.model.PutObjectResult; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Date; import java.util.List; import java.util.UUID; /** * 阿里云上傳文件 */ @Component public class OssUtil { @Value("${aliyun.oss.endpoint}") private String endpoint; @Value("${aliyun.oss.accessKeyId}") private String accessKeyId; @Value("${aliyun.oss.accessKeySecret}") private String accessKeySecret; @Value("${aliyun.oss.bucketName}") private String bucketName; //文件存儲目錄(自定義阿里云的) private String fileDir = "clouFile/"; /** * 單個文件上傳 * * @param file * @return */ public String uploadFile(MultipartFile file) { // 調(diào)用封裝好的上傳文件方法 String fileUrl = uploadImg2Oss(file); // 返回完整的路徑 String str = getFileUrl(fileUrl); return str.trim(); } /** * 單個文件上傳(指定文件名需要后綴名) * * @param file * @param fileName * @return */ public String uploadFile(MultipartFile file, String fileName) { try { InputStream inputStream = file.getInputStream(); this.uploadFile2OSS(inputStream, fileName); return fileName; } catch (Exception e) { return "上傳失敗"; } } /** * 多個文件的上傳,返回路徑用,分割 * * @param fileList * @return */ public String uploadFile(List<MultipartFile> fileList) { String fileUrl = ""; String str = ""; String photoUrl = ""; for (int i = 0; i < fileList.size(); i++) { fileUrl = uploadImg2Oss(fileList.get(i)); str = getFileUrl(fileUrl); if (i == 0) { photoUrl = str; } else { photoUrl += "," + str; } } return photoUrl.trim(); } /** * 獲取完整的路徑名 * * @param fileUrl * @return */ private String getFileUrl(String fileUrl) { if (fileUrl != null && fileUrl.length() > 0) { String[] split = fileUrl.split("/"); String url = this.getUrl(this.fileDir + split[split.length - 1]); return url; } return null; } /** * 獲取去掉參數(shù)的完整路徑 * * @param url * @return */ private String getShortUrl(String url) { String[] imgUrls = url.split("\\?"); return imgUrls[0].trim(); } /** * 獲取url地址 * * @param key * @return */ private String getUrl(String key) { // 設(shè)置URL過期時間為20年 3600l* 1000*24*365*20 Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 20); // 生成URL OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); URL url = ossClient.generatePresignedUrl(bucketName, key, expiration); if (url != null) { return getShortUrl(url.toString()); } return null; } private String uploadImg2Oss(MultipartFile file) { //1、限制最大文件為20M if (file.getSize() > 1024 * 1024 * 20) { return "圖片太大"; } //2、重命名文件 String fileName = file.getOriginalFilename(); String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); //文件后綴 String uuid = UUID.randomUUID().toString(); String name = uuid + suffix; try { InputStream inputStream = file.getInputStream(); this.uploadFile2OSS(inputStream, name); return name; } catch (Exception e) { return "上傳失敗"; } } /** * 使用阿里云上傳文件 * * @param inStream 輸入流 * @param fileName 文件名稱 * @return */ private String uploadFile2OSS(InputStream inStream, String fileName) { String ret = ""; try { //創(chuàng)建上傳Object的Metadata ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(inStream.available()); objectMetadata.setCacheControl("no-cache"); objectMetadata.setHeader("Pragma", "no-cache"); objectMetadata.setContentType(getContentType(fileName.substring(fileName.lastIndexOf(".")))); objectMetadata.setContentDisposition("inline;filename=" + fileName); //上傳文件 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); PutObjectResult putResult = ossClient.putObject(bucketName, fileDir + fileName, inStream, objectMetadata); ret = putResult.getETag(); } catch (IOException e) { System.out.println(e.getMessage()); } finally { try { if (inStream != null) { inStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return ret; } /** * 獲取文件類型 * * @param FilenameExtension * @return */ private static String getContentType(String FilenameExtension) { if (FilenameExtension.equalsIgnoreCase(".bmp")) { return "image/bmp"; } if (FilenameExtension.equalsIgnoreCase(".gif")) { return "image/gif"; } if (FilenameExtension.equalsIgnoreCase(".jpeg") || FilenameExtension.equalsIgnoreCase(".jpg") || FilenameExtension.equalsIgnoreCase(".png")) { return "image/jpeg"; } if (FilenameExtension.equalsIgnoreCase(".html")) { return "text/html"; } if (FilenameExtension.equalsIgnoreCase(".txt")) { return "text/plain"; } if (FilenameExtension.equalsIgnoreCase(".vsd")) { return "application/vnd.visio"; } if (FilenameExtension.equalsIgnoreCase(".pptx") || FilenameExtension.equalsIgnoreCase(".ppt")) { return "application/vnd.ms-powerpoint"; } if (FilenameExtension.equalsIgnoreCase(".docx") || FilenameExtension.equalsIgnoreCase(".doc")) { return "application/msword"; } if (FilenameExtension.equalsIgnoreCase(".xml")) { return "text/xml"; } //PDF if (FilenameExtension.equalsIgnoreCase(".pdf")) { return "application/pdf"; } return "image/jpeg"; } }
到了這里,關(guān)于spring-boot中實現(xiàn)分片上傳文件的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!