Para implementar una funcionalidad que permita exportar datos de consulta directamente a un archivo Excel, primero necesitamos definir una estructura de encabezados. Esta clase mapea los campos de datos a sus respectivos nombres de columna en la hoja de cálculo.
package model.export;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Configuración de encabezados para exportación Excel
*/
public class ExcelColumnConfig implements Serializable {
private static final long serialVersionUID = 8943726192837465L;
private String dataField;
private String displayLabel;
public ExcelColumnConfig(String dataField, String displayLabel) {
this.dataField = dataField;
this.displayLabel = displayLabel;
}
public Map<String, String> buildColumnMapping() {
Map<String, String> mapping = new LinkedHashMap<>();
mapping.put("code", "Identificador");
mapping.put("description", "Descripción");
mapping.put("transactionRef", "Referencia");
mapping.put("status", "Estado");
mapping.put("errorCode", "Código Error");
mapping.put("errorMessage", "Mensaje Error");
mapping.put("timestamp", "Fecha/Hora");
mapping.put("operator", "Operador");
mapping.put("originIp", "Dirección IP");
mapping.put("duration", "Duración (ms)");
mapping.put("createdAt", "Creación");
mapping.put("modifiedAt", "Modificación");
return mapping;
}
// Getters y setters omitidos por brevedad
}
El servicio principal se encarga de obtener los datos de consulta, generar el archivo Excel temporalmente y convertirlo a formato Base64 para transmisión al cliente.
package service.export;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import model.export.ExcelColumnConfig;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.List;
import java.util.Map;
@Service
public class DataExportService {
@Value("${export.max.rows:50000}")
private int maximumExportRows;
/**
* Genera representación Base64 del archivo Excel
* @param dataList Lista de datos a exportar
* @return Cadena Base64 del archivo Excel
*/
public String generateExcelBase64(List<Map<String, Object>> dataList) {
validateDataSize(dataList);
ExcelColumnConfig config = new ExcelColumnConfig();
Map<String, String> columnMap = config.buildColumnMapping();
Path tempFile = null;
try {
tempFile = Files.createTempFile("export_", ".xlsx");
ExcelWriter writer = ExcelUtil.getWriter(tempFile.toFile());
configureColumns(writer, columnMap);
writer.write(dataList, true);
writer.close();
return encodeFileToBase64(tempFile);
} catch (IOException e) {
throw new RuntimeException("Error en exportación: " + e.getMessage(), e);
} finally {
cleanupTempFile(tempFile);
}
}
private void validateDataSize(List<?> data) {
if (data.size() > maximumExportRows) {
throw new IllegalStateException(
String.format("Límite excedido: %d filas (máximo %d)",
data.size(), maximumExportRows));
}
}
private void configureColumns(ExcelWriter writer, Map<String, String> columns) {
columns.forEach((field, header) -> writer.addHeaderAlias(field, header));
}
private String encodeFileToBase64(Path filePath) throws IOException {
byte[] fileBytes = Files.readAllBytes(filePath);
return Base64.getEncoder().encodeToString(fileBytes);
}
private void cleanupTempFile(Path filePath) {
if (filePath != null) {
try {
Files.deleteIfExists(filePath);
} catch (IOException ignored) {
// Log en producción
}
}
}
}
En el front-end, el proceso implica realizar una solicitud al endpoint, recibir la cadena Base64 y convertirla en un archivo descargable usando técnicas modernas de JavaScript.
// Función principal de exportación
async function exportDataToExcel(parameters) {
const endpoint = '/api/export/generate';
try {
const response = await fetch(`${endpoint}?${new URLSearchParams(parameters)}`);
const result = await response.json();
if (!response.ok || !result.success) {
showError(result.message || 'Error en la exportación');
return;
}
downloadExcelFile(result.data, 'consulta_exportada.xlsx');
} catch (error) {
console.error('Fallo en exportación:', error);
showError('Error de conexión');
}
}
function downloadExcelFile(base64Content, fileName) {
const binaryString = atob(base64Content);
const byteArray = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
byteArray[i] = binaryString.charCodeAt(i);
}
const blob = new Blob([byteArray], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const downloadUrl = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = downloadUrl;
anchor.download = fileName;
document.body.appendChild(anchor);
anchor.click();
// Limpieza de recursos
setTimeout(() => {
document.body.removeChild(anchor);
URL.revokeObjectURL(downloadUrl);
}, 100);
}
function showError(message) {
// Implementación del sistema de notificaciones
alert(message);
}
Para verificar la funcionalidad, se puede implementar un caso de prueba que evalúe el flujo completo de exportación con datos simulados.
// Ejemplo de prueba unitaria
@Test
void testExcelExportProcess() {
DataExportService exportService = new DataExportService();
// Datos de prueba
List<Map<String, Object>> testData = Arrays.asList(
Map.of("code", "001", "description", "Prueba 1", "status", "ACTIVE"),
Map.of("code", "002", "description", "Prueba 2", "status", "INACTIVE")
);
String base64Result = exportService.generateExcelBase64(testData);
assertNotNull(base64Result);
assertTrue(base64Result.length() > 100);
// Verificar que el Base64 es decodificable
byte[] decodedBytes = Base64.getDecoder().decode(base64Result);
assertTrue(decodedBytes.length > 0);
}