在 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 解决方案
我们建议按照下一部分中的说明并逐步创建应用程序。但是,你可以直接转到完成的示例。
-
下载并解压缩源代码
或者
-
克隆 Git 储存库:
git clone https://github.com/grails-guides/grails-file-download-excel.git
然后,将 cd
切换到下载/克隆的项目根目录中的 complete
文件夹。
3 编写应用程序
grails create-app example.grails.complete --features=events,geb2,hibernate5
我们正在使用包括 Geb 2 和 Gradle WebDriver 二进制文件 插件的 geb2 特性。Geb 2 要求 JDK 1.8 或更高版本。 |
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:2.0.0.RC1'
compile 'builders.dsl:spreadsheet-builder-groovy:2.0.0.RC1'
}
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 CLI 使用geb2
功能。
Grails geb2
功能包括必要的 Geb 依赖关系
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 选项来控制下载路径。
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 链接
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 "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 文件是否已下载,并且内容是否符合我们的预期。
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 端口启动应用程序。