显示导航

在 Grails 应用中下载 Excel 文件

了解使用 Grails 和 Spreadsheet Builder 库下载 Excel 文件的方法。

作者:Sergio del Amo

Grails 版本 3.3.9

1 培训

Grails 培训 - 由创建并积极维护 Grails 框架的人员开发和提供!

2 入门

在本指南中,我们将展示通过创建一个下载包含书籍列表的 Excel 文件的应用程序,来演示 Grails 文件传输功能。

2.1 你需要

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

  • 空闲时间

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

  • 已安装且正确配置了 JAVA_HOME 的 JDK 1.7 或以上版本

2.2 解决方案

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

或者

然后,将 cd 切换到下载/克隆的项目根目录中的 complete 文件夹。

3 编写应用程序

grails create-app example.grails.complete --features=events,geb2,hibernate5

我们正在使用包括 Geb 2Gradle WebDriver 二进制文件 插件的 geb2 特性。Geb 2 要求 JDK 1.8 或更高版本。

3.1 书籍

创建 Book POGO

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:2.0.0.RC1'
    compile 'builders.dsl:spreadsheet-builder-groovy:2.0.0.RC1'
}

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 CLI 使用geb2功能。

Grails geb2功能包括必要的 Geb 依赖关系

build.gradle
    testCompile "org.grails.plugins:geb"
    testCompile "org.seleniumhq.selenium:selenium-remote-driver:3.6.0"
    testCompile "org.seleniumhq.selenium:selenium-api:3.6.0"
    testCompile "org.seleniumhq.selenium:selenium-support:3.6.0"
    testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:3.6.0"
    testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:3.6.0"

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

ChromeOptions options = new ChromeOptions()
if ( System.getProperty('download.folder') ) {
    options.setExperimentalOption("prefs", [
            "profile.default_content_settings.popups":  0, (1)
            "download.default_directory": System.getProperty('download.folder') (2)
    ])
}

environments {

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

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

    // 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 "com.bertramlabs.plugins:asset-pipeline-gradle:2.15.1"
    }
}

apply plugin:"com.energizedwork.webdriver-binaries"

dependencies {
...
..
.
}

webdriverBinaries {
    chromedriver '2.45'
    geckodriver '0.23.0'
}

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 运行应用程序

使用命令 `./gradlew bootRun` 运行应用程序,该命令将在 8080 端口启动应用程序。

5 有关 Grails 的帮助

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

OCI 是 Grails 的发源地

认识团队