引言
文件上传是Web应用开发中常见的需求之一,而实时显示文件上传的进度条可以提升用户体验。本教程将介绍如何使用Java后端和Vue前端实现文件上传进度条功能,借助阿里云的OSS服务进行文件上传。
技术栈
- 后端:Java、Spring Boot 、WebSocket Server
- 前端:Vue、WebSocket Client
前端实现
安装依赖
npm install websocket sockjs-client
UploadFiles文件上传组件
注意
:异步请求接口的时候,后端返回数据结构如下,实际根据自己需求调整返回。
{"code": 200,"message": "成功","data": {"requestId": "file_1697165869563",}
}
创建UploadFiles组件,前端主业务逻辑:上传文件方法和初始化websocket服务方法。这里requestId也是你上传文件后到OSS的Bucket桶的文件名。后面后端展示逻辑有显示,实际根据自己需求调整。
<template><div><input type="file" @change="handleFileChange" /><button @click="uploadFile">上传</button><div>{{ progress }}</div></div>
</template><script>import axios from 'axios';import { Message } from 'element-ui';import { w3cwebsocket as WebSocket } from 'websocket';export default {data() {return {file: null,progress: '0%',requestId: '',websocket: null,isWebSocketInitialized: false,};},methods: {handleFileChange(event) {this.file = event.target.files[0];},initConnect(){if (!this.isWebSocketInitialized) {this.initWebSocket();this.isWebSocketInitialized = true;}},generateUniqueID() {// 使用时间戳来生成唯一IDconst timestamp = new Date().getTime();// 在ID前面添加一个前缀,以防止与其他ID冲突const uniqueID = 'file_' + timestamp;return uniqueID;},uploadFile() {this.initConnect();console.log("isWebSocketInitialized="+this.isWebSocketInitialized)const formData = new FormData();formData.append('file', this.file);formData.append('requestId', this.generateUniqueID());axios.post('http://localhost:8886/test/upload', formData, {headers: {'Content-Type': 'multipart/form-data',},onUploadProgress: (progressEvent) => {this.progress = `${Math.round((progressEvent.loaded * 100) / progressEvent.total)}%`;},}).then((response) => {if(response.data.code===200){this.requestId = response.data.data.requestId;console.log('requestId=' + response.data.data.requestId);}else{// 弹框报错 response.data.messageconsole.log("code="+response.data.code+",message="+response.data.message)Message.error(response.data.message);}this.initWebSocket();}).catch((error) => {console.error('Failed to upload file:', error);});},initWebSocket() {this.websocket = new WebSocket('ws://localhost:8886/test/upload-progress');this.websocket.onmessage = (event) => {const progress = event.data;console.log('上传进度=' + progress);this.progress = progress;// if (progress === '100%') {// this.websocket.close();// }};this.websocket.onclose = () => {console.log('WebSocket connection closed');};},},};
</script>
使用上传组件
测试演示,所以直接在App.vue中使用UploadFiles组件。
<template><div id="app"><UploadFiles /></div>
</template><script>
import UploadFiles from './components/UploadFiles.vue';export default {name: 'App',components: {UploadFiles}
};
</script>
后端实现
添加依赖
maven中添加socket服务依赖
<!--websocket服务-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketConfig配置类
创建WebSocket配置类,配置socket服务注册节点、处理跨域问题和添加监听处理器。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {private final UploadProgressHandler uploadProgressHandler;public WebSocketConfig(UploadProgressHandler uploadProgressHandler) {this.uploadProgressHandler = uploadProgressHandler;}@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(uploadProgressHandler, "/test/upload-progress").setAllowedOrigins("*").addInterceptors(new HttpSessionHandshakeInterceptor());}/*** 引入定时任务bean,防止和项目中quartz定时依赖冲突*/@Bean@Nullablepublic TaskScheduler taskScheduler() {ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler();threadPoolScheduler.setThreadNamePrefix("SockJS-");threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());threadPoolScheduler.setRemoveOnCancelPolicy(true);return threadPoolScheduler;}}
UploadProgressHandler处理器
创建文件上传进程的处理器,继承TextWebSocketHandler,记录文件上传监听器和记录WebSocketSession会话。
import xxxxxx.PutObjectProgressListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class UploadProgressHandler extends TextWebSocketHandler {private final Map<String, PutObjectProgressListener> uploadProgressMap = new ConcurrentHashMap<>();private static final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) {uploadProgressMap.forEach((requestId, progressListener) -> {try {session.sendMessage(new TextMessage(progressListener.getProgress()));} catch (IOException e) {e.printStackTrace();}});}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception{sessionMap.put(session.getId(), session);super.afterConnectionEstablished(session);}public static Map<String, WebSocketSession> getSessionMap() {return sessionMap;}public void addProgressListener(String requestId, PutObjectProgressListener progressListener) {uploadProgressMap.put(requestId, progressListener);}public void removeProgressListener(String requestId) {uploadProgressMap.remove(requestId);}
}
PutObjectProgressListener文件上传监听器
创建文件上传监听器,监听文件上传的进度,并且同步到socket通信会话中
import com.aliyun.oss.event.ProgressEvent;
import com.aliyun.oss.event.ProgressListener;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;import java.io.IOException;/*** 重写上传文件监听器* @author yangz* @date 2023/10/11*/
public class PutObjectProgressListener implements ProgressListener {private final long fileSize;private long bytesWritten = 0;private WebSocketSession session;public PutObjectProgressListener(WebSocketSession session, long fileSize) {this.session = session;this.fileSize = fileSize;}public String getProgress() {if (fileSize > 0) {int percentage = (int) (bytesWritten * 100.0 / fileSize);return percentage + "%";}return "0%";}@Overridepublic void progressChanged(ProgressEvent progressEvent) {bytesWritten += progressEvent.getBytes();if (fileSize > 0) {int percentage = (int) (bytesWritten * 100.0 / fileSize);try {if (session.isOpen()) {session.sendMessage(new TextMessage(percentage + "%"));System.out.println("上传进度="+percentage + "%");}} catch (IOException e) {e.printStackTrace();}}}
}
OSSUtil工具类
创建文件上传工具类
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.socket.WebSocketSession;
import java.io.*;
import java.util.*;
@Slf4j
public class OSSUtil {public static final String endpoint = "http://xxxxx.aliyuncs.com";public static final String accessKeyId = "yourAccessKeyId";public static final String accessKeySecret = "yourAccessKeySecret";private static final String bucketName = yourBucketName;/*** 文件上传并监听进度* @param file,requestId,session* @return {@link String }* @author yangz* @date 2023/10/11*/public static String uploadFile(MultipartFile file, String requestId, WebSocketSession session) throws IOException {OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);// 获取文件大小long fileSize = file.getSize();String originalFilename = file.getOriginalFilename();PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, requestId+originalFilename.substring(originalFilename.lastIndexOf(".")), file.getInputStream());// 文件上传请求附加监听器putObjectRequest.setProgressListener(new PutObjectProgressListener(session,fileSize));ossClient.putObject(putObjectRequest);ossClient.shutdown();return requestId;}
}
Controller控制器
创建一个测试Controller,API测试文件上传和监听进度
import xxxxxx.UploadProgressHandler;
import xxxxxx.BusinessException;
import xxxxxx.OSSUtil;
import xxxxxx.PutObjectProgressListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.*;@RestController
@Slf4j
@RequestMapping("/test")
public class TestController {@Autowiredprivate UploadProgressHandler uploadProgressHandler;/*** 文件上传并监听进度* @param file* @param requestId* @return {@link Map }<{@link String }, {@link String }>* @author yangz* @date 2023/10/12*/@PostMapping("/upload")public Map<String, String> uploadFile(@RequestParam("file") MultipartFile file, String requestId) throws IOException {//获取处理器监听到的WebSocketSession集合Map<String, WebSocketSession> sessionMap = UploadProgressHandler.getSessionMap();Collection<WebSocketSession> sessions = sessionMap.values();List<WebSocketSession> values = new ArrayList<>(sessions);int size = values.size();if (size<1){throw new BusinessException(500,"Websocket服务未连接!");}// 关闭除最后一个之外的其他WebSocketSessionfor (int i = 0; i < size - 1; i++) {WebSocketSession session = values.get(i);session.close();sessionMap.remove(session.getId());}WebSocketSession webSocketSession = values.get(size-1);//添加websocket服务监听文件上传进程PutObjectProgressListener progressListener = new PutObjectProgressListener(webSocketSession, file.getSize());uploadProgressHandler.addProgressListener(requestId, progressListener);// 将 WebSocketSession 传递给 OSSUtil.uploadFile方法OSSUtil.uploadFile(file, requestId, webSocketSession);//上传完成,移除websocket服务监听uploadProgressHandler.removeProgressListener(requestId);Map<String, String> resultMap = new HashMap<>();resultMap.put("requestId", requestId);return resultMap;}
}
结果展示
步骤:1、选择文件。2、点击上传按钮。3、可以看到进度标签实时展示百分比进度
结语
通过以上步骤,我们实现了一个包含上传文件和实时显示上传进度的文件上传功能。前端使用Vue编写了上传组件,后端使用Java和Spring Boot进行文件上传处理。通过调用阿里云OSS服务和监听上传文件字节来计算进度,我们能够实时显示文件上传的进度条,提升用户体验。
结束语:人生最大的浪费不是金钱的浪费,而是时间的浪费、认知的迟到