精通 Grails: 文件上传和 Atom 联合

来源:developerWorks 中国 作者:Scott Davis
  
在这一期的 精通 Grails 中,Scott Davis 展示如何将文件上传到 Grails 应用程序,并设置一个 Atom syndication feed。完成最后这些部分之后,Blogito 便成为一个完整的博客服务器。

在过去几期的 精通 Grails 文章中,您一直在逐步构建一个小型的博客服务(Blogito)。在这篇文章中,Blogito 将最终完工,成为一个实用的博客应用程序。您将为博客条目主体实现文件上传功能,并添加自己制作的用于聚合的 Atom feed。

关于本系列

Grails 是一种新型 Web 开发框架,它将常见的 Spring 和 Hibernate 等 Java 技术与当前流行的约定优于配置等实践相结合。Grails 是用 Groovy 编写的,它可以提供与遗留 Java 代码的无缝集成,同时还可以加入脚本编程语言的灵活性和动态性。学习完 Grails 之后,您将彻底改变看待 Web 开发的方式。

但是,在开始之前,请注意在上一篇文章(“身份验证和授权”)中,我加入的认证使 UI 中出现一个细小的 bug。在加入新的特性之前,应该修复这个 bug。

修复隐藏的 bug

启动 Grails 时,grails-app/conf/Bootstrap.groovy 增加 2 个用户和 4 个新的博客条目。但是,如果尝试通过 Web 界面增加博客条目,会发生什么?可以使用下面的步骤试试看:

  1. 以用户名 jsmith 和密码 wordpass 登录。
  2. 单击 New Entry。
  3. 添加标题和摘要。
  4. 单击 Create。

您将看到以下错误:Property [author] of class [class Entry] cannot be null。那么,这个 bug 是如何引入到应用程序中的?毕竟,bootstrap 代码还能正常工作。

在第一篇 Blogito 文章(“改变 Grails 应用程序的外观”)中,我让您通过输入 grails generate-views Entry 生成 Groovy Server Pages(GSP)视图。在随后的文章中,我更改了 domain 类,但是从未让您再回过头来生成视图。当我添加 Entry 与 User 之间的 1:M 关系时,磁盘上的 create.gsp 视图一直不变,如清单 1 所示。(还记得吗,belongsTo 创建一个名为 author 的字段,该字段的类型为 User)。


清单 1. 打破 GSP 的 1:M 关系
				
class Entry {
  static belongsTo = [author:User]

  String title
  String summary
  Date dateCreated
  Date lastUpdated
}

不得不说,要使一切同步,最安全的方式还是通过动态脚手架生成视图 — 特别是在开发的早期,域模型不断变化的时候,更是如此。当然,不能仅仅依靠通过脚手架生成的视图,但是,当您在磁盘上生成 GSP 时,使它们保持最新的责任就从 Grails 转移到您自己身上。

如果现在为 Entry 类生成视图的话,Grails 会提供一个组合框,其中显示一个 Author 列表,如清单 2 所示。您自己不要 这样做 — 这只是为了演示。稍后我将提供两种不同的选项。


清单 2. 为 1:M 关系生成的组合框
				
<g:form action="save" method="post" >
  <div class="dialog">
    <table>
      <tbody>
        <!-- SNIP -->
        <tr class="prop">
          <td valign="top" class="name">
            <label for="author">Author:</label>
          </td>
          <td valign="top"
              class="value ${hasErrors(bean:entryInstance,
                                       field:'author','errors')}">
            <g:select optionKey="id"
                      from="${User.list()}"
                      name="author.id"
                      value="${entryInstance?.author?.id}" ></g:select>
          </td>
        </tr>
        <!-- SNIP -->
      </tbody>
    </table>
  </div>
</g:form>

注意 <g:select> 元素。字段名为 author.id。在 “GORM - 有趣的名称,严肃的技术” 中可以了解到,列表中显示的文本来自 User.toString() 方法。该文本通常也是表单提交时作为字段值发回到服务器的值。在这里,optionKey 属性覆盖字段值,从而发回 Author 的 id。(要了解更多关于 <g:select> 标记的信息,请参阅 参考资料)。

为 EntryController.groovy 提供 author.id 字段的最快方式是将一个隐藏字段添加到表单中,如清单 3 所示。由于执行 create 动作前必须登录,而登录的 User 是博客条目的 author,因此对于这个值可以安全地使用 session.user.id。


清单 3. 从表单传递 author.id 字段
				
<g:form action="save" method="post" >
  <input type="hidden" name="author.id" value="${session.user.id}" />
  <!-- SNIP -->
</g:form>

对于像 Blogito 这样的简单的应用程序,这样也许就足够了。但是,这样做留下了一个漏洞,使客户端的黑客有机会为 author.id 注入不同的值。为确保彻底的安全,可以在 save 闭包中添加 Entry.author,如清单 4 所示:


清单 4. 将 author.id 保存在服务器上
				
def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)
    if(!entryInstance.hasErrors() && entryInstance.save()) {
        flash.message = "Entry ${entryInstance.id} created"
        redirect(action:show,id:entryInstance.id)
    }
    else {
        render(view:'create',model:[entryInstance:entryInstance])
    }
}

这是生成控制器时得到的标准 save 闭包,再加上一行定制的代码。entryInstance.author 行根据 session.user.id 值从数据库获取 User,并填充 Entry.author 字段。

在下一节中,您将定制 save 闭包,以处理文件上传,所以您仍可能在安全性方面犯错误,将 清单 4 中的代码添加到 EntryController.groovy 中。重新启动 Grails,确保可以通过 HTML 表单成功地添加新的 Entry。





文件上传

现在又可以创建 Entry,接下来该添加另一个特性。我希望用户在创建新的 Entry 时可以上传文件。这种文件可以是包含整个博客条目的 HTML,也可以是图像或任何其他文件。为实现该特性,需要涉及到 Entry domain 类、EntryController 和 GSP 视图 — 并且要增加一个新的 TagLib。

首先,看看 grails-app/views/entry/create.gsp。添加一个新字段,用于上传文件,如清单 5 所示:


清单 5. 添加一个用于文件上传的字段
				
<g:uploadForm action="save" method="post" >
  <!-- SNIP -->
  <tr class="prop">
    <td valign="top" class="name">
      <label for="payload">File:</label>
    </td>
    <td valign="top">
      <input type="file" id="payload" name="payload"/>
    </td>
  </tr>
</g:uploadForm>

注意,<g:form> 标记已经被改为 <g:uploadForm>。这样便支持从 HTML 表单上传文件。实际上,也可以保留 <g:form> 标记,并增加一个 enctype="multipart/form-data" 属性。(用于 HTML 表单的默认 enctype 是 application/x-www-form-urlencoded)。

如果正确设置了表单的 enctype(或者使用 <g:uploadForm>),就可以添加 <input type="file" /> 字段。这样便为用户提供了一个按钮,用于浏览本地文件系统,并选择上传的文件,如图 1 所示。我的例子使用 Grails 徽标;您也可以使用任何自己喜欢的图像。


图 1. 包含文件上传字段的 Create Entry 表单

现在,客户端表单已经做好了,接下来可以调整服务器端代码,以便用上传的文件做有用的事情。在文本编辑器中打开 grails-app/controllers/EntryController.groovy,将清单 6 中的代码添加到 save 闭包中:


清单 6. 显示关于上传的文件的信息
				
def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)

    //handle uploaded file
    def uploadedFile = request.getFile('payload')
    if(!uploadedFile.empty){
      println "Class: ${uploadedFile.class}"
      println "Name: ${uploadedFile.name}"
      println "OriginalFileName: ${uploadedFile.originalFilename}"
      println "Size: ${uploadedFile.size}"
      println "ContentType: ${uploadedFile.contentType}"
    }

    if(!entryInstance.hasErrors() && entryInstance.save()) {
        flash.message = "Entry ${entryInstance.id} created"
        redirect(action:show,id:entryInstance.id)
    }
    else {
        render(view:'create',model:[entryInstance:entryInstance])
    }
}

时间:2009-07-06 17:18 来源:developerWorks 中国 作者:Scott Davis 原文链接

好文,顶一下
(3)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量