我有一个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 文件?
让控制器知道它将使用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>
有关完整示例,请看一眼 - 这里,这里和这里。
基本上,您首先需要了解的几点
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资源
,则不需要此包装器。
因为您使用的是< 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注释的异常处理程序类不起作用。