计划

计划

  • 完善小程序——流程
  • 完成视频管理网页
  • 完成相册管理网页
  • 项目的合并和项目内某个目录的移动
  • 修改项目名称、目录名称,自动修改硬盘上的文件夹目录。或者用数据库里存放的id代替真实的文件夹名称?不好,不直观。
  • 项目目录的拖动
  • 制作docker镜像,包含mysql数据库配置,onlyoffice简化配置
  • websocket聊天室功能——已完成
  • gis:Cesium+three.js

文件夹上传
https://juejin.cn/post/7292323606875553843

前端如何直接上传文件夹

xintianyou
2023-10-22
2,586
阅读5分钟
前面写了一篇仿写el-upload组件,彻底搞懂文件上传,实现了选择/拖拽文件上传,我们经常看到一些网站支持直接选择整个文件夹上传,例如:宝塔面板、cloudflare托管、对象存储网站等等需要模拟文件路径存储文件的场景。那是怎么实现的呢?

依然从两方面来说:

input选择文件夹
拖拽文件夹
input选择文件夹
在props.js中加一个属性,upload-folder是否支持上传文件夹

export default {
  // 前面的省略了...
  // 是否支持选择文件夹
  'upload-folder': {
    type: Boolean,
    default: false
  }
}

改一下input标签,依然是根据props的值动态判断是否支持上传文件夹。主要是webkitdirectory这个属性,由于不是一个标准属性,需要加浏览器前缀。

<input 
  type="file" 
  id="file" 
  :multiple="multiple"
  :accept="accept"
  :webkitdirectory="uploadFolder"
  :mozdirectory="uploadFolder"
  :odirectory="uploadFolder"
  @change="handleChange"
>

注意:支持选择文件夹时就只能选择文件夹,无法选择文件。

那么如何获取选择的文件夹呢?其实我们最终要上传的依然是文件,也就是file对象,文件夹也是一个特殊的文件。

依然是通过input的onchange事件回调拿到上传的event。

或者直接获取input这个dom对象,然后拿到files属性,结果是一样的。

// input选择文件回调
const handleChange = (event) => {
  console.log('[ files ] >', event.target.files)
  const inputDom = document.querySelector('#file')
  console.log('[ files ] >', inputDom.files)
}

可以看到,比选择单个文件时,多了一个webkitRelativePath属性,并且它是递归选择的文件夹,拿到这个文件夹及其子文件夹下所有的文件,我们可以通过这个属性拿到上传时文件所在的文件夹名称和路径。

拖拽文件夹
上篇文章讲过拖拽如何拿到文件,首先要准备一个用于拖拽放置的区域。 调用upload组件时,传入drag=true。

<div 
  class="drag-box" 
  @dragover="handleDragOver"
  @dragleave="handleDragLeave"
  @drop="handleDrop"
>
  将文件拖到此处,或<span>点击上传</span>
</div>
// 拖放进入目标区域
const handleDragOver = (event) => {
  event.preventDefault()
}
// 拖拽放置
const handleDrop = (event) => {
  event.preventDefault()
  console.log('[ event ] >', event)
}

注意:和input上传不同,拖拽时,是可以同时拖拽文件和文件夹的。

因为可以同时拖拽文件和文件夹,我们就不能直接使用event.dataTransfer.files,如果刚好拖拽进来的是一个文件,那可以这么获取,如果是个文件夹呢?那就不行了。

同时拖拽一个文件和一个文件夹

这时候就要用到event.dataTransfer.items了

// 拖拽放置
const handleDrop = (event) => {
  event.preventDefault()
  console.log(event.dataTransfer.items)
}

打印一下看看:得到一个List类型的数据,里面是两个DataTransferItem,控制台无法直接查看它到底是个什么玩意儿。看MDN,也看不出它具体是个啥。既然是List,遍历一下看看:

const handleDrop = (event) => {
  event.preventDefault()
  console.log(event.dataTransfer.items)
  for (const item of event.dataTransfer.items) {
    console.log('[ item ] >', item)
  }
}

可以看到不管是文件还是文件夹,都被识别成了file,只不过图片是直接能识别出type为image/png。

查看MDN,developer.mozilla.org/zh-CN/docs/…

点击查看item的Prototype,发现里面有个webkitGetAsEntry方法,执行它就能拿到item的具体信息。

看方法名,带了个webkit,但是这个方法除了Android Firefox浏览器以外都可以用。

for (const item of event.dataTransfer.items) {
  const entry = item.webkitGetAsEntry()
  console.log(entry)
}

依然拖动上面那个图片文件和一个文件夹:可以看出,文件夹里面还有文件和文件夹,但是只显示了一个文件和一个文件夹,看来拖拽和input上传不一样,它不会自动的把里面所有的文件递归列出来。

通过isDirectory属性,就能区分是文件还是文件夹。除了这些基础属性以外,继续查看Prototype,可以看到还有一系列方法:

先看怎么拿到文件
当entry是一个文件时,它有两个方法:createWriter()和file(),查看MDN,developer.mozilla.org/en-US/docs/…createWriter()已经废弃了,而且也不是我们今天要用的。file()才是我们要找的。这不就是我们熟悉的file对象吗,跟input上传拿到的一毛一样。

再看怎么拿到文件夹
查看MDN的Drop API webkitGetAsEntry()方法,developer.mozilla.org/zh-CN/docs/… 可得,如果是文件夹,可以通过createReader方法创建一个文件目录阅读器,然后通过readEntries方法,重新拿到每个item,这就是event.dataTransfer.items里面的每个item。我们写一下试试依然是之前那个图片和文件夹只打印出了跟目录下一级的一个文件和一个文件夹,那下面还有一个文件怎么办呢?

递归呀!

写一个递归读文件的方法。

const readFiles = async (item) => {
  if (item.isDirectory) {
    // 是一个文件夹
    console.log('=======文件夹=======');
    const directoryReader = item.createReader();
    // readEntries是一个异步方法
    const entries = await new Promise((resolve, reject) => {
      directoryReader.readEntries(resolve, reject);
    });

    let files = [];
    for (const entry of entries) {
      const resultFiles = await readFiles(entry);
      files = files.concat(resultFiles);
    }
    return files;
  } else {
    // 是一个文件
    console.log('=======文件=======');
    // file也是一个异步方法
    const file = await new Promise((resolve, reject) => {
      item.file(resolve, reject);
    });
    console.log('[ file ] >', file);
    return [file];
  }
}

handleDrop方法也要改一下

// 拖拽放置
const handleDrop = async (event) => {
  event.preventDefault()
  console.log(event.dataTransfer.items)

  const files = [];
  const promises = [];
  for (const item of event.dataTransfer.items) {
    const entry = item.webkitGetAsEntry();
    console.log('[ entry ] >', entry);
    promises.push(readFiles(entry));
  }

  const resultFilesArrays = await Promise.all(promises);
  const allFiles = resultFilesArrays.flat();

  console.log('[ All files ] >', allFiles);
}

再次拖拽上传看看三个文件我们都拿到了。

总结
上传文件夹,还是直接使用input比较简单,使用它能直接拿到文件夹下所有的文件,以及每个文件在本地的路径,代码量也少很多。

拖拽的好处是文件和文件夹能一起上传

https://blog.csdn.net/tangran0526/article/details/104156857

上传三合一:拖拽上传、上传文件、上传文件夹,一次搞定!

呀呀夫斯基

于 2020-02-03 18:52:41 发布

阅读量7.9k
收藏 28

点赞数 14
分类专栏: 经验总结
版权

经验总结
专栏收录该内容
64 篇文章2 订阅
订阅专栏
拖拽上传、上传文件、上传文件夹,三合一!

1 拖拽上传
在 drop 事件中通过 e.dataTransfer.items 递归获取拖拽的文件(夹)。具体看这篇文章:拖拽本地文件夹到浏览器中,展示所有文件结构

2 上传文件
代码:

<template>
  <section>
    <!-- 添加 multiple 属性,可以同时选择多个文件 -->
    <input type="file" multiple id="uploadFile" @change="handleChange" />
  </section>
</template>
<script>
export default {
  methods: {
    handleChange() {
      let files = document.getElementById("uploadFile").files; // FileList 伪数组对象
      for (let i = 0; i <= files.length - 1; i++) {
        let file = files[i];  // File 对象
        console.log(file);
      }
    }
  }
};
</script>

效果:

3 上传文件夹
代码:

<template>
  <section>
    <!-- 添加 webkitdirectory 属性,上传文件夹 -->
    <input type="file" webkitdirectory id="uploadFile" @change="handleChange" />
  </section>
</template>
<script>
export default {
  methods: {
    handleChange() {
      let files = document.getElementById("uploadFile").files; // FileList 伪数组对象
      for (let i = 0; i <= files.length - 1; i++) {
        let file = files[i]; // File 对象
        console.log(file);
      }
    }
  }
};
</script>

File 对象的 webkitRelativePath 属性是文件的相对路径
效果:

4 三合一
这三种上传途径,最终都拿到 File 对象:

途径 得到 File 对象的过程
拖拽上传 在 drop 事件中获取 e.dataTransfer.items ,是一个 DataTransferItemList 对象,遍历得到 DataTransferItem 对象
用 webkitGetAsEntry 方法得到 FileSystemEntry 对象
根据 isFile 属性判断 entry 是文件还是文件夹。是文件的话,用 file 方法获取 File 对象;是文件夹的话,递归地用 reader 读取包含的文件
上传文件 在 change 事件中获取 input.files ,是一个 FileList 对象,遍历得到 File 对象
上传文件夹 同上
但是,文件的相对路径格式不统一,需要处理,统一格式为 文件夹1/文件夹2/a.txt 。

途径 相对路径 说明
拖拽上传 entry.fullPath.substring(1) file.webkitRelativePath 都是空。所以从 entry 中取,注意要把 entry.fullPath 最前面的斜杠去掉
上传文件 file.name file.webkitRelativePath 都是空。直接用 file.name
上传文件夹 file.webkitRelativePath file.webkitRelativePath 就是想要的格式,直接用
三合一代码:

class SuperUploader{

    constructor({ uploadFileEl, uploadDirectoryEl, dropAreaEl, successCallback }) {
        uploadFileEl && this.initUploadFile(uploadFileEl, successCallback);
        uploadDirectoryEl && this.initUploadDirectory(uploadDirectoryEl, successCallback);
        dropAreaEl && this.initDragAndDrop(dropAreaEl, successCallback);
    }


    initUploadFile(el, successCallback) {
        const oRealButton = document.createElement("input");
        oRealButton.setAttribute("type", "file");
        oRealButton.setAttribute("multiple", true);
        oRealButton.style.display = "none";
        oRealButton.addEventListener("change", e => {
            let files = oRealButton.files;
            for (let i = 0; i <= files.length - 1; i++) {
                let file = files[i];
                // 这里的 file.webkitRelativePath 都是 "" ,不是我们想要的.要用 file.name
                successCallback({ file, path: file.name });
            }
        })
        document.body.appendChild(oRealButton);
        el.addEventListener("click", e => {
            oRealButton.click();
        });
    }

    initUploadDirectory(el, successCallback) {
        const oRealButton = document.createElement("input");
        oRealButton.setAttribute("type", "file");
        oRealButton.setAttribute("webkitdirectory", true);
        oRealButton.style.display = "none";
        oRealButton.addEventListener("change", e => {
            let files = oRealButton.files;
            for (let i = 0; i <= files.length - 1; i++) {
                let file = files[i];
                // 这里的 file.webkitRelativePath 就是我们想要的格式
                successCallback({ file, path: file.webkitRelativePath });
            }
        })
        document.body.appendChild(oRealButton);
        el.addEventListener("click", e => {
            oRealButton.click();
        });
    }

    initDragAndDrop(el, successCallback) {
        el.addEventListener("dragover", e => {
            e.preventDefault();
        });
        el.addEventListener("drop", e => {
            let items = e.dataTransfer.items;
            for (let i = 0; i <= items.length - 1; i++) {
                let item = items[i];
                if (item.kind === "file") {
                    let entry = item.webkitGetAsEntry();
                    this.getFileFromEntryRecursively(entry, successCallback);
                }
            }
            e.preventDefault();
        });
    }

    getFileFromEntryRecursively(entry, successCallback) {
        if (entry.isFile) {
            entry.file(file => {
                // 这里的 file.webkitRelativePath 都是 "" ,不是我们想要的.
                // entry.fullPath 是前面带斜杠的,要把斜杠去掉的
                let path = entry.fullPath.substring(1);
                successCallback({ file, path });
            }, e => { console.log(e); });
        } else {
            let reader = entry.createReader();
            reader.readEntries(entries => {
                entries.forEach(entry => this.getFileFromEntryRecursively(entry, successCallback));
            }, e => { console.log(e); });
        }
    }
}

使用:

<style>
    span {
        border: 1px solid;
    }
    .dropArea {
        border: 10px dashed;
        height: 150px;
    }
</style>
<span id="uploadFileButton">上传文件</span>
<span id="uploadDirectoryButton">上传文件夹</span>
<section class="dropArea" id="dropArea">把文件拽进来</section>
<script>
    new Superloader({
        uploadFileEl: document.getElementById("uploadFileButton"),
        uploadDirectoryEl: document.getElementById("uploadDirectoryButton"),
        dropAreaEl: document.getElementById("dropArea"),
        // “三合一”的成功回调,参数 file:文件实体,path:相对路径
        successCallback: ({ file, path }) => {
            console.log("文件名称:" + file.name + ",相对路径:" + path);
        },
    });
</script>

————————————————
版权声明:本文为CSDN博主「呀呀夫斯基」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tangran0526/article/details/104156857

https://blog.csdn.net/fu_qin/article/details/78741854

go语言使用sftp包上传文件和文件夹到远程服务器

奔流入海

于 2017-12-07 15:25:36 发布

阅读量1w
收藏 11

点赞数 1
分类专栏: go语言 文章标签: go语言 sftp 文件上传 服务器
版权

go语言
专栏收录该内容
4 篇文章0 订阅
订阅专栏
使用go语言的第三方包:github.com/pkg/sftp和golang.org/x/crypto/ssh实现文件和文件夹传输。

1、创建connect方法:

func connect(user, password, host string, port int) (*sftp.Client, error) {
   var (
      auth         []ssh.AuthMethod
      addr         string
      clientConfig *ssh.ClientConfig
      sshClient    *ssh.Client
      sftpClient   *sftp.Client
      err          error
   )
   // get auth method
   auth = make([]ssh.AuthMethod, 0)
   auth = append(auth, ssh.Password(password))

   clientConfig = &ssh.ClientConfig{
      User:            user,
      Auth:            auth,
      Timeout:         30 * time.Second,
      HostKeyCallback: ssh.InsecureIgnoreHostKey(), //ssh.FixedHostKey(hostKey),
   }

   // connet to ssh
   addr = fmt.Sprintf("%s:%d", host, port)
   if sshClient, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
      return nil, err
   }

   // create sftp client
   if sftpClient, err = sftp.NewClient(sshClient); err != nil {
      return nil, err
   }
   return sftpClient, nil
}

2、上传文件

func uploadFile(sftpClient *sftp.Client, localFilePath string, remotePath string) {
   srcFile, err := os.Open(localFilePath)
   if err != nil {
      fmt.Println("os.Open error : ", localFilePath)
      log.Fatal(err)

   }
   defer srcFile.Close()

   var remoteFileName = path.Base(localFilePath)

   dstFile, err := sftpClient.Create(path.Join(remotePath, remoteFileName))
   if err != nil {
      fmt.Println("sftpClient.Create error : ", path.Join(remotePath, remoteFileName))
      log.Fatal(err)

   }
   defer dstFile.Close()

   ff, err := ioutil.ReadAll(srcFile)
   if err != nil {
      fmt.Println("ReadAll error : ", localFilePath)
      log.Fatal(err)

   }
   dstFile.Write(ff)
   fmt.Println(localFilePath + "  copy file to remote server finished!")
}

3、上传文件夹

func uploadDirectory(sftpClient *sftp.Client, localPath string, remotePath string) {
   localFiles, err := ioutil.ReadDir(localPath)
   if err != nil {
      log.Fatal("read dir list fail ", err)
   }

   for _, backupDir := range localFiles {
      localFilePath := path.Join(localPath, backupDir.Name())
      remoteFilePath := path.Join(remotePath, backupDir.Name())
      if backupDir.IsDir() {
         sftpClient.Mkdir(remoteFilePath)
         uploadDirectory(sftpClient, localFilePath, remoteFilePath)
      } else {
         uploadFile(sftpClient, path.Join(localPath, backupDir.Name()), remotePath)
      }
   }

   fmt.Println(localPath + "  copy directory to remote server finished!")
}

4、上传测试

func DoBackup(host string, port int, userName string, password string, localPath string, remotePath string) {
   var (
      err        error
      sftpClient *sftp.Client
   )
   start := time.Now()
   sftpClient, err = connect(userName, password, host, port)
   if err != nil {
      log.Fatal(err)
   }
   defer sftpClient.Close()

   _, errStat := sftpClient.Stat(remotePath)
   if errStat != nil {
      log.Fatal(remotePath + " remote path not exists!")
   }

   backupDirs, err := ioutil.ReadDir(localPath)
   if err != nil {
      log.Fatal(localPath + " local path not exists!")
   }
   uploadDirectory(sftpClient, localPath, remotePath)
   elapsed := time.Since(start)
   fmt.Println("elapsed time : ", elapsed)
}

————————————————
版权声明:本文为CSDN博主「奔流入海」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fu_qin/article/details/78741854
https://blog.csdn.net/mingzhehaolove/article/details/53393126
https://www.bookstack.cn/read/beego-2.0.7-zh/web-file-README.md

https://blog.csdn.net/sunhoms/article/details/128345126

上一篇:
下一篇: