• 完整教程:Java+Vue+Websocket实现OSS文件上传进度条功能


    引言

    文件上传是Web应用开发中常见的需求之一,而实时显示文件上传的进度条可以提升用户体验。本教程将介绍如何使用Java后端和Vue前端实现文件上传进度条功能,借助阿里云的OSS服务进行文件上传。

    技术栈

    • 后端:Java、Spring Boot 、WebSocket Server
    • 前端:Vue、WebSocket Client

    前端实现

    安装依赖

    npm install websocket sockjs-client
    
    • 1

    UploadFiles文件上传组件

    注意:异步请求接口的时候,后端返回数据结构如下,实际根据自己需求调整返回。

    {
        "code": 200,
        "message": "成功",
        "data": {
            "requestId": "file_1697165869563",
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    创建UploadFiles组件,前端主业务逻辑:上传文件方法和初始化websocket服务方法。这里requestId也是上传文件到OSS的Bucket桶后的文件名,后面Java后端展示逻辑的时候有显示,这里我服务端的端口是8886。实际根据自己需求调整。需要注意的是,后端服务程序启动的时候,端口号是与websocket服务共用的,websocket服务不需要额外设置端口号。

    <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>
    
    • 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

    使用上传组件

    测试演示,所以直接在App.vue中使用UploadFiles组件。

    <template>
      <div id="app">
        <UploadFiles />
      </div>
    </template>
    
    <script>
    import UploadFiles from './components/UploadFiles.vue';
    
    export default {
      name: 'App',
      components: {
        UploadFiles
      }
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    后端实现

    添加依赖

    maven中添加socket服务依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-websocketartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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;
        }
        @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;
        }
    
    }
    
    • 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

    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<>();
    
        @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);
        }
    }
    
    
    • 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

    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%";
        }
    
        @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();
                }
            }
        }
    }
    
    • 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

    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;
        }
    }   
    
    • 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

    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 {
     @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
    • 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

    结果展示

    步骤:1、选择文件。2、点击上传按钮。3、可以看到进度标签实时展示百分比进度
    在这里插入图片描述

    结语

    通过以上步骤,我们实现了一个包含上传文件和实时显示上传进度的文件上传功能。前端使用Vue编写了上传组件,后端使用Java和Spring Boot进行文件上传处理。通过调用阿里云OSS服务和监听上传文件字节来计算进度,我们能够实时显示文件上传的进度条,提升用户体验。

    结束语:人生最大的浪费不是金钱的浪费,而是时间的浪费、认知的迟到

  • 相关阅读:
    XSS攻击理解与预防
    如何快速识别图片中的文字?建议使用者两种方法
    git更新代码时显示“auto-detection of host provider took too long“移除方法
    光伏并网逆变器低电压穿越技术研究(Simulink仿真)
    数据结构习题(快期末了)
    曾仕强老师视频+音频+电子书合集百度网盘资源
    mysql数据库表的数据显示到前端tableView
    【C++】priority_queue&&仿函数
    C++设计模式_17_Mediator 中介者
    html2Canvas截图包含滚动条解决思路
  • 原文地址:https://blog.csdn.net/Da_zhenzai/article/details/133808545