dropzone.js - 将目录和文件上传到 Spring MVC

dropzone.js - upload directories and files to Spring MVC

我可以使用 dropzone.js 将文件和文件夹内的文件上传到我的 Spring 控制器,但我没有得到带有拖动文件夹的文件。例如,如果我拖放包含 image1.jpg 和 image2.jpg 的目录 ~/media/,我得到的是 jpg 文件,但不是拖放的目录(在本例中是 [=38= .jpg).我需要能够允许拖放文件夹,以便可以上传 SD 卡并保持原始结构。

ProjectAjaxController:

@RestController("ProjectAjaxController")
@Validated
public class ProjectAjaxController extends BaseController { 

    private static final String INVALID_FILE_SUPPLIED_PLEASE_SELECT_A_FILE_TO_UPLOAD = "Invalid file supplied. Please select a file to upload. ";

    @RequestMapping("/ajax/project/upload") 
    public AjaxResponse uploadProjectFilesAndFolders(MultipartHttpServletRequest request, 
            @RequestParam("projectPath") String projectPath, 
            @RequestParam("file") MultipartFile[] file) {
        AjaxResponse ajaxResponse = new AjaxResponse();

        if (file == null) {
            this.userErrors.add(INVALID_FILE_SUPPLIED_PLEASE_SELECT_A_FILE_TO_UPLOAD);
            logger.error(INVALID_FILE_SUPPLIED_PLEASE_SELECT_A_FILE_TO_UPLOAD);
            ajaxResponse.setMessage(INVALID_FILE_SUPPLIED_PLEASE_SELECT_A_FILE_TO_UPLOAD);
            ajaxResponse.setStatusCode(-2);
        } else {

            try {
                for (MultipartFile multipartFile: file) {
                    logger.info("current file " + multipartFile.getmultipartFilename());
                    ...
                }
            } catch (IOException ioe) {
                this.userErrors.add("Unable to upload files ");
                logger.warn("Unable to upload files. " + ioe.getMessage(), ioe);
                ajaxResponse.setStatusCode(-2);
                ajaxResponse.setMessage(String.join(" " + this.userErrors));
            } catch (Exception e) {
                this.userErrors.add("Error uploading files " + e.getMessage());
                logger.warn("Error uploading files. " + e.getMessage(), e);
                ajaxResponse.setStatusCode(-2);
                ajaxResponse.setMessage(String.join(" " + this.userErrors));
            }
        }
        return ajaxResponse;
    }

}

网页内容:

jQuery(document).ready(function($) {

  $(".search-display-block").each(function() {
    var details = $(this).find(".portfolio-dropzone-details");
    var portfolioPath = details.data("portfolio-path");
    $(this).dropzone({
      url: "/ajax/project/upload",
      uploadMultiple: true,
      parallelUploads: 20,
      autoProcessQueue: true,
      createImageThumbnails: false,
      previewsContainer: "#template-preview",
      maxFilesize: 4000,
      timeout: 0,
      webkitDirectory: true,
      params: {
        'projectPath': portfolioPath
      },
      success: function(file, response) {
        console.dir(response);
        if (response == null || response.statusCode != 200) {
          //console.log("Error occurred uploading file");
          var errorMessage = (response == undefined || response.message == undefined) ? "Error occurred uploading file " : response.message;
          $("#dropzone-error-messsage-block").append("<span>" + errorMessage + "</span>");
        } else {
          console.log("Succesfully uploaded file ");
          //console.dir(file);
        }
      },

      uploadprogress: function(file, progress, bytesSent) {
        var percent = round(progress, 2);
        var progressParent = $(file.previewElement).find(".dz-progress");
        var progressElement = $(file.previewElement).find(".dz-upload");
        progressElement.html(percent + "%");
        var size = progressParent.width() * (percent / 100);
        progressElement.width(size + "px");
      },
      //Called just before each file is sent
      sending: function(file, xhr, formData) {
        //Execute on case of timeout only
        xhr.ontimeout = function(e) {
          var errorMessage = "The server timed out transfering file " + file.name + ". Please try again or contact your administrator.";
          $("#dropzone-error-messsage-block").append("<span>" + errorMessage + "</span>");
        };


      }
    });

  });

});
.card {
  transition: 0.3s;
}

.portfolio-container {
  background-color: #1d3c5c;
}

.card-member-span {
  padding-right: 1em;
}

.project-dialog {
  overflow: auto;
  background-color: #1d3c5c;
}

.portfolio-dialog {
  overflow: auto;
  background-color: #ffffff;
}

.dialog {
  display: none;
}

.project-block {
  padding-left: 0.25em;
  padding-bottom: 0.25em;
  margin-bottom: 0;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.42857143;
  white-space: nowrap;
  vertical-align: middle;
  cursor: pointer;
}

.project-block.focus,
.project-block:focus,
.project-block:hover {
  color: #333;
}

.project-block-primary {
  color: #1d3c5c;
  background-color: #ddd;
  font-weight: 700;
}

.list-cards {
  -webkit-box-flex: 1;
  -webkit-flex: 1 1 auto;
  -ms-flex: 1 1 auto;
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  margin: 0 4px;
  padding: 0 4px;
  z-index: 1;
  min-height: 0;
  border-radius: 1em;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}

.portfolio-card {
  width: 100%;
  min-height: 3em;
  background-color: #ddd;
  color: #000;
  padding-left: 3px;
}

.list-card-members {
  background-color: #ffffff;
  padding: 0.5em;
}

.list-card-section {}

.list-card-project-section {
  background-color: #ffffff;
}

.list-card {
  margin-bottom: 0.5em;
  background-color: #1d3c5c;
  border-radius: 1em;
  overflow: hidden;
}


/* Add rounded corners to the top left and the top right corner of the image */

img {
  vertical-align: inherit;
}


/* On mouse-over, add a deeper shadow */

.card:hover {
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}


/* Add some padding inside the card container */

.container {
  padding: 2px 16px;
}

#board {
  /*     user-select: none; */
  display: flex;
  white-space: nowrap;
  margin-bottom: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 10px;
  position: relative;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.list {
  /*  background-color: #5b5353; */
  background-color: #1d3c5c;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  max-height: 100%;
  position: relative;
  white-space: normal;
  margin-top: 2px;
  padding-left: 5px;
  padding-right: 5px;
}

.board-menu-container,
.list {
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
}

.list-wrapper {
  width: 270px;
  margin: 0 5px;
  height: 100%;
  display: inline-block;
  vertical-align: top;
}

.search-filter {
  padding-top: 5px;
  padding-bottom: 5px;
  display: none;
  font-size: 12px;
}

.search-filter-block {
  padding-top: 5px;
  padding-bottom: 5px;
}

.search-filter-tag-block {
  margin-top: 5px;
}

.search-filter-select {
  height: 100%;
  font-size: 12px;
}

.search-colour-button {
  color: white;
  padding: 0.5em 0.5em;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 12px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 1em;
  -webkit-border-radius: 1em;
  -moz-border-radius: 1em;
}

.search-colour-button.active {
  box-shadow: inset 0 0 0 3px #b3b3b3, inset 0 5px 10px #e6e6e6;
  font-weight: 800;
}

.project-card {
  position: relative;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  border-radius: 1em;
  overflow: hidden;
  background-color: #1d3c5c;
  min-height: 10%;
  max-height: 90%;
  min-width: 10%;
  max-width: 90%;
  margin: 1em;
}

.project-card-content {
  background-color: #fff;
}

.portfolio-card-title {
  font-weight: 600;
  padding: 0.5em;
  cursor: pointer;
}

.portfolio-projects-title {
  font-weight: 600;
}

.search-tags-form {
  margin: 4px 2px;
}

.list-header {
  color: #ddd;
}

.list-header-name-assist {
  text-shadow: 2px 2px 4px #000000;
}

hr {
  display: block;
  height: 1px;
  border: 0;
  border-top: 1px solid #ccc;
  margin: 0px;
  padding: 0;
}

.portfolio-dialog {
  background-color: #1d3c5c;
  color: #fff;
}

.portfolio-dialog-content {
  background-color: #1d3c5c;
  color: #ffffff;
}

dl {
  margin-bottom: 3px;
}

.bg-dark {
  color: #333333;
  background-color: #ffffff;
}

.portfolio-card-content {
  margin-bottom: 0.5em;
  border-radius: 1em;
  padding-top: 0.5em;
  padding-bottom: 1em;
}

.fps-tag-block {
  padding-left: 1em;
  padding-right: 1em;
}

.portfolio-icon-list {
  color: #000;
}

.ui-dialog {
  background-color: #1d3c5c;
}

.ui-dialog-titlebar {
  background-color: #1d3c5c;
  color: #ddd;
  border: 0px;
}

.ui-dialog-buttonpane {
  background-color: #1d3c5c;
  color: #ddd;
  border: 0px;
}

.ui-dialog .ui-dialog-title {
  text-align: center;
}

.ui-dialog-buttonset {
  color: #1d3c5c;
}

.ui-dialog-titlebar-close {
  color: #1d3c5c;
  content: "X";
}

.ui-widget-content a {
  color: #333333;
}

.portfolio-toggle:before {
  content: "▸";
}

.portfolio-toggle.collapsed:before {
  content: "▾";
}

.project-block-rounded {
  border-radius: 0.5em;
  padding: 0.25em;
  margin: 0.25em;
}

.open-portfolio-dialog-btn {
  cursor: pointer;
}

.portfolio-overview-btn {
  cursor: pointer;
}

.media-capture-btn {
  cursor: pointer;
}

#search-filter-block {
  cursor: pointer;
}

.dl-horizontal dt {
  text-align: left;
}

.dl-horizontal dd {
  width: auto;
}

.portfolio-card-filter {
  padding-left: 1em;
}

.portfolio-metadata {
  color: #000;
}

.portfolio-overview {
  color: #000;
}

.fixed-header {
  position: fixed;
  top: 0em;
  background-color: #1d3c5c;
  padding: 1em;
  width: 270px;
  text-align: center;
}

.sortable-list {
  padding-bottom: 100px;
}

.search-highlighter {
  border: 2px solid red;
}

.portfolio-specific-btn {
  padding-right: 0.5em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1:jquery.min.js/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.css" rel="stylesheet" />

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.js"></script>

<div class="container-fluid" id="core-content">
  <div class="col-xs-12 portfolio-page">
    <div class="col-xs-2 " style="">
      <div>Success/Error info goes here</div>
      <div id="dropzone-status-updates-block">
        <div id="dropzone-error-messsage-block"></div>
        <div id="dropzone-progress-block"></div>
      </div>
      <div id="template-preview"></div>
    </div>
    <div class="col-xs-10 portfolio-board-container">
      <div id="portfolio-container" class="container-fluid portfolio-container" style="">
        <form action="#" id="portfolio-form" name="portfolio-form" method="POST" class="">
          <div class="board-canvas">
            <div id="board" class="u-fancy-scrollbar js-no-higher-edits js-list-sortable ui-sortable">
              <div class="js-list list-wrapper ">
                <div class="list js-list-content">
                  <div class="list-header js-list-header u-clearfix is-menu-shown">
                    <div class="list-header-target js-editing-target"></div>
                    <h4 class="list-header-name-assist js-list-name-assist text-center" dir="auto">
                      Awaiting Approval
                    </h4>
                  </div>
                  <div class="sortable-list ui-sortable" data-column-id="awaitapprove" data-column-value="awaitapprove Awaiting Approval">
                    <div data-portfolio-path="Tennis" class="list-card js-member-droppable is-covered ui-droppable search-display-block sfilter ui-sortable-handle">
                      <div class="list-card-cover js-card-cover portfolio-card" style="background-color: #02bf6f;
                 color: #000000; ">
                        <span class="hidden portfolio-dropzone-details" data-portfolio-path="Tennis"></span>
                        <div class="portfolio-card-filter-block">
                          <div class="portfolio-card-title portfolio-card-filter-heading ">
                            <div class="accordion-toggle collapsed portfolio-toggle" data-toggle="collapse" data-parent="#portfolioAccordion" data-target="#portfolio5ac752e61c99a112046cf391" aria-expanded="false">
                              <span class="portfolio-accordion"></span>

                              <i class="fa fa-user-circle " aria-hidden="true" title="You are a member of this portfolio"></i>&nbsp;

                              <span class="archiware-archive-state" data-portfolio-path="Tennis"><i class="fa fa-database" aria-hidden="true" title="Has been archived"></i></span> Tennis
                              <span class="text-right"></span>
                            </div>
                          </div>
                        </div>
                      </div>
                      <div class="list-card-details text-center">
                        <div class="list-card-members js-list-card-members">
                          <div class="portfolio-icon-list " data-example-id="portfolio-icon-list">
                            <div class="btn-group portfolio-specific-btn portfolio-overview-btn" title="Portfolio Overview">
                              <a href="#" class="portfolio-overview">
                                <i class="fa fa-eye fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>
                            <div class="btn-group portfolio-specific-btn portfolio-checklist-btn" title="Portfolio Checklist">
                              <a href="#" class="portfolio-checklist">
                                <i class="fa fa-list fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>

                            <div class="btn-group portfolio-specific-btn open-portfolio-metadata-btn" data-dialog-id="5ac752e61c99a112046cf391" title="Portfolio Metadata">
                              <a href="#" class="portfolio-metadata">
                                <i class="fa fa-file-text fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>

                            <div class="btn-group portfolio-specific-btn open-portfolio-forum-btn" data-dialog-id="5ac752e61c99a112046cf391" title="Portfolio Forum">
                              <a href="#" class="portfolio-forum">
                                <i class="fa fa-commenting fa-1-5-font-size" aria-hidden="true"></i></a>

                            </div>

                          </div>
                        </div>
                        <div class="list-card-project-section">
                          <div class="project-block project-block-primary open-project-dialog-btn" name="open-project-dialog-btn" data-dialog-id="project-dialog-motion-5ac752e61c99a112046cf391">
                            Motion Sports: <span class="badge">1</span>
                          </div>
                        </div>
                      </div>
                    </div>
                    <div data-portfolio-path="Badminton" class="list-card js-member-droppable is-covered ui-droppable search-display-block sfilter ui-sortable-handle">
                      <div class="list-card-cover js-card-cover portfolio-card" style="background-color: #02bf6f;
                 color: #000000; ">
                        <span class="hidden portfolio-dropzone-details" data-portfolio-path="Badminton"></span>
                        <div class="portfolio-card-filter-block">
                          <div class="portfolio-card-title portfolio-card-filter-heading ">
                            <div class="accordion-toggle collapsed portfolio-toggle" data-toggle="collapse" data-parent="#portfolioAccordion" data-target="#portfolio5ac752e61c99a112046cf391" aria-expanded="false">
                              <span class="portfolio-accordion"></span>

                              <i class="fa fa-user-circle " aria-hidden="true" title="You are a member of this portfolio"></i>&nbsp;

                              <span class="archiware-archive-state" data-portfolio-path="Badminton"><i class="fa fa-database" aria-hidden="true" title="Has been archived"></i></span> Badminton
                              <span class="text-right"></span>
                            </div>
                          </div>
                        </div>
                      </div>
                      <div class="list-card-details text-center">
                        <div class="list-card-members js-list-card-members">
                          <div class="portfolio-icon-list " data-example-id="portfolio-icon-list">
                            <div class="btn-group portfolio-specific-btn portfolio-overview-btn" title="Portfolio Overview">
                              <a href="#" class="portfolio-overview">
                                <i class="fa fa-eye fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>
                            <div class="btn-group portfolio-specific-btn portfolio-checklist-btn" title="Portfolio Checklist">
                              <a href="#" class="portfolio-checklist">
                                <i class="fa fa-list fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>

                            <div class="btn-group portfolio-specific-btn open-portfolio-metadata-btn" data-dialog-id="5ac752e61c99a112046cf391" title="Portfolio Metadata">
                              <a href="#" class="portfolio-metadata">
                                <i class="fa fa-file-text fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>

                            <div class="btn-group portfolio-specific-btn open-portfolio-forum-btn" data-dialog-id="5ac752e61c99a112046cf391" title="Portfolio Forum">
                              <a href="#" class="portfolio-forum">
                                <i class="fa fa-commenting fa-1-5-font-size" aria-hidden="true"></i></a>
                            </div>
                          </div>
                        </div>
                        <div class="list-card-project-section">
                          <div class="project-block project-block-primary open-project-dialog-btn" name="open-project-dialog-btn" data-dialog-id="project-dialog-motion-5ac752e61c99a112046cf391">
                            Motion Sports: <span class="badge">1</span>
                          </div>
                        </div>
                      </div>
                    </div>

                  </div>
                </div>
              </div>
              <div class="js-list list-wrapper ">
                <div class="list js-list-content">
                  <div class="list-header js-list-header u-clearfix is-menu-shown">
                    <div class="list-header-target js-editing-target"></div>
                    <h4 class="list-header-name-assist js-list-name-assist text-center" dir="auto">
                      Approved
                    </h4>
                  </div>
                  <div class="sortable-list ui-sortable" data-column-id="approved" data-column-value="approved Approved">

                    <div class="ui-sortable-handle">

                    </div>

                  </div>
                </div>
              </div>

              <div class="js-list list-wrapper ">
                <div class="list js-list-content">
                  <div class="list-header js-list-header u-clearfix is-menu-shown">
                    <div class="list-header-target js-editing-target"></div>
                    <h4 class="list-header-name-assist js-list-name-assist text-center" dir="auto">
                      Editing in Progress
                    </h4>
                  </div>
                  <div class="sortable-list ui-sortable" data-column-id="editinprog" data-column-value="editinprog Editing in Progress">

                    <div class="ui-sortable-handle">

                    </div>

                  </div>
                </div>
              </div>

              <div class="js-list list-wrapper ">
                <div class="list js-list-content">
                  <div class="list-header js-list-header u-clearfix is-menu-shown">
                    <div class="list-header-target js-editing-target"></div>
                    <h4 class="list-header-name-assist js-list-name-assist text-center" dir="auto">
                      Awaiting Graphics
                    </h4>
                  </div>
                  <div class="sortable-list ui-sortable" data-column-id="awaitgraph" data-column-value="awaitgraph Awaiting Graphics">

                    <div class="ui-sortable-handle">

                    </div>

                  </div>
                </div>
              </div>


              <div class="js-list list-wrapper ">
                <div class="list js-list-content">
                  <div class="list-header js-list-header u-clearfix is-menu-shown">
                    <div class="list-header-target js-editing-target"></div>
                    <h4 class="list-header-name-assist js-list-name-assist text-center" dir="auto">
                      Completed
                    </h4>
                  </div>
                  <div class="sortable-list ui-sortable" data-column-id="completed" data-column-value="completed Completed">

                    <div class="ui-sortable-handle">

                    </div>

                  </div>
                </div>
              </div>


            </div>
          </div>
          <div class="spacer-sml"></div>

        </form>

      </div>
    </div>
  </div>
  <div class="row">
    <div class="spacer-sml"></div>
  </div>
</div>

我正在 chrome 上进行测试,发现并非所有浏览器都支持 webkit。我在输入字段中设置了 webkitdirectory。我只是不知道如何将文件夹名称附加到文件名。

我只是在寻找一种方法来获取被拖放的文件夹,而不是文件系统文件夹。

所以我终于弄清楚了如何使用 dropzone 函数 renameFile 获取与文件名一起传递的目录名:

renameFile: function (file) {
    let newFilename = file.fullPath;
    return newFilename;
},

是啊!太好了,我想。现在该文件被视为文件夹而不是文件,当我对文件执行 mkdirs 时,它会创建一个文件夹作为文件名,例如media/image1.jpg,我也尝试过 createNewFile 来创建一个带有文件名的目录。然后,当我尝试使用 BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream( projectFilePath )); 读取流时我收到错误:

java.io.FileNotFoundException: /Volumes/share/Project/Tennis/media/image1.jpg (Invalid argument)

您不会得到目录,那是用户 select 的来源。如果你这样做了,你会得到他们的全部 windows/users/ ect ect.

首先设置目录或让用户创建它们然后上传到它们

经过多次试验和错误后,我最终将目录添加到表单数据中,然后更改控制器,使其采用额外的参数。我还删除了 renameFile 方法。

js:

sending: function(file, xhr, formData) {
    //Execute on case of timeout only
    xhr.ontimeout = function(e) {
        var errorMessage = "The server timed out transferring file " + file.name + ". Please try again or contact your administrator.";
        $("#dropzone-error-messsage-block").append("<span>" + errorMessage + "</span>");
    };
    formData.append('fullPath', file.fullPath);
}

控制器:

@RequestMapping("/ajax/project/upload") 
public AjaxResponse uploadProjectFilesAndFolders(MultipartHttpServletRequest request, 
        @RequestParam("projectPath") String projectPath,
        @RequestParam("fullPath") String fullPath,
        @RequestParam("file") MultipartFile[] file) {