From 025f5cf0b95b0ea997a6fcbd4092adc457c0ad68 Mon Sep 17 00:00:00 2001 From: happysoy Date: Fri, 16 Feb 2024 15:50:20 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20get=20userinfo=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chart-batch.iml | 6 + chart-consumer.iml | 6 + .../chart-server/chart-batch/build.gradle.kts | 26 ++++ .../chart/batch/ChartBatchApplication.kt | 11 ++ .../lalala/chart/batch/config/BatchConfig.kt | 7 ++ .../lalala/chart/batch/job/SimpleJobConfig.kt | 6 + .../chart/batch/tasklet/SimpleTasklet.kt | 35 ++++++ .../src/main/resources/application.yml | 38 ++++++ .../chart-consumer/build.gradle.kts | 25 ++++ .../consumer/ChartConsumerApplication.kt | 16 +++ .../config/JpaAuditingConfiguration.kt | 9 ++ .../chart/consumer/entity/BaseTimeEntity.kt | 21 ++++ .../chart/consumer/entity/StreamingLog.kt | 13 ++ .../consumer/listener/StreamingListener.kt | 20 +++ .../repository/StreamingLogRepository.kt | 6 + .../consumer/service/StreamingLogService.kt | 17 +++ .../src/main/resources/application.yml | 45 +++++++ .../listener/StreamingListenerTest.kt | 29 +++++ src/backend/chart-server/infra/00_scheme.sql | 5 + src/backend/music-server/infra/00_schema.sql | 117 ++++++++++++++++++ .../music/controller/MusicLikeController.java | 42 +++++++ .../lalala/music/entity/MusicLikeEntity.java | 40 ++++++ .../music/repository/MusicLikeRepository.java | 9 ++ .../music/service/MusicLikeService.java | 42 +++++++ .../streaming/config/async/AsyncConfig.kt | 26 ++++ .../streaming/external/kafka/KafkaProducer.kt | 16 +++ 26 files changed, 633 insertions(+) create mode 100644 chart-batch.iml create mode 100644 chart-consumer.iml create mode 100644 src/backend/chart-server/chart-batch/build.gradle.kts create mode 100644 src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/ChartBatchApplication.kt create mode 100644 src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/config/BatchConfig.kt create mode 100644 src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/job/SimpleJobConfig.kt create mode 100644 src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/tasklet/SimpleTasklet.kt create mode 100644 src/backend/chart-server/chart-batch/src/main/resources/application.yml create mode 100644 src/backend/chart-server/chart-consumer/build.gradle.kts create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/ChartConsumerApplication.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/config/JpaAuditingConfiguration.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/BaseTimeEntity.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/StreamingLog.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/listener/StreamingListener.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/repository/StreamingLogRepository.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/service/StreamingLogService.kt create mode 100644 src/backend/chart-server/chart-consumer/src/main/resources/application.yml create mode 100644 src/backend/chart-server/chart-consumer/src/test/kotlin/com/lalala/chart/consumer/listener/StreamingListenerTest.kt create mode 100644 src/backend/chart-server/infra/00_scheme.sql create mode 100644 src/backend/music-server/infra/00_schema.sql create mode 100644 src/backend/music-server/music-api/src/main/java/com/lalala/music/controller/MusicLikeController.java create mode 100644 src/backend/music-server/music-api/src/main/java/com/lalala/music/entity/MusicLikeEntity.java create mode 100644 src/backend/music-server/music-api/src/main/java/com/lalala/music/repository/MusicLikeRepository.java create mode 100644 src/backend/music-server/music-api/src/main/java/com/lalala/music/service/MusicLikeService.java create mode 100644 src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/config/async/AsyncConfig.kt create mode 100644 src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/external/kafka/KafkaProducer.kt diff --git a/chart-batch.iml b/chart-batch.iml new file mode 100644 index 00000000..6545bf69 --- /dev/null +++ b/chart-batch.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/chart-consumer.iml b/chart-consumer.iml new file mode 100644 index 00000000..077c36c0 --- /dev/null +++ b/chart-consumer.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/backend/chart-server/chart-batch/build.gradle.kts b/src/backend/chart-server/chart-batch/build.gradle.kts new file mode 100644 index 00000000..082d31e8 --- /dev/null +++ b/src/backend/chart-server/chart-batch/build.gradle.kts @@ -0,0 +1,26 @@ +tasks.getByName("bootJar") { + enabled = true +} + +dependencies { + implementation(project(":common-module:common")) + implementation(project(":common-module:common-mvc")) + + // Kotlin Libraries + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + // Spring + testImplementation("org.springframework.boot:spring-boot-starter-test") + + // Spring Batch + implementation("org.springframework.boot:spring-boot-starter-batch") + testImplementation("org.springframework.batch:spring-batch-test") + + // Spring JPA + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + + // MySQL + runtimeOnly("com.mysql:mysql-connector-j") +} diff --git a/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/ChartBatchApplication.kt b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/ChartBatchApplication.kt new file mode 100644 index 00000000..91587ea2 --- /dev/null +++ b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/ChartBatchApplication.kt @@ -0,0 +1,11 @@ +package com.lalala.chart.batch; + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class ChartBatchApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/config/BatchConfig.kt b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/config/BatchConfig.kt new file mode 100644 index 00000000..8e94099f --- /dev/null +++ b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/config/BatchConfig.kt @@ -0,0 +1,7 @@ +package com.lalala.chart.batch.config + +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing + +@EnableBatchProcessing +class BatchConfig { +} \ No newline at end of file diff --git a/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/job/SimpleJobConfig.kt b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/job/SimpleJobConfig.kt new file mode 100644 index 00000000..1a861403 --- /dev/null +++ b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/job/SimpleJobConfig.kt @@ -0,0 +1,6 @@ +package com.lalala.chart.batch.job + +class SimpleJobConfig( + val jobBuilderFactor +) { +} \ No newline at end of file diff --git a/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/tasklet/SimpleTasklet.kt b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/tasklet/SimpleTasklet.kt new file mode 100644 index 00000000..c3cf7abd --- /dev/null +++ b/src/backend/chart-server/chart-batch/src/main/kotlin/com/lalala/chart/batch/tasklet/SimpleTasklet.kt @@ -0,0 +1,35 @@ +package com.lalala.chart.batch.tasklet + +import org.springframework.batch.core.Job +import org.springframework.batch.core.Step +import org.springframework.batch.core.StepContribution +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory +import org.springframework.batch.core.scope.context.ChunkContext +import org.springframework.batch.repeat.RepeatStatus +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class SimpleTasklet( + private val jobBuilderFactory: JobBuilderFactory, + private val stepBuilderFactory: StepBuilderFactory +) { + + @Bean + fun singleStepJob(): Job { + return jobBuilderFactory["singleStepJob"] + .start(singleStep()) + .build() + } + + @Bean + fun singleStep(): Step { + return stepBuilderFactory["singleStep"] + .tasklet { _: StepContribution, _: ChunkContext -> + log.info { "Single Step!!" } + RepeatStatus.FINISHED + } + .build() + } +} \ No newline at end of file diff --git a/src/backend/chart-server/chart-batch/src/main/resources/application.yml b/src/backend/chart-server/chart-batch/src/main/resources/application.yml new file mode 100644 index 00000000..1d6aa18e --- /dev/null +++ b/src/backend/chart-server/chart-batch/src/main/resources/application.yml @@ -0,0 +1,38 @@ +server: + port: 27000 + +spring: + application: + name: chart-batch-server + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: "jdbc:mysql://localhost:27100/chart?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&serverTimezone=Asia/Seoul" + username: root + password: admin + + jpa: + database-platform: org.hibernate.dialect.MySQLDialect + hibernate: + ddl-auto: validate + properties: + dialect: org.hibernate.dialect.MySQLDialect + hibernate: + format_sql: true + show_sql: true + use_sql_comments: true + +management: + tracing: + sampling: + probability: 1.0 + propagation: + consume: [b3, w3c] + produce: [b3, w3c] + zipkin: + tracing: + endpoint: "http://localhost:43000/api/v2/spans" + +logging: + pattern: + level: "%5p [%X{traceId:-},%X{spanId:-}]" diff --git a/src/backend/chart-server/chart-consumer/build.gradle.kts b/src/backend/chart-server/chart-consumer/build.gradle.kts new file mode 100644 index 00000000..a1f3cc7d --- /dev/null +++ b/src/backend/chart-server/chart-consumer/build.gradle.kts @@ -0,0 +1,25 @@ +tasks.getByName("bootJar") { + enabled = true +} + +dependencies { + implementation(project(":common-module:common")) + implementation(project(":common-module:common-mvc")) + + // Kotlin + implementation("org.jetbrains.kotlin:kotlin-reflect") + + // Spring + implementation("org.springframework.boot:spring-boot-starter") + testImplementation("org.springframework.boot:spring-boot-starter-test") + + // Spring Kafka + implementation("org.springframework.kafka:spring-kafka") + testImplementation("org.springframework.kafka:spring-kafka-test") + + // Spring JPA + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + + // MySQL + runtimeOnly("com.mysql:mysql-connector-j") +} diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/ChartConsumerApplication.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/ChartConsumerApplication.kt new file mode 100644 index 00000000..4c3594c5 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/ChartConsumerApplication.kt @@ -0,0 +1,16 @@ +package com.lalala.chart.consumer; + +import com.lalala.config.KafkaConsumerConfig +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Import + +@Import( + KafkaConsumerConfig::class, +) +@SpringBootApplication +class ChartBatchApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/config/JpaAuditingConfiguration.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/config/JpaAuditingConfiguration.kt new file mode 100644 index 00000000..1f07bff5 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/config/JpaAuditingConfiguration.kt @@ -0,0 +1,9 @@ +package com.lalala.chart.consumer.config + +import org.springframework.context.annotation.Configuration +import org.springframework.data.jpa.repository.config.EnableJpaAuditing + +@Configuration +@EnableJpaAuditing +class JpaAuditingConfiguration { +} \ No newline at end of file diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/BaseTimeEntity.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/BaseTimeEntity.kt new file mode 100644 index 00000000..96accdc6 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/BaseTimeEntity.kt @@ -0,0 +1,21 @@ +package com.lalala.chart.consumer.entity + +import jakarta.persistence.Column +import jakarta.persistence.EntityListeners +import jakarta.persistence.MappedSuperclass +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.LocalDateTime + +@EntityListeners(AuditingEntityListener::class) +@MappedSuperclass +abstract class BaseTimeEntity { + @CreatedDate + @Column(name = "created_at", insertable = false, updatable = false) + private var createdAt: LocalDateTime? = null + + @LastModifiedDate + @Column(name = "updated_at", insertable = false, updatable = false) + private var updatedAt: LocalDateTime? = null +} diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/StreamingLog.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/StreamingLog.kt new file mode 100644 index 00000000..c0b4f681 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/entity/StreamingLog.kt @@ -0,0 +1,13 @@ +package com.lalala.chart.consumer.entity + +import jakarta.persistence.Column +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id + +data class StreamingLog( + @Id + @GeneratedValue + val id: Long = 0, + @Column(nullable = false) + val musicId: Long, +) : BaseTimeEntity() \ No newline at end of file diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/listener/StreamingListener.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/listener/StreamingListener.kt new file mode 100644 index 00000000..7fd0e8f3 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/listener/StreamingListener.kt @@ -0,0 +1,20 @@ +package com.lalala.chart.consumer.listener + +import com.lalala.chart.consumer.service.StreamingLogService +import com.lalala.event.StreamingCompleteEvent +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.stereotype.Component + +@Component +class StreamingListener( + val service: StreamingLogService +) { + @KafkaListener( + topics = ["streaming_complete"], + groupId = "\${spring.kafka.consumer.group-id}", + containerFactory = "kafkaListenerContainerFactory" + ) + fun consumeStreamingComplete(event: StreamingCompleteEvent) { + service.create(event.musicId) + } +} diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/repository/StreamingLogRepository.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/repository/StreamingLogRepository.kt new file mode 100644 index 00000000..8456e43e --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/repository/StreamingLogRepository.kt @@ -0,0 +1,6 @@ +package com.lalala.chart.consumer.repository + +import com.lalala.chart.consumer.entity.StreamingLog +import org.springframework.data.jpa.repository.JpaRepository + +interface StreamingLogRepository : JpaRepository \ No newline at end of file diff --git a/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/service/StreamingLogService.kt b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/service/StreamingLogService.kt new file mode 100644 index 00000000..c5a72100 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/kotlin/com/lalala/chart/consumer/service/StreamingLogService.kt @@ -0,0 +1,17 @@ +package com.lalala.chart.consumer.service + +import com.lalala.chart.consumer.entity.StreamingLog +import com.lalala.chart.consumer.repository.StreamingLogRepository +import org.springframework.transaction.annotation.Transactional + +class StreamingLogService( + val repository: StreamingLogRepository +) { + @Transactional + fun create(musicId: Long): StreamingLog { + val log = StreamingLog( + musicId = musicId + ) + return repository.save(log) + } +} diff --git a/src/backend/chart-server/chart-consumer/src/main/resources/application.yml b/src/backend/chart-server/chart-consumer/src/main/resources/application.yml new file mode 100644 index 00000000..37107717 --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/main/resources/application.yml @@ -0,0 +1,45 @@ +server: + port: 27500 + +spring: + application: + name: chart-consumer-server + + kafka: + bootstrap-servers: localhost:40000,localhost:40001,localhost:40002 + + consumer: + group-id: chart-service + auto-offset-reset: latest + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: "jdbc:mysql://localhost:27100/chart?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&serverTimezone=Asia/Seoul" + username: root + password: admin + + jpa: + database-platform: org.hibernate.dialect.MySQLDialect + hibernate: + ddl-auto: validate + properties: + dialect: org.hibernate.dialect.MySQLDialect + hibernate: + format_sql: true + show_sql: true + use_sql_comments: true + +management: + tracing: + sampling: + probability: 1.0 + propagation: + consume: [b3, w3c] + produce: [b3, w3c] + zipkin: + tracing: + endpoint: "http://localhost:43000/api/v2/spans" + +logging: + pattern: + level: "%5p [%X{traceId:-},%X{spanId:-}]" diff --git a/src/backend/chart-server/chart-consumer/src/test/kotlin/com/lalala/chart/consumer/listener/StreamingListenerTest.kt b/src/backend/chart-server/chart-consumer/src/test/kotlin/com/lalala/chart/consumer/listener/StreamingListenerTest.kt new file mode 100644 index 00000000..e772067e --- /dev/null +++ b/src/backend/chart-server/chart-consumer/src/test/kotlin/com/lalala/chart/consumer/listener/StreamingListenerTest.kt @@ -0,0 +1,29 @@ +package com.lalala.chart.consumer.listener + +import com.lalala.event.StreamingCompleteEvent +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.kafka.test.context.EmbeddedKafka + + +@SpringBootTest +@EmbeddedKafka +internal class EventConsumerTest { + @Autowired + private val eventConsumer: StreamingListener? = null + + @Test + fun consumePaintCreatedEvent() { + val event: StreamingCompleteEvent = generatedEvent() + eventConsumer?.consumeStreamingComplete(event) + } + + companion object { + private fun generatedEvent(): StreamingCompleteEvent { + return StreamingCompleteEvent( + 1L, + ) + } + } +} diff --git a/src/backend/chart-server/infra/00_scheme.sql b/src/backend/chart-server/infra/00_scheme.sql new file mode 100644 index 00000000..d34c7f7f --- /dev/null +++ b/src/backend/chart-server/infra/00_scheme.sql @@ -0,0 +1,5 @@ +CREATE DATABASE IF NOT EXISTS chart + DEFAULT CHARACTER SET utf8 + DEFAULT COLLATE utf8_general_ci; + +USE chart; diff --git a/src/backend/music-server/infra/00_schema.sql b/src/backend/music-server/infra/00_schema.sql new file mode 100644 index 00000000..5be689a4 --- /dev/null +++ b/src/backend/music-server/infra/00_schema.sql @@ -0,0 +1,117 @@ +CREATE DATABASE IF NOT EXISTS music + DEFAULT CHARACTER SET utf8 + DEFAULT COLLATE utf8_general_ci; + +USE music; + +-- 테이블 순서는 관계를 고려하여 한 번에 실행해도 에러가 발생하지 않게 정렬되었습니다. + +-- artists Table Create SQL +-- 테이블 생성 SQL - artists +CREATE TABLE artists +( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '고유 번호', + `name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '이름', + `gender` VARCHAR(6) NOT NULL DEFAULT 'MALE' COMMENT '성별. MALE / FEMALE / MIX', + `type` VARCHAR(5) NOT NULL DEFAULT 'SOLO' COMMENT '아티스트 구분. SOLO / GROUP', + `agency` VARCHAR(50) NOT NULL COMMENT '소속사', + `created_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '생성 시각', + `updated_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '수정 시각', + PRIMARY KEY (id) +); + +-- 테이블 Comment 설정 SQL - artists +ALTER TABLE artists COMMENT '아티스트 테이블'; + + +-- albums Table Create SQL +-- 테이블 생성 SQL - albums +CREATE TABLE albums +( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '고유 번호', + `artist_id` BIGINT NOT NULL COMMENT '아티스트', + `type` VARCHAR(7) NOT NULL DEFAULT 'SINGLE' COMMENT '앨범 구분. SINGLE / REGULAR', + `title` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '제목', + `released_at` DATETIME NOT NULL COMMENT '발매일', + `cover_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '앨범 커버 주소', + `like_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '좋아요', + `created_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '생성 시각', + `updated_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '수정 시각', + PRIMARY KEY (id) +); + +-- 테이블 Comment 설정 SQL - albums +ALTER TABLE albums COMMENT '앨범 테이블'; + +-- Foreign Key 설정 SQL - albums(artist_id) -> artists(id) +ALTER TABLE albums + ADD CONSTRAINT FK_albums_artist_id_artists_id FOREIGN KEY (artist_id) + REFERENCES artists (id) ON DELETE RESTRICT ON UPDATE RESTRICT; + +-- Foreign Key 삭제 SQL - albums(artist_id) +-- ALTER TABLE albums +-- DROP FOREIGN KEY FK_albums_artist_id_artists_id; + + +-- musics Table Create SQL +-- 테이블 생성 SQL - musics +CREATE TABLE musics +( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '고유 번호', + `album_id` BIGINT NOT NULL COMMENT '앨범', + `artist_id` BIGINT NOT NULL COMMENT '아티스트', + `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '제목', + `play_time` SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '재생 시간', + `lyrics` TEXT NOT NULL COMMENT '가사', + `file_url` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '파일 주소', + `format_type` VARCHAR(5) NOT NULL DEFAULT 'MP3' COMMENT '파일 형식. MP3 / FLAC', + `like_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '좋아요', + `created_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '생성 시각', + `updated_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '수정 시각', + PRIMARY KEY (id) +); + +-- 테이블 Comment 설정 SQL - musics +ALTER TABLE musics COMMENT '음원 테이블'; + +-- Foreign Key 설정 SQL - musics(artist_id) -> artists(id) +ALTER TABLE musics + ADD CONSTRAINT FK_musics_artist_id_artists_id FOREIGN KEY (artist_id) + REFERENCES artists (id) ON DELETE RESTRICT ON UPDATE RESTRICT; + +-- Foreign Key 삭제 SQL - musics(artist_id) +-- ALTER TABLE musics +-- DROP FOREIGN KEY FK_musics_artist_id_artists_id; + +-- Foreign Key 설정 SQL - musics(album_id) -> albums(id) +ALTER TABLE musics + ADD CONSTRAINT FK_musics_album_id_albums_id FOREIGN KEY (album_id) + REFERENCES albums (id) ON DELETE RESTRICT ON UPDATE RESTRICT; + +-- Foreign Key 삭제 SQL - musics(album_id) +-- ALTER TABLE musics +-- DROP FOREIGN KEY FK_musics_album_id_albums_id; + +-- musics Table Create SQL +-- 테이블 생성 SQL - musics +CREATE TABLE music_likes +( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '고유 번호', + `music_id` BIGINT NOT NULL COMMENT '음원', + `user_id` BIGINT NOT NULL COMMENT '유저', + `created_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '생성 시각', + `updated_at` DATETIME NOT NULL DEFAULT NOW() COMMENT '수정 시각', + PRIMARY KEY (id) +); + +-- 테이블 Comment 설정 SQL - musics +ALTER TABLE music_likes COMMENT '음원 좋아요 테이블'; + +-- Foreign Key 설정 SQL - musics(album_id) -> albums(id) +ALTER TABLE music_likes + ADD CONSTRAINT FK_music_likes_music_id_music_id FOREIGN KEY (music_id) + REFERENCES musics (id) ON DELETE RESTRICT ON UPDATE RESTRICT; + +-- Foreign Key 삭제 SQL - music_likes(music_id) +-- ALTER TABLE music_likes +-- DROP FOREIGN KEY FK_music_likes_music_id_music_id; diff --git a/src/backend/music-server/music-api/src/main/java/com/lalala/music/controller/MusicLikeController.java b/src/backend/music-server/music-api/src/main/java/com/lalala/music/controller/MusicLikeController.java new file mode 100644 index 00000000..584f6312 --- /dev/null +++ b/src/backend/music-server/music-api/src/main/java/com/lalala/music/controller/MusicLikeController.java @@ -0,0 +1,42 @@ +package com.lalala.music.controller; + +import com.lalala.aop.AuthenticationContext; +import com.lalala.aop.PassportAuthentication; +import com.lalala.music.dto.CreateMusicRequestDTO; +import com.lalala.music.dto.MusicDTO; +import com.lalala.music.dto.MusicDetailDTO; +import com.lalala.music.dto.MusicRetrieveRequestDTO; +import com.lalala.music.dto.UpdateMusicRequestDTO; +import com.lalala.music.service.MusicLikeService; +import com.lalala.music.service.MusicService; +import com.lalala.music.service.MusicUploaderService; +import com.lalala.response.BaseResponse; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/v1/api/musics") +public class MusicLikeController { + private final MusicLikeService service; + + @PostMapping("/{musicId}/likes") + @PassportAuthentication + public ResponseEntity> createLike(@PathVariable("musicId") Long musicId) { + Long userId = AuthenticationContext.getUserInfo().id(); + boolean isLike = service.create(userId, musicId); + return ResponseEntity.ok(BaseResponse.from(HttpStatus.OK.value(), "좋아요를 눌렀습니다.", isLike)); + } +} diff --git a/src/backend/music-server/music-api/src/main/java/com/lalala/music/entity/MusicLikeEntity.java b/src/backend/music-server/music-api/src/main/java/com/lalala/music/entity/MusicLikeEntity.java new file mode 100644 index 00000000..bd7db47e --- /dev/null +++ b/src/backend/music-server/music-api/src/main/java/com/lalala/music/entity/MusicLikeEntity.java @@ -0,0 +1,40 @@ +package com.lalala.music.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "music_likes") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MusicLikeEntity extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(name = "user_id", nullable = false) + Long userId; + + @Column(name = "music_id", nullable = false) + Long musicId; + + public MusicLikeEntity(Long userId, Long musicId) { + this.userId = userId; + this.musicId = musicId; + } + + public void updateUser(Long userId) { + this.userId = userId; + } + + public void updateMusic(Long musicId) { + this.musicId = musicId; + } +} \ No newline at end of file diff --git a/src/backend/music-server/music-api/src/main/java/com/lalala/music/repository/MusicLikeRepository.java b/src/backend/music-server/music-api/src/main/java/com/lalala/music/repository/MusicLikeRepository.java new file mode 100644 index 00000000..087f2c2a --- /dev/null +++ b/src/backend/music-server/music-api/src/main/java/com/lalala/music/repository/MusicLikeRepository.java @@ -0,0 +1,9 @@ +package com.lalala.music.repository; + +import com.lalala.music.entity.MusicLikeEntity; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MusicLikeRepository extends JpaRepository { + Optional findByUserIdAndMusicId(Long userId, Long musicId); +} diff --git a/src/backend/music-server/music-api/src/main/java/com/lalala/music/service/MusicLikeService.java b/src/backend/music-server/music-api/src/main/java/com/lalala/music/service/MusicLikeService.java new file mode 100644 index 00000000..9299855d --- /dev/null +++ b/src/backend/music-server/music-api/src/main/java/com/lalala/music/service/MusicLikeService.java @@ -0,0 +1,42 @@ +package com.lalala.music.service; + +import com.lalala.music.entity.MusicEntity; +import com.lalala.music.entity.MusicLikeEntity; +import com.lalala.music.repository.MusicLikeRepository; +import com.lalala.music.repository.MusicRepository; +import com.lalala.music.util.MusicUtils; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MusicLikeService { + private final MusicLikeRepository repository; + private final MusicRepository musicRepository; + + @Transactional + public boolean create(Long userId, Long musicId) { + MusicEntity music = MusicUtils.findById(musicId, musicRepository); + + Optional musicLike = repository.findByUserIdAndMusicId(userId, music.getId()); + if (musicLike.isPresent()) { + repository.delete(musicLike.get()); + + music.decreaseLikeCount(); + musicRepository.save(music); + + return false; + } else { + MusicLikeEntity createdMusicLike = new MusicLikeEntity(userId, music.getId()); + repository.save(createdMusicLike); + + music.increaseLikeCount(); + musicRepository.save(music); + + return true; + } + } +} diff --git a/src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/config/async/AsyncConfig.kt b/src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/config/async/AsyncConfig.kt new file mode 100644 index 00000000..aed53cd7 --- /dev/null +++ b/src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/config/async/AsyncConfig.kt @@ -0,0 +1,26 @@ +package com.lalala.streaming.config.async + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableAsync +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor +import java.util.concurrent.Executor + + +@Configuration +@EnableAsync +internal class AsyncConfig { + @Bean(name = ["asyncProducerExecutor"]) + fun asyncProducerExecutor(): Executor { + val executor = ThreadPoolTaskExecutor() + executor.corePoolSize = 10 + executor.maxPoolSize = 100 + executor.queueCapacity = 100 + executor.setThreadNamePrefix("Easel-Produce-Thread") + executor.keepAliveSeconds = 180 + executor.setAllowCoreThreadTimeOut(false) + executor.setPrestartAllCoreThreads(true) + executor.initialize() + return executor + } +} diff --git a/src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/external/kafka/KafkaProducer.kt b/src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/external/kafka/KafkaProducer.kt new file mode 100644 index 00000000..3d1ff2be --- /dev/null +++ b/src/backend/streaming-server/src/main/kotlin/com/lalala/streaming/external/kafka/KafkaProducer.kt @@ -0,0 +1,16 @@ +package com.lalala.streaming.external.kafka + +import com.lalala.event.LalalaEvent +import org.springframework.kafka.core.KafkaTemplate +import org.springframework.scheduling.annotation.Async +import org.springframework.stereotype.Component + +@Component +class KafkaProducer( + private val template: KafkaTemplate +) { + @Async + fun execute(event: LalalaEvent) { + template.send(event.topic, event) + } +}