LoginSignup
4
2

More than 3 years have passed since last update.

gRPC, Spring Boot, Kotlin,MySQLで作ったSNSアプリ(通話、チャット)のAPIサンプル

Posted at

どんなアプリのAPIか。

  • チャット、通話機能があるSNSアプリのAPIです。
  • ユーザー登録、プッシュ通知、ブロック・通報機能などのsnsアプリに必要な一通りのAPIを実装しています。

システムの構成

  • フレームワーク:Spring Boot
  • 言語:Kotlin
  • API:Protocol Buffersで定義、gRPCで実装。
  • DB:MySQL

コード

コメント

  • gRPC, Protocol Buffersがどんなものかは他に沢山情報があるので省略します。
  • 画像ファイルのアップロードはこのサンプルに含みません。
  • iOSプッシュ通知の用のp8ファイルや、その他の認証用の文字列は無効なものになっています。

APIの一覧

アカウント

  • ユーザー登録
  • プロフィール編集
  • 退会

ユーザー情報:

  • ユーザーのリスト取得
  • 指定したidのユーザーを取得

通話

  • 通話を開始。(電話を掛ける)
  • 通話を受ける。
  • 通話を切る。
  • 通話が継続していることを記録。
  • 通話のステータス受信(開始、通話中、終了)

チャット

  • メッセージ送信
  • メッセージ受信
  • すべてのメッセージを取得

プッシュ通知

  • デバイストークン_iOS / 登録, 更新
  • ON/OFF更新

その他

  • ユーザーをブロック
  • ユーザーを通報

Protocol BuffersによるAPI仕様定義

API(gRPC)のコントローラークラス

build.gradle: protoファイルから、APIのソースを生成

group 'com.talking'

// gRPCのコード生成コマンド: ./gradlew generateProto

// ドキュメント: gRPCの実装
// https://github.com/LogNet/grpc-spring-boot-starter

buildscript {
    ext.kotlin_version = '1.3.61' // Required for Kotlin integration
    ext.spring_boot_version = '2.2.0.RELEASE'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Required for Kotlin integration
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
        classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"

        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.5"
    }
}

apply plugin: 'kotlin' // Required for Kotlin integration
apply plugin: "kotlin-spring" // https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
apply plugin: 'org.springframework.boot'
apply plugin: 'com.google.protobuf'

def grpcVersion = '1.25.0'

repositories {
    mavenCentral()
}

sourceSets {
    main.kotlin.srcDirs += 'src/main/kotlin'
    main.java.srcDirs += 'src/main/java'
    main.java.srcDirs += 'src/main/generated-proto'
}

dependencies {
    compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
    compile("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
    // testCompile('org.springframework.boot:spring-boot-starter-test')

    // for web
    compile("org.springframework.boot:spring-boot-starter-web:$spring_boot_version")
    compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8")

    // for db
    compile("org.springframework.boot:spring-boot-starter-data-jpa:$spring_boot_version")
    runtime("mysql:mysql-connector-java:8.0.15")

    compile("org.springframework.boot:spring-boot-starter-security:$spring_boot_version")

    // grpc
    compile("io.github.lognet:grpc-spring-boot-starter:3.5.0")
    compile "io.grpc:grpc-api:${grpcVersion}"
    compile "io.netty:netty-codec-http2:4.1.42.Final"
    compile "io.grpc:grpc-core:${grpcVersion}"
    compile "io.grpc:grpc-protobuf:${grpcVersion}"
    compile "io.grpc:grpc-stub:${grpcVersion}"

    // cache
    compile(group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.8.1')
    // iOSプッシュ通知
    compile(group: 'com.eatthepath', name: 'pushy', version: '0.13.11')

}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.5.0"
    }

    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    outputSubDir = 'generated-proto'
                }
            }
            task.plugins {
                grpc {
                    outputSubDir = 'generated-proto'
                }
            }
        }
    }

    generatedFilesBaseDir = "$projectDir/src/"
}

ddl

テーブル

  • ユーザー
  • プロフィール画像
  • チャット
  • 通話履歴
  • 通報
  • ブロック
  • プッシュ通知デバイストークン_iOS

工夫したところ

ログインしているユーザーしか利用できないAPIの認証処理を共通化

AuthInterceptor内でrequestのheader内のtoken,userIdで認証。

@Component
class AuthInterceptor : ServerInterceptor {

    @Autowired
    private lateinit var userService: UserService

    // 参考ソース:
    // https://github.com/saturnism/grpc-by-example-java/blob/master/metadata-context-example/src/main/java/com/example/grpc/server/JwtServerInterceptor.java

    override fun <ReqT : Any?, RespT : Any?> interceptCall(
        call: ServerCall<ReqT, RespT>,
        headers: Metadata,
        next: ServerCallHandler<ReqT, RespT>
    ): ServerCall.Listener<ReqT> {

        val userId = headers.get(GrpcConstant.USER_ID_METADATA_KEY)?.toLong() ?:
                throw StatusRuntimeException(Status.UNAUTHENTICATED, headers)

        val apiToken = headers.get(GrpcConstant.API_TOKEN_METADATA_KEY) ?:
                throw StatusRuntimeException(Status.UNAUTHENTICATED, headers)

        val user = userService.findByIdAndToken(userId, apiToken) ?:
            throw StatusRuntimeException(Status.UNAUTHENTICATED, headers)

        val ctx = Context.current().withValue(GrpcConstant.AUTH_USER_CONTEXT_KEY, user)
        return Contexts.interceptCall(ctx, call, headers, next)
    }
}

認証処理を利用したいAPIのクラスにAuthInterceptorを設定
https://github.com/yusuke-imagawa/talking-sns-api-sample/blob/master/src/main/kotlin/com/talking/api/grpc/AccountGrpcService.kt

@GRpcService(interceptors = [AuthInterceptor::class])
class AccountGrpcService: AccountServiceGrpc.AccountServiceImplBase() {
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2