Spring Boot Controller экспортирует Excel - PullRequest
0 голосов
/ 29 августа 2018

У меня есть загрузочное приложение java / spring, где я хочу создать конечную точку API, которая создает и возвращает загружаемый файл Excel. Вот моя конечная точка контроллера:

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

Этот код успешно создает правильный файл Excel с использованием библиотеки Apache POI. Но это не вернет его из контроллера должным образом, потому что ByteArrayResource::getFilename всегда возвращает ноль:

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

Какой тип ресурса я могу использовать для возврата сгенерированного файла Excel?

Ответы [ 5 ]

0 голосов
/ 19 февраля 2019

По сути, есть несколько моментов, которые вам сначала необходимо понять, а затем решить, что вы хотите сделать,

1. Требуется ли создание Excel на диске или вы можете передавать его из памяти?

Если загружается всплывающее окно, пользователь может держать его открытым в течение долгого времени, и память будет занята в течение этого периода (недостаток подхода с использованием памяти).

Во-вторых, если сгенерированный файл должен быть новым для каждого запроса (т. Е. Экспортируемые данные различны), то нет смысла хранить его на диске (недостаток подхода на диске).

В-третьих, для кода API будет сложно выполнить очистку диска, потому что вы никогда не знаете заранее, как, когда пользователь завершит свою загрузку (недостаток в подходе к диску).

Answer by Fizik26 - это In-Memory подход, при котором вы не создаете файл на диске. , Из этого ответа единственное, что вам нужно отслеживать длину массива 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()));

и inputStreamWrapper это как,

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

}

Что касается имени файла, если имя файла не является вводом в конечную точку - это означает, что ..its система сгенерирована (комбинация постоянной строки плюс переменная часть для пользователя). Я не уверен, почему вы должны получить это из ресурса.

Эта оболочка вам не понадобится, если вы ее используете - org.springframework.core.io.ByteArrayResource

0 голосов
/ 11 февраля 2019

Вы можете использовать это:

 headers.add("Content-Disposition", "attachment; filename=NAMEOFYOURFILE.xlsx");
ByteArrayInputStream in = fooService.export();
return ResponseEntity
            .ok()
            .headers(headers)
            .body(new InputStreamResource(in));

Он загрузит файл Excel при вызове этой конечной точки.

В вашем методе экспорта в вашем сервисе вы должны вернуть что-то вроде этого:

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
        workbook.write(out);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return new ByteArrayInputStream(out.toByteArray());
0 голосов
/ 29 августа 2018

Вы должны установить имя файла для заголовка ответа, используя Content-disposition. Попробуйте это

@GetMapping("/export")
public ResponseEntity export(HttpServletResponse response) {
        fooService.export(response);      
}

Измените свой метод обслуживания следующим образом

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

private void export(String filename,  HttpServletResponse response) throws IOException {
      try (Workbook workbook = generateExcel()) {
          FileOutputStream fos = write(workbook, filename);
          IOUtils.copy(new FileInputStream(fos.getFD()),               
                                     servletResponse.getOutputStream());//IOUtils is from apache commons io
          response.setContentType("application/vnd.ms-excel");
          response.setHeader("Content-disposition", "attachment; filename=" + filename);
     }catch(Exception e) {
       //catch if any checked exception
     }finally{
        //Close all the streams
     }
}
0 голосов
/ 05 февраля 2019

Уведомление контроллера всегда лучше, что он собирается написать, используя 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>

Для полного примера, взгляните - здесь , здесь и здесь .

0 голосов
/ 29 августа 2018

Поскольку вы используете ByteArrayResource, вы можете использовать приведенный ниже код контроллера, предполагая, что 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);
}
...