吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1711|回复: 23
收起左侧

[其他原创] JavaScript+PHP实现视频文件分片上传源码

[复制链接]
yuupuu 发表于 2024-2-27 18:34
本帖最后由 yuupuu 于 2024-2-28 09:56 编辑

视频文件分片上传,整体思路是利用JavaScript将文件切片,然后循环调用上传接口 upload.php 将切片上传到服务器。
这样将由原来的一个大文件上传变为多个小文件同时上传,节省了上传时间,这就是文件分片上传的其中一个好处。

image.png

测试了一个100多M的视频,也能很快完成上传。

image.png

关键代码


index.html


[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>视频文件分片上传</title>
		<style>
		    *{
		        padding: 0;
		        margin: 0;
		    }
		    body {
		        background: #eee;
		    }
		    .title {
		        text-align: center;
		        font-size: 25px;
		        margin-top: 50px;
		    }
		    .video_upload {
		        width: 500px;
		        height: 210px;
		        line-height: 60px;
		        background-color: #fff;
		        margin: 30px auto 0;
		        border: 2px dashed #ccc;
		        border-radius: 10px;
		        position: relative;
		        cursor: pointer;
		        text-align: center;
		        line-height: 200px;
		        font-size: 17px;
		    }
		    .upload_icon {
		        width: 30px;
		        height: 30px;
		        margin: 90px auto 0;
		        display: block;
		        opacity: 0.5;
		    }
		    #fileInput {
		        width: 100%;
		        height: 100%;
		        position: absolute;
		        left: 0;
		        top: 0;
		        opacity: 0;
		        cursor: pointer;
		    }
		    #uploadButton {
		        width: 500px;
		        height: 40px;
		        border: none;
		        outline: none;
		        border-radius: 5px;
		        font-size: 17px;
		        margin: 15px auto;
		        background: #39f;
		        color: #fff;
		        cursor: pointer;
		    }
		    .progress {
		        width: 500px;
		        margin: 15px auto;
		    }
		    .progress progress {
		        width: 500px;
		        height: 30px;
		    }
		    #ret {
		        text-align: center;
		        font-size: 16px;
		        margin-top: 20px;
		    }
		    #ret video {
		        width: 300px;
		    }
		    #retUrl {
		        text-align: center;
		        font-size: 16px;
		        margin-top: 20px;
		    }
		</style>
	</head>
	<body>
	    
        <p class="title">JavaScript+PHP实现视频文件分片上传</p>
        <div class="video_upload">
            <img src="upload.png" class="upload_icon" />
            <input type="file" id="fileInput" accept="video/*">
        </div>
		<button id="uploadButton" style="display:none;">开始上传</button>
		<div class="progress"></div>
		<p id="ret"></p>
		<p id="retUrl"></p>

		<script>
		
			// 定义全局变量
			let videoFile = null;
			let chunkSize = 1024 * 1024; // 1MB 分片大小
			
			// 当文件选择框的值改变时触发该函数
			function handleFileSelect(event) {
			    const fileList = event.target.files;
			    if (fileList.length > 0) {
			        videoFile = fileList[0];
			        console.log("选择了文件: ", videoFile.name);
			        document.querySelector('.video_upload').innerHTML = videoFile.name;
			        document.querySelector('#uploadButton').style.display = 'block';
			    }
			}
			
			// 分片并上传文件
			async function uploadFile() {
			    
                if (!videoFile) {
                    console.error("请选择一个视频文件");
                    return;
                }
            
                const fileSize = videoFile.size;
                let start = 0;
                let end = Math.min(chunkSize, fileSize);
                let chunkIndex = 0;
                let totalChunks = Math.ceil(fileSize / chunkSize); // 总分片数
            
                // 获取文件名
                const fileName = videoFile.name;
            
                while (start < fileSize) {
                    const chunk = videoFile.slice(start, end); // 从文件中截取一个分片
            
                    // 使用FormData来构建multipart/form-data格式的请求体
                    const formData = new FormData();
                    formData.append('file', chunk);
                    formData.append('chunkIndex', chunkIndex);
                    formData.append('fileName', fileName); // 将文件名作为 formData 的一部分
            
                    try {
                        const response = await fetch('upload.php', {
                            method: 'POST',
                            body: formData
                        });
            
                        if (!response.ok) {
                            throw new Error('上传失败');
                        }
            
                        console.log('上传分片 ', chunkIndex, ' 成功');
            
                        // 计算并显示上传进度
                        let progress = Math.round(((chunkIndex + 1) / totalChunks) * 100);
                        document.querySelector('.video_upload').textContent = '上传进度 ' + progress + '%';
                        document.querySelector('#uploadButton').textContent = '正在上传...';
                        document.querySelector('.progress').innerHTML = '<progress value="' + progress + '" max="100"><span>' + progress + '</span>%</progress>';
                        console.log('上传进度:', progress + '%');
            
                    } catch (error) {
                        console.error('上传分片 ', chunkIndex, ' 失败: ', error.message);
                        return;
                    }
            
                    start = end;
                    end = Math.min(start + chunkSize, fileSize);
                    chunkIndex++;
                }
            
                console.log('文件上传完成');
            
                // 上传完成后发送通知给服务器进行合并
                notifyServerForMerge(fileName);
            }

			
			// 发送通知给服务器进行合并
			async function notifyServerForMerge(fileName) {
			    try {
			        const response = await fetch('merge_chunks.php', {
			            method: 'POST',
			            headers: {
			                'Content-Type': 'application/json'
			            },
			            body: JSON.stringify({ fileName: fileName })
			        });
			
			        if (!response.ok) {
			            throw new Error('无法通知服务器进行合并');
			        }
			        
			        const res_data = await response.json();
			
			        console.log('已通知服务器进行合并');
			        document.querySelector('.video_upload').textContent = '上传完成!';
			        document.querySelector('#ret').innerHTML = '<video autoplay controls src="'+res_data.filePath+'"></video>';
			        document.querySelector('#retUrl').textContent = 'MP4视频直链:' + res_data.filePath;
			        document.querySelector('#uploadButton').style.display = 'none';
			    } catch (error) {
			        console.error('通知服务器进行合并时发生错误: ', error.message);
			    }
			}
			
			// 注册文件选择框的change事件
			document.getElementById('fileInput').addEventListener('change', handleFileSelect);
			
			// 注册上传按钮的click事件
			document.getElementById('uploadButton').addEventListener('click', uploadFile);
		</script>

	</body>
</html>



upload.php


[PHP] 纯文本查看 复制代码
<?php

    // 设置允许跨域访问
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Methods: POST");
    
    // 检查是否接收到文件和分片索引
    if (isset($_FILES['file']['error']) && isset($_POST['chunkIndex']) && isset($_POST['fileName'])) {
        
        $error = $_FILES['file']['error'];
        $chunkIndex = $_POST['chunkIndex'];
        $fileName = $_POST['fileName']; // 获取文件名
        
        // 检查是否有错误
        if ($error !== UPLOAD_ERR_OK) {
            http_response_code(500);
            echo json_encode(array(
                'error' => '文件上传失败'
            ));
            exit();
        }
        
        // 检查分片是不是MP4
        if(pathinfo($fileName)['extension'] !== 'mp4') {
            
            http_response_code(400);
            echo json_encode(array(
                'error' => '文件类型不符合'
            ));
            exit();
        }
        
        // 设置存储目录和文件名
        $uploadDir = './uploads/';
        $filePath = $uploadDir . $fileName . '.' . $chunkIndex;
        
        // 将分片移动到指定的目录
        if (move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) {
            
            echo json_encode(array(
                'success' => '分片上传成功'
            ));
        } else {
            
            http_response_code(500);
            echo json_encode(array(
                'error' => '分片上传失败'
            ));
        }
    } else {
        
        http_response_code(400);
        echo json_encode(array(
            'error' => '缺少文件、分片索引或文件名'
        ));
    }
    
?>



merge_chunks.php

[PHP] 纯文本查看 复制代码
<?php

    // 设置允许跨域访问
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Methods: POST");
    header("Content-Type: application/json");
    
    // 获取请求体中的文件名
    $data = json_decode(file_get_contents("php://input") , true);
    $fileName = isset($data['fileName']) ? $data['fileName'] : null;
    if ($fileName) {
        
        $uploadDir = './uploads/';
        $finalFilePath = $uploadDir . $fileName;
        $totalChunks = count(glob($uploadDir . $fileName . '.*'));
        
        // HTTP协议
        $protoCol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
        $videoUrl = $protoCol.'://'.$_SERVER['SERVER_NAME'].dirname($_SERVER["REQUEST_URI"]).'/uploads/'.$fileName;
        
        // 检查是否所有分片都已上传
        if ($totalChunks > 0) {
            
            // 所有分片都已上传,开始合并
            $finalFile = fopen($finalFilePath, 'wb');
            
            // 逐个读取分片并写入最终文件
            for ($i = 0; $i < $totalChunks; $i++) {
                $chunkFilePath = $uploadDir . $fileName . '.' . $i;
                $chunkFile = fopen($chunkFilePath, 'rb');
                stream_copy_to_stream($chunkFile, $finalFile);
                fclose($chunkFile);
                unlink($chunkFilePath); // 删除已合并的分片
                
            }
            
            fclose($finalFile);
            http_response_code(200);
            echo json_encode(array(
                'success' => '文件合并成功',
                'filePath' => $videoUrl
            ));
        } else {
            
            http_response_code(400);
            echo json_encode(array(
                'error' => '没有上传的分片'
            ));
        }
    } else {
        
        http_response_code(400);
        echo json_encode(array(
            'error' => '缺少文件名'
        ));
    }
?>


源码:https://likeyun.lanzout.com/ioDQ51pl8lkh

免费评分

参与人数 4吾爱币 +5 热心值 +4 收起 理由
ytw6176 + 1 + 1 谢谢@Thanks!
zhamg520 + 1 + 1 热心回复!
柒呀柒 + 2 + 1 谢谢@Thanks!
Pwaerm + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

爱飞的猫 发表于 2024-2-28 02:04

进行了下简单的代码审计,可以发现合并/处理上传文件时并不会检查 $finalFilePath 文件的后缀名。

例如上传 .php 文件并访问上传后的地址,会导致服务器执行上传的 php 代码文件。

建议上传处理文件时,在后端(php 代码)对后缀名进行过滤,并拒绝含有非法字符(如 / 斜杠)的文件名。

免费评分

参与人数 3吾爱币 +4 热心值 +1 收起 理由
倾城丶 + 2 + 1 用心讨论,共获提升!
阿里巴巴董事长 + 1 我很赞同!
开创者 + 1 热心回复!

查看全部评分

Pwaerm 发表于 2024-2-27 18:38
柒呀柒 发表于 2024-2-27 19:00
seawaycao 发表于 2024-2-27 19:02

学习学习,谢谢分享!
dingqh 发表于 2024-2-27 19:06
学习啥学习,拿来直接用。。。
头像被屏蔽
hjsen 发表于 2024-2-27 19:35
提示: 作者被禁止或删除 内容自动屏蔽
sxf_0328 发表于 2024-2-27 20:57
学习一下,谢谢分享
头像被屏蔽
moruye 发表于 2024-2-27 21:03
提示: 作者被禁止或删除 内容自动屏蔽
meder 发表于 2024-2-27 21:04
感谢分享
xiajin 发表于 2024-2-27 21:20
支持一个,百度的 Webupload 也有同样的功能。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 07:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表