文件上传类
<?php
/**
* 文件上传
*
* 前台使用
* <link rel='stylesheet' type='text/css' href='/Public/static/kcs/kcs.css'>
* <script src='/Public/static/kcs/kcs.src.js'></script>
* <script src='/Public/static/kcs/kcs.js'></script>
* <script> KCS.uploader(el,{ width:'120', height:'120', url:'{:U('upload')}'}); </script>
*/
class Upload
{
const CHUNK_UPLOADED = 1;
const FILE_UPLOADED = 2;
// 配置项 : 允许上传的文件类型,不区分大小写
protected $allowedType = array(
'jpg',
'jpeg',
'png',
'bmp',
'gif',
'txt',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'pdf',
'zip',
'rar',
'mp3',
'wav',
'csv'
);
// 配置项 : 临时文件过期时间, 单位秒(s)
protected $tempFileExpire = 3600;
// 配置项 : 文件上传完毕后移动到此目录, 默认不移动
protected $uploadDir = null;
// 配置项 : 文件自动保存目录的 web 路径, 未配置时使用 /+$uploadDir 作为默认值
protected $uploadPath = null;
// 配置项 : 文件上传临时目录, 必填
protected $tempDir = null;
// 配置项 : 文件临时目录的 web 路径, 未配置时使用 /+$tempDir 作为默认值
protected $tempPath = null;
// 配置项 : 上传成功回调
protected $success = null;
// 配置项 : 上传错误回调
protected $error = null;
// 当前上传文件的[临时]文件名
protected $filename = null;
// 当前上传文件的[原始]文件名
protected $filenameOri = null;
// 当前上传文件的[存储]文件名
protected $filenameSave = null;
// 当前文件类型
protected $fileType = null;
// 错误通知类型
protected static $notifyInfo = array(
1000 => '无法获取文件名',
1001 => '文件名不合法',
1002 => '创建临时文件失败',
1003 => '文件上传错误',
1004 => '文件未上传',
1005 => '无法读取上传临时文件',
1006 => '无法读取输入流',
1007 => '无法移动文件到上传目录',
1008 => '无法移动文件到临时目录',
1009 => '创建上传目录失败',
1010 => '未设置临时目录',
1011 => '创建临时目录失败',
1012 => '文件类型错误'
);
protected $application;
protected $webPath;
static $instace;
public static function getInstance ()
{
return self::$instace ? : self::$instace = new self();
}
/**
* 文件上传处理流程
*/
public function handle ($config = array(), $return = true)
{
try
{
$this->_loadConfig($config);
// 获取文件信息
$this->_loadFileinfo();
// 文件保存路径
$filePath = $this->tempDir . $this->filename . '.part';
// 清理过期文件
// $this->_cleanUp();
// 分段上传 : 当前分段序号
$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
// 分段上传 : 分段总数
$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
// 打开或创建临时文件
$out = fopen("{$filePath}", $chunks ? "ab" : "wb") or $this->_notify(1002);
// 从上传文件或输入中读取数据
if (! empty($_FILES) && isset($_FILES['kcs_uploader_file']))
{
$_FILES["kcs_uploader_file"]["error"] and $this->_notify(1003, $_FILES["file"]["error"]);
is_uploaded_file($_FILES["kcs_uploader_file"]["tmp_name"]) or $this->_notify(1004);
$in = fopen($_FILES["kcs_uploader_file"]["tmp_name"], "rb") or $this->_notify(1005);
}
else
{
$in = fopen("php://input", "rb") or $this->_notify(1006);
}
// 追加到临时文件
while ($buff = fread($in, 4096))
{
fwrite($out, $buff);
}
fclose($out);
fclose($in);
// 分段上传
if ($chunks && $chunk < $chunks - 1)
{
return $this->_notify(self::CHUNK_UPLOADED);
}
// 上传完毕
if (! $chunks || $chunk == $chunks - 1)
{
// 重命名上传文件
$newFilename = (is_null($this->filenameSave) ? uniqid(rand(100, 999)) : $this->filenameSave)
. '.' . $this->fileType;
// 上传图片临时路径
$newFile = $this->tempDir . $newFilename;
// 上传图片的 web 路径
$webPath = $this->tempPath . $newFilename;
// 重命名临时文件
copy($filePath, $newFile) or $this->_notify(1008, $filePath . '=>' . $newFile);
unlink($filePath);
// web 路径修正
$webPath = str_replace('\\', '/', $webPath);
// 发送成功通知
$response = array(
'url' => $webPath,
'file' => $newFile,
'title' => $this->filenameOri
);
$this->_notify(self::FILE_UPLOADED, $response);
}
}
catch (\Exception $e)
{
$this->_notify(- 1, $e->getMessage());
}
}
/**
* 移动上传文件
*/
public function move ($webPath, $config = array())
{
$this->_loadConfig($config);
$path = $this->tempDir ? : $this->uploadDir;
$oldFile = $path . substr($webPath, strlen($path) - 1);
$newFile = $this->uploadDir . basename($oldFile);
if (! file_exists($oldFile))
{
throw new \Exception("文件不存在 : $oldFile");
}
// 未配置临时目录时, 不需要移动上传文件
$status = $oldFile == $newFile;
$status or $status = (copy($oldFile, $newFile) and unlink($oldFile));
if (! $status)
{
throw new \Exception("移动文件失败 : $oldFile -> $newFile");
}
$fileInfo = array(
'path' => $newFile,
'url' => $this->uploadPath . basename($newFile),
'md5' => md5_file($newFile),
'sha1' => sha1_file($newFile)
);
return $fileInfo;
}
/**
* 加载配置项, 并校验
*/
protected function _loadConfig ($config)
{
is_array($config) or $config = array();
foreach ($config as $_key => $_value)
{
if (property_exists($this, $_key))
{
$this->$_key = $_value;
}
}
// 检查临时目录
($this->tempDir = $this->tempDir ? : $this->uploadDir) or $this->_notify(1010);
if (! file_exists($this->tempDir))
{
mkdir($this->tempDir, 0777, true) or $this->_notify(1011, $this->$this->tempDir);
}
$this->tempDir = rtrim($this->tempDir, '/') . '/';
// 修正临时目录 web 路径
$this->tempPath or $this->tempPath = '/' . trim($this->tempDir, './');
$this->tempPath = rtrim($this->tempPath, '/') . '/';
// 检查上传目录
if (! $this->uploadDir)
{
return;
}
// 创建上传目录
if (! file_exists($this->uploadDir))
{
mkdir($this->uploadDir, 0777, true) or $this->_notify(1009, $this->uploadDir);
}
$this->uploadDir = rtrim($this->uploadDir, '/') . '/';
// 修正上传目录 web 路径
$this->uploadPath or $this->uploadPath = '/' . trim($this->uploadDir, './');
$this->uploadPath = rtrim($this->uploadPath, '/') . '/';
}
/**
* 获得上传文件的文件名和类型并校验
*/
protected function _loadFileinfo ()
{
if (isset($_REQUEST["name"]))
{
$filename = $_REQUEST["name"];
}
elseif (! empty($_FILES))
{
$filename = @$_FILES["kcs_uploader_file"]["name"];
}
else
{
// 获取文件名失败
$this->_notify(1000);
}
// 文件名只允许包含 数字,字母,下划线和点号
// preg_match('/^[a-zA-Z0-9_\.]+$/', $filename) or $this->_notify(1001, '文件名无效 : ' . $filename);
$this->filenameOri = $filename;
$filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$this->filename = md5($_REQUEST['kcs_uploader_id'] . $filename) . '.' . $filetype;
// 文件类型检测
in_array($filetype, $this->allowedType) or $this->_notify(1012, '文件类型无效 : ' . $filetype);
$this->fileType = $filetype;
}
/**
* 清除过期的临时文件, 删除的临时文件写入清理日志
*/
protected function _cleanUp ($targetDir)
{
while (($file = readdir($this->tempDir)) !== false)
{
$tmpfilePath = $this->tempDir . $file;
// Remove temp file if it is older than the max age and is not the current file
if (filemtime($tmpfilePath) < time() - $this->tempFileExpire)
{
$filesize = number_format(filesize($tmpfilePath) / (1024 * 1024), 2) . 'KB';
@$status = unlink($tmpfilePath);
self::_log("清理过期临时文件 [" . ($status ? '1' : '0') . "] : $file (" . $filesize . ")");
}
}
closedir($dir);
}
/**
* 发送通知
*/
protected function _notify ($status = '', $info = null)
{
$json = array(
'jsonrpc' => '2.0',
'id' => 'id'
);
// 分段上传完毕
if ($status == self::CHUNK_UPLOADED)
{
exit(json_encode($json));
}
// 文件上传完毕
if ($status == self::FILE_UPLOADED)
{
$json['result'] = $info;
if (is_callable($this->success))
{
return call_user_func($this->success, $json);
}
exit(json_encode($json));
}
// 返回错误信息
$msg = self::$notifyInfo[$status] ? : $info;
$json['error'] = array(
'code' => $status,
'message' => $msg
);
self::_log($msg . ' | ' . $info);
if (is_callable($this->error))
{
return call_user_func($this->error, $msg);
}
exit(json_encode($json));
}
protected function _log ($msg)
{
function_exists('_log') && _log($msg);
}
}
调用文件上传
1 <?php 2 $operate=$_REQUEST["operate"]; 3 $operate=empty($operate)?"upload":$operate; 4 5 switch ($operate){ 6 case 'upload'; 7 file_upload(); 8 break; 9 case 'download'; 10 file_download(); 11 break; 12 case 'del'; 13 file_del(); 14 break; 15 } 16 17 function file_download(){ 18 $file_code=intval($_REQUEST["file_code"]); 19 $sql="select FILE_PATH,FILE_NAME from app_file where DOWN_CODE='{$file_code}' and STATUS='1'"; 20 $file_info=N_query($sql); 21 if(!empty($file_info)){ 22 $file_info=$file_info[1]; 23 //以只读和二进制模式打开文件 24 $file = fopen ( $file_info["FILE_PATH"], "rb" ); 25 26 //告诉浏览器这是一个文件流格式的文件 27 Header ( "Content-type: application/octet-stream" ); 28 //请求范围的度量单位 29 Header ( "Accept-Ranges: bytes" ); 30 //Content-Length是指定包含于请求或响应中数据的字节长度 31 Header ( "Accept-Length: " . filesize ( $file_info["FILE_PATH"] ) ); 32 //用来告诉浏览器,文件是可以当做附件被下载,下载后的文件名称为$file_name该变量的值。 33 Header ( "Content-Disposition: attachment; filename=" . $file_info["FILE_NAME"] ); 34 35 //读取文件内容并直接输出到浏览器 36 echo fread ( $file, filesize ( $file_info["FILE_PATH"] ) ); 37 fclose ( $file ); 38 } 39 } 40 41 function file_del(){ 42 $file_code=intval($_REQUEST["file_code"]); 43 $sql="select FILE_PATH,FILE_NAME from app_file where DOWN_CODE='{$file_code}' and STATUS='1'"; 44 $file_info=N_query($sql); 45 if(!empty($file_info)){ 46 $file_info=$file_info[1]; 47 $current_time=time(); 48 $sql="update app_file set STATUS='0',DEL_TIME={$current_time} where DOWN_CODE='{$file_code}' and STATUS='1'"; 49 N_query($sql); 50 @unlink( $file_info["FILE_PATH"]); 51 } 52 $return_result["status"]=1; 53 $return_result["msg"]="删除成功"; 54 echo json_encode($return_result,256); 55 exit; 56 57 } 58 59 function file_upload(){ 60 $type_arr=["projectLending"]; 61 $type=trim($_REQUEST["type"]); 62 63 $type=empty($type)?"projectLending":$type; 64 if(!in_array($type,$type_arr)){ 65 throw new \Exception("参数错误"); 66 } 67 68 if($type=="projectLending"){ 69 //项目放款流程 70 uploadProjectLending(); 71 } 72 } 73 74 function uploadProjectLending(){ 75 $config = array( 76 // 文件上传目录, 默认不移动 77 'uploadDir' => './uploads/projectLending/' . date('Y/m/d'), 78 'success' => 'uploadProjectLendingSuccessReturn', 79 'error' => 'uploadProjectLendingErrorReturn', 80 //"allowedType"=>['xls','xlsx','csv'], 81 ); 82 \N\Upload::getInstance()->handle($config); 83 } 84 function uploadProjectLendingErrorReturn($result){ 85 $return_result["status"]=1; 86 $return_result["msg"]=$result; 87 echo json_encode($return_result,256); 88 exit; 89 } 90 function uploadProjectLendingSuccessReturn($result){ 91 $now_time=time(); 92 $info = $result['result']; 93 $USR_UID=$_SESSION['USER_LOGGED']; 94 $APP_UID=trim($_REQUEST["app_uid"]); 95 $TAS_UID=trim($_REQUEST["tas_uid"]); 96 $UPLOAD_TYPE='projectLending'; 97 $FILE_NAME=$info['title']; 98 $FILE_PATH=$info['file']; 99 $FILE_SIZE=$info["file_size"]; 100 $FILE_EXT=$info["file_ext"]; 101 $ISIMAGEL=0; 102 $STATUS=1; 103 $UPLOAD_TIME=$now_time; 104 $DEL_TIME=0; 105 $DOWN_CODE=md5($USR_UID."|".$now_time); 106 107 $sql="insert into app_file (`USR_UID`,`APP_UID`,`TAS_UID`,`UPLOAD_TYPE`,`FILE_NAME`,`FILE_PATH`,`FILE_SIZE`,`FILE_EXT`,`ISIMAGEL`,`STATUS`,`UPLOAD_TIME`,`DEL_TIME`,`DOWN_CODE`) VALUES ( 108 '{$USR_UID}','{$APP_UID}','{$TAS_UID}','{$UPLOAD_TYPE}','{$FILE_NAME}','{$FILE_PATH}','{$FILE_SIZE}','{$FILE_EXT}','{$ISIMAGEL}','{$STATUS}','{$UPLOAD_TIME}','{$DEL_TIME}','{$DOWN_CODE}')"; 109 N_query($sql); 110 111 112 $return_result["status"]=0; 113 $return_result["msg"]="上传成功"; 114 $return_result["url"]=$info['url']; 115 $return_result["file_ext"]=$info['file_ext']; 116 $return_result["file_size"]=$info['file_size']; 117 $return_result["title"]=$info['title']; 118 $return_result["file_id"]=$DOWN_CODE; 119 $return_result["time"]=date("Y-m-d H:i:s",$now_time); 120 $return_result["download_url"]="/sysworkflow/zh/neoclassic/ncfpms_ajax/uploadFile?operate=download&file_code=".$DOWN_CODE; 121 $return_result["del_url"]="/sysworkflow/zh/neoclassic/ncfpms_ajax/uploadFile?operate=del&file_code=".$DOWN_CODE; 122 echo json_encode($return_result); 123 exit; 124 }