前端用ajax切片上传文件到php后端并保存的方法,跨域解决
如果要上传大文件,可以使用切片上传的方式来提高上传效率和稳定性。具体步骤如下:
在前端页面中,使用input标签的type属性为file来创建一个文件上传控件,并添加一个上传按钮。
<input type="file" id="fileInput">
<button id="uploadBtn">上传</button>
在JavaScript中,获取到文件上传控件的值,并使用FileReader对象来读取文件内容,并将文件内容切片。
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
var chunkSize = 1024 * 1024; // 每个切片的大小为1MB
var chunks = Math.ceil(file.size / chunkSize); // 计算文件切片的数量
var currentChunk = 0; // 当前上传的切片编号
var fileReader = new FileReader();
fileReader.onload = function(e) {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.php', true);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.setRequestHeader('X-File-Name', encodeURIComponent(file.name));
xhr.setRequestHeader('X-File-Size', file.size);
xhr.setRequestHeader('X-File-Chunk', currentChunk);
xhr.setRequestHeader('X-File-Chunks', chunks);
xhr.onload = function() {
if (xhr.status === 200) {
console.log('切片上传成功');
currentChunk++;
if (currentChunk < chunks) {
uploadChunk(currentChunk);
} else {
console.log('文件上传完成');
}
} else {
console.log('切片上传失败');
}
};
xhr.send(e.target.result);
};
function uploadChunk(chunk) {
var start = chunk * chunkSize;
var end = Math.min(start + chunkSize, file.size);
fileReader.readAsArrayBuffer(file.slice(start, end));
}
在PHP后端中,使用$_FILES数组来获取上传的文件切片,并使用file_put_contents函数将文件切片保存到指定的目录中。
if (isset($_SERVER['HTTP_X_FILE_NAME']) && isset($_SERVER['HTTP_X_FILE_SIZE']) && isset($_SERVER['HTTP_X_FILE_CHUNK']) && isset($_SERVER['HTTP_X_FILE_CHUNKS'])) {
$name = urldecode($_SERVER['HTTP_X_FILE_NAME']);
$size = intval($_SERVER['HTTP_X_FILE_SIZE']);
$chunk = intval($_SERVER['HTTP_X_FILE_CHUNK']);
$chunks = intval($_SERVER['HTTP_X_FILE_CHUNKS']);
$tmp_name = $_FILES['file']['tmp_name'];
$chunk_name = 'uploads/' . $name . '.part' . $chunk;
file_put_contents($chunk_name, file_get_contents($tmp_name), FILE_APPEND);
}
在PHP后端中,当所有文件切片上传完成后,使用fopen函数打开文件,并使用fread函数读取所有文件切片的内容,并使用file_put_contents函数将文件内容保存到指定的目录中。
if ($chunk === $chunks - 1) {
$file_name = 'uploads/' . $name;
$fp = fopen($file_name, 'wb');
for ($i = 0; $i < $chunks; $i++) {
$chunk_name = 'uploads/' . $name . '.part' . $i;
$chunk_content = file_get_contents($chunk_name);
fwrite($fp, $chunk_content);
unlink($chunk_name);
}
fclose($fp);
}
注意:在PHP后端中,需要确保上传的文件大小不超过php.ini中upload_max_filesize和post_max_size的限制。同时,也需要确保上传的文件类型是允许的。
如果服务端不是同一服务器,需要注意跨域的解决,下面是解决了跨域和使用jq的代码:
// HTML部分
<input type="file" id="fileInput">
<button id="uploadBtn">上传</button>
// JavaScript部分
var fileInput = $('#fileInput');
var uploadBtn = $('#uploadBtn');
uploadBtn.on('click', function() {
var file = fileInput[0].files[0];
var fileSize = file.size;
var chunkSize = 1024 * 1024; // 切片大小为1MB
var chunkCount = Math.ceil(fileSize / chunkSize);
var currentChunk = 0;
var fileReader = new FileReader();
fileReader.onload = function(e) {
$.ajax({
url: 'http://localhost/upload.php',
type: 'POST',
data: e.target.result,
processData: false,
contentType: 'application/octet-stream',
headers: {
'X-File-Name': encodeURIComponent(file.name),
'X-File-Size': fileSize,
'X-File-Chunk-Size': chunkSize,
'X-File-Current-Chunk': currentChunk
},
success: function() {
currentChunk++;
if (currentChunk < chunkCount) {
loadNext();
} else {
console.log('上传完成');
}
}
});
};
function loadNext() {
var start = currentChunk * chunkSize;
var end = Math.min(start + chunkSize, fileSize);
fileReader.readAsArrayBuffer(file.slice(start, end));
}
loadNext();
});
php部分
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Access-Control-Allow-Origin, X-Requested-With, X-File-Name, X-File-Size, X-File-Chunk-Size, X-File-Current-Chunk, Content-Type');
$fileName = urldecode($_SERVER['HTTP_X_FILE_NAME']);
$fileSize = $_SERVER['HTTP_X_FILE_SIZE'];
$chunkSize = $_SERVER['HTTP_X_FILE_CHUNK_SIZE'];
$currentChunk = $_SERVER['HTTP_X_FILE_CURRENT_CHUNK'];
$filePath = 'uploads/' . $fileName . '.' . $currentChunk;
$fp = fopen($filePath, 'w');
fwrite($fp, file_get_contents('php://input'));
fclose($fp);
if ($currentChunk == $chunkCount - 1) {
$fp = fopen('uploads/' . $fileName, 'w');
for ($i = 0; $i < $chunkCount; $i++) {
$filePath = 'uploads/' . $fileName . '.' . $i;
fwrite($fp, file_get_contents($filePath));
unlink($filePath);
}
fclose($fp);
}
注意,这里的跨域问题需要在PHP后端代码中添加header('Access-Control-Allow-Origin: *');
来解决,f12调试时如果出现提示被拦截,就加入到里面:
header('Access-Control-Allow-Headers: Access-Control-Allow-Origin, X-Requested-With, X-File-Name, X-File-Size, X-File-Chunk-Size, X-File-Current-Chunk, Content-Type');
只要被拦截的头部就加到里面。
如果要实现秒传等功能,建议使用插件:https://www.dpwlkj.cn/js/upload.js文件。
完整示例代码,前端:
<div class="container">
<div class="row justify-content-center">
<div class="col-md-9">
<div class="card">
<div class="card-header">微附件-{{Auth::user()->username}}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<h4 class="m-3 text-center">
<a style="text-decoration:none;" href="https://mp.weixin.qq.com/s/BW2GsUhuQtmu7-YqXGFVhQ">微附件-公众号中插入附件【使用方法】</a>
</h4>
<form method="POST" action="/wfjsave">
@csrf
<input id="file" type="hidden" class="form-control" name="file">
<div class="row mb-3">
<label for="name" class="col-md-3 col-form-label text-md-end star" required>文件名</label>
<div class="col-md-7">
<input id="name" type="text" class="form-control" name="name" required>
</div>
</div>
<div class="row mb-3 mt-4">
<label for="fileInput" class="col-md-3 col-form-label text-md-end star">上传文件</label>
<div class="col-md-7">
<button id="upid2" class="form-control" type="button">选择上传</button>
<div class="progress mt-3">
<div class="progress-bar" id="jd" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-4">
<button type="submit" id="save" class="btn btn-primary" disabled style="width:150px;">确认保存</button>
</div>
</form>
<hr>
<h3 class="m-4 text-center">我的文件</h3>
<div class="m-3">
<table class="table">
<thead class="thead-dark">
<tr>
<th>名称</th>
<th>公众号链接(点击复制)</th>
</tr>
</thead>
@foreach($paginator as $value)
<tr>
<td>{{$value->mc}}</td>
<td><span onclick="copyContent(this);" title="点击复制">{{ '/pages/video/file?id='.$curlk::encode($value->id); }}</span></td>
</tr>
@endforeach
</table>
<input id="copy_content" type="text" value="" style="position: absolute;top: 0;left: 0;opacity: 0;z-index: -10;"/>
</div>
<div class="text-center m-4">
{{ $paginator->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/upload.js"></script>
<script type="text/javascript">
function copyContent(ElementObj){
var clickContent = ElementObj.innerText;
var inputElement = document.getElementById("copy_content");
inputElement.value = clickContent;
inputElement.select();
document.execCommand("Copy");
alert('已复制');
}
function Progress2(value) {
$('#jd').text(value + '%');
$('#jd').css('width', value + '%');
}
let up2 = new fcup({
id: "upid2", // 绑定id
url: '{{$upload}}' + 'upload.php', // url地址
check_url: '{{$upload}}' + "check.php", // 检查上传url地址
type: "jpg,png,jpeg,doc,docx,xls,xlsx,pdf,ppt,pptx,zip,rar,mp3,mp4", // 限制上传类型,为空不限制
shard_size: "1", // 每次分片大小,单位为M,默认1M
min_size: '', // 最小文件上传M数,单位为M,默认为无
max_size: "200", // 上传文件最大M数,单位为M,默认200M
// headers: {"version": "fcup-v2.0"}, // 附加的文件头,默认为null, 请注意指定header头时将不能进行跨域操作
// apped_data: {}, //每次上传的附加数据
// 定义错误信息
error_msg: {
1000: "未找到上传id",
1001: "类型不允许上传",
1002: "上传文件过小",
1003: "上传文件过大",
1004: "上传请求超时"
},
// 错误提示
error: (msg) => {
alert(msg);
},
// 初始化事件
start: () => {
console.log('上传已准备就绪');
Progress2(0);
},
// 等待上传事件,可以用来loading
before_send: () => {
console.log('等待请求中');
},
// 上传进度事件
progress: (num, other) => {
Progress2(num);
// console.log(num);
// console.log('上传进度' + num);
// console.log("上传类型" + other.type);
// console.log("已经上传" + other.current);
// console.log("剩余上传" + other.surplus);
// console.log("已用时间" + other.usetime);
// console.log("预计时间" + other.totaltime);
},
// 检查地址回调,用于判断文件是否存在,类型,当前上传的片数等操作
check_success: (res) => {
let data = res ? eval('(' + res + ')') : '';
let status = data.status;
let url = data.url;
let msg = data.message;
// 错误提示
if (status == 1 ) {
alert(msg);
return false;
}
// 已经上传
if (status == 2) {
Progress2(100);
$('#file').val(url);
$("#save").removeAttr("disabled");
alert('文件已存在');
return false;
}
// 如果提供了这个参数,那么将进行断点上传的准备
if(data.file_index){
// 起始上传的切片要从1开始
let file_index = data.file_index ? parseInt(data.file_index) : 1;
// 设置上传切片的起始位置
up2.set_shard(file_index);
}
// 如果接口没有错误,必须要返回true,才不会终止上传
return true;
},
// 上传成功回调,回调会根据切片循环,要终止上传循环,必须要return false,成功的情况下要始终返回true;
success: (res) => {
let data = res ? eval('(' + res + ')') : '';
let url = data.url + "?" + Math.random();
let file_index = data.file_index ? parseInt(data.file_index) : 1;
if (data.status == 2) {
$('#file').val(data.url);
$("#save").removeAttr("disabled");
alert('上传完成');
}
// 如果接口没有错误,必须要返回true,才不会终止上传循环
return true;
}
});
</script>
<style>
.star::before {
content:"*";
color:red
}
.hidden{
display: none;
}
</style>
php后端有两个文件:
check.php
<?php
/**
* 检查上传文件
* author:lovefc
*/
// 设置跨域头
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:PUT,POST,GET,DELETE,OPTIONS');
header('Access-Control-Allow-Headers:x-requested-with,content-type');
header('Content-Type:application/json; charset=utf-8');
$name = isset($_POST['file_name']) ? $_POST['file_name']:null; // 文件名
$md5 = isset($_POST['file_md5']) ? $_POST['file_md5'] :''; //文件的md5值
$size = isset($_POST['file_size']) ? $_POST['file_size'] :''; //文件大小
// 输出json信息
function jsonMsg($status,$message,$url='',$index=1){
$arr['status'] = $status;
$arr['message'] = $message;
$arr['url'] = $url;
$arr['file_index'] = $index;
echo json_encode($arr);
die();
}
//jsonMsg(0,'','',201);
if(!$md5){
jsonMsg(1,'没有文件');
}
// 简单的判断文件类型s
$info = pathinfo($name);
// 取得文件后缀
$ext = isset($info['extension'])?$info['extension']:'';
/* 判断文件类型 */
$imgarr = array('jpg','png','jpeg','doc','docx','xls','xlsx','pdf','ppt','pptx','zip','rar','mp3','mp4');
if(!in_array($ext,$imgarr)){
jsonMsg(1,'文件类型出错');
}
// 在实际使用中,用md5来给文件命名,这样可以减少冲突
$file_name = $md5.'.'.$ext;
$newfile = 'files/'.$file_name;
$log_file = '.files/'.$md5.'.txt';
// 文件可访问的地址
$url = 'https://video.dpwlkj.cn:4430/files/'.$file_name;
/** 判断是否重复上传 **/
// 清除文件状态
clearstatcache($newfile);
// 文件大小一样的,说明已经上传过了
if(is_file($newfile) && ($size == filesize($newfile))){
jsonMsg(2,'已经上传过了',$url);
}
if(is_file($log_file)){
// 读取当前片数的时候要向前偏移1个
$index = file_get_contents($log_file);
$index = $index + 1;
}else{
$index = 1;
}
jsonMsg(0,'','',$index);
upload.php
<?php
/**
* 接受处理上传文件
* author:lovefc
*/
// 设置跨域头
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:PUT,POST,GET,DELETE,OPTIONS');
header('Access-Control-Allow-Headers:x-requested-with,content-type');
header('Content-Type:application/json; charset=utf-8');
$file = isset($_FILES['file_data']) ? $_FILES['file_data']:null; //分段的文件
$name = isset($_POST['file_name']) ? $_POST['file_name']:null; //要保存的文件名
$total = isset($_POST['file_total']) ? $_POST['file_total']:0; //总片数
$index = isset($_POST['file_index']) ? $_POST['file_index']:0; //当前片数
$md5 = isset($_POST['file_md5']) ? $_POST['file_md5'] : 0; //文件的md5值
$size = isset($_POST['file_size']) ? $_POST['file_size'] : null; //文件大小
$chunksize = isset($_POST['file_chunksize']) ? $_POST['file_chunksize'] : null; //当前切片的文件大小
$suffix = isset($_POST['file_suffix']) ? $_POST['file_suffix'] : null; //当前上传的文件后缀
//echo '总片数:'.$total.'当前片数:'.$index;
// 输出json信息
function jsonMsg($status,$message,$url='',$index=0){
$arr['status'] = $status;
$arr['message'] = $message;
$arr['url'] = $url;
$arr['file_index'] = $index;
echo json_encode($arr);
die();
}
// 在实际使用中,用md5来给文件命名,这样可以减少冲突
// 简单的判断文件类型s
$info = pathinfo($name);
// 取得文件后缀
$ext = isset($info['extension'])?$info['extension']:'';
$file_name = $md5.'.'.$ext;
$newfile = 'files/'.$file_name;
$log_file = 'files/'.$md5.'.txt';
// 文件可访问的地址
$url = 'https://video.dpwlkj.cn:4430/files/'.$file_name;
// 这里判断有没有上传的文件流
if ($file['error'] == 0) {
file_put_contents($log_file,$index);
// 如果文件不存在,就创建
if (!file_exists($newfile)) {
if (!move_uploaded_file($file['tmp_name'], $newfile)) {
jsonMsg(0,'无法移动文件');
}
// 片数相等,等于完成了
if($index == $total ){
jsonMsg(2,'上传完成',$url,$index);
}
jsonMsg(1,'正在上传','',$index);
}
// 如果当前片数小于等于总片数,就在文件后继续添加
if($index <= $total){
$content = file_get_contents($file['tmp_name']);
if (!file_put_contents($newfile, $content, FILE_APPEND)) {
jsonMsg(0,'无法写入文件');
}
// 片数相等,等于完成了
if($index == $total ){
jsonMsg(2,'上传完成',$url,$index);
}
jsonMsg(1,'正在上传','',$index);
}
} else {
jsonMsg(0,'没有上传文件');
}
记录完毕!
本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱: 2554509967@qq.com