当前位置: 首页 > 知识库问答 >
问题:

Spring Boot控制器导出Excel

柯建业
2023-03-14

我有一个java/Spring Boot应用程序,我想在其中构建一个APIendpoint,创建并返回一个可下载的excel文件。这是我的控制器endpoint:

@RestController
@RequestMapping("/Foo")
public class FooController {
    private final FooService fooService;

    @GetMapping("/export")
    public ResponseEntity export() {
        Resource responseFile = fooService.export();

        return ResponseEntity.ok()
                             .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+responseFile.getFilename())
                             .contentType(MediaType.MULTIPART_FORM_DATA)
                             .body(responseFile);
    }
}

然后是服务类

public class FooService {
  public Resource export() throws IOException {
    StringBuilder filename = new StringBuilder("Foo Export").append(" - ")
                                                            .append("Test 1.xlsx");
    return export(filename);
  }

  private ByteArrayResource export(String filename) throws IOException {
      byte[] bytes = new byte[1024];
      try (Workbook workbook = generateExcel()) {
          FileOutputStream fos = write(workbook, filename);
          fos.write(bytes);
          fos.flush();
          fos.close();
      }

      return new ByteArrayResource(bytes);
  }

  private Workbook generateExcel() {
      Workbook workbook = new XSSFWorkbook();
      Sheet sheet = workbook.createSheet();

      //create columns and rows

      return workbook;
  }

  private FileOutputStream write(final Workbook workbook, final String filename) throws IOException {
      FileOutputStream fos = new FileOutputStream(filename);
      workbook.write(fos);
      fos.close();
      return fos;
  }  
}

这段代码使用Apache POI库成功地创建了正确的excel文件。但是这不会正确地将它从控制器中返回,因为< code > ByteArrayResource::getFilename 总是返回null:

/**
 * This implementation always returns {@code null},
 * assuming that this resource type does not have a filename.
 */
@Override
public String getFilename() {
    return null;
}

我可以使用什么类型的资源来返回生成的 excel 文件?

共有3个答案

谭勇
2023-03-14

让控制器知道它将使用ReponseEntity编写的内容总是更好的。在服务级别,只需创建和播放对象即可。@RestController或@Controller在这里无关紧要。

您在控制器中期待的内容有点像这样(示例)-

@GetMapping(value = "/alluserreportExcel")
public ResponseEntity<InputStreamResource> excelCustomersReport() throws IOException {
    List<AppUser> users = (List<AppUser>) userService.findAllUsers();
    ByteArrayInputStream in = GenerateExcelReport.usersToExcel(users);
    // return IO ByteArray(in);
    HttpHeaders headers = new HttpHeaders();
    // set filename in header
    headers.add("Content-Disposition", "attachment; filename=users.xlsx");
    return ResponseEntity.ok().headers(headers).body(new InputStreamResource(in));
}

生成Excel类-

public class GenerateExcelReport {

public static ByteArrayInputStream usersToExcel(List<AppUser> users) throws IOException {
...
...
//your list here
int rowIdx = 1;
        for (AppUser user : users) {
            Row row = sheet.createRow(rowIdx++);

            row.createCell(0).setCellValue(user.getId().toString());
            ...
        }

  workbook.write(out);
  return new ByteArrayInputStream(out.toByteArray());

最后,在你看来,在某个地方——

<a href="<c:url value='/alluserreportExcel'  />"
                target="_blank">Export all users to MS-Excel</a>

有关完整示例,请看一眼 - 这里,这里和这里。

阎德义
2023-03-14

基本上,您首先需要了解的几点

1.是否需要在磁盘上创建Excel,或者您可以从内存中流式传输它?

如果它是一个下载弹出窗口,用户可能会让它打开很长时间

其次,如果生成的文件对于每个请求都必须是新的(即要导出的数据是不同的),那么将它保存在磁盘上就没有意义了(磁盘方法的缺点)。

第三,API代码很难清理磁盘,因为你永远不知道用户什么时候会完成他的下载(磁盘方法的缺点)。

Fizik26的回答是这种内存中的方法,你不需要在磁盘上创建一个文件。。这个答案的唯一内容是,您需要跟踪数组< code>out.toByteArray()的长度

2.下载文件时,您的代码需要一个文件一个文件地流式传输——这就是Java流的用途。类似下面的代码可以做到这一点。

return ResponseEntity.ok().contentLength(inputStreamWrapper.getByteCount())
            .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
            .cacheControl(CacheControl.noCache())
            .header("Content-Disposition", "attachment; filename=" + "SYSTEM_GENERATED_FILE_NM")
            .body(new InputStreamResource(inputStreamWrapper.getByteArrayInputStream()));

inpuStreamWrapper就像,

public class ByteArrayInputStreamWrapper {
    private ByteArrayInputStream byteArrayInputStream;
    private int byteCount;


    public ByteArrayInputStream getByteArrayInputStream() {
    return byteArrayInputStream;
    }


    public void setByteArrayInputStream(ByteArrayInputStream byteArrayInputStream) {
    this.byteArrayInputStream = byteArrayInputStream;
    }


    public int getByteCount() {
    return byteCount;
    }


    public void setByteCount(int byteCount) {
    this.byteCount = byteCount;
    }

}

关于文件名,如果文件名不是endpoint的输入-这意味着它的系统生成(每个用户的常量字符串加上变量部分的组合)。我不知道你为什么需要从资源中得到这个。

如果使用-org.springframework.core.io.ByteArray资源,则不需要此包装器。

陈兴朝
2023-03-14

因为您使用的是< code>ByteArrayResource,所以可以使用下面的控制器代码,假设< code>FooService在控制器类中是自动连接的。

@RequestMapping(path = "/download_excel", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String fileName) throws IOException {

ByteArrayResource resource = fooService.export(fileName);

return ResponseEntity.ok()
        .headers(headers) // add headers if any
        .contentLength(resource.contentLength())
        .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
        .body(resource);
}
 类似资料:
  • 问题内容: 题 如何仅使用按钮的touch up内部事件从一个视图控制器导航到另一个视图控制器? 更多信息 我在一个示例项目中尝试执行的步骤是: 创建示例单视图应用程序。 为用户界面(ViewController2)添加一个新文件->具有XIB的Objective-C类。 在ViewController.xib中添加一个按钮,并控制单击ViewController.h的按钮以创建内部补全事件。 转

  • 我是Spring Boot的新手,并试图构建一个简单的Web应用程序。我定义了一个包含我的url映射的控制器类,但在浏览器上它给我一个白标页面错误(404)。我无法理解为什么它无法映射。我尝试过更改我的组件扫描基础包,但它仍然没有将我重定向到控制台中的页面“abc”或打印“在控制器中”。 pom.xml 控制器类 主类

  • 我是springboot的新手,非常需要你的专业知识。请帮帮我。 我需要在点击按钮时将数据传递给控制器。现在我面临着下面的错误,我的代码到底做错了什么? 控制器 HTML 模型

  • 请参见黑色导航栏和分组的表格视图垂直细条纹之间的空白?该空间是导航栏的一部分。如果我滚动表格视图,空格将保持在原来的位置。 我需要navigationController框架仅在这个viewController中更短,这样就没有提供的屏幕截图中显示的空白。 (可能与此有关的是一个带有分组tableview的viewController)。。 以下是此视图控制器的唯一其他子视图: 此导航栏在应用程序

  • 因此,我正在进行我的第一个Spring Boot项目,我一直在进行测试。我查了很多例子,但似乎都不管用。 这是我的控制器的当前测试: 这是可行的,但在sonarqube上,我发现我的代码覆盖率为0%,而我似乎找不到一个测试,它的覆盖率甚至超过了零。有谁能给我一个关于如何为控制器编写一个好的单元测试的例子,然后我就可以根据您的例子自己解决这个问题。 这是我的控制器: 这是我的服务(以防您需要): 还

  • 我有一个spring-boot应用程序,没有任何控制器类。如何为该应用程序编写异常处理程序。用@ControllerAdvice注释的异常处理程序类不起作用。