add: Onedrive large file uploading

pull/95/head
HFO4 7 years ago
parent e989aa47d9
commit 7a34a5ffb8

@ -42,6 +42,7 @@ class Task extends Command
$task->Do();
if($task->status=="error"){
$output->writeln("[Error] ".$task->errorMsg);
Db::name("task")->where("id",$newTaskInfo["id"])->update(["status"=>"error|".$task->errorMsg]);
}
}
}

@ -757,7 +757,7 @@ class AdminHandler extends Model{
}
public function oneDriveTest(){
$policyId = \think\Session::get('onedrive.pid');
$policyId =1;
$policyData = Db::name("policy")->where("id",$policyId)->find();
$onedrive = new Client([
@ -772,8 +772,17 @@ class AdminHandler extends Model{
Db::name("policy")->where("id",$policyId)->update([
"sk" => json_encode($onedrive->getState()),
]);
$file = fopen("C:/Users/i/Downloads/Video/test.mp4","r");
$onedrive->createFile(urlencode("Git提交代码简教程.txt"),"/me/drive/root:/sdfdsf",$file);
// $file = fopen("C:/Users/i/Downloads/Video/test.mp4","r");
// $onedrive->createFile(urlencode("Git提交代码简教程.txt"),"/me/drive/root:/sdfdsf",$file);
//$uploadUrl = $onedrive->apiPost("/me/drive/root:/test.m4a:/createUploadSession",[])->uploadUrl;
//echo $uploadUrl;
// $file = fopen("F:/qampp/htdocs/public/uploads/chunks/oDAjV3vT.chunk","r");
// $chunksize = filesize("F:/qampp/htdocs/public/uploads/chunks/oDAjV3vT.chunk");
// $headers[] = "Content-Length: ".$chunksize;
// $headers[] = "Content-Range: bytes 8388608-".(8388608+$chunksize-1)."/11628372";
// var_dump($headers);
// var_dump($onedrive->sendFileChunk("https://cquedu-my.sharepoint.com/personal/abslant_cquedu_onmicrosoft_com/_api/v2.0/drive/items/013RFVFIF6Y2GOVW7725BZO354PWSELRRZ/uploadSession?guid='0fa969a7-72d6-411f-9538-fc456913ff34'&path='~tmp41_test.m4a'&overwrite=True&rename=False&dc=0&tempauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAvY3F1ZWR1LW15LnNoYXJlcG9pbnQuY29tQGQwZDgxY2Q1LTgwNjUtNDYwNS1hODg2LTFjODllN2UwNzc4ZSIsImlzcyI6IjAwMDAwMDAzLTAwMDAtMGZmMS1jZTAwLTAwMDAwMDAwMDAwMCIsIm5iZiI6IjE1MzYyMjM4NDYiLCJleHAiOiIxNTM2MzEwMjQ2IiwiZW5kcG9pbnR1cmwiOiJ6Q1psKy9nVTJwdVErbFd3Q29hM0dlOEMxMzgxNjJFcVJ5ZVdkNzFKUE40PSIsImVuZHBvaW50dXJsTGVuZ3RoIjoiMjQzIiwiaXNsb29wYmFjayI6IlRydWUiLCJjaWQiOiJOalppWlRVeE9UQXRNVGM1T1MwME0yVmtMV0U0T1RJdFpqZzFZbUpoT0RSbU9HRmwiLCJ2ZXIiOiJoYXNoZWRwcm9vZnRva2VuIiwic2l0ZWlkIjoiWXpVNE1UTTNOekF0WlRZNE1pMDBNVE14TFdFME5UVXRaVE5rWldVM1ptWmxPR1JrIiwiYXBwX2Rpc3BsYXluYW1lIjoiQ2xvdWRyZXZlRGV2IiwiYXBwaWQiOiJjNWM0Zjk3ZC1mOWIwLTQzNjAtOWEzZS0xM2JiM2MyNzZkYWEiLCJ0aWQiOiJkMGQ4MWNkNS04MDY1LTQ2MDUtYTg4Ni0xYzg5ZTdlMDc3OGUiLCJ1cG4iOiJhYnNsYW50QGNxdWVkdS5vbm1pY3Jvc29mdC5jb20iLCJwdWlkIjoiMTAwMzdGRkVBRDdGRjJDMyIsInNjcCI6ImFsbGZpbGVzLndyaXRlIiwidHQiOiIyIiwidXNlUGVyc2lzdGVudENvb2tpZSI6bnVsbH0.aWh2N2szZE4rRDhXdUp3Vm9GWnlUcHczMzhaWXJrY1diVklyRWtzTlhTOD0",$headers,$file));
// fclose($file);
}

@ -22,6 +22,15 @@ class FileManage extends Model{
public $policyData;
public $deleteStatus = true;
private $adapter;
/**
* construct function
*
* @param string $path 文件路径/文件ID
* @param int $uid 用户ID
* @param boolean $byId 是否根据文件ID寻找文件
*/
public function __construct($path,$uid,$byId=false){
if($byId){
$fileRecord = Db::name('files')->where('id',$path)->find();
@ -40,8 +49,22 @@ class FileManage extends Model{
$this->userID = $uid;
$this->userData = Db::name('users')->where('id',$uid)->find();
$this->policyData = Db::name('policy')->where('id',$this->fileData["policy_id"])->find();
switch ($this->policyData["policy_type"]) {
case 'local':
$this->adapter = new \app\index\model\LocalAdapter($this->fileData,$this->policyData,$this->userData);
break;
default:
# code...
break;
}
}
/**
* 获取文件外链地址
*
* @return void
*/
public function Source(){
if(!$this->policyData["origin_link"]){
die('{"url":"此文件不支持获取源文件URL"}');
@ -50,35 +73,41 @@ class FileManage extends Model{
}
}
/**
* 获取可编辑文件内容
*
* @return void
*/
public function getContent(){
$sizeLimit=(int)Option::getValue("maxEditSize");
if($this->fileData["size"]>$sizeLimit){
die('{ "result": { "success": false, "error": "您当前用户组最大可编辑'.$sizeLimit.'字节的文件"} }');
}else{
switch ($this->policyData["policy_type"]) {
case 'local':
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
$fileContent = $this->getLocalFileContent($filePath);
break;
case 'qiniu':
$fileContent = $this->getQiniuFileContent();
break;
case 'oss':
$fileContent = $this->getOssFileContent();
break;
case 'upyun':
$fileContent = $this->getUpyunFileContent();
break;
case 's3':
$fileContent = $this->getS3FileContent();
break;
case 'remote':
$fileContent = $this->getRemoteFileContent();
break;
default:
# code...
break;
}
$fileContent = $this->adapter->getFileContent();
// switch ($this->policyData["policy_type"]) {
// case 'local':
// $filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
// $fileContent = $this->getLocalFileContent($filePath);
// break;
// case 'qiniu':
// $fileContent = $this->getQiniuFileContent();
// break;
// case 'oss':
// $fileContent = $this->getOssFileContent();
// break;
// case 'upyun':
// $fileContent = $this->getUpyunFileContent();
// break;
// case 's3':
// $fileContent = $this->getS3FileContent();
// break;
// case 'remote':
// $fileContent = $this->getRemoteFileContent();
// break;
// default:
// # code...
// break;
// }
$result["result"] = $fileContent;
if(empty(json_encode($result))){
$result["result"] = iconv('gb2312','utf-8',$fileContent);
@ -87,69 +116,102 @@ class FileManage extends Model{
}
}
public function getLocalFileContent($path){
$fileObj = fopen($path,"r");
$fileContent = fread($fileObj,filesize($path)+1);
return $fileContent;
}
/**
* 获取七牛策略文本文件内容
*
* @return string 文件内容
*/
public function getQiniuFileContent(){
return file_get_contents($this->qiniuPreview()[1]);
}
/**
* 获取OSS策略文本文件内容
*
* @return string 文件内容
*/
public function getOssFileContent(){
return file_get_contents($this->ossPreview()[1]);
}
/**
* 获取又拍云策略文本文件内容
*
* @return string 文件内容
*/
public function getUpyunFileContent(){
return file_get_contents($this->upyunPreview()[1]);
}
/**
* 获取S3策略文本文件内容
*
* @return string 文件内容
*/
public function getS3FileContent(){
return file_get_contents($this->s3Preview()[1]);
}
/**
* 获取远程策略文本文件内容
*
* @return string 文件内容
*/
public function getRemoteFileContent(){
return file_get_contents($this->remotePreview()[1]);
}
/**
* 保存可编辑文件
*
* @param string $content 要保存的文件内容
* @return void
*/
public function saveContent($content){
$contentSize = strlen($content);
$originSize = $this->fileData["size"];
if(!FileManage::sotrageCheck($this->userID,$contentSize)){
die('{ "result": { "success": false, "error": "空间容量不足" } }');
}
switch ($this->policyData["policy_type"]) {
case 'local':
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
file_put_contents($filePath, "");
file_put_contents($filePath, $content);
break;
case 'qiniu':
$this->saveQiniuContent($content);
break;
case 'oss':
$this->saveOssContent($content);
break;
case 'upyun':
$this->saveUpyunContent($content);
break;
case 's3':
$this->saveS3Content($content);
break;
case 'remote':
$this->saveRemoteContent($content);
break;
default:
# code...
break;
}
$this->adapter->saveContent($content);
// switch ($this->policyData["policy_type"]) {
// case 'local':
// $filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
// file_put_contents($filePath, "");
// file_put_contents($filePath, $content);
// break;
// case 'qiniu':
// $this->saveQiniuContent($content);
// break;
// case 'oss':
// $this->saveOssContent($content);
// break;
// case 'upyun':
// $this->saveUpyunContent($content);
// break;
// case 's3':
// $this->saveS3Content($content);
// break;
// case 'remote':
// $this->saveRemoteContent($content);
// break;
// default:
// # code...
// break;
// }
FileManage::storageGiveBack($this->userID,$originSize);
FileManage::storageCheckOut($this->userID,$contentSize);
Db::name('files')->where('id', $this->fileData["id"])->update(['size' => $contentSize]);
echo ('{ "result": { "success": true} }');
}
/**
* 保存七牛文件内容
*
* @param string $content 文件内容
* @return bool
*/
public function saveQiniuContent($content){
$auth = new Auth($this->policyData["ak"], $this->policyData["sk"]);
$expires = 3600;
@ -164,6 +226,12 @@ class FileManage extends Model{
}
}
/**
* 保存OSS文件内容
*
* @param string $content 文件内容
* @return void
*/
public function saveOssContent($content){
$accessKeyId = $this->policyData["ak"];
$accessKeySecret = $this->policyData["sk"];
@ -180,6 +248,12 @@ class FileManage extends Model{
}
}
/**
* 保存Upyun文件内容
*
* @param string $content 文件内容
* @return void
*/
public function saveUpyunContent($content){
$bucketConfig = new Config($this->policyData["bucketname"], $this->policyData["op_name"], $this->policyData["op_pwd"]);
$client = new Upyun($bucketConfig);
@ -189,17 +263,35 @@ class FileManage extends Model{
$res=$client->write($this->fileData["pre_name"],$content);
}
/**
* 保存S3文件内容
*
* @param string $content 文件内容
* @return void
*/
public function saveS3Content($content){
$s3 = new \S3\S3($this->policyData["ak"], $this->policyData["sk"],false,$this->policyData["op_pwd"]);
$s3->setSignatureVersion('v4');
$s3->putObjectString($content, $this->policyData["bucketname"], $this->fileData["pre_name"]);
}
/**
* 保存远程文件内容
*
* @param string $content 文件内容
* @return void
*/
public function saveRemoteContent($content){
$remote = new Remote($this->policyData);
$remote->updateContent($this->fileData["pre_name"],$content);
}
/**
* 文件名合法性初步检查
*
* @param string $value 文件名
* @return bool 检查结果
*/
static function fileNameValidate($value){
$validate = new Validate([
'val' => 'require|max:250',
@ -214,6 +306,15 @@ class FileManage extends Model{
return true;
}
/**
* 处理重命名
*
* @param string $fname 原文件路径
* @param string $new 新文件路径
* @param int $uid 用户ID
* @param boolean $notEcho 过程中是否不直接输出结果
* @return mixed
*/
static function RenameHandler($fname,$new,$uid,$notEcho = false){
$folderTmp = $new;
$originFolder = $fname;
@ -259,6 +360,15 @@ class FileManage extends Model{
echo ('{ "result": { "success": true} }');
}
/**
* 处理目录重命名
*
* @param string $fname 原文件路径
* @param string $new 新文件路径
* @param int $uid 用户ID
* @param boolean $notEcho 过程中是否不直接输出结果
* @return void
*/
static function folderRename($fname,$new,$uid,$notEcho = false){
$newTmp = $new;
$nerFolderTmp = explode("/",$new);
@ -320,6 +430,12 @@ class FileManage extends Model{
echo ('{ "result": { "success": true} }');
}
/**
* 根据文件路径获取文件名和父目录路径
*
* @param string 文件路径
* @return array
*/
static function getFileName($path){
$pathSplit = explode("/",$path);
$fileName = end($pathSplit);
@ -336,38 +452,50 @@ class FileManage extends Model{
return [$fileName,$path];
}
/**
* 处理文件预览
*
* @param boolean $isAdmin 是否为管理员预览
* @return array 重定向信息
*/
public function PreviewHandler($isAdmin=false){
switch ($this->policyData["policy_type"]) {
case 'qiniu':
$Redirect = $this->qiniuPreview();
return $Redirect;
break;
case 'local':
$Redirect = $this->localPreview($isAdmin);
return $Redirect;
break;
case 'oss':
$Redirect = $this->ossPreview();
return $Redirect;
break;
case 'upyun':
$Redirect = $this->upyunPreview();
return $Redirect;
break;
case 's3':
$Redirect = $this->s3Preview();
return $Redirect;
break;
case 'remote':
$Redirect = $this->remotePreview();
return $Redirect;
break;
default:
# code...
break;
}
return $this->adapter->Preview($isAdmin);
// switch ($this->policyData["policy_type"]) {
// case 'qiniu':
// $Redirect = $this->qiniuPreview();
// return $Redirect;
// break;
// case 'local':
// $Redirect = $this->localPreview($isAdmin);
// return $Redirect;
// break;
// case 'oss':
// $Redirect = $this->ossPreview();
// return $Redirect;
// break;
// case 'upyun':
// $Redirect = $this->upyunPreview();
// return $Redirect;
// break;
// case 's3':
// $Redirect = $this->s3Preview();
// return $Redirect;
// break;
// case 'remote':
// $Redirect = $this->remotePreview();
// return $Redirect;
// break;
// default:
// # code...
// break;
// }
}
/**
* 获取图像缩略图
*
* @return array 重定向信息
*/
public function getThumb(){
switch ($this->policyData["policy_type"]) {
case 'qiniu':
@ -395,6 +523,12 @@ class FileManage extends Model{
}
}
/**
* 处理文件下载
*
* @param boolean $isAdmin 是否为管理员请求
* @return array 文件下载URL
*/
public function Download($isAdmin=false){
switch ($this->policyData["policy_type"]) {
case 'qiniu':
@ -421,6 +555,13 @@ class FileManage extends Model{
}
}
/**
* 处理目录删除
*
* @param string $path 目录路径
* @param int $uid 用户ID
* @return void
*/
static function DirDeleteHandler($path,$uid){
global $toBeDeleteDir;
global $toBeDeleteFile;
@ -441,6 +582,13 @@ class FileManage extends Model{
}
}
/**
* 列出待删除文件或目录
*
* @param string $path 对象路径
* @param int $uid 用户ID
* @return void
*/
static function listToBeDelete($path,$uid){
global $toBeDeleteDir;
global $toBeDeleteFile;
@ -461,6 +609,13 @@ class FileManage extends Model{
}
}
/**
* 删除目录
*
* @param string $path 目录路径
* @param int $uid 用户ID
* @return void
*/
static function deleteDir($path,$uid){
Db::name('folders')
->where("owner",$uid)
@ -469,6 +624,13 @@ class FileManage extends Model{
])->delete();
}
/**
* 处理删除请求
*
* @param string $path 路径
* @param int $uid 用户ID
* @return array
*/
static function DeleteHandler($path,$uid){
if(empty($path)){
return ["result"=>["success"=>true,"error"=>null]];
@ -511,6 +673,15 @@ class FileManage extends Model{
return ["result"=>["success"=>true,"error"=>null]];
}
/**
* 处理移动
*
* @param array $file 文件路径列表
* @param array $dir 目录路径列表
* @param string $new 新路径
* @param int $uid 用户ID
* @return void
*/
static function MoveHandler($file,$dir,$new,$uid){
if(in_array($new,$dir)){
die('{ "result": { "success": false, "error": "不能移动目录到自身" } }');
@ -557,10 +728,24 @@ class FileManage extends Model{
echo ('{ "result": { "success": true} }');
}
/**
* ToDo 移动文件
*
* @param array $file
* @param string $path
* @return void
*/
static function moveFile($file,$path){
}
/**
* 删除某一策略下的指定本地文件
*
* @param array $fileList 待删除文件的数据库记录
* @param array $policyData 待删除文件的上传策略信息
* @return void
*/
static function localDelete($fileList,$policyData){
$fileListTemp = array_column($fileList, 'pre_name');
foreach ($fileListTemp as $key => $value) {
@ -572,6 +757,13 @@ class FileManage extends Model{
self::deleteFileRecord(array_column($fileList, 'id'),array_sum(array_column($fileList, 'size')),$fileList[0]["upload_user"]);
}
/**
* 删除某一策略下的指定七牛文件
*
* @param array $fileList 待删除文件的数据库记录
* @param array $policyData 待删除文件的上传策略信息
* @return void
*/
static function qiniuDelete($fileList,$policyData){
$auth = new Auth($policyData["ak"], $policyData["sk"]);
$config = new \Qiniu\Config();
@ -582,6 +774,13 @@ class FileManage extends Model{
self::deleteFileRecord(array_column($fileList, 'id'),array_sum(array_column($fileList, 'size')),$fileList[0]["upload_user"]);
}
/**
* 删除某一策略下的指定OSS文件
*
* @param array $fileList 待删除文件的数据库记录
* @param array $policyData 待删除文件的上传策略信息
* @return void
*/
static function ossDelete($fileList,$policyData){
$accessKeyId = $policyData["ak"];
$accessKeySecret = $policyData["sk"];
@ -599,6 +798,13 @@ class FileManage extends Model{
self::deleteFileRecord(array_column($fileList, 'id'),array_sum(array_column($fileList, 'size')),$fileList[0]["upload_user"]);
}
/**
* 删除某一策略下的指定upyun文件
*
* @param array $fileList 待删除文件的数据库记录
* @param array $policyData 待删除文件的上传策略信息
* @return void
*/
static function upyunDelete($fileList,$policyData){
foreach (array_column($fileList, 'pre_name') as $key => $value) {
self::deleteUpyunFile($value,$policyData);
@ -824,30 +1030,6 @@ class FileManage extends Model{
}
}
public function localPreview($isAdmin=false){
$speedLimit = Db::name('groups')->where('id',$this->userData["user_group"])->find();
$rangeTransfer = $speedLimit["range_transfer"];
$speedLimit = $speedLimit["speed"];
$sendFileOptions = Option::getValues(["download"]);
if($sendFileOptions["sendfile"] == "1" && !empty($sendFileOptions)){
$this->sendFile($speedLimit,$rangeTransfer,false,$sendFileOptions["header"]);
}else{
if($isAdmin){
$speedLimit="";
}
if($speedLimit == "0"){
exit();
}else if(empty($speedLimit)){
header("Cache-Control: max-age=10800");
$this->outputWithoutLimit(false,$rangeTransfer);
exit();
}else if((int)$speedLimit > 0){
header("Cache-Control: max-age=10800");
$this->outputWithLimit($speedLimit);
}
}
}
public function localDownload($isAdmin=false){
$speedLimit = Db::name('groups')->where('id',$this->userData["user_group"])->find();
$rangeTransfer = $speedLimit["range_transfer"];
@ -870,160 +1052,6 @@ class FileManage extends Model{
}
}
private function sendFile($speed,$range,$download=false,$header="X-Sendfile"){
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
$realPath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
if($header == "X-Accel-Redirect"){
$filePath = '/public/uploads/' . $this->fileData["pre_name"];
}
if($download){
$filePath = str_replace("\\","/",$filePath);
if($header == "X-Accel-Redirect"){
ob_flush();
flush();
echo "s";
}
//保证如下顺序否则最终浏览器中得到的content-type为'text/html'
//1,写入 X-Sendfile 头信息
$pathToFile = str_replace('%2F', '/', rawurlencode($filePath));
header($header.": ".$pathToFile);
//2,写入Content-Type头信息
$mime_type = self::getMimetypeOnly($realPath);
header('Content-Type: '.$mime_type);
//3,写入正确的附件文件名头信息
$orign_fname = $this->fileData["orign_name"];
$ua = $_SERVER["HTTP_USER_AGENT"]; // 处理不同浏览器的兼容性
if (preg_match("/Firefox/", $ua)) {
$encoded_filename = rawurlencode($orign_fname);
header("Content-Disposition: attachment; filename*=\"utf8''" . $encoded_filename . '"');
} else if (preg_match("/MSIE/", $ua) || preg_match("/Edge/", $ua) || preg_match("/rv:/", $ua)) {
$encoded_filename = rawurlencode($orign_fname);
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
} else {
// for Chrome,Safari etc.
header('Content-Disposition: attachment;filename="'. $orign_fname .'";filename*=utf-8'."''". $orign_fname);
}
exit;
}else{
$filePath = str_replace("\\","/",$filePath);
header('Content-Type: '.self::getMimetype($realPath));
if($header == "X-Accel-Redirect"){
ob_flush();
flush();
echo "s";
}
header($header.": ".str_replace('%2F', '/', rawurlencode($filePath)));
ob_flush();
flush();
}
}
public function outputWithoutLimit($download = false,$reload = false){
ignore_user_abort(false);
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
set_time_limit(0);
session_write_close();
$file_size = filesize($filePath);
$ranges = $this->getRange($file_size);
if($reload == 1 && $ranges!=null){
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges:bytes');
header(sprintf('content-length:%u',$ranges['end']-$ranges['start']));
header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end']-1, $file_size));
}
if($download){
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($filePath));
$encoded_fname = rawurlencode($this->fileData["orign_name"]);
header('Content-Disposition: attachment;filename="'.$encoded_fname.'";filename*=utf-8'."''".$encoded_fname);
ob_flush();
flush();
}
if(file_exists($filePath)){
if(!$download){
header('Content-Type: '.self::getMimetype($filePath));
ob_flush();
flush();
}
$fileObj = fopen($filePath,"rb");
if($reload == 1){
fseek($fileObj, sprintf('%u', $ranges['start']));
}
while(!feof($fileObj)){
echo fread($fileObj,10240);
ob_flush();
flush();
}
fclose($fileObj);
}
}
public function outputWithLimit($speed,$download = false){
ignore_user_abort(false);
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileData["pre_name"];
set_time_limit(0);
session_write_close();
if($download){
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($filePath));
$encoded_fname = rawurlencode($this->fileData["orign_name"]);
header('Content-Disposition: attachment;filename="'.$encoded_fname.'";filename*=utf-8'."''".$encoded_fname);
ob_flush();
flush();
}else{
header('Content-Type: '.self::getMimetype($filePath));
ob_flush();
flush();
}
if(file_exists($filePath)){
$fileObj = fopen($filePath,"r");
while (!feof($fileObj)){
echo fread($fileObj,round($speed*1024));
ob_flush();
flush();
sleep(1);
}
fclose($fileObj);
}
}
static function getMimetype($path){
//FILEINFO_MIME will output something like "image/jpeg; charset=binary"
$finfoObj = finfo_open(FILEINFO_MIME);
$mimetype = finfo_file($finfoObj, $path);
finfo_close($finfoObj);
return $mimetype;
}
static function getMimetypeOnly($path){
//FILEINFO_MIME_TYPE will output something like "image/jpeg"
$finfoObj = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfoObj, $path);
finfo_close($finfoObj);
return $mimetype;
}
private function getRange($file_size){
if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])){
$range = $_SERVER['HTTP_RANGE'];
$range = preg_replace('/[\s|,].*/', '', $range);
$range = explode('-', substr($range, 6));
if(count($range)<2){
$range[1] = $file_size;
}
$range = array_combine(array('start','end'), $range);
if(empty($range['start'])){
$range['start'] = 0;
}
if(empty($range['end'])){
$range['end'] = $file_size;
}
return $range;
}
return null;
}
/**
* [List description]
* @param [type] $path [description]

@ -0,0 +1,272 @@
<?php
namespace app\index\model;
use think\Model;
use \app\index\model\Option;
class LocalAdapter extends Model{
private $fileModel;
private $policyModel;
private $userModel;
public function __construct($file,$policy,$user){
$this->fileModel = $file;
$this->policyModel = $policy;
$this->userModel = $user;
}
/**
* 获取文本文件内容
*
* @return string 内容
*/
public function getFileContent(){
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileModel["pre_name"];
$fileObj = fopen($filePath,"r");
$fileContent = fread($fileObj,filesize($filePath)+1);
return $fileContent;
}
/**
* 保存可编辑文件
*
* @param string $content 要保存的文件内容
* @return void
*/
public function saveContent($content){
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileModel["pre_name"];
file_put_contents($filePath, "");
file_put_contents($filePath, $content);
}
/**
* 输出预览文件
*
* @param boolean $isAdmin 是否为管理员请求
* @return mixed 文件数据
*/
public function Preview($isAdmin = false){
$speedLimit = Db::name('groups')->where('id',$this->userModel["user_group"])->find();
$rangeTransfer = $speedLimit["range_transfer"];
$speedLimit = $speedLimit["speed"];
$sendFileOptions = Option::getValues(["download"]);
if($sendFileOptions["sendfile"] == "1" && !empty($sendFileOptions)){
$this->sendFile($speedLimit,$rangeTransfer,false,$sendFileOptions["header"]);
}else{
if($isAdmin){
$speedLimit="";
}
if($speedLimit == "0"){
exit();
}else if(empty($speedLimit)){
header("Cache-Control: max-age=10800");
$this->outputWithoutLimit(false,$rangeTransfer);
exit();
}else if((int)$speedLimit > 0){
header("Cache-Control: max-age=10800");
$this->outputWithLimit($speedLimit);
}
}
}
/**
* 使用Sendfile模式发送文件数据
*
* @param int $speed 下载限速
* @param boolean $range 是否支持断点续传
* @param boolean $download 是否为下载请求
* @param string $header Sendfile Header
* @return void
*/
private function sendFile($speed,$range,$download=false,$header="X-Sendfile"){
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileModel["pre_name"];
$realPath = ROOT_PATH . 'public/uploads/' . $this->fileModel["pre_name"];
if($header == "X-Accel-Redirect"){
$filePath = '/public/uploads/' . $this->fileModel["pre_name"];
}
if($download){
$filePath = str_replace("\\","/",$filePath);
if($header == "X-Accel-Redirect"){
ob_flush();
flush();
echo "s";
}
//保证如下顺序否则最终浏览器中得到的content-type为'text/html'
//1,写入 X-Sendfile 头信息
$pathToFile = str_replace('%2F', '/', rawurlencode($filePath));
header($header.": ".$pathToFile);
//2,写入Content-Type头信息
$mime_type = self::getMimetypeOnly($realPath);
header('Content-Type: '.$mime_type);
//3,写入正确的附件文件名头信息
$orign_fname = $this->fileModel["orign_name"];
$ua = $_SERVER["HTTP_USER_AGENT"]; // 处理不同浏览器的兼容性
if (preg_match("/Firefox/", $ua)) {
$encoded_filename = rawurlencode($orign_fname);
header("Content-Disposition: attachment; filename*=\"utf8''" . $encoded_filename . '"');
} else if (preg_match("/MSIE/", $ua) || preg_match("/Edge/", $ua) || preg_match("/rv:/", $ua)) {
$encoded_filename = rawurlencode($orign_fname);
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
} else {
// for Chrome,Safari etc.
header('Content-Disposition: attachment;filename="'. $orign_fname .'";filename*=utf-8'."''". $orign_fname);
}
exit;
}else{
$filePath = str_replace("\\","/",$filePath);
header('Content-Type: '.self::getMimetype($realPath));
if($header == "X-Accel-Redirect"){
ob_flush();
flush();
echo "s";
}
header($header.": ".str_replace('%2F', '/', rawurlencode($filePath)));
ob_flush();
flush();
}
}
/**
* 无限速发送文件数据
*
* @param boolean $download 是否为下载
* @param boolean $reload 是否支持断点续传
* @return void
*/
public function outputWithoutLimit($download = false,$reload = false){
ignore_user_abort(false);
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileModel["pre_name"];
set_time_limit(0);
session_write_close();
$file_size = filesize($filePath);
$ranges = $this->getRange($file_size);
if($reload == 1 && $ranges!=null){
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges:bytes');
header(sprintf('content-length:%u',$ranges['end']-$ranges['start']));
header(sprintf('content-range:bytes %s-%s/%s', $ranges['start'], $ranges['end']-1, $file_size));
}
if($download){
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($filePath));
$encoded_fname = rawurlencode($this->fileModel["orign_name"]);
header('Content-Disposition: attachment;filename="'.$encoded_fname.'";filename*=utf-8'."''".$encoded_fname);
ob_flush();
flush();
}
if(file_exists($filePath)){
if(!$download){
header('Content-Type: '.self::getMimetype($filePath));
ob_flush();
flush();
}
$fileObj = fopen($filePath,"rb");
if($reload == 1){
fseek($fileObj, sprintf('%u', $ranges['start']));
}
while(!feof($fileObj)){
echo fread($fileObj,10240);
ob_flush();
flush();
}
fclose($fileObj);
}
}
/**
* 有限速发送文件数据
*
* @param int $speed 最大速度
* @param boolean $download 是否为下载请求
* @return void
*/
public function outputWithLimit($speed,$download = false){
ignore_user_abort(false);
$filePath = ROOT_PATH . 'public/uploads/' . $this->fileModel["pre_name"];
set_time_limit(0);
session_write_close();
if($download){
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($filePath));
$encoded_fname = rawurlencode($this->fileModel["orign_name"]);
header('Content-Disposition: attachment;filename="'.$encoded_fname.'";filename*=utf-8'."''".$encoded_fname);
ob_flush();
flush();
}else{
header('Content-Type: '.self::getMimetype($filePath));
ob_flush();
flush();
}
if(file_exists($filePath)){
$fileObj = fopen($filePath,"r");
while (!feof($fileObj)){
echo fread($fileObj,round($speed*1024));
ob_flush();
flush();
sleep(1);
}
fclose($fileObj);
}
}
/**
* 获取文件MIME Type
*
* @param string $path 文件路径
* @return void
*/
static function getMimetype($path){
//FILEINFO_MIME will output something like "image/jpeg; charset=binary"
$finfoObj = finfo_open(FILEINFO_MIME);
$mimetype = finfo_file($finfoObj, $path);
finfo_close($finfoObj);
return $mimetype;
}
/**
* 获取文件MIME Type
*
* @param string $path 文件路径
* @return void
*/
static function getMimetypeOnly($path){
//FILEINFO_MIME_TYPE will output something like "image/jpeg"
$finfoObj = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfoObj, $path);
finfo_close($finfoObj);
return $mimetype;
}
/**
* 获取断点续传时HTTP_RANGE头
*
* @param int $file_size 文件大小
* @return void
*/
private function getRange($file_size){
if(isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])){
$range = $_SERVER['HTTP_RANGE'];
$range = preg_replace('/[\s|,].*/', '', $range);
$range = explode('-', substr($range, 6));
if(count($range)<2){
$range[1] = $file_size;
}
$range = array_combine(array('start','end'), $range);
if(empty($range['start'])){
$range['start'] = 0;
}
if(empty($range['end'])){
$range['end'] = $file_size;
}
return $range;
}
return null;
}
}
?>

@ -10,10 +10,11 @@ use think\console\Input;
use think\console\Output;
use \Krizalys\Onedrive\Client;
use Sabre\DAV\Mock\File;
class Task extends Model{
public $taskModel;
public $taskModel;
public $taskName;
public $taskType;
public $taskContent;
@ -23,6 +24,7 @@ class Task extends Model{
public $status = "success";
public $errorMsg;
public $policyModel;
public function __construct($id=null){
@ -31,6 +33,11 @@ class Task extends Model{
}
}
/**
* 保存任务至数据库
*
* @return void
*/
public function saveTask(){
Db::name("task")->insert([
"task_name" => $this->taskName,
@ -41,20 +48,111 @@ class Task extends Model{
]);
}
/**
* 开始执行任务
*
* @return void
*/
public function Do(){
switch ($this->taskModel["type"]){
case "uploadSingleToOnedrive":
$this->uploadSingleToOnedrive();
break;
case "uploadChunksToOnedrive":
$this->uploadChunksToOnedrive();
break;
default:
$this->output->writeln("Unknown task type");
break;
}
}
/**
* 上传 分片的大文件至Onedrive
*
* @return void
*/
private function uploadChunksToOnedrive(){
$this->taskContent = json_decode($this->taskModel["attr"],true);
$policyData = Db::name("policy")->where("id",$this->taskContent["policyId"])->find();
$this->policyModel = $policyData;
$onedrive = new Client([
'stream_back_end' => \Krizalys\Onedrive\StreamBackEnd::TEMP,
'client_id' => $policyData["bucketname"],
// Restore the previous state while instantiating this client to proceed in
// obtaining an access token.
'state' => json_decode($policyData["sk"]),
]);
//创建分片上传Session,获取上传URL
try{
$uploadUrl = $onedrive->apiPost("/me/drive/root:/".rawurlencode($this->taskContent["savePath"] . "/" . $this->taskContent["objname"]).":/createUploadSession",[])->uploadUrl;
}catch(\Exception $e){
$this->status="error";
$this->errorMsg = $e->getMessage();
$this->cleanTmpChunk();
return;
}
//逐个上传文件分片
$offset = 0;
foreach ($this->taskContent["chunks"] as $key => $value) {
$chunkPath = ROOT_PATH . 'public/uploads/chunks/'.$value["obj_name"].".chunk";
if(!$file = @fopen($chunkPath,"r")){
$this->status="error";
$this->errorMsg = "File chunk not exist.";
$this->cleanTmpChunk();
return;
}
$headers = [];
$chunksize = filesize($chunkPath);
$headers[] = "Content-Length: ".$chunksize;
$headers[] = "Content-Range: bytes ".$offset."-".($offset+$chunksize-1)."/".$this->taskContent["fsize"];
//发送单个分片数据
try{
$onedrive->sendFileChunk($uploadUrl,$headers,$file);
}catch(\Exception $e){
$this->status="error";
$this->errorMsg = $e->getMessage();
$this->cleanTmpChunk();
return;
}
$this->output->writeln("[Info] Chunk Uploaded. Offset:".$offset);
$offset += $chunksize;
fclose($file);
}
$jsonData = array(
"path" => $this->taskContent["path"],
"fname" => $this->taskContent["fname"],
"objname" => $this->taskContent["savePath"]."/".$this->taskContent["objname"],
"fsize" => $this->taskContent["fsize"],
);
$addAction = FileManage::addFile($jsonData,$policyData,$this->taskModel["uid"],$this->taskContent["picInfo"]);
if(!$addAction[0]){
$this->setError($addAction[1],true,"/me/drive/root:/".rawurlencode($this->taskContent["savePath"] . "/" . $this->taskContent["objname"]),$onedrive);
$this->cleanTmpChunk();
return;
}
$this->cleanTmpChunk();
}
/**
* 上传单文件(<=4mb)至Onedrive
*
* @return void
*/
private function uploadSingleToOnedrive(){
$this->taskContent = json_decode($this->taskModel["attr"],true);
$policyData = Db::name("policy")->where("id",$this->taskContent["policyId"])->find();
$this->policyModel = $policyData;
$onedrive = new Client([
'stream_back_end' => \Krizalys\Onedrive\StreamBackEnd::TEMP,
'client_id' => $policyData["bucketname"],
@ -67,10 +165,11 @@ class Task extends Model{
$filePath = ROOT_PATH . 'public/uploads/'.$this->taskContent["savePath"] . "/" . $this->taskContent["objname"];
if($file = @fopen($filePath,"r")){
try{
$onedrive->createFile(urlencode($this->taskContent["objname"]),"/me/drive/root:/".$this->taskContent["savePath"],$file);
$onedrive->createFile(rawurlencode($this->taskContent["objname"]),"/me/drive/root:/".$this->taskContent["savePath"],$file);
}catch(\Exception $e){
$this->status="error";
$this->errorMsg = $e->getMessage();
$this->cleanTmpFile();
return;
}
@ -83,20 +182,67 @@ class Task extends Model{
$addAction = FileManage::addFile($jsonData,$policyData,$this->taskModel["uid"],$this->taskContent["picInfo"]);
if(!$addAction[0]){
// $tmpFileName = $Uploadinfo->getSaveName();
// unset($Uploadinfo);
// $this->setError($addAction[1],true,$tmpFileName,$savePath);
$this->setError($addAction[1],true,"/me/drive/root:/".$this->taskContent["savePath"]."/".rawurlencode($this->taskContent["objname"]),$onedrive);
$this->cleanTmpFile();
return;
}
//TO-DO删除本地文件
fclose($file);
$this->cleanTmpFile();
}else{
$this->status = "error";
$this->errorMsg = "Failed to open file [".$filePath."]";
}
}
/**
* 删除本地临时文件
*
* @return bool 是否成功
*/
private function cleanTmpFile(){
return @unlink(ROOT_PATH . 'public/uploads/'.$this->taskContent["savePath"] . "/" . $this->taskContent["objname"]);
}
/**
* 删除本地临时分片
*
* @return void
*/
private function cleanTmpChunk(){
foreach ($this->taskContent["chunks"] as $key => $value) {
@unlink( ROOT_PATH . 'public/uploads/chunks/'.$value["obj_name"].".chunk");
}
}
/**
* 设置为出错状态并清理远程文件
*
* @param string $msg 错误消息
* @param bool $delete 是否删除文件
* @param string $path 文件路径
* @param mixed $adapter 远程操作适配器
* @return void
*/
private function setError($msg,$delete,$path,$adapter){
$this->status="error";
$this->errorMsg = $msg;
if($delete){
switch($this->taskModel["type"]){
case "uploadSingleToOnedrive":
$adapter->deleteObject($path);
break;
case "uploadChunksToOnedrive":
$adapter->deleteObject($path);
break;
default:
break;
}
}
FileManage::storageGiveBack($this->taskModel["uid"],$this->taskContent["fsize"]);
}
}

@ -76,13 +76,25 @@ class UploadHandler extends Model{
return 0;
}
/**
* 组合分片并生成最终文件
*
* @param array $ctx 文件片校验码
* @param string $fname 最终文件名
* @param string $path 储存目录
* @return void
*/
public function generateFile($ctx,$fname,$path){
$ctxTmp = explode(",",$ctx);
$chunks = Db::name('chunks')->where([
'ctx' => ["in",$ctxTmp],
])->order('id asc')->select();
$file = $this->combineChunks($chunks);
$this->filterCheck($file,$fname);
$file = null;
if($this->policyContent["policy_type"] != "onedrive"){
$file = $this->combineChunks($chunks);
}
$this->filterCheck($file,$fname,$chunks);
$suffixTmp = explode('.', $fname);
$fileSuffix = array_pop($suffixTmp);
if($this->policyContent['autoname']){
@ -96,30 +108,67 @@ class UploadHandler extends Model{
if(file_exists($savePath.DS.$fileName)){
$this->setError("文件重名",true,$file,ROOT_PATH . 'public/uploads/chunks/');
}
if(!@rename(ROOT_PATH . 'public/uploads/chunks/'.$file,$savePath.DS.$fileName)){
$this->setError("文件创建失败",true,$file,ROOT_PATH . 'public/uploads/chunks/');
}else{
if($this->policyContent["policy_type"] == "onedrive"){
if($path == "ROOTDIR"){
$path = "";
}
$jsonData = array(
$task = new Task();
$task->taskName = "Upload Big File " . $fname . "to Onedrive";
$task->taskType = "uploadChunksToOnedrive";
$task->taskContent = json_encode([
"path" => $path,
"fname" => $fname,
"objname" => $generatePath."/".$fileName,
"objname" => $fileName,
"savePath" => $generatePath,
"fsize" => $this->fileSizeTmp,
);
@list($width, $height, $type, $attr) = getimagesize($savePath.DS.$fileName);
$picInfo = empty($width)?" ":$width.",".$height;
$addAction = FileManage::addFile($jsonData,$this->policyContent,$this->userId,$picInfo);
"picInfo" => "",
"chunks" => $chunks,
"policyId" => $this->policyContent['id']
]);
$task->userId = $this->userId;
$task->saveTask();
echo json_encode(array("key" => $fname));
}else{
if(!@rename(ROOT_PATH . 'public/uploads/chunks/'.$file,$savePath.DS.$fileName)){
$this->setError("文件创建失败",true,$file,ROOT_PATH . 'public/uploads/chunks/');
}else{
if($path == "ROOTDIR"){
$path = "";
}
$jsonData = array(
"path" => $path,
"fname" => $fname,
"objname" => $generatePath."/".$fileName,
"fsize" => $this->fileSizeTmp,
);
@list($width, $height, $type, $attr) = getimagesize($savePath.DS.$fileName);
$picInfo = empty($width)?" ":$width.",".$height;
$addAction = FileManage::addFile($jsonData,$this->policyContent,$this->userId,$picInfo);
if(!$addAction[0]){
$this->setError($addAction[1],true,$fileName,$savePath);
}
echo json_encode(array("key" => $fname));
echo json_encode(array("key" => $fname));
}
}
}
protected function countTotalChunkSize($chunks){
$size = 0;
foreach ($chunks as $key => $value) {
$size += @filesize(ROOT_PATH . 'public/uploads/chunks/'.$value["obj_name"].".chunk");
}
return $size;
}
public function filterCheck($file,$fname){
$fileSize = filesize(ROOT_PATH . 'public/uploads/chunks/'.$file);
public function filterCheck($file,$fname,$chunks){
if($file !=null){
$fileSize = filesize(ROOT_PATH . 'public/uploads/chunks/'.$file);
}else{
$fileSize = $this->countTotalChunkSize($chunks);
}
$suffixTmp = explode('.', $fname);
$fileSuffix = array_pop($suffixTmp);
$allowedSuffix = explode(',', self::getAllowedExt(json_decode($this->policyContent["filetype"],true)));
@ -134,6 +183,12 @@ class UploadHandler extends Model{
$this->fileSizeTmp = $fileSize;
}
/**
* 组合文件分片
*
* @param array $fname 文件分片数据库记录
* @return void
*/
public function combineChunks($fname){
$fileName = "file_".self::getRandomKey(8);
$fileObj=fopen (ROOT_PATH . 'public/uploads/chunks/'.$fileName,"a+");

@ -1,2 +0,0 @@
*
!.gitignore
Loading…
Cancel
Save