【问题标题】:Multiple file upload using GWT and AppEngine Blobstore?使用 GWT 和 AppEngine Blobstore 上传多个文件?
【发布时间】:2012-12-17 21:47:51
【问题描述】:

我将如何在 GWT 和 AppEngine Blobstore 中创建类似于 Gmail 的现代多文件上传?

最常提出的解决方案是gwtupload,这是一个由 Manolo Carrasco 编写的优秀 GWT 组件。但是,最新版本 0.6.6 不能与 blobstore 一起使用(至少我不能让它工作),并且它不支持多文件选择。在最新的 0.6.7 快照中有一个多文件选择补丁,但尽管它允许选择多个文件(使用 HTML5 中的“multiple”属性),它仍然在一个巨大的 POST 请求中发送它们(并显示进度一大堆文件)。

还有其他关于 SO 的问题(例如 herehere),但答案通常使用 HTML5“multiple”属性并将它们作为一个大的 POST 请求发送。它有效,但不是我所追求的。

【问题讨论】:

  • Java 还是 python ?为什么只使用 @jos 建议的 java-plugin plupload,然后在每次文件上传时调用单个文件上传处理程序?
  • Jimmy,我其实是一步完成的,一问一答,供别人查找和使用(不过两天后才能接受答案)。该代码用于 GWT,即 Java。
  • 抱歉,我的电脑工作 8 小时后眼睛不好......

标签: html google-app-engine gwt file-upload blobstore


【解决方案1】:

尼克约翰逊为此写了一些great blog posts。他使用称为Plupload 的通用且广为接受的JavaScript 上传组件,并将文件上传到用Python 编写的AppEngine 应用程序。 Plupload 支持不同的后端(运行时)以支持多个文件选择(HTML5、flash、Silverlight 等)并处理上传进度和其他与上传相关的客户端事件。

他的解决方案的问题是 (1) 它在 Python 中,并且 (2) 它在 JavaScript 中。这是gwt-plupload 进入图片的地方。它是由 Samuli Järvelä 编写的 Plupload 的 JSNI-wrapper,它允许在 GWT 环境中使用 Plupload。但是,该项目已经过时(自 2010 年以来没有提交),但我们可以从中获得灵感。

因此,以下是构建多文件上传组件的分步说明。这将全部在一个项目中,但它(尤其是 JSNI 包装器)可以提取到它自己的 .jar 文件或库中,以便在其他项目中重用。源代码在on Bitbucket here.

应用程序在 AppEngine 上可用(不计费,所以不要指望它可用或工作)http://gwt-gaemultiupload-example.appspot.com/

截图

第 1 步 - Servlet

Blobstore 的工作方式如下:

  1. 客户端向 blobstore 询问可用于上传文件的 URL。
  2. 客户端将文件发布到收到的 URL。
  3. 收到整个 POST 后,blobstore 会将客户端重定向到成功 URL(在创建上传 URL 时指定)。

为了支持这一点,我们需要两个 servlet。一个用于生成文件上传的 URL(请注意,每个文件上传都需要一个唯一的 URL),一个用于接收完成的上传。两者都将非常简单。下面是 URL 生成器 servlet,它将以纯文本形式将 URL 写入 HTTP 响应。

public class BlobstoreUrlGeneratorServlet extends HttpServlet {     
    private static BlobstoreService blobstore = BlobstoreServiceFactory.getBlobstoreService();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Content-Type", "text/plain");
        resp.getWriter().write(blobstore.createUploadUrl("/uploadfinished"));
    }
}

然后,用于接收成功上传的 servlet,它将 blobkey 打印到System.out

public class BlobstoreUploadFinishedServlet extends HttpServlet {
    private static BlobstoreService blobstore = BlobstoreServiceFactory.getBlobstoreService();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Map<String, List<BlobKey>> blobs = blobstore.getUploads(req);
        List<BlobKey> blobKeyList = blobs.get("file");

        if (blobKeyList.size() == 0)
            return;

        BlobKey blobKey = blobKeyList.get(0);

        System.out.println("File with blobkey " + blobKey.getKeyString() + " was saved in blobstore.");
    }
}

我们还需要在web.xml注册这些。

<servlet>
    <servlet-name>urlGeneratorServlet</servlet-name>
    <servlet-class>gaemultiupload.server.BlobstoreUrlGeneratorServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>urlGeneratorServlet</servlet-name>
    <url-pattern>/generateblobstoreurl</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>uploadFinishedServlet</servlet-name>
    <servlet-class>gaemultiupload.server.BlobstoreUploadFinishedServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>uploadFinishedServlet</servlet-name>
    <url-pattern>/uploadfinished</url-pattern>
</servlet-mapping>

如果我们现在运行应用程序并访问http://127.0.0.1:8888/generateblobstoreurl,我们会看到类似

http://<computername>:8888/_ah/upload/ahpnd3QtZ2FlbXVsdGl1cGxvYWQtZXhhbXBsZXIbCxIVX19CbG9iVXBsb2FkU2Vzc2lvbl9fGAEM 

如果我们将文件发布到该 URL,它将保存在 blobstore 中。但是请注意,本地开发 Web 服务器的默认 URL 是 http://127.0.0.1:8888/,而 blobstore 生成的 URL 是 http://&lt;computername&gt;:8888/。这将在以后引起问题,因为出于安全原因,Plupload 将无法将文件 POST 到另一个域。这只发生在本地开发服务器上,发布的应用程序只有一个 URL。通过在 Eclipse 中编辑运行配置来修复它,将 -bindAddress &lt;computername&gt; 添加到参数中。这将导致本地开发服务器在 http://&lt;computername&gt;:8888/ 上托管 Web 应用程序。您可能需要在 GWT 浏览器插件中允许 &lt;computername&gt; 才能在此更改后加载应用程序。

到目前为止一切顺利,我们拥有所需的 servlet。

第 2 步 - 上传

下载Plupload(我用的是最新版本,1.5.4),解压,把js文件夹复制到我们GWT应用程序的war目录下。对于这个例子,我们不会使用jquery.plupload.queuejquery.ui.plupload,因为我们将创建自己的GUI。我们还需要jQuery,我从Google APIs下载的。

接下来,我们需要在应用程序中包含 JavaScript,因此编辑 index.html 并将以下内容添加到 &lt;head&gt; 标记中。

<script type="text/javascript" language="javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" language="javascript" src="js/plupload.full.js"></script>

所以现在我们的应用程序中包含了 Plupload。接下来,我们需要将它包装起来以便能够与 GWT 一起使用。这是使用 gwt-plupload 的地方。我没有使用项目中的 jar 文件,而是复制了源文件以便能够对它们进行修改。包装器的主要对象是Plupload 类,由PluploadBuilder 构造。还有接口PluploadListener,可以实现接收客户端事件。

第 3 步 - 组合起来

所以现在我们需要在我们的 GWT 应用程序中实际使用 Plupload。我将以下内容添加到Index.ui.xml UIBinder:

<g:Button text="Browse" ui:field="btnBrowse" />
<g:Button text="Start Upload" ui:field="btnStart" /><br />
<br />
<h:CellTable width="600px" ui:field="tblFiles" />

有一个用于浏览文件的按钮、一个用于开始上传的按钮和一个用于显示上传状态的 CellTable。在Index.java中,我们初始化Plupload如下:

btnBrowse.getElement().setId("btn-browse");
PluploadBuilder builder = new PluploadBuilder();
builder.runtime("html5");
builder.useQueryString(false);
builder.multipart(true);
builder.browseButton(btnBrowse.getElement().getId());
builder.listener(this);
plupload = builder.create();
plupload.init();

runtime 属性告诉 Plupload 使用哪个后端(我只测试了 HTML5,但其他的应该也可以)。 Blobstore 需要启用 multipart。我们还需要为浏览按钮设置一个 ID,然后告诉 Plupload 使用该 ID。单击此按钮将弹出 Plupload 的文件选择对话框。最后,我们将自己添加为侦听器(实现PluploadListener)和create()init() Plupload。

要显示准备上传的文件,我们只需要将数据添加到来自UploadListener 的事件中的tblFilesDataProvider 列表数据提供者。

@Override
public void onFilesAdded(Plupload p, List<File> files) {
    tblFilesDataProvider.getList().addAll(files);
}

要显示进度,我们只需在收到进度更改通知时更新列表:

@Override
public void onFileUploadProgress(Plupload p, File file) {
    tblFilesDataProvider.refresh();
}

我们还为btnStart 实现了一个点击处理程序,它只是告诉 Plupload 开始上传。

@UiHandler("btnStart")
void btnStart_Click(ClickEvent event) {
    plupload.start();
}

现在可以选择文件,它们将被添加到待上传列表中,我们可以开始上传。剩下的唯一部分就是实际使用我们之前实现的 servlet。目前,Plupload 不知道 POST 上传到哪个 URL,所以我们需要告诉它。这是我对 gwt-plupload 源代码进行更改的地方(除了小错误修复);我向 Plupload 添加了一个名为 fetchNewUploadUrl 的函数。它的作用是在我们之前定义的 servlet 上执行 Ajax GET 请求以获取上传 URL。它同步执行此操作(为什么稍后会清楚)。当请求返回时,它将这个 URL 设置为 Plupload 的 POST URL。

private native void fetchNewUploadUrl(Plupload pl) /*-{
    $wnd.$.ajax({
        url: '/generateblobstoreurl',
        async: false,
        success: function(data) {
          pl.settings.url = data;
        },
    });
}-*/;

public void fetchNewUploadUrl() {
    fetchNewUploadUrl(this);
}

Plupload 将在其自己的 POST 请求中发布每个文件。这意味着我们需要在每次上传开始之前给它一个新的 URL。幸运的是,PluploadListener 中有一个事件我们可以实现。这就是请求必须同步的原因:否则在我们在下面的事件处理程序中收到上传 URL 之前,上传就会开始(pl.fetchNewUploadUrl() 会立即返回)。

@Override
public void onBeforeUpload(Plupload pl, File cast) {
    pl.fetchNewUploadUrl();
}

就是这样!您现在拥有 GWT HTML5 多文件上传功能,可将文件放置在 AppEngine Blobstore 中!

传递参数

如果您想添加其他参数(例如上传文件所属实体的 ID),我添加了一个关于如何添加的示例。 Plupload 上有一个名为 setExtraValue() 的方法,我实现为:

public native void setExtraValue(String value) /*-{
    this.settings.multipart_params = {extravalue: value}
}-*/;

额外的值可以作为multipart_params 传递。这是一个映射,因此可以扩展功能以允许许多任意键值对。该值可以在onBeforeUpload() 事件处理程序中设置

@Override
public void onBeforeUpload(Plupload pl, File cast) {
    pl.setExtraValue(System.currentTimeMillis() + " is unique.");
    pl.fetchNewUploadUrl();
}

并在接收完成上传的 servlet 中检索

String value = req.getParameter("extravalue");

示例项目包含此示例代码。

最后的话

我绝不是专业的 GWT 开发人员。这是我经过数小时的挫折后想出的,没有找到我想要的功能。在我开始工作后,我想我应该写一个完整的示例,因为我使用/关注的每个组件/博客文章/等都遗漏了一些部分。我并不以任何方式暗示这是最佳实践代码。欢迎提出意见、改进和建议!

【讨论】:

  • 很好的答案!帮了我很多!
  • 哇!多么传奇的发布这个。非常感谢!!
  • 对我来说太棒了!这真的很棒。 +1
猜你喜欢
  • 2011-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-13
  • 2011-06-06
相关资源
最近更新 更多