nas_docker_compose/kodbox/site/app/kod/Uploader.class.php
2024-08-31 01:03:37 +08:00

317 lines
10 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/
class Uploader{
public $fileName;
public $uploadFile;
public $tempFile;
public function __construct(){
global $in;
$this->in = &$in;
if (!empty($_FILES)) {
$files = $_FILES['file'];
$this->uploadFile = $files["tmp_name"];
if(!$this->uploadFile && $files['error']> 0){
show_json($this->errorInfo($files['error']),false);
}
}else if (isset($in["name"])) {
$this->uploadFile = "php://input";
}
// $fp = @fopen("php://input","rb");$head = fread($fp, 1024*10);fclose($fp);$S = $_SERVER;
// write_log([$in,$this->uploadFile,@filesize($this->uploadFile),$S['CONTENT_LENGTH'],$S['CONTENT_TYPE'],$head],'test');
if(isset($in['base64Upload']) && isset($in['base64str']) && $in['base64str']){
$this->uploadFile = 'base64';
}
$this->fileName = self::fileName();
$this->statusData = false;
$this->checkSize();
$this->tempPathInit();
}
/**
* 文件上传处理。大文件支持分片上传
* post上传base64Upload=1;file=base64str;name=filename
*
* chunk,chunks; 选填; 没有或chunks小于等于1则认为不分片;
* 分片则 size 必须传入
*/
public function upload(){
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' ){exit;};//跨域请求时,浏览器发送options接口会影响正常流程;
$chunk = isset($this->in["chunk"]) ? intval($this->in["chunk"]) : 0;
$chunks = isset($this->in["chunks"]) ? intval($this->in["chunks"]) : 1;
$dest = $this->tempFile.'.part'.$chunk;
$size = isset($this->in["size"]) ? intval($this->in["size"]) : 0;
$chunkSize = isset($this->in["chunkSize"]) ? intval($this->in["chunkSize"]) : 0;
if( $chunks > 1 && $chunkSize <= 0 ){
show_json('chunkSize error!',false);
}
if($chunkSize > $size){$chunks = 1;}
if($chunks <= 1) {// 没有分片;
// 清除秒传or断点续传请求checkChunk写入的数据;
$this->statusSet(false);
$this->tempFile = $this->tempFile.rand_string(5);
$result = $this->moveUploadedFile($this->tempFile);
return $this->uploadResult($result);
}
$fileHashSimple = '';
$fileChunkSize = ($chunk < $chunks - 1) ? $chunkSize : ($size - ($chunks - 1) * $chunkSize);
//分片上传必须携带文件大小,及分片切分大小;
CacheLock::lock($this->tempFile,30);
$this->initFileTemp();
CacheLock::unlock($this->tempFile);
// 流式上传性能优化;不写入临时文件;直接写入到目标占位文件中;
if($this->uploadFile == "php://input"){
$chunkFile = $this->uploadFile;
}else{
$chunkFile = $this->moveUploadedFile($dest);
if($size > 0 && filesize($chunkFile) == 0){$this->showJson('0 Byte upload',false);}
if(!$chunkFile){$this->showJson(LNG('explorer.moveError'),false);}
$fileHashSimple = IO::hashSimple($chunkFile);
}
$offset = $chunk * $chunkSize;
if(!$outFp = @fopen($this->tempFile, "r+")){
$this->showJson('fopen file error:'.$this->tempFile,false);
}
fseek_64($outFp,$offset);
$success = $this->writeTo($chunkFile,$outFp,$this->tempFile);
if(!$fileHashSimple){
$outFp = fopen($this->tempFile,'r');
if(!$outFp){$this->showJson('fopen file error',false);}
fseek_64($outFp,$offset);
$fileHashSimple = PathDriverStream::hash($outFp,$fileChunkSize);
fclose($outFp);
}
@unlink($chunkFile);
if(!$success){$this->showJson('chunk_error:'.$chunk,false);}
//分片成功:
CacheLock::lock($this->tempFile.'.statusGet',30);
$data = $this->statusGet();
$data['chunkTotal'] = $chunks;
$data['chunkArray']['chunk'.$chunk] = array(
'offset' => $offset,
'index' => $chunk,
'size' => $fileChunkSize,
'hashSimple'=> $fileHashSimple,
);
$this->statusSet($data);
CacheLock::unlock($this->tempFile.'.statusGet');
if(count($data['chunkArray']) != $data['chunkTotal'] ){
$this->showJson('chunk_success_'.$chunk,true);
}
// 所有分片完成,检测分片hash值一致性;
if(!$this->checkChunkHash($data)){
$this->showJson(LNG('explorer.upload.error')."[chunk hash error!]",false);
}
return $this->uploadResult($this->tempFile);
}
// 上传完成临时文件检测;
private function uploadResult($file){
$this->statusSet(false);//上传成功,清空相关配置;
$size = isset($this->in["size"]) ? intval($this->in["size"]) : 0;
$hash = isset($this->in["checkHashSimple"]) ? $this->in["checkHashSimple"] : '';
if(!file_exists($file)){
show_json(LNG('explorer.upload.error').' [temp not exist!]',false);
}
if($size && $size != filesize($file)){
@unlink($file);
show_json(LNG('explorer.upload.error').'[temp size error]',false);
}
return $file;
}
private function checkSize(){
if(phpBuild64() || $this->in['size'] < PHP_INT_MAX) return;
show_json(LNG('explorer.uploadSizeError'),false);
}
private function showJson($data,$code){
CacheLock::unlock($this->tempFile);
if(!$code){$this->clearData();}
show_json($data,$code);
}
public function clearData(){
$this->statusSet(false);
if(file_exists($this->tempFile)){
@unlink($this->tempFile);return;
}
}
// 上传临时目录; 优化: 默认存储io为本地时,临时目录切换到对应目录的temp/下;(减少从头temp读取->写入到存储i)
private function tempPathGet(){
// return '/mnt/usb/tmp/';
$tempPath = TEMP_FILES;
$path = isset($GLOBALS['in']['path']) ? $GLOBALS['in']['path']:'';
$driverInfo = KodIO::pathDriverType($path);
if($driverInfo && $driverInfo['type'] == 'local'){
$truePath = rtrim($driverInfo['path'],'/').'/';
$isSame = KodIO::isSameDisk($truePath,TEMP_FILES);
if(!$isSame && file_exists($truePath)){$tempPath = $truePath;}
}
if(!file_exists($tempPath)){
@mk_dir($tempPath);
touch($tempPath.'index.html');
}
return $tempPath;
}
// 临时文件;
private function tempPathInit(){
$tempPath = $this->tempPathGet();
$tempName = isset($this->in['checkHashSimple']) ? $this->in['checkHashSimple']:false;
if(strlen($tempName) < 30){ //32+大小;
$tempName = md5(USER_ID.$this->in['path'].$this->fileName.$this->in['size']);
}
$name = ".upload_".md5($tempName.$this->in['chunkSize']);
$this->tempFile = $this->tempPathGet().$name;
}
// 兼容move_uploaded_file 和 流的方式上传
private function moveUploadedFile($dest){
$fromPath = $this->uploadFile;
if($fromPath == "base64"){
@file_put_contents($dest,base64_decode($_REQUEST['base64str']));
}else if($fromPath == "php://input"){
$out = @fopen($dest, "wb");
$this->writeTo($fromPath,$out,$dest);
}else{
if(!move_uploaded_file($fromPath,$dest)){return false;}
}
return $dest;
}
private function writeTo($from,$outFp,$outName){
$lockKey = 'Uploader.writeTo'.$outName;
//$isLock= CacheLock::lock($lockKey,1); //不用锁;[多个进程独立写入]
$in = @fopen($from,"rb");
if(!$in || !$outFp){return false;}
while (!feof($in)) {
fwrite($outFp, fread($in, 1024*500));
}
fclose($in);fclose($outFp);
return true;
}
private function statusGet(){
if( is_array($this->statusData)) return $this->statusData;
$file = $this->tempFile.'.cfg';
$content = false;
if(file_exists($file)){
$content = @file_get_contents($file);
}
if($content){
$this->statusData = json_decode($content,true);
}
if(!$this->statusData){
$defaultData = array(
'name' => $this->fileName,
'chunkTotal'=> 0,
'chunkArray'=> array(), // chunk2=>{offset:xxx,index:2,hashSimple:xxx}
);
$this->statusSet($defaultData);
}
return $this->statusData;
}
public function statusSet($data){
$file = $this->tempFile.'.cfg';
if(!$data){
return @unlink($file);
}
$this->statusData = $data;
return file_put_contents($file,json_encode($data));
}
// 生成占位文件;
private function initFileTemp(){
if( file_exists($this->tempFile) ) return;
if(!$fp = fopen($this->tempFile,'wb+')){
$this->showJson('fopen file error:'.$this->tempFile,false);
}
fseek_64($fp,$this->in['size']-1,SEEK_SET);
fwrite($fp,'0');
fclose($fp);
}
//文件分块检测是否已上传,已上传则忽略;断点续传
public function checkChunk(){
$result = array();
CacheLock::lock($this->tempFile);
$data = $this->statusGet();
CacheLock::unlock($this->tempFile);
foreach ($data['chunkArray'] as $item) {
$hash = $item['hashSimple'];
if($hash){
$result['part_'.$item['index']] = $hash;
}
}
return $result;
}
// 所有分片完成,检测simpleHash 及md5;
private function checkChunkHash($data){
if(count($data['chunkArray']) != $data['chunkTotal'] ){return false;}
$fileHash = _get($this->in,'checkHashSimple');
if($fileHash){return IO::hashSimple($this->tempFile) == $fileHash;}
if(!$fp = fopen($this->tempFile,'r')) return false;
$success = true;
foreach ($data['chunkArray'] as $item) {
fseek_64($fp,$item['offset']);
$chunkHash = PathDriverStream::hash($fp,$item['size']);
if($item['hashSimple'] != $chunkHash){
$success = false;break;
}
}
fclose($fp);
return $success;
}
//拍照上传等情况兼容处理;
public static function fileName(){
global $in;
$fileName = isset($in['name']) ? $in['name'] :'';
if (!empty($_FILES)) {
$fileName = $fileName ? $fileName : $_FILES['file']["name"];
}
if(!is_wap()) return KodIO::clear($fileName);
//ios 上传没有文件名问题处理
$time = strtotime($in['lastModifiedDate']);
$time = $time ? $time : time();
$beforeName = strtolower($fileName);
if($beforeName == "image.jpg" || $beforeName == "image.jpeg"){
$fileName = date('Ymd',$time).'_'.$in['size'].'.jpg';
}else if($beforeName == "capturedvideo.mov"){
$fileName = date('Ymd',$time).'_'.$in['size'].'.mov';
}
return KodIO::clear($fileName);
}
private function errorInfo($error){
$status = array(
'UPLOAD_ERR_OK', //0 没有错误发生,文件上传成功。
'UPLOAD_ERR_INI_SIZE', //1 上传的文件超过了php.ini 中 upload_max_filesize 选项限制的值。
'UPLOAD_ERR_FORM_SIZE', //2 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
'UPLOAD_ERR_PARTIAL', //3 文件只有部分被上传。
'UPLOAD_ERR_NO_FILE', //4 没有文件被上传。
'UPLOAD_UNKNOW', //5 未定义
'UPLOAD_ERR_NO_TMP_DIR',//6 找不到临时文件夹。php 4.3.10 和 php 5.0.3 引进。
'UPLOAD_ERR_CANT_WRITE',//7 文件写入失败。php 5.1.0 引进。
);
return $error.':'.$status[$error];
}
}