Vue3+SpringBoot实战:手把手教你给RuoYi-Plus项目添加视频上传功能(含进度条与移除)

发布时间:2026/5/27 16:10:29

Vue3+SpringBoot实战:手把手教你给RuoYi-Plus项目添加视频上传功能(含进度条与移除) Vue3SpringBoot实战RuoYi-Plus项目视频上传功能全流程解析在前后端分离的企业级项目中文件上传是高频需求场景。以RuoYi-Plus这种主流快速开发框架为例视频上传模块的实现需要前后端协同配合。本文将完整演示从零构建视频上传功能的实践路径包含以下关键环节1. 前端工程化配置1.1 初始化上传组件在Vue3的Composition API环境下首先需要配置符合业务需求的el-upload组件。建议采用以下配置方案template el-upload classvideo-uploader :actionuploadConfig.apiPath :headersauthHeaders :limit1 :file-listfileList :before-uploadvalidateVideo :on-progresshandleProgress :on-successhandleSuccess :on-errorhandleError el-button typeprimary选择视频文件/el-button template #tip div classupload-tip 支持MP4/AVI/FLV格式单文件不超过5GB /div /template /el-upload /template关键配置参数说明参数类型必填说明actionstring是后端API接口地址headersobject是携带认证信息的请求头limitnumber否最大上传文件数file-listarray否已上传文件列表before-uploadfunction否上传前校验钩子1.2 状态管理与校验逻辑采用Reactive API管理上传状态const uploadState reactive({ progress: 0, isUploading: false, fileList: [] }) const validateVideo (file) { const validTypes [video/mp4, video/avi, video/x-flv] const isTypeValid validTypes.includes(file.type) const isSizeValid file.size / 1024 / 1024 / 1024 5 // 5GB限制 if (!isTypeValid) { ElMessage.error(仅支持MP4/AVI/FLV格式视频) return false } if (!isSizeValid) { ElMessage.error(视频大小不能超过5GB) return false } uploadState.isUploading true return true }2. 进度反馈与用户体验优化2.1 实时进度显示通过on-progress事件获取上传进度const handleProgress (event) { uploadState.progress Math.floor(event.percent) }配合Element Plus的进度条组件el-progress v-ifuploadState.isUploading :percentageuploadState.progress :stroke-width16 :text-insidetrue /2.2 上传结果可视化成功上传后显示视频预览div v-ifuploadState.fileList.length 0 classvideo-preview video :srcuploadState.fileList[0].url controls classpreview-player / el-button typedanger clickhandleRemove classremove-btn 移除视频 /el-button /div移除功能实现const handleRemove () { ElMessageBox.confirm(确认移除已上传视频).then(() { uploadState.fileList [] uploadState.progress 0 }) }3. 后端文件处理实现3.1 SpringBoot接收配置创建文件上传控制器RestController RequestMapping(/api/video) public class VideoUploadController { Value(${ruoyi.profile}) private String uploadPath; PostMapping(/upload) public ResponseEntityMapString, Object uploadVideo( RequestParam(file) MultipartFile file) { MapString, Object result new HashMap(); try { // 文件校验逻辑 String originalName file.getOriginalFilename(); String fileExt FilenameUtils.getExtension(originalName); if (!Arrays.asList(mp4, avi, flv).contains(fileExt)) { result.put(code, 400); result.put(msg, 不支持的文件格式); return ResponseEntity.badRequest().body(result); } // 生成存储路径 String newFileName UUID.randomUUID() . fileExt; Path storagePath Paths.get(uploadPath, videos, newFileName); Files.createDirectories(storagePath.getParent()); // 保存文件 file.transferTo(storagePath); // 构造返回结果 result.put(code, 200); result.put(path, /videos/ newFileName); result.put(name, originalName); return ResponseEntity.ok(result); } catch (Exception e) { result.put(code, 500); result.put(msg, 文件上传失败); return ResponseEntity.internalServerError().body(result); } } }3.2 配置文件存储在application.yml中配置存储路径ruoyi: profile: /data/ruoyi/upload添加跨域配置开发环境Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(*) .maxAge(3600); } }4. 生产环境优化方案4.1 大文件分片上传前端分片处理逻辑const chunkSize 5 * 1024 * 1024 // 5MB分片 const uploadChunk async (file) { const chunks Math.ceil(file.size / chunkSize) const fileMd5 await calculateMD5(file) for (let i 0; i chunks; i) { const chunk file.slice(i * chunkSize, (i 1) * chunkSize) const formData new FormData() formData.append(chunk, chunk) formData.append(chunkNumber, i) formData.append(totalChunks, chunks) formData.append(identifier, fileMd5) await axios.post(/api/video/chunk, formData, { headers: { Content-Type: multipart/form-data } }) } // 通知后端合并分片 await axios.post(/api/video/merge, { identifier: fileMd5, fileName: file.name }) }后端分片接收接口PostMapping(/chunk) public ResponseEntity? uploadChunk( RequestParam(chunk) MultipartFile chunk, RequestParam(chunkNumber) int chunkNumber, RequestParam(totalChunks) int totalChunks, RequestParam(identifier) String identifier) { // 创建临时目录 String tempDir uploadPath /temp/ identifier; new File(tempDir).mkdirs(); // 保存分片 String chunkName chunkNumber .part; try { chunk.transferTo(new File(tempDir, chunkName)); return ResponseEntity.ok().build(); } catch (IOException e) { return ResponseEntity.internalServerError().build(); } }4.2 安全防护措施文件类型二次校验private boolean isVideoFile(MultipartFile file) throws IOException { byte[] fileHeader new byte[12]; file.getInputStream().read(fileHeader); // MP4文件头校验 if (Arrays.equals(Arrays.copyOfRange(fileHeader, 4, 8), new byte[]{0x66, 0x74, 0x79, 0x70})) { return true; } // AVI文件头校验 if (Arrays.equals(Arrays.copyOfRange(fileHeader, 0, 4), new byte[]{0x52, 0x49, 0x46, 0x46})) { return true; } return false; }在项目开发过程中视频上传功能的稳定性直接影响用户体验。建议在测试阶段重点关注以下场景网络中断后恢复上传重复上传相同文件服务端存储空间不足并发上传冲突处理

相关新闻