HTML5+PHP 实现 保存文件夹相对路径 递归上传 在线浏览

这是最近花了一周多手工马出来的,前段用了MetroUI,后台是ThinkPHP,数据库MySQL,先看看效果吧。由于项目涉及敏感词汇我就码了一下。

1.选择要上传的文件夹,上传以后默认都在根目录下。

2.看看后台管理界面的效果,实现多级目录,可以显示图片内容,返回上一级

正文:

谈到文件夹上传,应该都不觉得难,一个input框加上一个php后台就够了。但是这次的需求说起来容易,但是其实还挺难的。要把一个文件夹的文件递归上传,保存目录结构,能够在浏览器里展示出来,其实是三个过程。

【1】上传时要保存文件的相对路径。这里主要有两个问题。第一是怎样获取到路径,第二是用什么方式传到服务器。

【2】后台接受并且构成出目录结构。这里也是两个问题。第一,目录结构要怎么从前台post来的数据中分离出来。第二,用什么样的结构去保存,用什么样的逻辑去存储。其实这就是后台的“算法”了。

【3】让前台显示文件目录。首先考虑到如果文件很多,你不能一次性从数据库中全都select出来。然后我希望代码尽量优雅,最好和后台交互一个函数就搞定。

解决方案:

最最重要,先确定开发的浏览器环境,这里选择Chrome,2016年6月以后的版本都算比较新,没有一一去试,chrome保证功能无误,且可以移动端联调。HTML5 chrome移动设备和电脑端联调

【1】

首先定义一个能够承载文件夹的标签。webkitdirectory是这里的大杀器,这个不仅定义了文件夹上传,而且记录了文件的相对路径。

<input type="file" name="multi_upload[]" id="multi_upload" multiple webkitdirectory />

文件本身的上传,是用到了ThinkPHP的upload类,一旦你把input的files信息post给后台,只要这样几行代码就能指定目录保存了。

$upload = new \Think\Upload();// 实例化上传类
	    $upload->maxSize   =     3145728 ;// 设置附件上传大小
	    $upload->exts      =     array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
	    $upload->rootPath  =     './Public/octdatabase/'; // 设置附件上传根目录
	    $upload->savePath  =     ''; // 设置附件上传(子)目录

		$targetFolder = '/Public/octdatabase/'; // Relative to the root

		$upload->saveName = array('myFun',array('__FILE__'));
	    $upload->autoSub = false;
	    // 上传文件
	    $info = $upload->upload();

相对路径又如何传给后台呢?相对路径没有包含在files的里面,需要单独挖出来上传。这里有一些小trick,比如files的最后两个其实不是图片文件本身,XMLHttpRequest是做了一个类似Ajax的交互,页面不会跳转,所以我把Ajax返回的内容显示在一个div标签里,这样就可以在网页中运行。 这部分代码主体参考了Alan的Blog:http://sapphion.com/2012/06/12/keep-directory-structure-when-uploading/ 。

$("#btn").click(function(){
		uploadFiles($("#multi_upload")[0].files);
	});
	function uploadFiles(files){
		// Create a new HTTP requests, Form data item (data we will send to the server) and an empty string for the file paths.
		xhr = new XMLHttpRequest();
		data = new FormData();
		var paths = new Array();

		// Set how to handle the response text from the server

		xhr.onreadystatechange = function(ev){
			if (xhr.readyState == 4){
				$("#info").html(xhr.responseText);
			}
		};

		// Loop through the file list
		for (var i in files){
			if (typeof files[i] != 'object'){
				continue;
			}
			// Append the current file path to the paths variable (delimited by tripple hash signs - ###)
			paths.push(files[i].webkitRelativePath);
			//paths += files[i].webkitRelativePath+"###";
			// Append current file to our FormData with the index of i
			data.append(i, files[i]);
		};
		// Append the paths variable to our FormData to be sent to the server
		// Currently, As far as I know, HTTP requests do not natively carry the path data
		// So we must add it to the request manually.
		data.append('paths', paths);

		// Open and send HHTP requests to upload.php
		xhr.open('POST', "/bgidb/Data/multiuploadify", true);
		xhr.send(this.data);
	}

【2】已完成:文件保存到了后台,文件相对路径传给了后台。现在处理将文件相对路径剖开成文件夹的树形结构。树形结构中每个非根节点都有一个父节点,每个叶子结点都没有孩子。所以只要记录下来每个节点的id,和父亲的id,就能存储和读取一个树形结构了。

当然这里面依旧有trick。首先考虑一下你的文件相对路径长什么样子,如这样:“images/folder1/folder2/xxx.jpg”。当然你可以dump到前台自己看看。“/”符号将每个节点隔开了,所以只要把所有这样的相对路径分离开,你就得到了每一个文件夹(文件)的名字,每一个文件夹(文件)的父亲。explode函数能够完成隔开节点,以及生成节点的功能。接下来就是一个节点一个节点地把树形结构写到数据库。

protected function insertFolderInfo(){
		//插入文件夹层级信息
		$reletivePath = explode(',',I('post.paths'));//把传到后台的paths变成数组

		$flag = true;

		foreach ($reletivePath as $key => $path) {
			$pathTree = explode('/',$path);
			$insertId = 0;
			$octFolder = M("octfolder");

			foreach ($pathTree as $key => $nodeName) {
				if ($key == 0){//舍去最高层目录
					continue;
				}

				$leaf = 0;
				if ($key == sizeof($pathTree) - 1){
					$leaf = 1;
				}
				$nodeAdd = array(
					'nodeName' 	=> $nodeName,
					'parent' 	=> $insertId,
					'leaf'		=> $leaf
				);
				$re = $octFolder->where($nodeAdd)->find();

				if (!$re){
					$insertId = $octFolder->add($nodeAdd);

					if (!$insertId){
						$flag = false;
						break;
					}
				}
				else{
					$insertId = $re['id'];
				}
			}
			if ($flag == false){
				break;
			}
		}
		return $flag;
	}

数据库表结构:

【3】树形结构也保存到了数据库。接下来从前台优雅的把它取出来。当看目录里的文件时,当前目录里的一级文件夹(文件)的父节点都是一样的,都是当前目录的id。所有只要定义一个query函数,每次显示一个id的所有孩子,事情就简单了。剩下的就是页面的更新,触发器的绑定(为了更好的操作体验)。

function query(id){
		        $.post(
		            '/bgidb/oct/octFileAjax',
		            {parent : id},
		            function(data){
		                //console.log(data);
		                if(data['wrongcode']==999){
		            		backStepId = data['backStepId'];
		                    var files = data['files'];
		                    filePanel.html(formatFiles(files));
		                    boundListener();
		                }else{
		                    alert(data['wrongmsg']);
		                }
		            },
		            "json");
		    }

后台只要根据这个parent参数去找到所有的子节点,然后传给前台,是图片的附个图片链接,然后还附上上一级目录的id以便回退就ok。前台接到了data之后,就把前端的文件夹都画出来,触发器绑好。

public function octFileAjax(){
        $WRONG_CODE = C('WRONG_CODE');
        $WRONG_MSG = C('WRONG_MSG');
        $data['wrongcode'] = $WRONG_CODE['totally_right'];

        $parent = I("post.parent", null);
        if ($parent == null){
            $data['wrongcode'] = $WRONG_CODE['query_data_invalid'];
        }
        else{
            $oF = M("octfolder");
            $cond = array(
                'parent' => $parent
            );
            $re = $oF->where($cond)->select();

            if (!$re){
                $data['wrongcode'] = $WRONG_CODE['not_exist'];
            }
            else{
                foreach ($re as $key => $file) {
                    if($file['leaf'] == 1){
                        $oct = M("oct");
                        $cond = array(
                            'name' => $file['nodename']
                        );
                        $img = $oct->field('imgsite')->where($cond)->find();
                        $re[$key]['imgsite'] = $img['imgsite'];
                    }
                }

                $data['files'] = $re;

                $oF = M("octfolder");
                $cond = array(
                    'id' => $parent
                );
                $backStepId = $oF->field('parent')->where($cond)->find();
                if (!$backStepId){
                    $data['backStepId'] = 0;
                }
                else{
                    $data['backStepId'] = $backStepId['parent'];
                }
            }
        }

        $data['wrongmsg'] = $WRONG_MSG[$data['wrongcode']];
        $this->ajaxReturn($data);
    }

完整浏览页面前端实现:这里实在不想自己画文件夹的前端了,就用了MetroUI里的图标和样式。

我自己完整实现:

<!DOCTYPE html>
<html>
<head>
	<title>OCT文件浏览系统</title>

	<link href="__PUBLIC__/metro/css/metro.min.css" rel="stylesheet">

	<script src="__PUBLIC__/js/jquery-2.1.1.min.js" type="text/javascript"></script>
	<script src="__PUBLIC__/metro/js/metro.min.js" type="text/javascript"></script>
	<script type="text/javascript">
		$(document).ready(function(){
		    var filePanel = $("#filePanel");
		    var backStepId = 0;

		    query(0);

		    function query(id){
		        $.post(
		            '/bgidb/oct/octFileAjax',
		            {parent : id},
		            function(data){
		                //console.log(data);
		                if(data['wrongcode']==999){
		            		backStepId = data['backStepId'];
		                    var files = data['files'];
		                    filePanel.html(formatFiles(files));
		                    boundListener();
		                }else{
		                    alert(data['wrongmsg']);
		                }
		            },
		            "json");
		    }

		    function formatFolder(file){
                //backStepId = file.parent;
		        return '<div class="list octFolder" folderId="' + file.id + '" folderParentId="' + file.parent + '" ><img src="/Public/metro/images/folder-images.png" class="list-icon"><span class="list-title">'+ file.nodename +'</span></div>';
		    }

		    function formatImg(file){
		        return '<div class="list octImage" name="' + file.nodename + '"><img src="' + file.imgsite + '" class="list-icon"><span class="list-title">'+ file.nodename +'</span></div>';
		    }

		    function formatStepBack(){
		        return  '<div class="list stepBack"><span class="list-icon icon-font-icon">..</span><span class="list-title">上级目录</span></div>';
		    }

		    function formatFiles(files){

		        var re = "";

		        re += formatStepBack(files);

		        for(var i = 0; i < files.length; i++){
		            var file = files[i];
		            if(file.leaf == 0){
		                re += formatFolder(file);
		            }
		            else{
		                re += formatImg(file);
		            }
		        }
		        return re;
		    }

		    function boundListener(){
		        $(".octImage").click(function(){
		            var nodeName = $(this).attr('name');
		            $("#input").val(nodeName);
		            $("#uploadAgent").submit();
		        })

		        $(".octFolder").click(function(){
		            var folderId = $(this).attr('folderId');
		            query(folderId);
		        })

		        $(".stepBack").click(function(){
		            query(backStepId);
		        })
		    }
		});

	</script>
</head>
<body>
	<div class="app-bar">
	    <a class="app-bar-element" href="__MODULE__/oct/octFileSys">OCT文件浏览系统</a>
   		<span class="app-bar-divider"></span>
	    <a class="app-bar-element" href="/">回到首页</a>
	</div>
	<form action="{:U('Bgidb/Oct/oct')}" enctype="multipart/form-data" method="post" id="uploadAgent" target='_blank' ">
		<input type="hidden" name="imageName" id="input"/>
	</form>

	<div class="listview " id="filePanel">
	</div>
</body>
</html>

这个其实是一个很大的系统的一部分,其它部分也都是我一直维护的,快两年了。

最后自己对于安全方面没有做工作,因为我对这个领域没有概念,我只是在理解力范围内随手减少代码的臃肿。

(自我介绍是:我有4年Acmer经历,现在在中南大学读硕二,希望接下来弄完实验室这个项目能够自己好好准备毕业和出国材料。希望不要被困于代码,放开自己的思想好好欣赏一下这个世界。)

时间: 2024-07-29 02:39:33

HTML5+PHP 实现 保存文件夹相对路径 递归上传 在线浏览的相关文章

C#项目打开/保存文件夹/指定类型文件,获取路径(转)

C#项目打开/保存文件夹/指定类型文件,获取路径 转:http://q1q2q363.xiaoxiang.blog.163.com/blog/static/1106963682011722424325/ 1.打开文件路径:                  OpenFileDialog ofd = new OpenFileDialog();                //new一个方法            ofd.Filter = "(*.et;*.xls;*.xlsx)|*.et;*.x

MFC 获取文件和文件夹的路径

1.获取文件的路径 CFileDialog fileDlg(TRUE); fileDlg.m_ofn.lpstrTitle=L"打开文件"; //fileDlg.m_ofn.lpstrFilter=_T("Program(*.exe)\0*.exe\0All Files(*.*)\0*.*\0\0"); if(IDOK==fileDlg.DoModal()) { LPWSTR lpPathName = fileDlg.m_ofn.lpstrFile; SetDlgI

CFileDialog 打开文件夹文件 保存文件夹文件

格式说明: explicit CFileDialog( BOOL bOpenFileDialog,                         //TRUE 为打开, FALSE 为保存 LPCTSTR lpszDefExt = NULL,                 // 默认文件扩展名 LPCTSTR lpszFileName = NULL,            //文件对话框中 初始的文件名称 DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVER

【.Net】C#获取Windows系统特殊文件夹的路径

系统特殊文件夹是包含公共信息的文件夹,如"Program Files"."Programs"."System"或"Startup".特殊文件夹在默认情况下由系统设置,或者由用户在安装 Windows 的某个版本时显式进行设置. Environment.GetFolderPath 方法 获取指向由指定枚举标识的系统特殊文件夹的路径. 命名空间:Systempublic static string GetFolderPath (

iOS 快速获取沙盒下任意文件夹的路径

NSLog(@"%@", NSHomeDirectory());//沙盒主目录 NSLog(@"%@", NSTemporaryDirectory());//沙盒中tmp文件夹的路径 NSLog(@"%@", [[NSBundle mainBundle] bundlePath]);//沙盒中*.app包的路径 NSLog(@"%@", [NSSearchPathForDirectoriesInDomains(NSDocumen

在使用Eclipse时出现的两个问题:“搞定 NiosII 工程文件夹目录路径改变”与“Connected system ID hash not found on target at expected base address”

问题一: “搞定 NiosII 工程文件夹目录路径改变”的过程中,按照<NiosII的奇幻漂流-v2.0.pdf>这本书附录一章<20.2 四步搞定 NiosII 工程文件夹目录路径改变>所说的方法进行问题解决的时候,总是会出现一些小问题,例如有一个问题“xxx.sopcinfo could not be found.”最后只能删掉工程,重建project,copy源码.虽然最后能够解决路径问题,但花费精力过多,无数次的删除原工程下的project,无数次的自己重建,虽然也能解决问

Python3基础 os.path.dirname 对路径字符串进行处理 返回所在文件夹的路径

? python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 conda : 4.5.11 type setting : Markdown ? code """ @Author : 行初心 @Date : 18-10-2 @Blog : www.cnblogs.com/xingchuxin @GitHub : github.com/GratefulHeartCoder """ im

如何将文件夹显示在工具栏上作为一个快捷方式

其实将文件夹显示在工具栏上,这样做的最适合的是编程开发中众多的api查询,当我们忘记某个方法或属性的用法时就不用缩小自己的编写代码的窗口去点开自己众多api帮助文档所在的文件夹去查找了,这时只要点击右下角文件夹名就可以列出所有文件,然后找到我们需要的点击就可以打开了. 如何做? 1.右击下方的工具栏 2.选择工具栏 3.选择新建工具栏 4.在弹出的窗口中选择自己要在工具栏显示的文件夹即可 5.上面的步骤做完后,会在右下角工具栏显示以该文件夹名显示的标题工具. 6.这样还不完美,因为该标题工具左边

springMVC 获取本地项目路径 以及上传文件的方法整理

String path=request.getSession().getServletContext().getRealPath("upload/img/product"); //二进制上传 MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; //获取文件 CommonsMultipartFile fpic=(CommonsMultipartFile) multipa