尼克约翰逊为此写了一些很棒的博客文章。他使用称为Plupload的通用且广为接受的 JavaScript 上传组件,并将文件上传到用 Python 编写的 AppEngine 应用程序。Plupload 支持不同的后端(运行时)以支持多个文件选择(HTML5、flash、Silverlight 等)并处理上传进度和其他与上传相关的客户端事件。
他的解决方案的问题是(1)它在 Python 中,(2)它在 JavaScript 中。这就是gwt-plupload的作用所在。它是由 Samuli Järvelä 编写的 Plupload的JSNI包装器,它允许在 GWT 环境中使用 Plupload。然而,该项目已经过时(自 2010 年以来没有提交),但我们可以使用它来获得灵感。
因此,以下是构建多文件上传组件的分步说明。这将全部在一个项目中,但它(尤其是 JSNI 包装器)可以提取到它自己的 .jar 文件或库中,以便在其他项目中重用。源代码可在 Bitbucket 上找到。
该应用程序在http://gwt-gaemultiupload-example.appspot.com/上的 AppEngine 上可用(不计费,所以不要指望它可用或工作)。
截图
第 1 步 - 小服务程序
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>
到参数中。这将导致本地开发服务器改为托管 Web 应用程序http://<computername>:8888/
。您可能需要<computername>
在 GWT 浏览器插件中允许它在此更改后加载应用程序。
到目前为止一切顺利,我们拥有了我们需要的 servlet。
第 2 步 - 上传
下载 Plupload(我使用的是最新版本,1.5.4),解压缩,并将js
文件夹复制到war
我们 GWT 应用程序的目录中。对于这个例子,我们不会使用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,但其他的应该也可以)。multipart
需要启用Blobstore 。我们还需要为浏览按钮设置一个 ID,然后告诉 Plupload 使用该 ID。单击此按钮将弹出 Plupload 的文件选择对话框。最后,我们将自己添加为侦听器(实现PluploadListener
)create()
和init()
Plupload。
要显示准备上传的文件,我们只需将数据添加到tblFilesDataProvider
事件中的列表数据提供程序 from UploadListener
。
@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 开发人员。这是我经过数小时的挫折后想出的,没有找到我想要的功能。在我开始工作后,我想我应该写一个完整的例子,因为我使用/关注的每个组件/博客文章/等都遗漏了一些部分。我并不以任何方式暗示这是最佳实践代码。欢迎提出意见、改进和建议!