首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

精通 Grails:檔案上傳與 Atom 聯合供稿完善部落格引擎

     編輯:Java 相關知識

Blogito 系列最終回:實作檔案上傳支援圖片/HTML/任意檔案顯示,並手寫 Atom Feed,打造功能完整的微型部落格引擎。

修復遺留 Bug:作者欄位不可為空

新增 Entry.author 關聯後,舊版 create.gsp 缺少作者欄位導致儲存失敗。兩種修法:

  1. 表單加隱藏欄位 <input type="hidden" name="author.id" value="${session.user.id}"/>
  2. 伺服器端強制綁定(推薦):
def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)  // 關鍵行
    // 處理檔案上傳...
    if (!entryInstance.hasErrors() && entryInstance.save()) { ... }
}

檔案上傳完整實作

1. View:啟用 multipart 表單

<g:uploadForm action="save" method="post">
    <tr class="prop">
        <td class="name"><label for="payload">File:</label></td>
        <td class="value"><input type="file" id="payload" name="payload"/></td>
    </tr>
</g:uploadForm>

含檔案上傳欄位的表單

2. Controller:處理上傳檔案

def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)
    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
    }
    // 儲存邏輯...
}

主控台輸出:

Class: class org.springframework.web.multipart.commons.CommonsMultipartFile
Name: payload
OriginalFileName: Grails_logo.jpg
Size: 8065
ContentType: image/jpeg

3. Domain:新增 filename 欄位

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, summary, filename
    Date dateCreated, lastUpdated
}

4. 顯示上傳內容:TagLib 多型處理

class EntryTagLib {
    def displayFile = { attrs, body ->
        def user = attrs['user']
        def filename = attrs['filename']
        if (filename) {
            def extension = filename.split("\\.")[-1].toUpperCase()
            def userDir = "payload/${user}"
            switch (extension) {
                case ['JPG', 'PNG', 'GIF']:
                    out << """<p><img src="${createLinkTo(dir: userDir, file: filename)}" alt="${filename}" title="${filename}"/></p>"""
                    break
                case 'HTML':
                    def webRootDir = servletContext.getRealPath("/")
                    out << new File(webRootDir + "/" + userDir, filename).text
                    break
                default:
                    out << """<p><a href="${createLinkTo(dir: userDir, file: filename)}">${filename}</a></p>"""
                    break
            }
        } else {
            out << "<!-- no file -->"
        }
    }
}

GSP 簡化為:

<g:displayFile filename="${entryInstance.filename}" user="${entryInstance.author.login}" />

顯示上傳圖片

支援圖片/HTML/下載連結三種顯示

手寫 Atom Feed:零插件依賴

Controller:atom 動作

def atom = {
    if (!params.max) params.max = 10
    def list = Entry.list(params)
    def lastUpdated = list[0]?.lastUpdated
    [entryInstanceList: list, lastUpdated: lastUpdated]
}

View:atom.gsp 產出標準 XML

<% response.setContentType("application/atom+xml") %>
<feed xmlns="https://www.w3.org/2005/Atom">
    <title type="text">News from Blogito.org</title>
    <link rel="alternate" type="text/html" href="https://blogito.org/"/>
    <link rel="self" type="application/atom+xml" href="https://blogito.org/entry/atom"/>
    <updated><g:atomDate>${lastUpdated}</g:atomDate></updated>
    <author><name>Blogito.org</name></author>
    <id>tag:blogito.org,2009-01-01:entry/atom</id>
    <generator uri="https://blogito.org" version="0.1">Hand-rolled Grails code</generator>
    <g:each in="${entryInstanceList}" var="entryInstance">
        <g:render template="atomEntry" bean="${entryInstance}" var="entryInstance"/>
    </g:each>
</feed>

TagLib:RFC 3339 日期格式

class DateTagLib {
    static final String ATOM_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'-07:00'"
    def atomDate = { attrs, body ->
        def b = attrs.body ?: body()
        def d = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(b)
        out << new SimpleDateFormat(ATOM_DATE_FORMAT).format(d)
    }
}

Partial Template:_atomEntry.gsp

<entry xmlns='https://www.w3.org/2005/Atom'>
    <author><name>${entryInstance.author.name}</name></author>
    <published><g:atomDate>${entryInstance.dateCreated}</g:atomDate></published>
    <updated><g:atomDate>${entryInstance.lastUpdated}</g:atomDate></updated>
    <link href="https://blogito.org/blog/${entryInstance.author.login}/${entryInstance.title.encodeAsUnderscore()}" rel="alternate" title="${entryInstance.title}" type="text/html"/>
    <id>tag:blogito.org,2009:/blog/${entryInstance.author.login}/${entryInstance.title.encodeAsUnderscore()}</id>
    <title type="text">${entryInstance.title}</title>
    <content type="xhtml">
        <div xmlns="https://www.w3.org/1999/xhtml">${entryInstance.summary}</div>
    </content>
</entry>

開放存取:調整 BeforeInterceptor

def beforeInterceptor = [action: this.&auth, except: ['index', 'list', 'show', 'atom']]

W3C Feed Validator 驗證通過

頁首含 Feed 圖示

最終代碼規模

$ grails stats
+----------------------+-------+-------+
| Name                 | Files | LOC   |
+----------------------+-------+-------+
| Controllers          | 2     | 127   |
| Domain Classes       | 2     | 34    |
| Tag Libraries        | 3     | 66    |
| Unit Tests           | 6     | 24    |
| Integration Tests    | 1     | 10    |
+----------------------+-------+-------+
| Totals               | 14    | 261   |
+----------------------+-------+-------+

**261 行代碼、4 篇文章篇幅、實際約 1 天開發量** 完成功能完整部落格引擎。

結語

Blogito 現具備:多用戶、身分驗證、授權、檔案上傳、Atom Feed。下一階段將引入插件機制擴充評論、標籤等功能,深入探索 Grails 插件生態系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved