大文件分片上传 文件上传到本地 Java代码 配置文件修改文件上传大小限制,和指定文件上传目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 8001 spring: application: name: service-edu servlet: multipart: enabled: true max-file-size: -1 max-request-size: -1 upload: directory: /apps/files
添加请求接口地址
1 2 3 4 5 6 7 8 @Autowired FileServiceImpl fileService; @ApiOperation("分片上传") @PostMapping("/splitUpload") public Response<Boolean> uploadFileByCondition (MultipartFile file, int chunkNumber, int totalChunks) { return Response.rspData(fileService.uploadFileByCondition(file,chunkNumber,totalChunks)); }
实现uploadFileByCondition方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Value("${upload.directory}") private String uploadDirectory;public Boolean uploadFileByCondition (MultipartFile file, int chunkNumber, int totalChunks) { File directory = new File(uploadDirectory); if (!directory.exists()) { directory.mkdirs(); } String fileName = file.getOriginalFilename(); try { String filePath = uploadDirectory + fileName + "_part_" + chunkNumber; Path path = Paths.get(filePath); Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING); if (chunkNumber == totalChunks) { mergeFile(uploadDirectory, fileName, totalChunks); } } catch (IOException e) { e.printStackTrace(); } return true ; } private void mergeFile (String samplePath, String filename, int totalChunks) { String mergedFilePath = samplePath + filename; Path mergedPath = Paths.get(mergedFilePath); for (int i = 1 ; i <= totalChunks; i++) { String chunkFilePath = samplePath + filename + "_part_" + i; Path chunkPath = Paths.get(chunkFilePath); try { Files.write(mergedPath, Files.readAllBytes(chunkPath), StandardOpenOption.CREATE, StandardOpenOption.APPEND); Files.delete(chunkPath); } catch (IOException e) { e.printStackTrace(); } } }
前端代码 发起请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <template> <div> <el-button type="primary" @click="selectFile">上传</el-button> </div> </template> <script> import {splitUploadFile} from "@/api/file"; export default { data() { return {} }, methods: { selectFile() { let fileInput = document.createElement('input') fileInput.type = 'file' fileInput.removeAttribute('value') // 添加change事件监听器 fileInput.addEventListener('change', (event) => { if (event) { const selectedFile = event.target.files[0] // 处理选中的文件 this.handleSelectedFile(selectedFile) // 上传完成后删除元素 fileInput.remove() } }) // 触发文件选择对话框 fileInput.click() }, handleSelectedFile(file) { splitUploadFile(file).then(() => { this.$message.success('上传成功') }) }, } } </script>
js逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 export function splitUploadFile (file ) { return new Promise (async (resolve, reject) => { const chunkSize = 1024 * 1024 * 2 const endpoint = '/splitUpload' let progress = 0 const chunks = createChunks(file, chunkSize) const totalChunks = chunks.length for (let i = 1 ; i <= totalChunks; i++) { const formData = new FormData() formData.append('file' , chunks[i - 1 ], file.name) formData.append('chunkNumber' , i) formData.append('totalChunks' , totalChunks) try { await request.post(endpoint, formData) progress = Math .round(((i + 1 ) / totalChunks) * 100 ) console .log(progress) } catch (error) { reject(false ) break } } resolve(true ) }) } function createChunks (file, chunkSize = 2 * 1024 * 1024 ) { const chunks = [] const fileSize = file.size let currentByte = 0 while (currentByte < fileSize) { const chunk = file.slice(currentByte, currentByte + chunkSize) chunks.push(chunk) currentByte += chunkSize } return chunks }
上传效果
分片上传到阿里云并添加进度条 java代码 安装依赖
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > 3.15.1</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-websocket</artifactId > </dependency >
添加配置文件
1 2 3 4 5 6 oss: endpoint: oss-cn-hangzhou.aliyuncs.com accessKeyId: 这里换成你自己的 accessKeySecret: 这里换成你自己的 bucketName: szx-bucket1 prefix: filttest/
添加请求方法
1 2 3 4 5 @ApiOperation("分片上传到OSS") @PostMapping("/ossUpload") public Response<String> ossUpload (MultipartFile file) { return Response.rspData(fileService.ossUpload(file)); }
实现ossUpload方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 @Value("${oss.endpoint}") private String endpoint;@Value("${oss.accessKeyId}") private String accessKeyId;@Value("${oss.accessKeySecret}") private String accessKeySecret;@Value("${oss.bucketName}") private String bucketName;@Value("${oss.prefix}") private String prefix;public OSS createOssClient () { return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); } public String ossUpload (MultipartFile file) { try { OSS ossClient = createOssClient(); String objectName = prefix + file.getOriginalFilename(); InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName); InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request); String uploadId = upresult.getUploadId(); List<PartETag> partETags = new ArrayList<PartETag>(); final long partSize = 2 * 1024 * 1024L ; long fileLength = file.getSize(); int partCount = (int ) (fileLength / partSize); if (fileLength % partSize != 0 ) { partCount++; } UploadProgressListener uploadProgressListener = new UploadProgressListener(fileLength); for (int i = 0 ; i < partCount; i++) { long startPos = i * partSize; long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize; UploadPartRequest uploadPartRequest = new UploadPartRequest(); uploadPartRequest.setBucketName(bucketName); uploadPartRequest.setKey(objectName); uploadPartRequest.setUploadId(uploadId); final InputStream instream = file.getInputStream(); instream.skip(startPos); uploadPartRequest.setInputStream(instream); uploadPartRequest.setPartSize(curPartSize); uploadPartRequest.setPartNumber(i + 1 ); uploadPartRequest.setProgressListener(uploadProgressListener); UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest); partETags.add(uploadPartResult.getPartETag()); } CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags); ossClient.completeMultipartUpload(completeMultipartUploadRequest); ossClient.shutdown(); return "http://" + bucketName + "." + endpoint + "/" + objectName; } catch (IOException e) { e.printStackTrace(); } return "" ; }
上面代码中用到的 UploadProgressListener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package com.szx.java.listener;import com.aliyun.oss.event.ProgressEvent;import com.aliyun.oss.event.ProgressEventType;import com.aliyun.oss.event.ProgressListener;import com.szx.java.handler.MyWebSocketHandler;import org.springframework.web.socket.TextMessage;import org.springframework.web.socket.WebSocketSession;import java.io.IOException;public class UploadProgressListener implements ProgressListener { private long bytesWritten = 0 ; private long totalBytes; private boolean succeed = false ; WebSocketSession session = null ; public UploadProgressListener (long totalBytes) { this .totalBytes = totalBytes; this .session = MyWebSocketHandler.getSession(); } @Override public void progressChanged (ProgressEvent progressEvent) { long bytes = progressEvent.getBytes(); ProgressEventType eventType = progressEvent.getEventType(); switch (eventType) { case REQUEST_BYTE_TRANSFER_EVENT: this .bytesWritten += bytes; int percent = (int ) (this .bytesWritten * 100.0 / this .totalBytes); if (session != null && session.isOpen()) { try { session.sendMessage(new TextMessage( percent + "%" )); } catch (IOException e) { e.printStackTrace(); } } break ; case TRANSFER_COMPLETED_EVENT: try { this .succeed = true ; this .session.close(); } catch (IOException e) { e.printStackTrace(); } break ; case TRANSFER_FAILED_EVENT: try { this .succeed = false ; this .session.close(); } catch (IOException e) { e.printStackTrace(); } break ; default : break ; } } public boolean isSucceed () { return succeed; } }
上传进度监听器中用到的 MyWebSocketHandler 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.szx.java.handler;import org.springframework.web.socket.TextMessage;import org.springframework.web.socket.WebSocketSession;import org.springframework.web.socket.handler.TextWebSocketHandler;public class MyWebSocketHandler extends TextWebSocketHandler { private static WebSocketSession session; @Override public void afterConnectionEstablished (WebSocketSession session) throws Exception { MyWebSocketHandler.session = session; } @Override protected void handleTextMessage (WebSocketSession session, TextMessage message) throws Exception { } public static WebSocketSession getSession () { return session; } }
添加配置类,启用 WebSocket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.szx.java.config;import com.szx.java.handler.MyWebSocketHandler;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.config.annotation.EnableWebSocket;import org.springframework.web.socket.config.annotation.WebSocketConfigurer;import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers (WebSocketHandlerRegistry webSocketHandlerRegistry) { webSocketHandlerRegistry.addHandler(new MyWebSocketHandler(), "/websocket" ) .setAllowedOrigins("*" ); } }
前端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <template> <div> <el-button type="primary" @click="selectFile">分片上传到OSS</el-button> <div> <p>上传进度:{{ progressMessage }}</p> <p v-if="fileUrl">文件地址:{{ fileUrl }}</p> </div> </div> </template> <script> import {fileUploadOssFun} from "@/api/file"; export default { data() { return { progressMessage: "", fileUrl: "" } }, methods: { // 选择文件 selectFile() { let fileInput = document.createElement('input') fileInput.type = 'file' fileInput.removeAttribute('value') // 添加change事件监听器 fileInput.addEventListener('change', (event) => { if (event) { const selectedFile = event.target.files[0] // 处理选中的文件 this.fileUploadOss(selectedFile) // 上传完成后删除元素 fileInput.remove() } }) // 触发文件选择对话框 fileInput.click() }, // 上传到阿里云 fileUploadOss(file) { const that = this; // 连接一个WebSocket,实时监听上传进度 const socket = new WebSocket('ws://127.0.0.1:8001/websocket'); socket.onmessage = function (event) { that.progressMessage = event.data; }; fileUploadOssFun(file).then((res) => { that.fileUrl = res.data that.$message.success('上传成功') }) } } } </script>
fileUploadOssFun
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export function fileUploadOssFun (file ) { let data = new FormData(); data.append("file" , file) return request({ url: '/ossUpload' , method: 'post' , data }) }
上传效果
文件下载 Java代码 配置文件
这里配置的地址为绝对地址,以 / 开头,表示绝对地址,指向当前项目运行地址的根目录
例如项目是在 D:/test/项目地址,那么这里directory就会指向 D:/apps/files
1 2 upload: directory: /apps/files/
下载方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Value("${upload.directory}") private String uploadDirectory;public void downloadFileByPath (String name, HttpServletResponse response) { try { File file = new File(uploadDirectory + name); String filename = file.getName(); FileInputStream fileInputStream = new FileInputStream(file); InputStream fis = new BufferedInputStream(fileInputStream); byte [] buffer = new byte [fis.available()]; fis.read(buffer); fis.close(); response.reset(); response.setCharacterEncoding("UTF-8" ); response.addHeader("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(filename, "UTF-8" )); response.addHeader("Content-Length" , "" + file.length()); OutputStream outputStream = new BufferedOutputStream(response.getOutputStream()); response.setContentType("application/octet-stream" ); outputStream.write(buffer); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }
前端代码 1 2 3 4 5 6 7 8 testDown ( ) { const link = document .createElement('a' ) link.href = buildRequestURL('szxtest/downloadFileByPath' , { path: '1.rar' , }) link.download = '1.rar' link.click() },
使用这种下载方式会触发浏览器的默认下载行为,不占用浏览器内存