首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

精通 Grails 文件上传和 Atom 联合(2)

精通 Grails 文件上传和 Atom 联合(2)

文件上传现在又可以创建 Entry,接下来该添加另一个特性。我希望用户在创建新的 Entry 时可以上传文件。这种文件可以是包含整个博客条目的 HTML,也可以是图像或任何其他文件。为实现该特性,需要涉及到 Entry domain 类、EntryController 和 GSP 视图 — 并且要增加一个新的 TagLib。
首先,看看 grails-app/views/entry/create.gsp。添加一个新字段,用于上传文件,如清单 5 所示:
清单 5. 添加一个用于文件上传的字段
1
2
3
4
5
6
7
8
9
10
11
<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. 显示关于上传的文件的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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])
    }
}




注意,这里使用 request.getFile() 方法获得上传的文件的引用。有了该引用后,便可以对它进行各种类型的内省。清单 7 显示上传 Grails 徽标后的控制台输出:
清单 7. 上传文件后的控制台输出
1
2
3
4
5
Class: class org.springframework.web.multipart.commons.CommonsMultipartFile
Name: payload
OriginalFileName: Grails_logo.jpg
Size: 8065
ContentType: image/jpeg




如果知道 Grails 在幕后使用 Spring MVC 框架,那么对此不应感到奇怪:上传的文件是作为 CommonsMultipartFile 对象提供给控制器的。除了公布 HTML 表单字段的名称外,这个类还允许访问原始文件名、文件大小(单位为字节)和文件的 MIME 类型。
接下来的步骤是将上传的文件保存到某个地方。在 save 闭包中添加几行代码,如清单 8 所示:
清单 8. 将上传的文件保存到磁盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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}"

      def webRootDir = servletContext.getRealPath("/")
      def userDir = new File(webRootDir, "/payload/${session.user.login}")
      userDir.mkdirs()
      uploadedFile.transferTo( new File( userDir, uploadedFile.originalFilename))
    }

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




在 Web root 下创建 payload/jsmith 目录后,就可以使用 uploadedFile.transferTo() 方法将文件保存到磁盘。File.mkdirs() 方法是无损的,所以可以多次调用该方法,而不必担心当目录已经存在时会丢失已有的文件。
接下来,将一个 String 字段添加到 Entry 类,以存储 filename,如清单 9 所示。注意要添加一个约束,使这个新字段同时为 blank(在 HTML 表单中)和 nullable(在数据库中)。
清单 9. 将 filename 字段添加到 Entry 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Entry {
  static constraints = {
    title()
    summary(maxSize:1000)
    filename(blank:true, nullable:true)
    dateCreated()
    lastUpdated()
  }

  static mapping = {
    sort "lastUpdated":"desc"
  }

  static belongsTo = [author:User]

  String title
  String summary
  String filename
  Date dateCreated
  Date lastUpdated
}




最后,将 filename 添加到 save 闭包中的 Entry 对象中。清单 10 显示完整的 save 闭包:
清单 10. 将 filename 存储在 Entry 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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}"

      def webRootDir = servletContext.getRealPath("/")
      def userDir = new File(webRootDir, "/payload/${session.user.login}")
      userDir.mkdirs()
      uploadedFile.transferTo( new File( userDir, uploadedFile.originalFilename))
      entryInstance.filename = uploadedFile.originalFilename
    }

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




将上传的文件保存到文件系统的另一种方法是将它们直接存储在数据库中。如果在 Entry 中创建一个名为 payload 的 byte[] 字段,那么可以完全绕过前面添加到 save 闭包的所有定制代码。但是,如果那样做的话,您将错过下一节中所有的趣事。
返回列表