使用 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 解决方案
我们建议按照下一部分中的说明进行操作,并逐步创建应用程序。但是,你可以直接进入已完成的示例。
-
下载并解压缩源代码
或者
-
克隆Git代码存储库:
git clone https://github.com/grails-guides/grails-file-download-excel.git
然后,在下载/克隆的项目的根项目中cd
到complete
文件夹。
3 编写应用程序
grails create-app example.grails.complete
3.1 图书
创建Book
POGO
package example.grails
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.TupleConstructor
@CompileStatic
@EqualsAndHashCode
@TupleConstructor
class Book {
String isbn
String name
}
创建一个示例服务,可以获取多本书
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),它不仅关注内容侧,还关注于轻松设置样式。
dependencies {
...
..
.
compile "builders.dsl:spreadsheet-builder-poi:$spreadsheetBuilderVersion"
compile "builders.dsl:spreadsheet-builder-groovy:$spreadsheetBuilderVersion"
}
3.3 创建 Excel
将样式配置外化到实现 builders.dsl.spreadsheet.builder.api.Stylesheet
接口的类中,以最大程度地提高代码重用。
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 文件的服务。
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 控制器
创建一个控制器
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 文件。
3.5 测试
通常,在许多应用程序中文件传输仍未经过测试。在本节中,您将了解测试文件下载及其下载文件内容是否符合我们的预期是一件多么容易的事情。
我们还使用了 Geb,它是一种浏览器自动化解决方案。
默认情况下,Grails 会包含必需的 Geb 依赖项
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 选项。
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 链接
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 二进制文件。
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 文件已下载并且内容符合我们的预期。
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