显示导航

使用 Grails 应用程序下载一个 Excel 文件

了解如何使用 Grails 和 Spreadsheet Builder 函数库下载一个 Excel 文件。

作者:塞尔吉奥·德尔·阿莫

Grails 版本 4.0.1

1 培训

Grails 培训 - 专为那些创建和积极维护 Grails 框架的人员而开发和提供的培训!

2 开始

在本指南中,我们将展示 Grails 文件传输功能,方法是创建一个应用程序来下载包含图书清单的 Excel 文件。

2.1 需要准备的

要完成本指南,你需要以下内容

  • 一些时间

  • 一个合适的文本编辑器或 IDE

  • 配置为按适当方式加入“JAVA_HOME”的 JDK 1.8 或更高版本

2.2 解决方案

我们建议按照下一部分中的说明进行操作,并逐步创建应用程序。但是,你可以直接进入已完成的示例

或者

然后,在下载/克隆的项目的根项目中cdcomplete文件夹。

3 编写应用程序

grails create-app example.grails.complete

3.1 图书

创建BookPOGO

src/main/groovy/example/grails/Book.groovy
package example.grails

import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.TupleConstructor

@CompileStatic
@EqualsAndHashCode
@TupleConstructor
class Book {
    String isbn
    String name
}

创建一个示例服务,可以获取多本书

grails-app/services/example/grails/BookService.groovy
package example.grails

import groovy.transform.CompileStatic

@CompileStatic
class BookService {

    List<Book> findAll() {
        [
                new Book("1491950358", "Building Microservices"),
                new Book("1680502395", "Release It!"),
                new Book("0321601912", "Continuous Delivery:"),
        ]
    }
}

3.2 电子表格生成器

添加电子表格生成器的依赖项

电子表格生成器提供了一种便捷的方式来读写 MS Excel OfficeOpenXML 文档(XSLX),它不仅关注内容侧,还关注于轻松设置样式。

build.gradle
dependencies {
    ...
    ..
    .
    compile "builders.dsl:spreadsheet-builder-poi:$spreadsheetBuilderVersion"
    compile "builders.dsl:spreadsheet-builder-groovy:$spreadsheetBuilderVersion"
}

3.3 创建 Excel

将样式配置外化到实现 builders.dsl.spreadsheet.builder.api.Stylesheet 接口的类中,以最大程度地提高代码重用。

src/main/groovy/example/grails/BookExcelStylesheet.groovy
package example.grails

import builders.dsl.spreadsheet.api.FontStyle
import builders.dsl.spreadsheet.builder.api.CanDefineStyle
import builders.dsl.spreadsheet.builder.api.Stylesheet
import groovy.transform.CompileStatic

@CompileStatic
class BookExcelStylesheet implements Stylesheet {
    public static final String STYLE_HEADER = "header"

    @Override
    void declareStyles(CanDefineStyle stylable) {
        stylable.style(STYLE_HEADER, { st ->
            st.font { f -> f.style(FontStyle.BOLD) }
        })
    }
}

创建一个生成 Excel 文件的服务。

grails-app/services/example/grails/BookExcelService.groovy
package example.grails

import builders.dsl.spreadsheet.builder.poi.PoiSpreadsheetBuilder
import groovy.transform.CompileStatic

@CompileStatic
class BookExcelService {
    public static final String SHEET_NAME = "Books"
    public static final String HEADER_ISBN = "Isbn"
    public static final String HEADER_NAME = "Name"
    public static final String EXCEL_FILE_SUFIX = ".xlsx"
    public static final String EXCEL_FILE_PREFIX = "books"
    public static final String EXCEL_FILENAME = EXCEL_FILE_PREFIX + EXCEL_FILE_SUFIX

    void exportExcelFromBooks(OutputStream outs, List<Book> bookList) {
        File file = File.createTempFile(EXCEL_FILE_PREFIX, EXCEL_FILE_SUFIX)
        PoiSpreadsheetBuilder.create(outs).build {
            apply BookExcelStylesheet
            sheet(SHEET_NAME) { s ->
                row {
                    [HEADER_ISBN, HEADER_NAME].each { header ->
                        cell {
                            value header
                            style BookExcelStylesheet.STYLE_HEADER
                        }
                    }
                }
                bookList.each { book ->
                    row {
                        cell(book.isbn)
                        cell(book.name)
                    }
                }
            }
        }
        file
    }
}

3.4 控制器

创建一个控制器

grails-app/controllers/example/grails/ExcelController.groovy
package example.grails

import grails.config.Config
import grails.core.support.GrailsConfigurationAware
import groovy.transform.CompileStatic

import static org.springframework.http.HttpStatus.OK

@CompileStatic
class ExcelController implements GrailsConfigurationAware { (1)

    BookService bookService
    BookExcelService bookExcelService

    String xlsxMimeType
    String encoding

    @Override
    void setConfiguration(Config co) {  (1)
        xlsxMimeType = co.getProperty('grails.mime.types.xlsxMimeType',
                String,
                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
        encoding = co.getProperty('grails.converters.encoding', String, 'UTF-8')
    }

    def index() {
        response.status = OK.value() (2)
        response.setHeader "Content-disposition", "attachment; filename=${BookExcelService.EXCEL_FILENAME}" (3)
        response.contentType = "${xlsxMimeType};charset=${encoding}" (4)
        OutputStream outs = response.outputStream
        bookExcelService.exportExcelFromBooks(outs, bookService.findAll()) (5)
        outs.flush()
        outs.close()
    }
}
1 实现 grails.core.support.GrailsConfigurationAware 以配置 MIME 类型和编码配置。
2 控制器方法可以访问 response 对象,该对象是 Servlet API 的 HttpServletResponse 类的实例
3 设置 Content-Disposition 以指示应下载文件。
4 设置下载 Content-Type
5 将 Excel 文件写入输出流、刷新并关闭它。

默认情况下,从头创建的 Grails 应用程序包含对应用程序中注册的每个控制器的链接。我们将测试单击该链接是否能下载 Excel 文件。

home

3.5 测试

通常,在许多应用程序中文件传输仍未经过测试。在本节中,您将了解测试文件下载及其下载文件内容是否符合我们的预期是一件多么容易的事情。

我们还使用了 Geb,它是一种浏览器自动化解决方案。

默认情况下,Grails 会包含必需的 Geb 依赖项

build.gradle
    testCompile ("org.grails.plugins:geb") {
        exclude group: 'org.gebish', module: 'geb-spock'
    }
    testCompile "org.gebish:geb-spock:$gebVersion"
    testCompile "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-api:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-support:$seleniumVersion"
    testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
    testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"
    testRuntime "org.seleniumhq.selenium:selenium-safari-driver:$seleniumSafariDriverVersion"

Grails geb2 功能会生成一个 src/integration-test/resources/GebConfig.groovy 文件,以配置适用于 Geb 的不同环境。修改它以配置一些用于控制下载路径的 Chrome 选项。

src/integration-test/resources/GebConfig.groovy
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions
import org.openqa.selenium.safari.SafariDriver

environments {

    // You need to configure in Safari -> Develop -> Allowed Remote Automation
    safari {
        driver = { new SafariDriver() }
    }

    // run via “./gradlew -Dgeb.env=chrome iT”
    chrome {
        driver = { new ChromeDriver() }
    }

    // run via “./gradlew -Dgeb.env=chromeHeadless iT”
    chromeHeadless {
        driver = {
            ChromeOptions o = new ChromeOptions()
            o.addArguments('headless')
            new ChromeDriver(o)
        }
    }

    // run via “./gradlew -Dgeb.env=firefoxHeadless iT”
    firefoxHeadless {
        driver = {
            FirefoxOptions o = new FirefoxOptions()
            o.addArguments('-headless')
            new FirefoxDriver(o)
        }
    }

    // run via “./gradlew -Dgeb.env=firefox iT”
    firefox {
        driver = { new FirefoxDriver() }
    }
}
1 禁用确认弹出窗口
2 配置下载文件夹

Geb 使用页面概念模式 - 页面对象模式为我们提供了一种以可重复使用且可维护的方式建模内容的常识方法。创建一个 Geb 页面来封装 Excel 链接

src/integration-test/groovy/example/grails/HomePage.groovy
package example.grails

import geb.Page

class HomePage extends Page {

    static at = { title == 'Welcome to Grails' }

    static url = '/'

    static content = {
        excelLink { $('a', text: contains('Excel'), 0) }
    }

    void downloadExcel() {
        excelLink.click()
    }
}

geb2 功能还会安装 webdriver-binaries Gradle 插件;该插件会下载并缓存与构建运行的操作系统相关的 WebDriver 二进制文件。

build.gradle
buildscript {
    repositories {
...
..
    }
    dependencies {
        classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:$webdriverBinariesVersion"
    }
}

apply plugin:"com.github.erdi.webdriver-binaries"

dependencies {
...
..
.
}

webdriverBinaries {
    chromedriver "${chromeDriverVersion}"
    geckodriver "${geckodriverVersion}"
}

tasks.withType(Test) {
    systemProperty "geb.env", System.getProperty('geb.env') (1)
    systemProperty "download.folder", System.getProperty('download.folder') (2)
    systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
}
1 将系统属性 geb.env 传递到测试中。
2 将系统属性 download.folder 传递到测试中。

创建一个测试来验证 Excel 文件已下载并且内容符合我们的预期。

src/integration-test/groovy/example/grails/DownloadExcelSpec.groovy
package example.grails

import builders.dsl.spreadsheet.query.api.SpreadsheetCriteria
import builders.dsl.spreadsheet.query.api.SpreadsheetCriteriaResult
import builders.dsl.spreadsheet.query.poi.PoiSpreadsheetCriteria
import geb.spock.GebSpec
import grails.testing.mixin.integration.Integration
import spock.lang.IgnoreIf
import spock.util.concurrent.PollingConditions

@Integration
class DownloadExcelSpec extends GebSpec {

    @IgnoreIf({ !sys['download.folder'] || sys['geb.env'] != 'chrome' })
    def "books can be downloaded as an excel file"() {
        given:
        PollingConditions conditions = new PollingConditions(timeout: 5)

        when:
        browser.to HomePage

        then:
        browser.at HomePage

        when: 'clicking excel button'
        String expectedPath = System.getProperty('download.folder') + "/" + BookExcelService.EXCEL_FILENAME
        File outputFile = new File(expectedPath)
        browser.page(HomePage).downloadExcel()

        then: 'an excel file is downloaded'
        conditions.eventually { outputFile.exists() }

        when: 'if we search for a row with a particular value (Building Microservices)'
        SpreadsheetCriteria query = PoiSpreadsheetCriteria.FACTORY.forFile(outputFile)
        SpreadsheetCriteriaResult result = query.query {
            sheet(BookExcelService.SHEET_NAME) {
                row {
                    cell {
                        value 'Building Microservices'
                    }
                }
            }
        }

        then: 'a row is found'
        result.cells.size() == 1

        cleanup:
        outputFile?.delete()
    }
}

要运行测试

$ ./gradlew -Dgeb.env=chrome -Ddownload.folder=/Users/sdelamo/Downloads integrationTest
$ open build/reports/tests/test/index.html

4 Grails 帮助

Object Computing, Inc. (OCI) 赞助了本指南的创建。提供各种咨询和支持服务。

OCI 是 Grails 的家

认识团队