文件上传是Web应用开发中常见的需求之一,而实时显示文件上传的进度条可以提升用户体验。本教程将介绍如何使用Java后端和Vue前端实现文件上传进度条功能,借助阿里云的OSS服务进行文件上传。
npm install websocket sockjs-client
注意
:异步请求接口的时候,后端返回数据结构如下,实际根据自己需求调整返回。
{
"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() {
// 使用时间戳来生成唯一ID
const 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.message
console.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服务依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
创建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;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(uploadProgressHandler, "/test/upload-progress").setAllowedOrigins("*").addInterceptors(new HttpSessionHandshakeInterceptor());
}
/**
* 引入定时任务bean,防止和项目中quartz定时依赖冲突
*/
@Bean
@Nullable
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler();
threadPoolScheduler.setThreadNamePrefix("SockJS-");
threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
threadPoolScheduler.setRemoveOnCancelPolicy(true);
return threadPoolScheduler;
}
}
创建文件上传进程的处理器,继承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<>();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
uploadProgressMap.forEach((requestId, progressListener) -> {
try {
session.sendMessage(new TextMessage(progressListener.getProgress()));
} catch (IOException e) {
e.printStackTrace();
}
});
}
@Override
public 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);
}
}
创建文件上传监听器,监听文件上传的进度,并且同步到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%";
}
@Override
public 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();
}
}
}
}
创建文件上传工具类
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,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 {
@Autowired
private 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服务未连接!");
}
// 关闭除最后一个之外的其他WebSocketSession
for (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服务和监听上传文件字节来计算进度,我们能够实时显示文件上传的进度条,提升用户体验。
结束语:人生最大的浪费不是金钱的浪费,而是时间的浪费、认知的迟到