parent
9ed031e574
commit
4977ee99df
|
@ -67,6 +67,7 @@ class SecurityConfig(
|
||||||
.antMatchers("/member/check/email").permitAll()
|
.antMatchers("/member/check/email").permitAll()
|
||||||
.antMatchers("/member/check/nickname").permitAll()
|
.antMatchers("/member/check/nickname").permitAll()
|
||||||
.antMatchers("/member/signup").permitAll()
|
.antMatchers("/member/signup").permitAll()
|
||||||
|
.antMatchers("/member/signup/v2").permitAll()
|
||||||
.antMatchers("/member/login").permitAll()
|
.antMatchers("/member/login").permitAll()
|
||||||
.antMatchers("/creator-admin/member/login").permitAll()
|
.antMatchers("/creator-admin/member/login").permitAll()
|
||||||
.antMatchers("/member/forgot-password").permitAll()
|
.antMatchers("/member/forgot-password").permitAll()
|
||||||
|
|
|
@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.member.following.CreatorFollowRequest
|
||||||
import kr.co.vividnext.sodalive.member.login.LoginRequest
|
import kr.co.vividnext.sodalive.member.login.LoginRequest
|
||||||
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||||
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
||||||
|
import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
import org.springframework.security.core.userdetails.User
|
import org.springframework.security.core.userdetails.User
|
||||||
|
@ -42,6 +43,21 @@ class MemberController(
|
||||||
@AuthenticationPrincipal user: User
|
@AuthenticationPrincipal user: User
|
||||||
) = ApiResponse.ok(service.updateNickname(profileUpdateRequest, user))
|
) = ApiResponse.ok(service.updateNickname(profileUpdateRequest, user))
|
||||||
|
|
||||||
|
@PostMapping("/signup/v2")
|
||||||
|
fun signupV2(@RequestBody request: SignUpRequestV2): ApiResponse<LoginResponse> {
|
||||||
|
val response = service.signUpV2(request)
|
||||||
|
|
||||||
|
if (!response.marketingPid.isNullOrBlank()) {
|
||||||
|
trackingService.saveTrackingHistory(
|
||||||
|
pid = response.marketingPid,
|
||||||
|
type = AdTrackingHistoryType.SIGNUP,
|
||||||
|
memberId = response.memberId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/signup")
|
@PostMapping("/signup")
|
||||||
fun signUp(
|
fun signUp(
|
||||||
@RequestPart("profileImage", required = false) profileImage: MultipartFile? = null,
|
@RequestPart("profileImage", required = false) profileImage: MultipartFile? = null,
|
||||||
|
|
|
@ -59,6 +59,10 @@ interface MemberQueryRepository {
|
||||||
fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): Map<String, List<List<String>>>
|
fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): Map<String, List<List<String>>>
|
||||||
|
|
||||||
fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse
|
fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse
|
||||||
|
|
||||||
|
fun existsByEmail(email: String): Boolean
|
||||||
|
fun existsByNickname(nickname: String): Boolean
|
||||||
|
fun findNicknamesWithPrefix(prefix: String): List<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -479,4 +483,34 @@ class MemberQueryRepositoryImpl(
|
||||||
.where(member.id.eq(memberId))
|
.where(member.id.eq(memberId))
|
||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun existsByEmail(email: String): Boolean {
|
||||||
|
return queryFactory
|
||||||
|
.selectOne()
|
||||||
|
.from(member)
|
||||||
|
.where(member.email.eq(email))
|
||||||
|
.fetchFirst() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun existsByNickname(nickname: String): Boolean {
|
||||||
|
return queryFactory
|
||||||
|
.selectOne()
|
||||||
|
.from(member)
|
||||||
|
.where(
|
||||||
|
member.nickname.eq(nickname),
|
||||||
|
member.isActive.isTrue
|
||||||
|
)
|
||||||
|
.fetchFirst() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findNicknamesWithPrefix(prefix: String): List<String> {
|
||||||
|
return queryFactory
|
||||||
|
.select(member.nickname)
|
||||||
|
.from(member)
|
||||||
|
.where(
|
||||||
|
member.nickname.startsWith(prefix),
|
||||||
|
member.isActive.isTrue
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,11 @@ import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||||
import kr.co.vividnext.sodalive.member.myPage.MyPageResponse
|
import kr.co.vividnext.sodalive.member.myPage.MyPageResponse
|
||||||
import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLog
|
import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLog
|
||||||
import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLogRepository
|
import kr.co.vividnext.sodalive.member.nickname.NicknameChangeLogRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.nickname.NicknameGenerateService
|
||||||
import kr.co.vividnext.sodalive.member.notification.MemberNotificationService
|
import kr.co.vividnext.sodalive.member.notification.MemberNotificationService
|
||||||
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpRequest
|
import kr.co.vividnext.sodalive.member.signUp.SignUpRequest
|
||||||
|
import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
|
||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
||||||
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
||||||
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
||||||
|
@ -77,6 +79,7 @@ class MemberService(
|
||||||
private val orderService: OrderService,
|
private val orderService: OrderService,
|
||||||
private val emailService: SendEmailService,
|
private val emailService: SendEmailService,
|
||||||
private val canPaymentService: CanPaymentService,
|
private val canPaymentService: CanPaymentService,
|
||||||
|
private val nicknameGenerateService: NicknameGenerateService,
|
||||||
private val memberNotificationService: MemberNotificationService,
|
private val memberNotificationService: MemberNotificationService,
|
||||||
|
|
||||||
private val s3Uploader: S3Uploader,
|
private val s3Uploader: S3Uploader,
|
||||||
|
@ -96,6 +99,46 @@ class MemberService(
|
||||||
|
|
||||||
private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
|
private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun signUpV2(request: SignUpRequestV2): SignUpResponse {
|
||||||
|
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
||||||
|
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||||
|
|
||||||
|
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
|
||||||
|
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
|
||||||
|
|
||||||
|
if (!request.isAgreePrivacyPolicy || !request.isAgreeTermsOfService) {
|
||||||
|
throw SodaException("약관에 동의하셔야 회원가입이 가능합니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicateCheckEmail(request.email)
|
||||||
|
validatePassword(request.password)
|
||||||
|
|
||||||
|
val nickname = nicknameGenerateService.generateUniqueNickname()
|
||||||
|
val member = Member(
|
||||||
|
email = request.email,
|
||||||
|
password = passwordEncoder.encode(request.password),
|
||||||
|
nickname = nickname,
|
||||||
|
profileImage = "profile/default-profile.png",
|
||||||
|
gender = Gender.NONE,
|
||||||
|
container = request.container
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!request.marketingPid.isNullOrBlank()) {
|
||||||
|
member.activePid = request.marketingPid
|
||||||
|
member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.save(member)
|
||||||
|
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
|
||||||
|
|
||||||
|
return SignUpResponse(
|
||||||
|
memberId = member.id!!,
|
||||||
|
marketingPid = request.marketingPid,
|
||||||
|
loginResponse = login(request.email, request.password)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun signUp(
|
fun signUp(
|
||||||
profileImage: MultipartFile?,
|
profileImage: MultipartFile?,
|
||||||
|
@ -334,7 +377,11 @@ class MemberService(
|
||||||
|
|
||||||
fun duplicateCheckEmail(email: String): ApiResponse<Any> {
|
fun duplicateCheckEmail(email: String): ApiResponse<Any> {
|
||||||
validateEmail(email)
|
validateEmail(email)
|
||||||
repository.findByEmail(email)?.let { throw SodaException("이미 사용중인 이메일 입니다.", "email") }
|
|
||||||
|
if (repository.existsByEmail(email)) {
|
||||||
|
throw SodaException("이미 사용중인 이메일 입니다.", "email")
|
||||||
|
}
|
||||||
|
|
||||||
return ApiResponse.ok(message = "사용 가능한 이메일 입니다.")
|
return ApiResponse.ok(message = "사용 가능한 이메일 입니다.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package kr.co.vividnext.sodalive.member.nickname
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class NicknameGenerateService(private val repository: MemberRepository) {
|
||||||
|
private val adjectives = listOf(
|
||||||
|
"감성적인", "몽환적인", "깊이있는", "따뜻한", "서정적인", "소울풀한", "잔잔한",
|
||||||
|
"선율의", "리드미컬한", "감미로운", "은은한", "잔향의", "울려퍼지는", "하모닉한",
|
||||||
|
"레트로한", "복고풍의", "아날로그적인", "빈티지한", "90년대감성", "옛날느낌의",
|
||||||
|
"시간을넘는", "과거에서온", "시간여행자의", "미래를보는", "초월적인", "운명적인",
|
||||||
|
"신비로운", "마법같은", "전설속의", "별빛의", "달빛의"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val nouns = listOf(
|
||||||
|
"소리", "울림", "속삭임", "청취자", "메아리", "목소리", "공명", "음색", "감성",
|
||||||
|
"멜로디", "선율", "리듬", "하모니", "사운드트랙", "나이트클럽", "라디오스타",
|
||||||
|
"레코드판", "카세트테이프", "LP음악", "복고댄스", "클래식기타", "빈티지마이크",
|
||||||
|
"시간여행", "타임머신", "평행세계", "운명의선택", "마법진", "신비한음색",
|
||||||
|
"달빛의향연", "별빛의꿈", "마법사의속삭임", "초월적인선율"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val numberRange = 1000..9999
|
||||||
|
|
||||||
|
private fun generateRandomNickname(): String {
|
||||||
|
val formatType = Random.nextInt(5)
|
||||||
|
return when (formatType) {
|
||||||
|
0 -> "${adjectives.random()}${nouns.random()}${numberRange.random()}"
|
||||||
|
1 -> "${nouns.random()}의${nouns.random()}${numberRange.random()}"
|
||||||
|
2 -> "${adjectives.random()}${nouns.random()}"
|
||||||
|
3 -> "${nouns.random()}의${nouns.random()}"
|
||||||
|
else -> "${adjectives.random()}${nouns.random()}의${nouns.random()}${numberRange.random()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateNonConflictingNickname(usedNicknames: Set<String>): String {
|
||||||
|
val usedNicknameSet = HashSet(usedNicknames) // 해시셋으로 변환 (O(1) 조회 가능)
|
||||||
|
val availableNumbers = (1000..9999).shuffled()
|
||||||
|
|
||||||
|
for (num in availableNumbers) { // 숫자를 먼저 결정 (무작위)
|
||||||
|
for (adj in adjectives.shuffled()) { // 형용사 순서 랜덤화
|
||||||
|
for (noun in nouns.shuffled()) { // 명사 순서 랜덤화
|
||||||
|
val candidate = "$adj$noun$num"
|
||||||
|
if (!usedNicknameSet.contains(candidate)) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw SodaException("회원가입을 하지 못했습니다.\n다시 시도해 주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateUniqueNickname(): String {
|
||||||
|
repeat(5) {
|
||||||
|
val candidates = (1..10).map { generateRandomNickname() }
|
||||||
|
val available = candidates.firstOrNull { !repository.existsByNickname(it) }
|
||||||
|
if (available != null) return available
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateNonConflictingNickname(repository.findNicknamesWithPrefix("").toSet())
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,3 +12,12 @@ data class SignUpRequest(
|
||||||
val isAgreePrivacyPolicy: Boolean,
|
val isAgreePrivacyPolicy: Boolean,
|
||||||
val container: String = "api"
|
val container: String = "api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class SignUpRequestV2(
|
||||||
|
val email: String,
|
||||||
|
val password: String,
|
||||||
|
val marketingPid: String? = null,
|
||||||
|
val isAgreeTermsOfService: Boolean,
|
||||||
|
val isAgreePrivacyPolicy: Boolean,
|
||||||
|
val container: String = "api"
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue