尼克约翰逊为此写了一些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 的工作方式如下:
- 客户端向 blobstore 询问可用于上传文件的 URL。
- 客户端将文件发布到收到的 URL。
- 收到整个 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://<computername>:8888/。这将在以后引起问题,因为出于安全原因,Plupload 将无法将文件 POST 到另一个域。这只发生在本地开发服务器上,发布的应用程序只有一个 URL。通过在 Eclipse 中编辑运行配置来修复它,将 -bindAddress <computername> 添加到参数中。这将导致本地开发服务器在 http://<computername>:8888/ 上托管 Web 应用程序。您可能需要在 GWT 浏览器插件中允许 <computername> 才能在此更改后加载应用程序。
到目前为止一切顺利,我们拥有所需的 servlet。
第 2 步 - 上传
下载Plupload(我用的是最新版本,1.5.4),解压,把js文件夹复制到我们GWT应用程序的war目录下。对于这个例子,我们不会使用jquery.plupload.queue 或jquery.ui.plupload,因为我们将创建自己的GUI。我们还需要jQuery,我从Google APIs下载的。
接下来,我们需要在应用程序中包含 JavaScript,因此编辑 index.html 并将以下内容添加到 <head> 标记中。
<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 开发人员。这是我经过数小时的挫折后想出的,没有找到我想要的功能。在我开始工作后,我想我应该写一个完整的示例,因为我使用/关注的每个组件/博客文章/等都遗漏了一些部分。我并不以任何方式暗示这是最佳实践代码。欢迎提出意见、改进和建议!