Help us understand the problem. What is going on with this article?

OpenAPI + Spring Boot(Kotlin)でファイルダウンロードAPIを作成する

はじめに

OpenAPIを使うとサーバーとクライアント両方のコードが生成出来て便利です。
本記事では/api/csvなどのURLにアクセスするとCSVがダウンロード出来たり、/api/imageにアクセスすると画像が表示出来るようなAPIを作成する方法をご紹介します。

作ってみる

大まかな流れとしては以下のようになります。
1. OpenAPI Spec Fileを作成
2. openapi-generatorを使って、Spring Boot(Kotlin)プロジェクトを生成
3. Serviceクラスを実装
4. ./gradlew bootRunで実行

CSVダウンロード

OpenAPI Spec Fileの作成

まずはSpec Fileを作成します。
今回はYAML形式で作成します。

http://localhost:8080/api/csvにGETするとCSVがダウンロードされるAPIは以下のように定義します。

  • contenttext/csvにする
  • schemetypestringにする
api.yml
openapi: "3.0.2"
info:
  title: API Title
  version: "1.0"
servers:
  - url: http://localhost:8080/api
tags:
  - name: Download
    description: ファイルダウンロード
paths:
  /csv:
    get:
      summary: CSVダウンロード
      tags:
        - Download
      responses:
        "200":
          description: OK
          content:
            text/csv:
              schema:
                type: string

Spring Boot(Kotlin)プロジェクトを作成

openapi-generatorがインストールされていない場合は、以下のコマンドでインストールします。

brew install openapi-generator

openapi-generator generateコマンドを使用して、Spec FileからSpring Bootプロジェクトを生成します。

openapi-generator generate -i api.yml -g kotlin-spring --additional-properties=serviceInterface=true

簡単なオプションの説明は以下の通りです。

  • -i (---input-spec)
    • インプットとなるSpec Fileのパスを指定します
  • -g (--generator-name)
  • --additional-properties

Serviceクラスを実装

DownloadApiServiceというInterfaceが自動生成されているので、それを継承したDownloadApiServiceImplを実装します。
戻り値がStringとなっているため、CSVとして出力したいStringを設定しておきます。

src/main/kotlin/org/openapitools/api/DownloadApiService.kt
package org.openapitools.api

import org.openapitools.model.ImageType
interface DownloadApiService {

    fun csvGet(): kotlin.String
}
src/main/kotlin/org/openapitools/api/DownloadApiServiceImpl.kt
package org.openapitools.api

import org.openapitools.model.ImageType
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.stereotype.Service

@Service
class DownloadApiServiceImpl : DownloadApiService {
    override fun csvGet(): String {
        return "商品名,価格\nりんご,100\nオレンジ,200"
    }
}

Spring Bootアプリケーションを実行

bootRunコマンドを実行して、APIにアクセスしてみます。

./gradlew bootRun

http://localhost:8080/api/image にアクセスするとCSVがダウンロードされました。

csv.csv
商品名,価格
りんご,100
オレンジ,200

Shift_JISで出力する

上記の方法だとUTF-8として出力されます。
Excelで簡単に取り込みたいとの要望から、Shift_JISで出力する場合を考えてみます。

http://localhost:8080/api/csv/sjisというAPIを新設します。pathに以下を追加してください。
text/csv; charset=Shift_JISのように指定するのがポイントです。

api.yml
path:
  # 省略
  /csv/sjis:
    get:
      summary: CSVダウンロード(sjis)
      tags:
        - Download
      responses:
        "200":
          description: OK
          content:
            text/csv; charset=Shift_JIS:
              schema:
                type: string

あとは違いはないので、openapi-generatorで再度生成して上書きし、同様にServiceを実装します。

src/main/kotlin/org/openapitools/api/DownloadApiServiceImpl.kt
package org.openapitools.api

import org.openapitools.model.ImageType
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.stereotype.Service

@Service
class DownloadApiServiceImpl : DownloadApiService {
    override fun csvGet(): String {
        return "商品名,価格\nりんご,100\nオレンジ,200"
    }

    override fun csvSjisGet(): String {
        return "商品名,価格\nりんご,100\nオレンジ,200"
    }
}

http://localhost:8080/api/csv/sjis にアクセスするとShift_JISエンコードのCSVが出力されます。

画像の表示

次は画像を表示するようなAPIを作成します。
Spec Fileに画像表示用のAPIを追加します。

  • contentimage/jpegにする
  • schemetypestringに、formatbinaryにする
api.yml
path:
  # 省略
  /image:
    get:
      summary: 画像ダウンロード
      tags:
        - Download
      responses:
        "200":
          description: OK
          content:
            image/jpeg:
              schema:
                type: string
                format: binary

openapi-generatorで再度生成すると、Resourceを返すようなメソッドが生成されています。
今回はsrc/main/resources/image.jpgに置いた画像を表示するようにするため、ClassPathResource("image.jpg")として指定します。

src/main/kotlin/org/openapitools/api/DownloadApiService.kt
package org.openapitools.api

import org.openapitools.model.ImageType
interface DownloadApiService {

    // 省略

    fun imageGet(): org.springframework.core.io.Resource
}
src/main/kotlin/org/openapitools/api/DownloadApiServiceImpl.kt
package org.openapitools.api

import org.openapitools.model.ImageType
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.stereotype.Service

@Service
class DownloadApiServiceImpl : DownloadApiService {

    // 省略

    override fun imageGet(): Resource {
        return ClassPathResource("image.jpg")
    }
}

http://localhost:8080/api/image にアクセスすると画像が表示されます。

MIME typeを切り替える

上記のAPIではMIME typeをimage/jpegとして指定していました。
pngの画像も同じAPIで返したいとなったらどうしたら良いでしょうか?
そのままだとpngの画像もimage/jpegとして返却されてしまいます。

contentには複数のMIME typeが設定できるので、複数設定するとSpring Boot側でいい感じに設定してもらえます。
query parameterでjpegかpngの画像を選択肢出来るようにしています。

api.yml
path:
  # 省略
  /image/jpeg_or_png:
    get:
      summary: 画像ダウンロード(jpeg or png)
      tags:
        - Download
      parameters:
        - in: query
          name: type
          required: true
          schema:
            $ref: "#/components/schemas/ImageType"
      responses:
        "200":
          description: OK
          content:
            image/jpeg:
              schema:
                type: string
                format: binary
            image/png:
              schema:
                type: string
                format: binary
components:
  schemas:
    ImageType:
      type: string
      enum:
        - jpeg
        - png

あとは同様にServiceクラスを実装するだけです。
src/main/resources/image.jpgにpngの画像を配置しておきます。

src/main/kotlin/org/openapitools/api/DownloadApiServiceImpl.kt
package org.openapitools.api

import org.openapitools.model.ImageType
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.stereotype.Service

@Service
class DownloadApiServiceImpl : DownloadApiService {

    // 省略

    override fun imageGet(): Resource {
        return ClassPathResource("image.jpg")
    }

    override fun imageJpegOrPngGet(type: ImageType): Resource {
        return when (type) {
            ImageType.jpeg -> ClassPathResource("image.jpg")
            ImageType.png -> ClassPathResource("image.png")
        }
    }
}

http://localhost:8080/api/image/jpeg_or_png?type=jpeg にアクセスするとimage/jpegのjpeg画像が、
http://localhost:8080/api/image/jpeg_or_png?type=png にアクセスするとimage/pngのpng画像が表示されます。

おわりに

今回作成したサンプルは以下で確認できます。

GitHub

boronngo/openapi-spring-boot-file-download: File download API with openapi and spring boot

デモ

https://openapi-spring-boot-file-dow.herokuapp.com/api/csv
https://openapi-spring-boot-file-dow.herokuapp.com/api/csv/sjis
https://openapi-spring-boot-file-dow.herokuapp.com/api/image
https://openapi-spring-boot-file-dow.herokuapp.com/api/image/jpeg_or_png?type=jpeg
https://openapi-spring-boot-file-dow.herokuapp.com/api/image/jpeg_or_png?type=png

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした