如何使用Apache POI和Kotlin在Spring Boot REST API中生成Excel报告

2020年12月30日10:42:05 发表评论 67 次浏览

本文概述

在本文中, 我想向你展示如何在.xls和.xlsx格式(也称为Open XML)Spring Boot REST API与Apache POI和Kotlin.

完成本指南后, 你将对如何创建自定义单元格格式, 样式和字体有基本的了解。最后, 我将向你展示如何创建Spring Boot REST端点, 以便你可以轻松下载生成的文件。

为了更好地可视化我们将学到的内容, 请查看结果文件的预览:

如何使用Apache POI和Kotlin在Spring Boot REST API中生成Excel报告1

步骤1:添加必要的导入

第一步, 我们创建一个Spring Boot项目(强烈建议你使用春季初始化页)并添加以下导入:

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.apache.poi:poi:4.1.2")
implementation("org.apache.poi:poi-ooxml:4.1.2")

让我解释一下每个库的目的:

  • 的Spring Boot Starter网站在我们的应用程序中创建REST API是必需的。
  • 的Apache POI是用于处理Excel文件的复杂Java库。如果我们只想与 .xls格式, 然后poi导入就足够了。在我们的情况下, 我们想添加对.xlsx格式, 所以poi-ooxml组件也是必要的。

步骤2:建立模型

下一步, 我们创建一个名为CustomCellStyle具有4个常量:

enum class CustomCellStyle {
    GREY_CENTERED_BOLD_ARIAL_WITH_BORDER, RIGHT_ALIGNED, RED_BOLD_ARIAL_WITH_BORDER, RIGHT_ALIGNED_DATE_FORMAT
}

尽管此枚举类的目的目前似乎有点难以理解, 但在接下来的部分中, 所有这些将变得显而易见。

步骤3:准备单元格样式

Apache POI库随附于单元格样式接口, 我们可以使用它定义行, 列和单元格中的自定义样式和格式。

让我们创建一个样式生成器组件, 它将负责准备包含我们自定义样式的地图:

@Component
class StylesGenerator {

    fun prepareStyles(wb: Workbook): Map<CustomCellStyle, CellStyle> {
        val boldArial = createBoldArialFont(wb)
        val redBoldArial = createRedBoldArialFont(wb)

        val rightAlignedStyle = createRightAlignedStyle(wb)
        val greyCenteredBoldArialWithBorderStyle =
            createGreyCenteredBoldArialWithBorderStyle(wb, boldArial)
        val redBoldArialWithBorderStyle =
            createRedBoldArialWithBorderStyle(wb, redBoldArial)
        val rightAlignedDateFormatStyle =
            createRightAlignedDateFormatStyle(wb)

        return mapOf(
            CustomCellStyle.RIGHT_ALIGNED to rightAlignedStyle, CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER to greyCenteredBoldArialWithBorderStyle, CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER to redBoldArialWithBorderStyle, CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT to rightAlignedDateFormatStyle
        )
    }
}

如你所见, 使用这种方法, 我们将一次创建每个样式并将其放置在地图中, 以便以后可以引用它。

我们可以在这里使用很多设计技术, 但是我相信使用map和enum常量是使代码更简洁, 更易于修改的最佳方法之一。

话虽如此, 让我们在生成器类中添加一些缺少的函数。让我们先从自定义字体开始:

private fun createBoldArialFont(wb: Workbook): Font {
    val font = wb.createFont()
    font.fontName = "Arial"
    font.bold = true
    return font
}

的createBoldArialFont函数将创建一个新的Arial Font粗体实例, 稍后我们将使用它。

同样, 让我们​​实现一个createRedBoldArialFont功能并将字体颜色设置为红色:

private fun createRedBoldArialFont(wb: Workbook): Font {
    val font = wb.createFont()
    font.fontName = "Arial"
    font.bold = true
    font.color = IndexedColors.RED.index
    return font
}

之后, 我们可以添加其他功能来创建个人单元格样式实例:

private fun createRightAlignedStyle(wb: Workbook): CellStyle {
    val style: CellStyle = wb.createCellStyle()
    style.alignment = HorizontalAlignment.RIGHT
    return style
}

private fun createBorderedStyle(wb: Workbook): CellStyle {
    val thin = BorderStyle.THIN
    val black = IndexedColors.BLACK.getIndex()
    val style = wb.createCellStyle()
    style.borderRight = thin
    style.rightBorderColor = black
    style.borderBottom = thin
    style.bottomBorderColor = black
    style.borderLeft = thin
    style.leftBorderColor = black
    style.borderTop = thin
    style.topBorderColor = black
    return style
}

private fun createGreyCenteredBoldArialWithBorderStyle(wb: Workbook, boldArial: Font): CellStyle {
    val style = createBorderedStyle(wb)
    style.alignment = HorizontalAlignment.CENTER
    style.setFont(boldArial)
    style.fillForegroundColor = IndexedColors.GREY_25_PERCENT.getIndex();
    style.fillPattern = FillPatternType.SOLID_FOREGROUND;
    return style
}

private fun createRedBoldArialWithBorderStyle(wb: Workbook, redBoldArial: Font): CellStyle {
    val style = createBorderedStyle(wb)
    style.setFont(redBoldArial)
    return style
}

private fun createRightAlignedDateFormatStyle(wb: Workbook): CellStyle {
    val style = wb.createCellStyle()
    style.alignment = HorizontalAlignment.RIGHT
    style.dataFormat = 14
    return style
}

请记住, 以上示例仅代表一小部分CellStyle的可能性。如果你想查看完整列表, 请参阅官方文档这里.

步骤4:创建ReportService类

下一步, 我们实现ReportService负责创建的类 .xlsx和.xls文件并将其作为ByteArray实例返回:

@Service
class ReportService(
    private val stylesGenerator: StylesGenerator
) {
    fun generateXlsxReport(): ByteArray {
        val wb = XSSFWorkbook()

        return generateReport(wb)
    }

    fun generateXlsReport(): ByteArray {
        val wb = HSSFWorkbook()

        return generateReport(wb)
    }
 }

如你所见, 这两种格式的生成之间唯一的区别是工作簿实施。用过的。对于 的 .xlsx 格式, 我们将使用XSSF工作簿类, 对于.xls, 我们将使用HSSF工作簿.

让我们将其余代码添加到ReportService:

private fun generateReport(wb: Workbook): ByteArray {
    val styles = stylesGenerator.prepareStyles(wb)
    val sheet: Sheet = wb.createSheet("Example sheet name")

    setColumnsWidth(sheet)

    createHeaderRow(sheet, styles)
    createStringsRow(sheet, styles)
    createDoublesRow(sheet, styles)
    createDatesRow(sheet, styles)

    val out = ByteArrayOutputStream()
    wb.write(out)

    out.close()
    wb.close()

    return out.toByteArray()
}

private fun setColumnsWidth(sheet: Sheet) {
    sheet.setColumnWidth(0, 256 * 20)

    for (columnIndex in 1 until 5) {
        sheet.setColumnWidth(columnIndex, 256 * 15)
    }
}

private fun createHeaderRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(0)

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("Column $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER]
    }
}

private fun createRowLabelCell(row: Row, styles: Map<CustomCellStyle, CellStyle>, label: String) {
    val rowLabel = row.createCell(0)
    rowLabel.setCellValue(label)
    rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}

private fun createStringsRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(1)
    createRowLabelCell(row, styles, "Strings row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("String $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}

private fun createDoublesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(2)
    createRowLabelCell(row, styles, "Doubles row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue(BigDecimal("${columnNumber}.99").toDouble())
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}

private fun createDatesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(3)
    createRowLabelCell(row, styles, "Dates row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue((LocalDate.now()))
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
    }
}

如你所见, 第一件事generateReport函数的作用是设置初始化样式。我们通过工作簿实例样式生成器作为回报, 我们得到了一张地图, 稍后将使用它来获取适当的CellStyles。

之后, 它将在我们的工作簿中创建一个新工作表, 并为其传递一个名称。

然后, 它调用负责设置列宽并在我们的工作表上逐行操作的函数。

最后, 它将工作簿写到ByteArrayOutputStream。

让我们花点时间分析一下每个函数的确切功能:

private fun setColumnsWidth(sheet: Sheet) {
    sheet.setColumnWidth(0, 256 * 20)

    for (columnIndex in 1 until 5) {
        sheet.setColumnWidth(columnIndex, 256 * 15)
    }
}

顾名思义, setColumnsWidth负责设置工作表中列的宽度。第一个参数传递给setColumnWidth表示columnIndex, 而第二个则设置宽度(以字符宽度的1/256为单位)。

private fun createRowLabelCell(row: Row, styles: Map<CustomCellStyle, CellStyle>, label: String) {
    val rowLabel = row.createCell(0)
    rowLabel.setCellValue(label)
    rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}

的createRowLabelCell函数负责在传递的行的第一列中添加一个单元格, 同时将其值设置为指定的标签并设置样式。我决定添加此功能以略微减少代码的冗余性。

以下所有功能都很相似。他们的目的是创建一个新行, 调用createRowLabelCell功能(除createHeaderRow), 然后在工作表中添加五列数据。

private fun createHeaderRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(0)

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("Column $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER]
    }
}
private fun createStringsRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(1)
    createRowLabelCell(row, styles, "Strings row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue("String $columnNumber")
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}
private fun createDoublesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(2)
    createRowLabelCell(row, styles, "Doubles row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue(BigDecimal("${columnNumber}.99").toDouble())
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
    }
}
private fun createDatesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
    val row = sheet.createRow(3)
    createRowLabelCell(row, styles, "Dates row")

    for (columnNumber in 1 until 5) {
        val cell = row.createCell(columnNumber)

        cell.setCellValue((LocalDate.now()))
        cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
    }
}

步骤5:实现REST ReportController

最后一步, 我们将实现一个名为ReportController。它将负责处理到达我们两个REST端点的POST请求:

  • / api / report / xlsx-在.xlsx格式
  • / api / report / xls-与上述相同, 但格式为.xls
@RestController
@RequestMapping("/api/report")
class ReportController(
    private val reportService: ReportService
) {

    @PostMapping("/xlsx")
    fun generateXlsxReport(): ResponseEntity<ByteArray> {
        val report = reportService.generateXlsxReport()

        return createResponseEntity(report, "report.xlsx")
    }

    @PostMapping("/xls")
    fun generateXlsReport(): ResponseEntity<ByteArray> {
        val report = reportService.generateXlsReport()

        return createResponseEntity(report, "report.xls")
    }

    private fun createResponseEntity(
        report: ByteArray, fileName: String
    ): ResponseEntity<ByteArray> =
        ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$fileName\"")
            .body(report)

}

上面代码中最有趣的部分是createResponseEntity函数, 该函数将传递的ByteArray及其生成的报告设置为响应主体。

此外, 我们将内容类型响应的标头为应用/八位字节流, 内容处置为附件; filename = <文件名>.

第6步:使用邮递员测试一切

最后, 我们可以运行和测试我们的Spring Boot应用程序, 例如使用Gradlew命令:

./gradlew bootRun

默认情况下, Spring Boot应用程序将在端口8080上运行, 因此让我们打开邮差(或其他任何工具), 请指定开机自检请求本地主机:8080 / api / report / xls并击中发送并下载按钮:

如何使用Apache POI和Kotlin在Spring Boot REST API中生成Excel报告2

如果一切顺利, 我们应该看到一个窗口, 可以保存.xls文件。

同样, 让我们​​测试第二个端点:

如何使用Apache POI和Kotlin在Spring Boot REST API中生成Excel报告3

这次, 文件扩展名应该是 .xlsx.

总结

这就是本文的全部!我们已经介绍了使用Apache POI和Kotlin在Spring Boot REST API中生成Excel报告的过程。

如果你喜欢它, 并希望通过类似的文章学习其他主题, 请访问我的博客, 编码器.

最后一件事:有关完整工作项目的源代码, 请参阅这个GitHub仓库.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: