Skip to content

Commit

Permalink
Merge branch 'develop' into feature/#176-공유_보관함_잠금_기능
Browse files Browse the repository at this point in the history
  • Loading branch information
Ji-Ha authored Sep 20, 2022
2 parents 5e8948f + 2a55e7d commit 29dae36
Show file tree
Hide file tree
Showing 29 changed files with 595 additions and 372 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import com.yapp.web2.domain.folder.controller.FolderController
import com.yapp.web2.security.jwt.TokenDto
import com.yapp.web2.util.AbstractControllerTest
import com.yapp.web2.util.Message
import com.yapp.web2.util.RandomUtils
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import org.apache.commons.lang3.RandomStringUtils
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
Expand Down Expand Up @@ -293,10 +293,12 @@ class AccountControllerTest : AbstractControllerTest() {
@Test
fun `비밀번호 재설정 시 임시 비밀번호를 생성하고 메일을 발송한다`() {
// given
val tempPassword = RandomStringUtils.randomAlphanumeric(12) + "!"
val tempPassword = RandomUtils.shuffleCharacters(
RandomUtils.getRandomAlphanumeric(12) + RandomUtils.getRandomSpecialCharacter()
)
every { accountService.createTempPassword() } returns tempPassword
every { accountService.updatePassword(any(), any()) } just Runs
every { accountService.sendMail(any(), tempPassword) } returns Message.SUCCESS_SEND_MAIL
every { accountService.sendMail(any(), tempPassword) } just Runs

// when
val resultAction = util.postResultAction("/api/v1/user/password/reset", "", mockMvc)
Expand Down
15 changes: 11 additions & 4 deletions src/main/kotlin/com/yapp/web2/batch/job/JobCompletionListener.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.yapp.web2.batch.job

import com.yapp.web2.infra.slack.SlackServiceImpl
import com.yapp.web2.infra.slack.SlackService
import org.slf4j.LoggerFactory
import org.springframework.batch.core.BatchStatus
import org.springframework.batch.core.JobExecution
Expand All @@ -9,20 +9,27 @@ import org.springframework.stereotype.Component
import java.util.stream.Collectors

@Component
class JobCompletionListener : JobExecutionListenerSupport() {
class JobCompletionListener(
private val slackApi: SlackService
) : JobExecutionListenerSupport() {

private val log = LoggerFactory.getLogger(javaClass)
private val slackApi: SlackServiceImpl = SlackServiceImpl()

override fun afterJob(jobExecution: JobExecution) {
val jobStatus = jobExecution.status
log.info("Job {} - {}", jobStatus, jobExecution.jobInstance.jobName)

if (jobStatus == BatchStatus.COMPLETED) {
slackApi.sendSlackAlarmToVerbose(
"*`${jobExecution.jobInstance.jobName}`* - executed"
)
}

if (jobStatus != BatchStatus.COMPLETED) {
val errorMessage = String.format(
"*`Batch Error`* - %s", getErrorMessage(jobExecution).replace("\"", "")
)
slackApi.sendMessage(errorMessage)
slackApi.sendSlackAlarm(errorMessage)
}
}

Expand Down
37 changes: 25 additions & 12 deletions src/main/kotlin/com/yapp/web2/batch/job/NotificationJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory
import org.springframework.batch.core.configuration.annotation.JobScope
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory
import org.springframework.batch.core.configuration.annotation.StepScope
import org.springframework.batch.core.launch.support.RunIdIncrementer
import org.springframework.batch.item.ItemProcessor
import org.springframework.batch.item.ItemReader
import org.springframework.batch.item.ItemWriter
import org.springframework.batch.item.support.ListItemReader
import org.springframework.context.annotation.Bean
Expand All @@ -21,54 +23,65 @@ import org.springframework.context.annotation.Configuration
class NotificationJob(
private val jobBuilderFactory: JobBuilderFactory,
private val stepBuilderFactory: StepBuilderFactory,
private val notificationService: RemindService,
private val remindService: RemindService,
private val jobCompletionListener: JobCompletionListener
) {

companion object {
const val SEND_NOTIFICATION_JOB = "SEND_NOTIFICATION_JOB"
const val SEND_NOTIFICATION_STEP = "SEND_NOTIFICATION_STEP"
}

private val log = LoggerFactory.getLogger(javaClass)

@Bean("bookmarkNotificationJob")
@Bean
fun bookmarkNotificationJob(): Job {
return jobBuilderFactory.get("bookmarkNotificationJob")
return jobBuilderFactory.get(SEND_NOTIFICATION_JOB)
.start(bookmarkNotificationStep())
.incrementer(RunIdIncrementer())
.listener(jobCompletionListener)
.build()
}

@Bean
@JobScope
fun bookmarkNotificationStep(): Step {
return stepBuilderFactory.get("bookmarkNotificationStep")
return stepBuilderFactory.get(SEND_NOTIFICATION_STEP)
.chunk<Bookmark, Bookmark>(10)
.reader(remindBookmarkReader())
.processor(remindBookmarkProcessor())
.writer(remindBookmarkWriter())
.build()
}

// TODO: ListItemReader의 경우 모든 데이터를 읽고 메모리에 올려두고 사용하기에 데이터가 많을경우 OOM 발생할 가능성이 존재함
@Bean
@StepScope
fun remindBookmarkReader(): ListItemReader<Bookmark> {
val bookmarkOfList = notificationService.getRemindBookmark()

return ListItemReader(bookmarkOfList)
fun remindBookmarkReader(): ItemReader<Bookmark> {
return ListItemReader(remindService.getRemindBookmark())
}

// TODO: Notification 실패 처리 -> Queue(Kafka) 적재 후 Retry 처리
@Bean
@StepScope
fun remindBookmarkProcessor(): ItemProcessor<Bookmark, Bookmark> {
return ItemProcessor {
notificationService.sendNotification(it)
log.info("Bookmark id: ${it.id}, 리마인드 발송 갯수: ${it.remindList.size}")
remindService.sendNotification(it)
it
}
}

@Bean
@StepScope
fun remindBookmarkWriter(): ItemWriter<Bookmark> {
return ItemWriter {
it.stream().forEach { bookmark ->
bookmark.successRemind()
notificationService.save(bookmark)
log.info("{} -> {} 푸쉬 발송", bookmark.userId, bookmark.title)
bookmark.remindList.forEach { remind ->
remind.successRemind()
}
remindService.save(bookmark)
log.info("${bookmark.userId} -> ${bookmark.title} Send Notification")
}
}
}
Expand Down
28 changes: 21 additions & 7 deletions src/main/kotlin/com/yapp/web2/batch/job/TrashRefreshJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory
import org.springframework.batch.core.configuration.annotation.JobScope
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory
import org.springframework.batch.core.configuration.annotation.StepScope
import org.springframework.batch.core.launch.support.RunIdIncrementer
import org.springframework.batch.item.ItemProcessor
import org.springframework.batch.item.ItemReader
import org.springframework.batch.item.ItemWriter
import org.springframework.batch.item.support.ListItemReader
import org.springframework.context.annotation.Bean
Expand All @@ -25,41 +28,52 @@ class TrashRefreshJob(
private val jobCompletionListener: JobCompletionListener
) {

companion object {
const val TRASH_BOOKMARKS_DELETE_JOB = "TRASH_BOOKMARKS_DELETE_JOB"
const val TRASH_BOOKMARKS_DELETE_STEP = "TRASH_BOOKMARKS_DELETE_STEP"
}

private val log = LoggerFactory.getLogger(TrashRefreshJob::class.java)

@Bean("bookmarkTrashRefreshJob")
@Bean
fun bookmarkTrashRefreshJob(): Job {
return jobBuilderFactory.get("bookmarkTrashRefreshJob")
return jobBuilderFactory.get(TRASH_BOOKMARKS_DELETE_JOB)
.start(trashRefreshStep())
.incrementer(RunIdIncrementer())
.listener(jobCompletionListener)
.build()
}

@Bean
@JobScope
fun trashRefreshStep(): Step {
return stepBuilderFactory.get("trashRefreshStep")
.chunk<Bookmark, Bookmark>(10)
return stepBuilderFactory.get(TRASH_BOOKMARKS_DELETE_STEP)
.chunk<Bookmark, Bookmark>(100)
.reader(deleteBookmarkReader())
.processor(deleteBookmarkProcessor())
.writer(NoOperationItemWriter())
.build()
}

@Bean
fun deleteBookmarkReader(): ListItemReader<Bookmark> {
@StepScope
fun deleteBookmarkReader(): ItemReader<Bookmark> {
val deleteCycle = 30L
val deleteBookmarkList = bookmarkRepository.findAllByDeletedIsTrueAndDeleteTimeBefore(
LocalDateTime.now().minusDays(30)
LocalDateTime.now().minusDays(deleteCycle)
)
log.info("휴지통에서 30일이 지난 북마크는 자동으로 삭제합니다. 삭제할 북마크 갯수: ${deleteBookmarkList.size}")

return ListItemReader(deleteBookmarkList)
}

@Bean
@StepScope
fun deleteBookmarkProcessor(): ItemProcessor<Bookmark, Bookmark> {
return ItemProcessor {
log.info("Bookmark to delete info => userId: ${it.userId}, folderId: ${it.folderId}, folderName: ${it.folderName} title: ${it.title}")
log.info("휴지통에서 삭제할 북마크 정보 => " +
"userId: ${it.userId}, folderId: ${it.folderId}, folderName: ${it.folderName} title: ${it.title}"
)
bookmarkRepository.delete(it)
it
}
Expand Down
1 change: 0 additions & 1 deletion src/main/kotlin/com/yapp/web2/config/SecurityConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class SecurityConfig(

http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
// .antMatchers("/api/v1/page/open/**").permitAll()
.anyRequest().authenticated()

http.apply(JwtSecurityConfig(jwtProvider))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ class AccountController(
val token = ControllerUtil.extractAccessToken(request)
val tempPassword = accountService.createTempPassword()
accountService.updatePassword(token, tempPassword)
accountService.sendMail(token, tempPassword)

return ResponseEntity.status(HttpStatus.OK).body(accountService.sendMail(token, tempPassword))
return ResponseEntity.status(HttpStatus.OK).body(Message.SUCCESS)
}

}
62 changes: 34 additions & 28 deletions src/main/kotlin/com/yapp/web2/domain/account/entity/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import com.yapp.web2.security.jwt.TokenDto
import io.swagger.annotations.ApiModel
import io.swagger.annotations.ApiModelProperty
import org.springframework.transaction.annotation.Transactional
import javax.persistence.*
import javax.persistence.CascadeType
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.OneToMany
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotEmpty

Expand All @@ -17,33 +20,6 @@ class Account(
var email: String
) : BaseTimeEntity() {

companion object {
fun signUpToAccount(dto: AccountRequestDto.SignUpRequest, encryptPassword: String, name: String): Account {
return Account(dto.email, encryptPassword, dto.fcmToken, name)
}

fun profileToAccount(dto: AccountProfile): Account {
return Account(dto.email, dto.image, dto.name, dto.socialType, dto.fcmToken)
}

fun accountToProfile(account: Account): AccountProfile {
return AccountProfile(
account.email,
account.name,
account.image,
account.socialType,
account.fcmToken ?: ""
)
}

fun accountToRemindElements(account: Account): RemindElements {
return RemindElements(account.remindCycle, account.remindToggle)
}

const val BASIC_IMAGE_URL: String = "https://yapp-bucket-test.s3.ap-northeast-2.amazonaws.com/basicImage.png"
}


@Column(nullable = true)
var password: String? = null

Expand Down Expand Up @@ -74,6 +50,36 @@ class Account(
@OneToMany(mappedBy = "account", cascade = [CascadeType.ALL], orphanRemoval = true)
var accountFolderList: MutableList<AccountFolder> = mutableListOf()

fun inverseRemindToggle(reverse: Boolean) {
this.remindToggle = reverse
}

companion object {
fun signUpToAccount(dto: AccountRequestDto.SignUpRequest, encryptPassword: String, name: String): Account {
return Account(dto.email, encryptPassword, dto.fcmToken, name)
}

fun profileToAccount(dto: AccountProfile): Account {
return Account(dto.email, dto.image, dto.name, dto.socialType, dto.fcmToken)
}

fun accountToProfile(account: Account): AccountProfile {
return AccountProfile(
account.email,
account.name,
account.image,
account.socialType,
account.fcmToken ?: ""
)
}

fun accountToRemindElements(account: Account): RemindElements {
return RemindElements(account.remindCycle, account.remindToggle)
}

const val BASIC_IMAGE_URL: String = "https://yapp-bucket-test.s3.ap-northeast-2.amazonaws.com/basicImage.png"
}

constructor(email: String, password: String) : this(email) {
this.password = password
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.yapp.web2.exception.custom.ExistNameException
import com.yapp.web2.exception.custom.FolderNotRootException
import com.yapp.web2.exception.custom.ImageNotFoundException
import com.yapp.web2.exception.custom.PasswordMismatchException
import com.yapp.web2.infra.slack.SlackService
import com.yapp.web2.security.jwt.JwtProvider
import com.yapp.web2.security.jwt.TokenDto
import com.yapp.web2.util.Message
Expand All @@ -35,7 +36,8 @@ class AccountService(
private val jwtProvider: JwtProvider,
private val s3Client: S3Client,
private val passwordEncoder: PasswordEncoder,
private val mailSender: JavaMailSender
private val mailSender: JavaMailSender,
private val slackApi: SlackService
) {

@Value("\${extension.version}")
Expand Down Expand Up @@ -78,8 +80,16 @@ class AccountService(
isRegistered = false
val newAccount = createUser(account)
folderService.createDefaultFolder(account)

val userCountMessage = """
${newAccount.id}: ${newAccount.name} 님이 회원가입을 진행하였습니다. 현재 회원 수: *`${accountRepository.count()}`*
""".trimIndent()

slackApi.sendSlackAlarmToVerbose(userCountMessage)

newAccount
}

else -> {
log.info("소셜로그인 => ${account.email} 계정이 이미 존재합니다.")
existAccount.fcmToken = account2.fcmToken
Expand All @@ -102,6 +112,12 @@ class AccountService(

log.info("${newAccount.email} account signUp succeed")

val userCountMessage = """
${newAccount.id}: ${newAccount.name} 님이 회원가입을 진행하였습니다. 현재 회원 수: *`${accountRepository.count()}`*
""".trimIndent()

slackApi.sendSlackAlarmToVerbose(userCountMessage)

return Account.AccountLoginSuccess(jwtProvider.createToken(newAccount), newAccount, false)
}

Expand Down Expand Up @@ -284,7 +300,8 @@ class AccountService(
return RandomUtils.shuffleCharacters(randomAlphanumeric + randomSpecialCharacter)
}

fun sendMail(token: String, tempPassword: String): String {
// TODO: 2022/07/16 비동기 처리
fun sendMail(token: String, tempPassword: String) {
val account = jwtProvider.getAccountFromToken(token)
val mailMessage = SimpleMailMessage()
mailMessage.setTo(account.email)
Expand All @@ -294,8 +311,6 @@ class AccountService(
mailSender.send(mailMessage)

log.info("${account.email} 계정으로 임시 비밀번호를 발송하였습니다.")

return Message.SUCCESS_SEND_MAIL
}

fun updatePassword(token: String, tempPassword: String) {
Expand Down
Loading

0 comments on commit 29dae36

Please sign in to comment.