如何为Grails编写XLSX自定义渲染器

我正在尝试使用grails自定义渲染器来渲染使用apache-poi库的Excel XLSX文件。 我做了一个渲染器类

class APIReportXLSXRenderer extends AbstractRenderer<APIReport> { APIReportXLSXRenderer() { super(APIReport, [new MimeType("application/vnd.ms-excel", "xlsx")] as MimeType[]) } @Override void render(APIReport output, RenderContext context) { context.contentType = GrailsWebUtil.getContentType("application/vnd.ms-excel", GrailsWebUtil.DEFAULT_ENCODING) def items = output.getItems() def fields = output.getFields() def headers = (fields.keySet() + items[0].keySet()) as List // convert maps to list of values each in order of the headers def values = (items ?: []).collect { Map item -> headers.collect { String h -> item?.containsKey(h) ? item[h] : output[h] } } def wos = new WriterOutputStream(context.writer) createXLSXFile(headers, values, wos) // FIXME: This currently produces corrupt files. } // Lifted from ApiController private static def createXLSXFile(List<String> headers = [], List items = [], OutputStream outputStream) { Workbook wb = new XSSFWorkbook(); Sheet sheet = wb.createSheet(); int rowcount = 0; // add header row if (headers) { Row row = sheet.createRow((short) rowcount++); headers.eachWithIndex { String entry, int i -> row.createCell(i).setCellValue(entry) } } // add cells items?.each { List entry -> Row row = sheet.createRow((short) rowcount++); entry.eachWithIndex { def value, int i -> row.createCell(i).setCellValue(value as String) } } wb.write(outputStream); } } 

和我的控制器响应与APIReport对象

 respond(report) 

这似乎产生一个损坏的文件,但是当我在控制器中以相同的方式做同样的事情:

 withFormat { xlsx { def items = output.getItems() def fields = output.getFields() response.setHeader("Content-Type", "application/vnd.ms-excel") response.setHeader("Content-disposition", "attachment; filename=\"${filename}.${params.format}\"") def headers = (fields.keySet() + items[0].keySet()) as List // convert maps to list of values each in order of the headers def values = (items?:[]).collect { headers.collect { String h -> it?.containsKey(h) ? it[h] : output[h] } } createXLSXFile(headers, values, response.outputStream) return } } 

它工作得很好。

APIReport类如下:

 class APIReport extends AbstractMap<String, Object> { // request call ApiParameters apicall; // response Map<String, Object> fields; Long itemCount; List<Map<String, Object>> items; Map<String, Object> summary; } 

我在渲染器中做错了什么? 或者在grails 2.3.8中创build自定义渲染器的首选方法是什么?

WriterOutputStream不是字符数据,而是使用BufferedOutputStream等二进制stream向浏览器写入数据

除了@tim_yates的答案。 我改变了我的实现一点点来访问响应对象,因此直接访问响应输出stream:

 class APIReportXLSXRenderer extends AbstractRenderer<APIReport> { APIReportXLSXRenderer() { super(APIReport, [new MimeType("application/vnd.ms-excel", "xlsx")] as MimeType[]) } @Override void render(APIReport output, RenderContext context) { if (!(context instanceof ServletRenderContext)) { throw new IllegalStateException("This renderer only works for servlet environment with ServletRendererContext") } ServletRenderContext ctx = (ServletRenderContext) context; context.contentType = GrailsWebUtil.getContentType("application/vnd.ms-excel", GrailsWebUtil.DEFAULT_ENCODING) def items = output.getItems() def fields = output.getFields() def headers = (fields.keySet() + items[0].keySet()) as List // convert maps to list of values each in order of the headers def values = (items ?: []).collect { Map item -> headers.collect { String h -> item?.containsKey(h) ? item[h] : output[h] } } createXLSXFile(headers, values, ctx.webRequest.response.outputStream) } // Lifted from ApiController private static def createXLSXFile(List<String> headers = [], List items = [], OutputStream outputStream) { Workbook wb = new XSSFWorkbook(); Sheet sheet = wb.createSheet(); int rowcount = 0; // add header row if (headers) { Row row = sheet.createRow((short) rowcount++); headers.eachWithIndex { String entry, int i -> row.createCell(i).setCellValue(entry) } } // add cells items?.each { List entry -> Row row = sheet.createRow((short) rowcount++); entry.eachWithIndex { def value, int i -> row.createCell(i).setCellValue(value as String) } } wb.write(outputStream); } } 

两个选项都起作用