În acest articol, aș dori să vă arăt cum să generați rapoarte Excel în .xls și .xlsx formate (cunoscut și ca Open XML) într-un Spring Boot REST API cu Apache POI și Kotlin.

După finalizarea acestui ghid, veți avea o înțelegere fundamentală despre cum să creați formate, stiluri și fonturi de celule personalizate. La final, vă voi arăta cum să creați puncte finale Spring Boot REST, astfel încât să puteți descărca cu ușurință fișierele generate.

Pentru a vizualiza mai bine ceea ce vom învăța, consultați previzualizarea fișierului rezultat:

Cum se genereaza un raport Excel intr un API REST Boot

Pasul 1: Adăugați importurile necesare

Ca prim pas, haideți să creăm un proiect Spring Boot (vă recomand cu drag să utilizați Initializr de primăvară pagină) și adăugați următoarele importuri:

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")

Permiteți-mi să explic scopul fiecărei biblioteci:

ad-banner
  • Spring Boot Web de pornire este necesar pentru a crea API REST în aplicația noastră.
  • Apache POI este o bibliotecă Java complexă pentru lucrul cu fișiere Excel. Dacă am vrea să lucrăm doar cu .xls format, apoi poi importul ar fi suficient. În cazul nostru, am dori să adăugăm suportul pentru .xlsx format, deci poi-ooxml componentă este necesară, de asemenea.

Pasul 2: Creați modelele

Ca pas următor, să creăm o clasă enum numită CustomCellStyle cu 4 constante:

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

Deși scopul acestei clase de enum ar putea părea cam enigmatic în acest moment, totul va deveni clar în următoarele secțiuni.

Pasul 3: Pregătiți stilurile de celule

Biblioteca Apache POI vine cu CellStyle interfață, pe care o putem folosi pentru a defini stilul personalizat și formatarea în rânduri, coloane și celule.

Să creăm un StylesGenerator componentă, care va fi responsabilă pentru pregătirea unei hărți care conține stilurile noastre personalizate:

@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
        )
    }
}

După cum puteți vedea, cu această abordare, creăm fiecare stil o dată și îl punem într-o hartă, astfel încât să ne putem referi la el mai târziu.

Există o mulțime de tehnici de proiectare pe care le-am putea folosi aici, dar cred că utilizarea unei hărți și constante enum este una dintre cele mai bune modalități de a păstra codul mai curat și mai ușor de modificat.

Acestea fiind spuse, să adăugăm câteva funcții lipsă în clasa generatorului. Să începem mai întâi cu fonturi personalizate:

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

createBoldArialFont funcția creează o nouă instanță Arial Font aldinată, pe care o vom folosi mai târziu.

În mod similar, să implementăm un createRedBoldArialFont funcția și setați culoarea fontului la roșu:

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

După aceea, putem adăuga alte funcții responsabile pentru crearea individuală CellStyle instanțe:

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
}

Rețineți că exemplele de mai sus reprezintă doar o mică parte din CellStyle’s posibilități. Dacă doriți să vedeți lista completă, vă rugăm să consultați documentația oficială aici.

Pasul 4: Creați clasa ReportService

Ca următor pas, să implementăm un ReportService clasă responsabilă pentru crearea .xlsx și .xls fișierele și returnarea lor ca instanțe 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)
    }
 }   

După cum puteți vedea, singura diferență dintre generația acestor două formate este tipul de Caiet de lucru implementarea pe care o avem. folosit. Pentru .xlsx format vom folosi XSSFWorkbook clasă, iar pentru .xls vom folosi HSSFWorkbook.

Să adăugăm restul codului la 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]
    }
}

După cum puteți vedea, primul lucru generează raport funcția pe care o face este aceea că stilizează inițializarea. Trecem de Caiet de lucru exemplu la StylesGenerator și, în schimb, obținem o hartă, pe care o vom folosi mai târziu pentru a obține stilurile CellS adecvate.

După aceea, se creează o foaie nouă în registrul nostru de lucru și se transmite un nume pentru aceasta.

Apoi, invocă funcții responsabile de setarea lățimilor coloanelor și de operare pe foaia noastră rând cu rând.

În cele din urmă, scrie cartea noastră de lucru într-un ByteArrayOutputStream.

Să luăm un minut și să analizăm ce face exact fiecare funcție:

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

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

Așa cum sugerează și numele, setColumnsWidth este responsabil pentru setarea lățimilor coloanelor din foaia noastră. Primul parametru trecut la setColumnWidth indică columnIndex, în timp ce al doilea setează lățimea (în unități de 1 / 256th dintr-o lățime de caractere).

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 funcția este responsabilă pentru adăugarea unei celule în prima coloană a rândului trecut, alături de setarea valorii acesteia la eticheta specificată și setarea stilului. Am decis să adaug această funcție pentru a reduce ușor redundanța codului.

Toate funcțiile de mai jos sunt destul de similare. Scopul lor este de a crea un nou rând, invocând createRowLabelCell funcție (cu excepția createHeaderRow) și adăugarea a cinci coloane cu date în foaia noastră.

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]
    }
}

Pasul 5: Implementați REST ReportController

Ca ultim pas, vom implementa o clasă numită ReportController. Acesta va fi responsabil pentru gestionarea cererilor POST care vin la cele două puncte finale REST:

  • / api / report / xlsx – crearea unui raport într-un .xlsx format
  • / api / report / xls – la fel ca mai sus, dar într-un format .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)

}

Cea mai interesantă parte a codului de mai sus este createResponseEntity funcție, care setează ByteArray trecut cu raportul său generat ca un corp de răspuns.

În plus, am setat Tipul de conținut antetul răspunsului ca aplicație / octet-stream, și Content-Disposition ca atașament; nume de fișier = .

Pasul 6: Testați totul cu Postman

În cele din urmă, putem rula și testa aplicația Spring Boot, de exemplu cu gradlew comanda:

./gradlew bootRun

În mod implicit, aplicația Spring Boot va rula pe portul 8080, deci să deschidem Poştaş (sau orice alt instrument), specificați POST solicită să localhost: 8080 / api / report / xls și a lovit Trimiteți și descărcați buton:

1611300006 746 Cum se genereaza un raport Excel intr un API REST Boot

Dacă totul a decurs bine, ar trebui să vedem o fereastră care ne permite să salvăm .xls fişier.

În mod similar, să testăm al doilea punct final:

1611300006 429 Cum se genereaza un raport Excel intr un API REST Boot

De data aceasta, extensia de fișier ar trebui să fie .xlsx.

rezumat

Atât pentru acest articol! Am acoperit procesul de generare a rapoartelor Excel într-un Spring Boot REST API cu Apache POI și Kotlin.

Dacă v-a plăcut și ați dori să aflați alte subiecte prin articole similare, vă rugăm să vizitați blogul meu, Codersee.

Și ultimul lucru: pentru codul sursă al unui proiect complet funcțional, vă rugăm să consultați acest depozit GitHub.