完整教程:Java+Vue+Websocket实现OSS文件上传进度条功能
引言
文件上传是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桶后的文件名,后面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() {// 使用时间戳来生成唯一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服务和监听上传文件字节来计算进度,我们能够实时显示文件上传的进度条,提升用户体验。
结束语:人生最大的浪费不是金钱的浪费,而是时间的浪费、认知的迟到
相关文章:
完整教程:Java+Vue+Websocket实现OSS文件上传进度条功能
引言 文件上传是Web应用开发中常见的需求之一,而实时显示文件上传的进度条可以提升用户体验。本教程将介绍如何使用Java后端和Vue前端实现文件上传进度条功能,借助阿里云的OSS服务进行文件上传。 技术栈 后端:Java、Spring Boot 、WebSock…...
【微服务 SpringCloud】实用篇 · 服务拆分和远程调用
微服务(2) 文章目录 微服务(2)1. 服务拆分原则2. 服务拆分示例1.2.1 导入demo工程1.2.2 导入Sql语句 3. 实现远程调用案例1.3.1 案例需求:1.3.2 注册RestTemplate1.3.3 实现远程调用1.3.4 查看效果 4. 提供者与消费者 …...
Linux 下I/O操作
一、文件IO 文件 IO 是 Linux 系统提供的接口,针对文件和磁盘进行操作,不带缓存机制;标准IO是C 语言函数库里的标准 I/O 模型,在 stdio.h 中定义,通过缓冲区操作文件,带缓存机制。 标准 IO 和文件 IO 常…...
C#内映射lua表
都是通过同一个方法得到的 例如得到List List<int> list LuaMgr.GetInstance().Global.Get<List<int>>("testList"); 只要把Get的泛型换成对应的类型即可 得到Dictionnary Dictionary<string, int> dic2 LuaMgr.GetInstance().Global…...
android studio检测不到真机
我的情况是: 以前能检测到,有一天我使用无线调试,发现调试有问题,想改为USB调试,但是半天没反应,我就点了手机上的撤销USB调试授权,然后就G了。 解决办法: 我这个情况比较简单&…...
【Eclipse】设置自动提示
前言: eclipse默认有个快捷键:alt /就可以弹出自动提示,但是这样也太麻烦啦!每次都需要手动按这个快捷键,下面给大家介绍的是:如何设置敲的过程中就会出现自动提示的教程! 先按路线找到需要的页…...
单片机TDL的功能、应用与技术特点 | 百能云芯
在现代电子领域中,单片机(Microcontroller)是一种至关重要的电子元件,广泛应用于各种应用中。TDL(Time Division Multiplexing,时分多路复用)是一种数据传输技术,结合单片机的应用&a…...
解决笔记本无线网络5G比2.4还慢的奇怪问题
环境:笔记本Dell XPS15 9570,内置无线网卡Killer Wireless-n/a/ac 1535 Wireless Network Adapter,系统win10家庭版,路由器H3C Magic R2Pro千兆版 因为笔记本用的不多,一直没怎么注意网络速度,直到最近因为…...
GitHub Action 通过SSH 自动部署到云服务器上
准备 正式开始之前,你需要掌握 GitHub Action 的基础语法: workflow (工作流程):持续集成一次运行的过程,就是一个 workflow。name: 工作流的名称。on: 指定次工作流的触发器。push 表示只要有人将更改推…...
【AOP系列】7.数据校验
在Java中,我们可以使用Spring AOP(面向切面编程)和自定义注解来做数据校验。以下是一个简单的示例: 首先,我们创建一个自定义注解,用于标记需要进行数据校验的方法: import java.lang.annotat…...
黑马JVM总结(三十七)
(1)synchronized-轻量级锁-无竞争 (2)synchronized-轻量级锁-锁膨胀 重量级锁就是我们前面介绍过的Monitor enter (3)synchronized-重量级锁-自旋 (4)synchronized-偏向锁 轻量级锁…...
企业如何通过媒体宣传扩大自身影响力
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 企业可以通过媒体宣传来扩大自身的影响力。可以通过以下的方法。 1. 制定媒体宣传战略: - 首先,制定一份清晰的媒体宣传战略,明确您的宣传目标、目标…...
处理vue直接引入图片地址时显示不出来的问题 src=“[object Module]“
在webpack中使用vue-loader编译template之后,发现图片加载不出来了,开发人员工具中显示src“[object Module]” 这是因为当vue-loader编译template块之后,会将所有的资源url转换为webpack模块请求 这是因为vue使用的是commonjs语法规范&…...
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
vue3 v-md-editor markdown编辑器和预览组件的使用 概述安装支持vue3版本使用1.使用markdown编辑器 VMdEditor2.markdown文本格式前端渲染 VMdPreview 例子效果代码部分 完整代码 概述 v-md-editor 是基于 Vue 开发的 markdown 编辑器组件 轻量版编辑器 轻量版编辑器左侧编辑…...
java正则表达式 及应用场景爬虫,捕获分组非捕获分组
正则表达式 通常用于校验 比如说qq号 看输入的是否符合规则就可以用这个 public class regex {public static void main(String[] args) {//正则表达式判断qq号是否正确//规则 6位及20位以内 0不能再开头 必须全是数子String qq"1234567890";System.out.println(qq…...
基于 Debian 稳定分支发行版的Zephix 7 发布
Zephix 是一个基于 Debian 稳定版的实时 Linux 操作系统。它可以完全从可移动媒介上运行,而不触及用户系统磁盘上存储的任何文件。 Zephix 是一个基于 Debian 稳定版的实时 Linux 操作系统。它可以完全从可移动媒介上运行,而不触及用户系统磁盘上存储的…...
MBR20100CT-ASEMI肖特基MBR20100CT参数、规格、尺寸
编辑:ll MBR20100CT-ASEMI肖特基MBR20100CT参数、规格、尺寸 型号:MBR20100CT 品牌:ASEMI 芯片个数:2 封装:TO-220 恢复时间:>50ns 工作温度:-65C~175C 浪涌电流:…...
修炼k8s+flink+hdfs+dlink(五:安装dockers,cri-docker,harbor仓库)
一:安装docker。(所有服务器都要安装) 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2添加软件源信息 sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/cent…...
github: kex_exchange_identification: Connection closed by remote host
问题描述 (base) ➜ test git:(dev) git pull kex_exchange_identification: Connection closed by remote host Connection closed by 192.30.255.113 port 22 致命错误:无法读取远程仓库。解决方案 参照下边文档 https://docs.github.com/en/authentication/tr…...
AWS香港Web3方案日,防御云安全实践案例受关注
9月26日,AWS合作伙伴之Web3解决方案日在香港举办。来自人工智能、Web3等领域的创业公司、技术专家、风险投资商,就元宇宙时代未来发展进行了深入交流。现场展示了顶象防御云在金融与Web3领域的安全实践案例。 Web3为互联网体系架构的一个整体演进和升级&…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
