我将如何在GWT和AppEngine Blobstore中创建类似Gmail的现代多文件上传格式?
最普遍提出的解决方案是gwtupload,这是Manolo Carrasco编写的出色的GWT组件。但是,最新版本0.6.6不适用于blobstore(至少我无法使其正常工作),并且它不支持多个文件选择。在最新的0.6.7快照中有一个用于选择多个文件的补丁程序,但是尽管它允许选择多个文件(使用HTML5中的“ multiple”属性),但仍会在一个巨大的POST请求中发送它们(并且显示了进度)一堆文件)。
SO上还有其他问题(例如here或here),但是答案通常使用HTML5“ multiple”属性并将其作为一个大型POST请求发送。它有效,但不是我追求的。
最佳答案
尼克·约翰逊(Nick Johnson)为此写了一些great blog posts。他使用了公认的通用JavaScript上载组件Plupload,并将文件上载到用Python编写的AppEngine-app中。 Plupload支持不同的后端(运行时),以支持多个文件选择(HTML5,Flash,Silverlight等),并处理上载进度和其他与上载相关的客户端事件。
他的解决方案存在的问题是(1)使用Python,以及(2)使用JavaScript。这是gwt-plupload输入图片的地方。它是SamuliJärvelä编写的用于Plupload的JSNI包装器,可在GWT环境中使用Plupload。但是,该项目已经过时(自2010年以来没有提交),但是我们可以将其用作灵感。
因此,遵循有关构建多个文件上载组件的分步说明。这全部都在一个项目中,但是可以将它(特别是JSNI-wrapper)提取到其自己的.jar文件或库中,以在其他项目中重用。源代码可用on Bitbucket here。
该应用程序可在http://gwt-gaemultiupload-example.appspot.com/上的AppEngine上使用(不可计费,因此不要指望它可用或正在运行)。
屏幕截图
第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无法将文件发布到另一个域,这将在以后引起问题。这仅在本地开发服务器上发生,已发布的应用程序将只有一个URL。通过在Eclipse中编辑“运行配置”进行修复,将-bindAddress <computername>
添加到参数中。这将导致本地开发服务器将Web应用托管在http://<computername>:8888/
上。您可能需要允许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。要显示准备上传的文件,我们只需要在
tblFilesDataProvider
事件中将数据添加到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的专业开发人员。经过数小时的挫折之后,我才想到了这个问题,但是却找不到我想要的功能。在工作之后,我想我应该写一个完整的示例,因为我使用/遵循的每个组件/博客文章/等等都遗漏了一部分。我不以任何方式暗示这是最佳实践代码。欢迎提出意见,改进和建议!
关于html - 使用GWT和AppEngine Blobstore上传多个文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14140483/