Skip to content

Commit

Permalink
Merge pull request #152 from PSR-Co/feat/#139-notification
Browse files Browse the repository at this point in the history
[feat] 알림 세팅 및 요청 관련 알림 구현
  • Loading branch information
psyeon1120 authored Aug 26, 2023
2 parents 3617471 + acab8fd commit ca3fe55
Show file tree
Hide file tree
Showing 19 changed files with 251 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
application-secret.yaml
firebase-service-key.json

### STS ###
.apt_generated
Expand Down
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ dependencies {

// random
implementation("org.apache.commons:commons-lang3:3.12.0")

// notification
implementation("com.google.firebase:firebase-admin:9.2.0")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
}


Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/com/psr/psr/global/Constant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,11 @@ class Constant {
const val POPULAR = "인기순"
}
}

class NotiSentence{
companion object NotiSentence{
const val NEW_ORDER_SENTENCE = "님의 요청을 확인해주세요!"
const val TWO_MONTH_ORDER_SENTENCE = "님의 요청 상태를 확인해주세요!"
}
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/psr/psr/notification/dto/Data.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.psr.psr.notification.dto

data class Data(
val relatedId: Long,
val notiType: String
)
6 changes: 6 additions & 0 deletions src/main/kotlin/com/psr/psr/notification/dto/FcmMessage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.psr.psr.notification.dto

data class FcmMessage (
val validate_only: Boolean,
val message: Message
)
7 changes: 7 additions & 0 deletions src/main/kotlin/com/psr/psr/notification/dto/Message.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.psr.psr.notification.dto

data class Message(
val notification: Notification,
val token: String,
val data: Data
)
48 changes: 48 additions & 0 deletions src/main/kotlin/com/psr/psr/notification/dto/NotiAssembler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.psr.psr.notification.dto

import com.psr.psr.notification.entity.NotificationType
import com.psr.psr.notification.entity.PushNotification
import com.psr.psr.user.entity.User
import org.springframework.stereotype.Component


@Component
class NotiAssembler {
fun toEntity(receiver: User, title: String, content: String, relatedId: Long, type: NotificationType): PushNotification {
return PushNotification(
user = receiver,
title = title,
content = content,
relatedId = relatedId,
type = type
)
}
fun toMessageDTO(targetToken: String, title: String, body: String, relatedId: Long, notiType: String): Message {
return Message(
notification = toNotificationDTO(title, body),
token = targetToken,
data = toDataDTO(relatedId, notiType)
)
}

fun toNotificationDTO(title: String, body: String): Notification {
return Notification(
title = title,
body = body
)
}

fun toDataDTO(relatedId: Long, notiType: String): Data {
return Data(
relatedId = relatedId,
notiType = notiType
)
}

fun makeMessage(targetToken: String, title: String, body: String, relatedId: Long, notiType: String): FcmMessage {
return FcmMessage(
validate_only = false,
message = toMessageDTO(targetToken, title, body, relatedId, notiType)
)
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/psr/psr/notification/dto/Notification.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.psr.psr.notification.dto

data class Notification(
val title: String,
val body: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.psr.psr.notification.entity

enum class NotificationType {
NEW_ORDER,
CHANGED_ORDER_STATUS,
TWO_MONTH_ORDER,
CHAT
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import jakarta.persistence.*
import org.jetbrains.annotations.NotNull

@Entity
data class Notification(
data class PushNotification(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long,
var id: Long? = null,

@ManyToOne
@JoinColumn(nullable = false, name = "user_id")
Expand All @@ -19,6 +19,12 @@ data class Notification(
var title: String,

@NotNull
var content: String
var content: String,

// 알림의 주체인 요청, 채팅 등의 ID
var relatedId: Long,

@NotNull
var type: NotificationType

) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.psr.psr.notification.repository

import com.psr.psr.notification.entity.Notification
import com.psr.psr.notification.entity.PushNotification
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface NotificationRepository: JpaRepository<Notification, Long>, NotificationCustom {
interface NotificationRepository: JpaRepository<PushNotification, Long>, NotificationCustom {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.psr.psr.notification.repository

import com.psr.psr.notification.dto.NotiList
import com.psr.psr.notification.dto.NotificationListRes
import com.psr.psr.notification.entity.QNotification.notification
import com.psr.psr.notification.entity.QPushNotification.pushNotification
import com.psr.psr.user.entity.User
import com.querydsl.core.group.GroupBy.groupBy
import com.querydsl.core.group.GroupBy.list
Expand All @@ -23,19 +23,19 @@ class NotificationRepositoryImpl(
override fun findNotificationByUserGroupByDate(user: User, pageable: Pageable): Page<NotificationListRes> {
val formattedDate: StringTemplate = Expressions.stringTemplate(
"DATE_FORMAT({0}, {1})",
notification.createdAt,
pushNotification.createdAt,
ConstantImpl.create("%Y-%m-%d")
)

val result = queryFactory
.selectFrom(notification)
.where(notification.user.eq(user))
.orderBy(notification.id.desc())
.selectFrom(pushNotification)
.where(pushNotification.user.eq(user))
.orderBy(pushNotification.id.desc())
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.transform(groupBy(formattedDate)
.list(Projections.constructor(NotificationListRes::class.java, formattedDate,
list(Projections.constructor(NotiList::class.java, notification.title, notification.content)))))
list(Projections.constructor(NotiList::class.java, pushNotification.title, pushNotification.content)))))
return PageImpl(result, pageable, result.size.toLong())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,132 @@
package com.psr.psr.notification.service

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.auth.oauth2.GoogleCredentials
import com.psr.psr.global.Constant.NotiSentence.NotiSentence.NEW_ORDER_SENTENCE
import com.psr.psr.global.Constant.NotiSentence.NotiSentence.TWO_MONTH_ORDER_SENTENCE
import com.psr.psr.notification.dto.FcmMessage
import com.psr.psr.notification.dto.NotiAssembler
import com.psr.psr.notification.dto.NotificationListRes
import com.psr.psr.notification.entity.NotificationType
import com.psr.psr.notification.repository.NotificationRepository
import com.psr.psr.order.entity.OrderStatus
import com.psr.psr.user.entity.User
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.springframework.beans.factory.annotation.Value
import org.springframework.core.io.ClassPathResource
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.http.HttpHeaders
import org.springframework.stereotype.Service

@Service
class NotificationService(
private val notificationRepository: NotificationRepository
private val notificationRepository: NotificationRepository,
private val notiAssembler: NotiAssembler,
@Value("\${firebase.sendUrl}") private val sendUrl: String,
private val objectMapper: ObjectMapper
) {
// 알림 목록 조회
fun getNotiList(user: User, pageable: Pageable): Page<NotificationListRes> {
return notificationRepository.findNotificationByUserGroupByDate(user, pageable)
}

// 새로운 요청 알림
fun sendNewOrderNoti(productName: String, orderReceiver: User, ordererName: String, orderId: Long) {
val messageBody = ordererName + NEW_ORDER_SENTENCE
notificationRepository.save(notiAssembler.toEntity(
orderReceiver,
productName,
messageBody,
orderId,
NotificationType.NEW_ORDER
))

if (isPushNotiAvailable(orderReceiver)) {
val message: FcmMessage = notiAssembler.makeMessage(
orderReceiver.deviceToken!!,
productName,
messageBody,
orderId,
NotificationType.NEW_ORDER.name
)
sendMessage(objectMapper.writeValueAsString(message))
}
}

// 요청 상태 변경 알림
fun sendChangeOrderStatusNoti(productName: String, orderer: User, orderStatus: OrderStatus, orderId: Long) {
val messageBody = orderStatus.notiSentence!!
notificationRepository.save(notiAssembler.toEntity(
orderer,
productName,
messageBody,
orderId,
NotificationType.CHANGED_ORDER_STATUS
))

if (isPushNotiAvailable(orderer)) {
val message: FcmMessage = notiAssembler.makeMessage(
orderer.deviceToken!!,
productName,
messageBody,
orderId,
NotificationType.CHANGED_ORDER_STATUS.name
)
sendMessage(objectMapper.writeValueAsString(message))
}
}

// 2달 뒤 요청상태 입력 요망 알림
fun send2MonthOrderNoti(productName: String, orderer: User, ordererName: String, orderId: Long) {
val messageBody = ordererName + TWO_MONTH_ORDER_SENTENCE
notificationRepository.save(notiAssembler.toEntity(
orderer,
productName,
messageBody,
orderId,
NotificationType.TWO_MONTH_ORDER
))

if (isPushNotiAvailable(orderer)) {
val message: FcmMessage = notiAssembler.makeMessage(
orderer.deviceToken!!,
productName,
messageBody,
orderId,
NotificationType.TWO_MONTH_ORDER.name
)
sendMessage(objectMapper.writeValueAsString(message))
}
}

// 알림 수신 상태 체크
fun isPushNotiAvailable(user: User): Boolean {
return user.deviceToken != null && user.notification
}

// firebase accessToken 발급
private fun getAccessToken(): String? {
val firebaseConfigPath = "firebase-service-key.json"
val googleCredentials = GoogleCredentials
.fromStream(ClassPathResource(firebaseConfigPath).inputStream)
.createScoped(listOf("https://www.googleapis.com/auth/cloud-platform"))
googleCredentials.refreshIfExpired()
return googleCredentials.accessToken.tokenValue
}

// 메세지 전송
private fun sendMessage(message: String): Response {
val client = OkHttpClient()
val requestBody: RequestBody = message.toRequestBody("application/json; charset=utf-8".toMediaType())
val request: Request = Request.Builder()
.url(sendUrl)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build()
return client.newCall(request).execute()
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/com/psr/psr/order/dto/OrderAssembler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class OrderAssembler {
fun toOrderResDTO(order: Order, isSeller: Boolean): OrderRes {
return OrderRes(
isSeller = isSeller,
status = order.orderStatus.statusName,
status = order.orderStatus.value,
orderUserId = order.user.id!!,
orderDate = order.createdAt.format(DateTimeFormatter.ISO_DATE),
productId = order.product.id!!,
Expand Down
15 changes: 8 additions & 7 deletions src/main/kotlin/com/psr/psr/order/entity/OrderStatus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ package com.psr.psr.order.entity

import com.psr.psr.global.exception.BaseException
import com.psr.psr.global.exception.BaseResponseCode
import com.psr.psr.global.resolver.EnumType

enum class OrderStatus(val statusName: String) {
ORDER_WAITING("요청대기"),
PROGRESSING("진행중"),
COMPLETED("진행완료"),
CANCELED("요청취소");
enum class OrderStatus(override val value: String, val notiSentence: String?): EnumType {
ORDER_WAITING("요청대기", null),
PROGRESSING("진행중", "요청이 진행되었습니다"),
COMPLETED("진행완료", "요청이 진행 완료되었습니다"),
CANCELED("요청취소", "요청이 취소되었습니다");

companion object {
fun findByName(statusName: String): OrderStatus {
return OrderStatus.values().find { it.statusName == statusName }
fun findByValue(value: String): OrderStatus {
return enumValues<OrderStatus>().find { it.value == value }
?: throw BaseException(BaseResponseCode.INVALID_ORDER_STATUS)
}
}
Expand Down
Loading

0 comments on commit ca3fe55

Please sign in to comment.