web-dev-qa-db-ja.com

Spring Boot ControllerがExcelをエクスポートする

ダウンロード可能なExcelファイルを作成して返すAPIエンドポイントを構築するJava/Spring Bootアプリケーションがあります。これが私のコントローラのエンドポイントです:

@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ファイルを正常に作成します。ただし、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ファイルを返すためにどのような種類のリソースを使用できますか?

4
Richard

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);
}
5
Arun

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
     }
}
4
pvpkiran

基本的に、最初に理解して、次に何をしたいかを決める必要がある点がいくつかあります。

1 .ディスク上でのExcelの作成は必要ですか、それともメモリからストリーミングできますか?

ダウンロードがポップアップした場合、ユーザーはそれを長時間開いたままにし、その期間中にメモリが占​​有される可能性があります(メモリ内アプローチの欠点)。

次に、生成されたファイルがリクエストごとに新しくなければならない場合(つまり、エクスポートされるデータが異なる場合)、それをディスクに保持しても意味がありません(ディスクアプローチのデメリット)。

第3に、ユーザーがいつダウンロードを完了するかを事前に知ることができないため、ディスクのクリーンアップをAPIコードで実行するのは困難です(ディスクアプローチのデメリット)。

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

}

ファイル名に関して、ファイル名がエンドポイントへの入力ではない場合、つまり、システムによって生成されます(定数文字列とユーザーごとの可変部分の組み合わせ)。リソースからそれを取得する必要がある理由はわかりません。

使用する場合、このラッパーは必要ありません-org.springframework.core.io.ByteArrayResource

3
Sabir Khan

コントローラーに知らせることは、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>

完全な例として、ピーク herehere および here を取り上げます。

2
Ajay Kumar

あなたはこれを使うことができます:

 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());
2
Fizik26