From 6475b6b73ffa82de66b3991847e33ac5962d6e22 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Tue, 1 Apr 2025 03:23:30 +0900 Subject: [PATCH 01/15] section 2 commit --- build.gradle.kts | 2 + .../study/common/annotation/ValidEnum.kt | 34 +++++++++++++ .../kotlin/study/common/status/EnumStatus.kt | 12 +++++ .../member/controller/MemberController.kt | 23 +++++++++ .../kotlin/study/member/dto/MemberDtos.kt | 51 +++++++++++++++++++ .../study/member/entity/MemberEntities.kt | 32 ++++++++++++ .../member/repository/MemberRepositories.kt | 8 +++ .../study/member/service/MemberService.kt | 37 ++++++++++++++ 8 files changed, 199 insertions(+) create mode 100644 src/main/kotlin/study/common/annotation/ValidEnum.kt create mode 100644 src/main/kotlin/study/common/status/EnumStatus.kt create mode 100644 src/main/kotlin/study/member/controller/MemberController.kt create mode 100644 src/main/kotlin/study/member/dto/MemberDtos.kt create mode 100644 src/main/kotlin/study/member/entity/MemberEntities.kt create mode 100644 src/main/kotlin/study/member/repository/MemberRepositories.kt create mode 100644 src/main/kotlin/study/member/service/MemberService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1af5bad..7e4334d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,8 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation + implementation("org.springframework.boot:spring-boot-starter-validation") } noArg{ diff --git a/src/main/kotlin/study/common/annotation/ValidEnum.kt b/src/main/kotlin/study/common/annotation/ValidEnum.kt new file mode 100644 index 0000000..a6ef514 --- /dev/null +++ b/src/main/kotlin/study/common/annotation/ValidEnum.kt @@ -0,0 +1,34 @@ +package study.common.annotation + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@Constraint(validatedBy = [ValidEnumValidator::class]) +annotation class ValidEnum ( + val message: String = "Invalid enum value", + val groups: Array> = [], + val payload: Array> = [], + val enumClass: KClass> +) + +//유효성 검사 +class ValidEnumValidator : ConstraintValidator { + private lateinit var enumValues: Array> + + override fun initialize(annotation: ValidEnum) { + enumValues = annotation.enumClass.java.enumConstants + } + + override fun isValid(value: Any?, context: ConstraintValidatorContext): Boolean { + if (value == null) { + return true + } + return enumValues.any {it.name == value.toString()} + } +} \ No newline at end of file diff --git a/src/main/kotlin/study/common/status/EnumStatus.kt b/src/main/kotlin/study/common/status/EnumStatus.kt new file mode 100644 index 0000000..3da7402 --- /dev/null +++ b/src/main/kotlin/study/common/status/EnumStatus.kt @@ -0,0 +1,12 @@ +package study.common.status + +//기숙사 타입 +enum class Dormitory(val desc: String) { + GOA( "고운A"), + GOB("고운B"), + GOC("고운C"), + KYUNG11("경상11"), + KYUNG12("경상12"), + KYUNG13("경상13"), + KYUNG14("경상14"), +} \ No newline at end of file diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt new file mode 100644 index 0000000..12257cc --- /dev/null +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -0,0 +1,23 @@ +package study.member.controller + +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import study.member.dto.MemberDtoRequest +import study.member.service.MemberService + +@RequestMapping("/api/member") +@RestController +class MemberController ( + private val memberService: MemberService +){ + /** + * 회원가입 + */ + @PostMapping("/signup") + fun signUp(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): String { + return memberService.signUp(memberDtoRequest) + } +} \ No newline at end of file diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt new file mode 100644 index 0000000..60f057d --- /dev/null +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -0,0 +1,51 @@ +package study.member.dto + +import com.fasterxml.jackson.annotation.JsonProperty +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern +import study.common.annotation.ValidEnum +import study.common.status.Dormitory +import java.time.LocalDate + +//회원가입시 입력받을 정보 +data class MemberDtoRequest ( + val id: Long?, + + @field:NotBlank + @JsonProperty("loginId") + private val _loginId: String?, + + @field:NotBlank + @field:Pattern( + regexp = "^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#\$%^&*])[a-zA-Z0-9!@#\$%^&*]{8,20}\$", + message = "영문, 숫자, 특수문자를 포함한 8~20자리로 입력해주세요" + ) + @JsonProperty("password") + private val _password: String?, + + @field:NotBlank + @JsonProperty("name") + private val _name: String?, + + @field:NotBlank + @field:Email + @JsonProperty("email") + private val _email: String?, + + @field:NotBlank + @field:ValidEnum(enumClass = Dormitory::class, message = "올바른 기숙사 타입을 선택해주세요.") + @JsonProperty("dormitory") + private val _dormitory: String?, +) { + val loginId: String + get() = _loginId!! + val password: String + get() = _password!! + val name: String + get() = _name!! + val email: String + get() = _email!! + val dormitory: Dormitory + get() = Dormitory.valueOf(_dormitory!!) +} \ No newline at end of file diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt new file mode 100644 index 0000000..8b5f987 --- /dev/null +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -0,0 +1,32 @@ +package study.member.entity + +import jakarta.persistence.* +import study.common.status.Dormitory + +@Entity +@Table( + //loginId 중복X + uniqueConstraints = [UniqueConstraint(name = "uk_member_login_id", columnNames = ["loginId"])] +) +class Member( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + var id: Long? = null, + + @Column(nullable = false, length = 30, updatable = false) + //updatable = false -> 업데이트시 loginId는 제외, 변경X + val loginId: String, + + @Column(nullable = false, length = 100) + val password: String, + + @Column(nullable = false, length = 10) + val name: String, + + @Column(nullable = false, length = 30) + val email: String, + + @Column(nullable = false, length = 10) + @Enumerated(EnumType.STRING) + val dormitory: Dormitory, +) \ No newline at end of file diff --git a/src/main/kotlin/study/member/repository/MemberRepositories.kt b/src/main/kotlin/study/member/repository/MemberRepositories.kt new file mode 100644 index 0000000..7bf779a --- /dev/null +++ b/src/main/kotlin/study/member/repository/MemberRepositories.kt @@ -0,0 +1,8 @@ +package study.member.repository + +import org.springframework.data.jpa.repository.JpaRepository +import study.member.entity.Member + +interface MemberRepository : JpaRepository { + fun findByLoginId(loginId: String): Member? +} \ No newline at end of file diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt new file mode 100644 index 0000000..04964d2 --- /dev/null +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -0,0 +1,37 @@ +package study.member.service + +import jakarta.transaction.Transactional +import org.springframework.stereotype.Service +import study.member.dto.MemberDtoRequest +import study.member.entity.Member +import study.member.repository.MemberRepository + +@Transactional +@Service +class MemberService( + private val memberRepository: MemberRepository +) { + /** + * 회원가입 + */ + fun signUp(memberDtoRequest: MemberDtoRequest): String { + //ID 중복 검사 + var member: Member? = memberRepository.findByLoginId(memberDtoRequest.loginId) + if (member != null) { + return "이미 등록된 ID 입니다." + } + + member = Member( //생성자 + null, + memberDtoRequest.loginId, + memberDtoRequest.password, + memberDtoRequest.name, + memberDtoRequest.email, + memberDtoRequest.dormitory + ) + + memberRepository.save(member) //insert + + return "회원가입이 완료되었습니다." + } +} \ No newline at end of file From e7bee90ae5d3f1baa53d1edfd8797847412f5840 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Tue, 1 Apr 2025 03:50:05 +0900 Subject: [PATCH 02/15] section 2 commit --- .../kotlin/study/common/dto/BaseResponse.kt | 9 +++++ .../exception/CustomExceptionHandler.kt | 38 +++++++++++++++++++ .../common/exception/InvalidInputException.kt | 7 ++++ .../kotlin/study/common/status/EnumStatus.kt | 5 +++ .../member/controller/MemberController.kt | 6 ++- .../kotlin/study/member/dto/MemberDtos.kt | 4 ++ .../study/member/service/MemberService.kt | 13 ++----- 7 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/study/common/dto/BaseResponse.kt create mode 100644 src/main/kotlin/study/common/exception/CustomExceptionHandler.kt create mode 100644 src/main/kotlin/study/common/exception/InvalidInputException.kt diff --git a/src/main/kotlin/study/common/dto/BaseResponse.kt b/src/main/kotlin/study/common/dto/BaseResponse.kt new file mode 100644 index 0000000..a063ae0 --- /dev/null +++ b/src/main/kotlin/study/common/dto/BaseResponse.kt @@ -0,0 +1,9 @@ +package study.common.dto + +import study.common.status.ResultCode + +data class BaseResponse ( + val resultCode: String = ResultCode.SUCCESS.name, + val data: T? = null, + val message: String = ResultCode.SUCCESS.msg, +) \ No newline at end of file diff --git a/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt b/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt new file mode 100644 index 0000000..c3308f3 --- /dev/null +++ b/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt @@ -0,0 +1,38 @@ +package study.common.exception + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.validation.FieldError +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import study.common.dto.BaseResponse +import study.common.status.ResultCode + +@RestControllerAdvice +class CustomExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException::class) + protected fun methodArgumentNotValidException(ex: MethodArgumentNotValidException) : ResponseEntity>> { + val errors = mutableMapOf() + ex.bindingResult.allErrors.forEach { error -> + val fieldName = (error as FieldError).field + val errorMessage = error.defaultMessage + errors[fieldName] = errorMessage ?: "Not Exception Message" + } + + return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) + } + + @ExceptionHandler(InvalidInputException::class) + protected fun invalidInputException(ex: InvalidInputException) : ResponseEntity>> { + val errors = mapOf(ex.fieldName to (ex.message ?: "Not Exception Message")) + return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) + } + + @ExceptionHandler(Exception::class) + protected fun defaultException(ex: InvalidInputException) : ResponseEntity>> { + val errors = mapOf("미처리 에러" to (ex.message ?: "Not Exception Message")) + return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) + } +} \ No newline at end of file diff --git a/src/main/kotlin/study/common/exception/InvalidInputException.kt b/src/main/kotlin/study/common/exception/InvalidInputException.kt new file mode 100644 index 0000000..464047f --- /dev/null +++ b/src/main/kotlin/study/common/exception/InvalidInputException.kt @@ -0,0 +1,7 @@ +package study.common.exception + +//예외처리 +class InvalidInputException ( + val fieldName: String = "", + message: String = "Invalid Input" +) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/study/common/status/EnumStatus.kt b/src/main/kotlin/study/common/status/EnumStatus.kt index 3da7402..891ad27 100644 --- a/src/main/kotlin/study/common/status/EnumStatus.kt +++ b/src/main/kotlin/study/common/status/EnumStatus.kt @@ -9,4 +9,9 @@ enum class Dormitory(val desc: String) { KYUNG12("경상12"), KYUNG13("경상13"), KYUNG14("경상14"), +} + +enum class ResultCode(val msg: String) { + SUCCESS("정상 처리 되었습니다."), + ERROR("에러가 발생했습니다.") } \ No newline at end of file diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index 12257cc..eecf137 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -5,6 +5,7 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import study.common.dto.BaseResponse import study.member.dto.MemberDtoRequest import study.member.service.MemberService @@ -17,7 +18,8 @@ class MemberController ( * 회원가입 */ @PostMapping("/signup") - fun signUp(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): String { - return memberService.signUp(memberDtoRequest) + fun signUp(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): BaseResponse { + val resultMsg: String = memberService.signUp(memberDtoRequest) + return BaseResponse(message = resultMsg) } } \ No newline at end of file diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index 60f057d..f88bd1b 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Pattern import study.common.annotation.ValidEnum import study.common.status.Dormitory +import study.member.entity.Member import java.time.LocalDate //회원가입시 입력받을 정보 @@ -48,4 +49,7 @@ data class MemberDtoRequest ( get() = _email!! val dormitory: Dormitory get() = Dormitory.valueOf(_dormitory!!) + + fun toEntity(): Member = + Member(id, loginId, password, name, email, dormitory) } \ No newline at end of file diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index 04964d2..1a5e89b 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -2,6 +2,7 @@ package study.member.service import jakarta.transaction.Transactional import org.springframework.stereotype.Service +import study.common.exception.InvalidInputException import study.member.dto.MemberDtoRequest import study.member.entity.Member import study.member.repository.MemberRepository @@ -18,18 +19,10 @@ class MemberService( //ID 중복 검사 var member: Member? = memberRepository.findByLoginId(memberDtoRequest.loginId) if (member != null) { - return "이미 등록된 ID 입니다." + throw InvalidInputException("loginId", "이미 등록된 ID 입니다.") } - member = Member( //생성자 - null, - memberDtoRequest.loginId, - memberDtoRequest.password, - memberDtoRequest.name, - memberDtoRequest.email, - memberDtoRequest.dormitory - ) - + member = memberDtoRequest.toEntity() memberRepository.save(member) //insert return "회원가입이 완료되었습니다." From 841bcddc0756744475af40f2e6900c69c9b37342 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Thu, 3 Apr 2025 02:05:04 +0900 Subject: [PATCH 03/15] section 3 commit --- .../kotlin/study/common/annotation/ValidEnum.kt | 8 ++++++++ src/main/kotlin/study/common/dto/BaseResponse.kt | 7 ++++--- .../common/exception/CustomExceptionHandler.kt | 4 ++++ .../study/common/exception/InvalidInputException.kt | 5 +++-- src/main/kotlin/study/common/status/EnumStatus.kt | 1 + .../study/member/controller/MemberController.kt | 4 ++++ src/main/kotlin/study/member/dto/MemberDtos.kt | 13 ++++++++----- .../kotlin/study/member/entity/MemberEntities.kt | 3 ++- .../study/member/repository/MemberRepositories.kt | 4 +++- .../kotlin/study/member/service/MemberService.kt | 4 +++- 10 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/study/common/annotation/ValidEnum.kt b/src/main/kotlin/study/common/annotation/ValidEnum.kt index a6ef514..e66a3cd 100644 --- a/src/main/kotlin/study/common/annotation/ValidEnum.kt +++ b/src/main/kotlin/study/common/annotation/ValidEnum.kt @@ -1,3 +1,4 @@ +//annotation: 사용자 생성 어노테이션 package study.common.annotation import jakarta.validation.Constraint @@ -6,10 +7,15 @@ import jakarta.validation.ConstraintValidatorContext import jakarta.validation.Payload import kotlin.reflect.KClass +//@Target: annotation 이 적용될 위치 선택 +//@Retention: 어노테이션을 컴파일된 클래스 파일에 저장할 것인지(SOURCE) 런타임에 반영할 것인지(RUNTIME) 정의 +//@MustBeDocumented: API의 일부분으로 문서화하기 위해 사용 +//@Constraint @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented @Constraint(validatedBy = [ValidEnumValidator::class]) +//annotation: 주석처럼 코드에 달아 클래스에 특별한 의미 부여, 기능 주입 ex)@Override annotation class ValidEnum ( val message: String = "Invalid enum value", val groups: Array> = [], @@ -25,10 +31,12 @@ class ValidEnumValidator : ConstraintValidator { enumValues = annotation.enumClass.java.enumConstants } + //value: 사용자로부터 받은 값 override fun isValid(value: Any?, context: ConstraintValidatorContext): Boolean { if (value == null) { return true } + //any: 조건을 만족하는 원소가 1개 이상 존재하면 true return enumValues.any {it.name == value.toString()} } } \ No newline at end of file diff --git a/src/main/kotlin/study/common/dto/BaseResponse.kt b/src/main/kotlin/study/common/dto/BaseResponse.kt index a063ae0..e2e7681 100644 --- a/src/main/kotlin/study/common/dto/BaseResponse.kt +++ b/src/main/kotlin/study/common/dto/BaseResponse.kt @@ -1,9 +1,10 @@ +//dto: 어플리케이션 전반에 공통적으로 사용할 수 있는 DTO package study.common.dto import study.common.status.ResultCode data class BaseResponse ( - val resultCode: String = ResultCode.SUCCESS.name, - val data: T? = null, - val message: String = ResultCode.SUCCESS.msg, + val resultCode: String = ResultCode.SUCCESS.name,//결과 코드 + val data: T? = null,//조회, 처리시 데이터를 담아서 반환해줄 data + val message: String = ResultCode.SUCCESS.msg,//처리 메세지 ) \ No newline at end of file diff --git a/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt b/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt index c3308f3..993813a 100644 --- a/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt +++ b/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt @@ -1,3 +1,4 @@ +//exception: 예외 처리 Handler package study.common.exception import org.springframework.http.HttpStatus @@ -12,6 +13,7 @@ import study.common.status.ResultCode @RestControllerAdvice class CustomExceptionHandler { + //@Valid(DTO의 validation)에서 발생한 exception @ExceptionHandler(MethodArgumentNotValidException::class) protected fun methodArgumentNotValidException(ex: MethodArgumentNotValidException) : ResponseEntity>> { val errors = mutableMapOf() @@ -24,12 +26,14 @@ class CustomExceptionHandler { return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) } + //사용자 생성 exception @ExceptionHandler(InvalidInputException::class) protected fun invalidInputException(ex: InvalidInputException) : ResponseEntity>> { val errors = mapOf(ex.fieldName to (ex.message ?: "Not Exception Message")) return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) } + //그 외의 처리하지 못 한 모든 exception @ExceptionHandler(Exception::class) protected fun defaultException(ex: InvalidInputException) : ResponseEntity>> { val errors = mapOf("미처리 에러" to (ex.message ?: "Not Exception Message")) diff --git a/src/main/kotlin/study/common/exception/InvalidInputException.kt b/src/main/kotlin/study/common/exception/InvalidInputException.kt index 464047f..e852886 100644 --- a/src/main/kotlin/study/common/exception/InvalidInputException.kt +++ b/src/main/kotlin/study/common/exception/InvalidInputException.kt @@ -1,7 +1,8 @@ +//exception: 예외 처리 package study.common.exception -//예외처리 +//DB 확인 후 발생하는 예외 처리 class InvalidInputException ( val fieldName: String = "", message: String = "Invalid Input" -) : RuntimeException(message) \ No newline at end of file +) : RuntimeException(message)//RuntimeException 상속 \ No newline at end of file diff --git a/src/main/kotlin/study/common/status/EnumStatus.kt b/src/main/kotlin/study/common/status/EnumStatus.kt index 891ad27..fbb5d84 100644 --- a/src/main/kotlin/study/common/status/EnumStatus.kt +++ b/src/main/kotlin/study/common/status/EnumStatus.kt @@ -1,3 +1,4 @@ +//어플리케이션에서 사용할 status package study.common.status //기숙사 타입 diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index eecf137..782e3f7 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -1,3 +1,4 @@ +//controller: Request를 받을 EndPoint package study.member.controller import jakarta.validation.Valid @@ -9,6 +10,7 @@ import study.common.dto.BaseResponse import study.member.dto.MemberDtoRequest import study.member.service.MemberService +//EndPoint: POST /api/member/signup @RequestMapping("/api/member") @RestController class MemberController ( @@ -18,6 +20,8 @@ class MemberController ( * 회원가입 */ @PostMapping("/signup") + //@Valid 추가: validation 체크 + //Unit: void fun signUp(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): BaseResponse { val resultMsg: String = memberService.signUp(memberDtoRequest) return BaseResponse(message = resultMsg) diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index f88bd1b..cdf98c3 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -1,3 +1,4 @@ +//dto: 회원 정보 관련 DTO package study.member.dto import com.fasterxml.jackson.annotation.JsonProperty @@ -7,18 +8,19 @@ import jakarta.validation.constraints.Pattern import study.common.annotation.ValidEnum import study.common.status.Dormitory import study.member.entity.Member -import java.time.LocalDate //회원가입시 입력받을 정보 data class MemberDtoRequest ( val id: Long?, - @field:NotBlank - @JsonProperty("loginId") + @field:NotBlank//빈 값을 받지 않음 + @JsonProperty("loginId")//loginId와 _loginId 연결, loginId 사용 private val _loginId: String?, @field:NotBlank @field:Pattern( + //정규 표현식: 특정한 규칙의 문자열 집합을 표현 + //-> 불특정 문자열이 특정 조건에 만족하는지 판별할 때 사용 regexp = "^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#\$%^&*])[a-zA-Z0-9!@#\$%^&*]{8,20}\$", message = "영문, 숫자, 특수문자를 포함한 8~20자리로 입력해주세요" ) @@ -38,7 +40,7 @@ data class MemberDtoRequest ( @field:ValidEnum(enumClass = Dormitory::class, message = "올바른 기숙사 타입을 선택해주세요.") @JsonProperty("dormitory") private val _dormitory: String?, -) { +) {//Custom Getter val loginId: String get() = _loginId!! val password: String @@ -47,9 +49,10 @@ data class MemberDtoRequest ( get() = _name!! val email: String get() = _email!! - val dormitory: Dormitory + val dormitory: Dormitory//String?을 enum class로 변환 get() = Dormitory.valueOf(_dormitory!!) + //Entity 반환 fun toEntity(): Member = Member(id, loginId, password, name, email, dormitory) } \ No newline at end of file diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt index 8b5f987..620d4a9 100644 --- a/src/main/kotlin/study/member/entity/MemberEntities.kt +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -1,3 +1,4 @@ +//entitiy: 회원 정보 관련 Entity package study.member.entity import jakarta.persistence.* @@ -27,6 +28,6 @@ class Member( val email: String, @Column(nullable = false, length = 10) - @Enumerated(EnumType.STRING) + @Enumerated(EnumType.STRING)//db에 Dormitory의 이름(STRING)을 그대로 입력 val dormitory: Dormitory, ) \ No newline at end of file diff --git a/src/main/kotlin/study/member/repository/MemberRepositories.kt b/src/main/kotlin/study/member/repository/MemberRepositories.kt index 7bf779a..8cd2140 100644 --- a/src/main/kotlin/study/member/repository/MemberRepositories.kt +++ b/src/main/kotlin/study/member/repository/MemberRepositories.kt @@ -1,8 +1,10 @@ +//repository: 회원 정보 관련 Repository package study.member.repository import org.springframework.data.jpa.repository.JpaRepository import study.member.entity.Member -interface MemberRepository : JpaRepository { +interface MemberRepository : JpaRepository {//JpaRepository 상속 + //loginId로 찾기, ID 중복 검사를 위해 필요 fun findByLoginId(loginId: String): Member? } \ No newline at end of file diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index 1a5e89b..ef00692 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -1,3 +1,4 @@ +//비즈니스 로직 package study.member.service import jakarta.transaction.Transactional @@ -15,8 +16,9 @@ class MemberService( /** * 회원가입 */ + //MemberDtoRequest class: 회원가입시 입력받을 정보 fun signUp(memberDtoRequest: MemberDtoRequest): String { - //ID 중복 검사 + //ID 중복 검사 -> ID 조회가 가능하면 member != null var member: Member? = memberRepository.findByLoginId(memberDtoRequest.loginId) if (member != null) { throw InvalidInputException("loginId", "이미 등록된 ID 입니다.") From 37c3e62f6ecea113ac7978a8f08651d4e14d13c8 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Thu, 3 Apr 2025 18:56:41 +0900 Subject: [PATCH 04/15] section 3 commit --- build.gradle.kts | 8 +- .../study/common/annotation/ValidEnum.kt | 2 +- .../authority/JwtAuthenticationFilter.kt | 38 ++++++++ .../common/authority/JwtTokenProvider.kt | 96 +++++++++++++++++++ .../study/common/authority/SecurityConfig.kt | 46 +++++++++ .../study/common/authority/TokenInfo.kt | 8 ++ .../kotlin/study/member/dto/MemberDtos.kt | 2 +- 7 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/study/common/authority/JwtAuthenticationFilter.kt create mode 100644 src/main/kotlin/study/common/authority/JwtTokenProvider.kt create mode 100644 src/main/kotlin/study/common/authority/SecurityConfig.kt create mode 100644 src/main/kotlin/study/common/authority/TokenInfo.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7e4334d..6ea03f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,8 +31,14 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation + //Validation 사용시 필요 implementation("org.springframework.boot:spring-boot-starter-validation") + //Spring Security 사용시 필요 + implementation("org.springframework.boot:spring-boot-starter-security") + //JWT 사용시 필요 + implementation("io.jsonwebtoken:jjwt-api:0.12.6") + runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") + runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") } noArg{ diff --git a/src/main/kotlin/study/common/annotation/ValidEnum.kt b/src/main/kotlin/study/common/annotation/ValidEnum.kt index e66a3cd..da9c8db 100644 --- a/src/main/kotlin/study/common/annotation/ValidEnum.kt +++ b/src/main/kotlin/study/common/annotation/ValidEnum.kt @@ -9,7 +9,7 @@ import kotlin.reflect.KClass //@Target: annotation 이 적용될 위치 선택 //@Retention: 어노테이션을 컴파일된 클래스 파일에 저장할 것인지(SOURCE) 런타임에 반영할 것인지(RUNTIME) 정의 -//@MustBeDocumented: API의 일부분으로 문서화하기 위해 사용 +//@MustBeDocumented: API 의 일부분으로 문서화하기 위해 사용 //@Constraint @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) diff --git a/src/main/kotlin/study/common/authority/JwtAuthenticationFilter.kt b/src/main/kotlin/study/common/authority/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..d056700 --- /dev/null +++ b/src/main/kotlin/study/common/authority/JwtAuthenticationFilter.kt @@ -0,0 +1,38 @@ +//JwtAuthenticationFilter: 토큰 정보 검사, Security Context Holder 에 정보 기록 +package study.common.authority + +import jakarta.servlet.FilterChain +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.HttpServletRequest +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.util.StringUtils +import org.springframework.web.filter.GenericFilterBean + +class JwtAuthenticationFilter ( + private val jwtTokenProvider: JwtTokenProvider +) : GenericFilterBean() {//GenericFilterBean 상속 + override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) { + val token = resolveToken(request as HttpServletRequest)//access token 정보 + + //정상 토큰이면 정보 추출 -> SecurityContextHolder 에 기록 + if (token != null && jwtTokenProvider.validateToken(token)) { + val authentication = jwtTokenProvider.getAuthentication(token) + SecurityContextHolder.getContext().authentication = authentication + } + + chain?.doFilter(request, response) + } + + //request 로부터 Header Authorization 문자를 가져와 bearer 와 맞는지 찾음 + //bearer 와 맞으면 key 값만 추출 + private fun resolveToken(request: HttpServletRequest): String? { + val bearerToken = request.getHeader("Authorization") + + return if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) { + bearerToken.substring(7) + } else { + null + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt new file mode 100644 index 0000000..20248e8 --- /dev/null +++ b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt @@ -0,0 +1,96 @@ +package study.common.authority + +import io.jsonwebtoken.* +import io.jsonwebtoken.io.Decoders +import io.jsonwebtoken.security.Keys +import io.jsonwebtoken.security.SecurityException +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.stereotype.Component +import java.util.* + +const val EXPIRATION_MILLISECONDS: Long = 1000 * 60 * 30//60초 * 30개 -> 30분 +//JwtTokenProvider: 토큰 생성, 정보 추출, 검증 +@Component +class JwtTokenProvider { + @Value("\${jwt.secret}") + lateinit var secretKey: String + + private val key by lazy { Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey))} + + /** + * Token 생성 + */ + fun createToken(authentication: Authentication): TokenInfo { + val authorities: String = authentication + .authorities + .joinToString(",", transform = GrantedAuthority::getAuthority) + + val now = Date() + val accessExpiration = Date(now.time + EXPIRATION_MILLISECONDS) + + //Access Token 생성 + val accessToken = Jwts + .builder() + .subject(authentication.name) + .claim("auth", authorities)//auth 라는 이름으로 권한을 담음 + .issuedAt(now)//토큰 발행 시간 + .expiration(accessExpiration)//유효 시간 + .signWith(key, Jwts.SIG.HS256)//사용한 알고리즘 + .compact() + + return TokenInfo("Bearer", accessToken) + } + + /** + * Token 정보 추출 + */ + fun getAuthentication(token: String): Authentication {//parameter: access token + val claims: Claims = getClaims(token) + + //auth 가 없으면 RuntimeException + val auth = claims["auth"] ?: throw RuntimeException("잘못된 토큰입니다.") + + //권한 정보 추출 + val authorities: Collection = (auth as String) + .split(",") + .map {SimpleGrantedAuthority(it)} + + val principal: UserDetails = User(claims.subject, "", authorities) + + return UsernamePasswordAuthenticationToken(principal, "", authorities) + } + + /** + * Token 검증 + */ + fun validateToken(token: String): Boolean { + try {//문제가 없으면 true 반환 + getClaims(token) + return true + } catch (e: Exception) {//exception 별 처리 + when (e) { + is SecurityException -> {} //Invalid JWT Token + is MalformedJwtException -> {} //Invalid JWT Token + is ExpiredJwtException -> {} //Expired JWT Token + is UnsupportedJwtException -> {} //Unsupported JWT Token + is IllegalArgumentException -> {} //JWT claims string is empty + else -> {} + } + println(e.message) + } + return false + } + + private fun getClaims(token: String): Claims = + Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .payload +} \ No newline at end of file diff --git a/src/main/kotlin/study/common/authority/SecurityConfig.kt b/src/main/kotlin/study/common/authority/SecurityConfig.kt new file mode 100644 index 0000000..c8ecfcb --- /dev/null +++ b/src/main/kotlin/study/common/authority/SecurityConfig.kt @@ -0,0 +1,46 @@ +//SecurityConfig: 인증, 인가 관리 +package study.common.authority + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.crypto.factory.PasswordEncoderFactories +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + +@Configuration +@EnableWebSecurity +class SecurityConfig ( + private val jwtTokenProvider: JwtTokenProvider +) { + @Bean//스프링 컨테이너를 통해 관리되는 객체 + fun filterChain(http: HttpSecurity): SecurityFilterChain { + http + .httpBasic { it.disable() } + .csrf { it.disable() } + //JWT 를 사용하기 때문에 Session 사용 X + .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } + //권한 관리 + //인증되지 않은 사용자만 "/api/member/signup" URL 호출 가능 + //그 외의 요청은 아무 권한 없이 모두 접근 가능 + .authorizeHttpRequests { + it.requestMatchers("/api/member/signup").anonymous() + .anyRequest().permitAll() + } + //뒤 필터를 실행하기 전에 앞 필터를 먼저 실행, 앞 필터가 통과되면 뒤 필터는 실행 X + .addFilterBefore( + JwtAuthenticationFilter(jwtTokenProvider), + UsernamePasswordAuthenticationFilter::class.java + ) + + return http.build() + } + + //코드 암호화 + @Bean + fun passwordEncoder(): PasswordEncoder = + PasswordEncoderFactories.createDelegatingPasswordEncoder() +} \ No newline at end of file diff --git a/src/main/kotlin/study/common/authority/TokenInfo.kt b/src/main/kotlin/study/common/authority/TokenInfo.kt new file mode 100644 index 0000000..12ad1f6 --- /dev/null +++ b/src/main/kotlin/study/common/authority/TokenInfo.kt @@ -0,0 +1,8 @@ +//authority: 관한 관련 기능 분류 +package study.common.authority + +//TokenInfo: 로그인 시 토큰 정보를 담아 클라이언트에게 전달 +data class TokenInfo ( + val grantType: String,//JWT 권한 인증 타입 + val accessToken: String,//실제 검증할 토큰 +) \ No newline at end of file diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index cdf98c3..68f6793 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -49,7 +49,7 @@ data class MemberDtoRequest ( get() = _name!! val email: String get() = _email!! - val dormitory: Dormitory//String?을 enum class로 변환 + val dormitory: Dormitory//String?을 enum class 로 변환 get() = Dormitory.valueOf(_dormitory!!) //Entity 반환 From dc1ffa53868abc1a6c881eda589ee88b84706ad3 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Thu, 10 Apr 2025 18:45:50 +0900 Subject: [PATCH 05/15] post commit --- src/main/kotlin/study/StudyApplication.kt | 2 ++ .../exception/CustomExceptionHandler.kt | 8 +++++ .../service/CustomUserDetailsService.kt | 29 +++++++++++++++++ .../kotlin/study/common/status/EnumStatus.kt | 4 +++ .../member/controller/MemberController.kt | 11 +++++++ .../kotlin/study/member/dto/MemberDtos.kt | 15 +++++++++ .../study/member/entity/MemberEntities.kt | 19 ++++++++++++ .../member/repository/MemberRepositories.kt | 5 ++- .../study/member/service/MemberService.kt | 31 ++++++++++++++++++- src/main/kotlin/study/post/dto/PostDtos.kt | 6 ++++ .../kotlin/study/post/entity/PostEntities.kt | 31 +++++++++++++++++++ .../study/post/repository/PostRepositories.kt | 6 ++++ .../kotlin/study/post/service/PostSevice.kt | 29 +++++++++++++++++ 13 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/study/common/service/CustomUserDetailsService.kt create mode 100644 src/main/kotlin/study/post/dto/PostDtos.kt create mode 100644 src/main/kotlin/study/post/entity/PostEntities.kt create mode 100644 src/main/kotlin/study/post/repository/PostRepositories.kt create mode 100644 src/main/kotlin/study/post/service/PostSevice.kt diff --git a/src/main/kotlin/study/StudyApplication.kt b/src/main/kotlin/study/StudyApplication.kt index 59392af..805a11a 100644 --- a/src/main/kotlin/study/StudyApplication.kt +++ b/src/main/kotlin/study/StudyApplication.kt @@ -2,8 +2,10 @@ package study import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.data.jpa.repository.config.EnableJpaAuditing @SpringBootApplication +@EnableJpaAuditing class StudyApplication fun main(args: Array) { diff --git a/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt b/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt index 993813a..1194258 100644 --- a/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt +++ b/src/main/kotlin/study/common/exception/CustomExceptionHandler.kt @@ -3,6 +3,7 @@ package study.common.exception import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.authentication.BadCredentialsException import org.springframework.validation.FieldError import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler @@ -33,6 +34,13 @@ class CustomExceptionHandler { return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) } + //로그인 실패 exception + @ExceptionHandler(BadCredentialsException::class) + protected fun badCredentialsException(ex: BadCredentialsException) : ResponseEntity>> { + val errors = mapOf("로그인 실패" to "아이디 혹은 비밀번호를 다시 확인하세요.") + return ResponseEntity(BaseResponse(ResultCode.ERROR.name, errors, ResultCode.ERROR.msg), HttpStatus.BAD_REQUEST) + } + //그 외의 처리하지 못 한 모든 exception @ExceptionHandler(Exception::class) protected fun defaultException(ex: InvalidInputException) : ResponseEntity>> { diff --git a/src/main/kotlin/study/common/service/CustomUserDetailsService.kt b/src/main/kotlin/study/common/service/CustomUserDetailsService.kt new file mode 100644 index 0000000..99f213e --- /dev/null +++ b/src/main/kotlin/study/common/service/CustomUserDetailsService.kt @@ -0,0 +1,29 @@ +package study.common.service + +import study.member.entity.Member +import study.member.repository.MemberRepository +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.stereotype.Service + +@Service +class CustomUserDetailsService ( + private val memberRepository: MemberRepository, + private val passwordEncoder: PasswordEncoder, +) : UserDetailsService { + override fun loadUserByUsername(username: String): UserDetails = + memberRepository.findByLoginId(username)//고객ID로 찾기 + ?.let { createUserDetails(it) } //검색되는 정보가 있음 createUserDetails() 호출 + ?: throw UsernameNotFoundException("해당하는 유저를 찾을 수 없습니다.") //검색되는 정보가 없음 + + private fun createUserDetails(member: Member): UserDetails = + User( + member.loginId, + passwordEncoder.encode(member.password), + member.memberRole!!.map { SimpleGrantedAuthority("ROLE_${it.role}")} + ) +} \ No newline at end of file diff --git a/src/main/kotlin/study/common/status/EnumStatus.kt b/src/main/kotlin/study/common/status/EnumStatus.kt index fbb5d84..a300b7d 100644 --- a/src/main/kotlin/study/common/status/EnumStatus.kt +++ b/src/main/kotlin/study/common/status/EnumStatus.kt @@ -15,4 +15,8 @@ enum class Dormitory(val desc: String) { enum class ResultCode(val msg: String) { SUCCESS("정상 처리 되었습니다."), ERROR("에러가 발생했습니다.") +} + +enum class ROLE { + MEMBER } \ No newline at end of file diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index 782e3f7..730222e 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -6,7 +6,9 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import study.common.authority.TokenInfo import study.common.dto.BaseResponse +import study.member.dto.LoginDto import study.member.dto.MemberDtoRequest import study.member.service.MemberService @@ -26,4 +28,13 @@ class MemberController ( val resultMsg: String = memberService.signUp(memberDtoRequest) return BaseResponse(message = resultMsg) } + + /** + * 로그인 + */ + @PostMapping("/login") + fun login(@RequestBody @Valid loginDto: LoginDto): BaseResponse { + val tokenInfo = memberService.login(loginDto) + return BaseResponse(data = tokenInfo) + } } \ No newline at end of file diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index 68f6793..0597272 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -55,4 +55,19 @@ data class MemberDtoRequest ( //Entity 반환 fun toEntity(): Member = Member(id, loginId, password, name, email, dormitory) +} + +data class LoginDto ( + @field:NotBlank//빈 값을 받지 않음, 필수값 + @JsonProperty("loginId") + private val _loginId: String?, + + @field:NotBlank + @JsonProperty("password") + private val _password: String?, +) {//custom getter + val loginId: String + get() = _loginId!! + val password: String + get() = _password!! } \ No newline at end of file diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt index 620d4a9..7a689a2 100644 --- a/src/main/kotlin/study/member/entity/MemberEntities.kt +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -3,6 +3,7 @@ package study.member.entity import jakarta.persistence.* import study.common.status.Dormitory +import study.common.status.ROLE @Entity @Table( @@ -30,4 +31,22 @@ class Member( @Column(nullable = false, length = 10) @Enumerated(EnumType.STRING)//db에 Dormitory의 이름(STRING)을 그대로 입력 val dormitory: Dormitory, +) {//1 : N 연결 + @OneToMany(fetch = FetchType.LAZY, mappedBy = "member") + val memberRole: List? = null +} + +@Entity +class MemberRole( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + var id: Long? = null, + + @Column(nullable = false, length = 30) + @Enumerated(EnumType.STRING) + val role: ROLE, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(foreignKey = ForeignKey(name = "fk_user_role_member_id")) + val member: Member, ) \ No newline at end of file diff --git a/src/main/kotlin/study/member/repository/MemberRepositories.kt b/src/main/kotlin/study/member/repository/MemberRepositories.kt index 8cd2140..850696d 100644 --- a/src/main/kotlin/study/member/repository/MemberRepositories.kt +++ b/src/main/kotlin/study/member/repository/MemberRepositories.kt @@ -3,8 +3,11 @@ package study.member.repository import org.springframework.data.jpa.repository.JpaRepository import study.member.entity.Member +import study.member.entity.MemberRole interface MemberRepository : JpaRepository {//JpaRepository 상속 //loginId로 찾기, ID 중복 검사를 위해 필요 fun findByLoginId(loginId: String): Member? -} \ No newline at end of file +} + +interface MemberRoleRepository : JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index ef00692..c1f513a 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -2,16 +2,27 @@ package study.member.service import jakarta.transaction.Transactional +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.stereotype.Service +import study.common.authority.JwtTokenProvider +import study.common.authority.TokenInfo import study.common.exception.InvalidInputException +import study.common.status.ROLE +import study.member.dto.LoginDto import study.member.dto.MemberDtoRequest import study.member.entity.Member +import study.member.entity.MemberRole import study.member.repository.MemberRepository +import study.member.repository.MemberRoleRepository @Transactional @Service class MemberService( - private val memberRepository: MemberRepository + private val memberRepository: MemberRepository, + private val memberRoleRepository: MemberRoleRepository, + private val authenticationManagerBuilder: AuthenticationManagerBuilder, + private val jwtTokenProvider: JwtTokenProvider, ) { /** * 회원가입 @@ -24,9 +35,27 @@ class MemberService( throw InvalidInputException("loginId", "이미 등록된 ID 입니다.") } + //사용자 정보 저장 member = memberDtoRequest.toEntity() memberRepository.save(member) //insert + //권한 저장 + val memberRole = MemberRole(null, ROLE.MEMBER, member) + memberRoleRepository.save(memberRole) + return "회원가입이 완료되었습니다." } + + /** + * 로그인 -> 토큰 발행 + */ + fun login(loginDto: LoginDto): TokenInfo { + val authenticationToken = + UsernamePasswordAuthenticationToken(loginDto.loginId, loginDto.password) + val authentication = + authenticationManagerBuilder.`object`.authenticate(authenticationToken) + //DB에 있는 유저네임과 비교, 문제가 없으면 사용자에게 토큰 발행 + + return jwtTokenProvider.createToken(authentication) + } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/dto/PostDtos.kt b/src/main/kotlin/study/post/dto/PostDtos.kt new file mode 100644 index 0000000..cac5e3e --- /dev/null +++ b/src/main/kotlin/study/post/dto/PostDtos.kt @@ -0,0 +1,6 @@ +package study.post.dto + +data class PostDtoRequest( + val title: String, + val content: String, +) \ No newline at end of file diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt new file mode 100644 index 0000000..f6db8a0 --- /dev/null +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -0,0 +1,31 @@ +package study.post.entity + +import jakarta.persistence.* +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import study.member.entity.Member +import java.time.LocalDateTime + +@Entity +@EntityListeners(AuditingEntityListener::class) +data class Post( + @Id + //자동 기본키 생성, DB에 위임 + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @Column(nullable = false, length = 100) + val title: String, + + @Column(nullable = false, columnDefinition = "TEXT") + val content: String, + + @CreatedDate + @Column(updatable = false) + var createdAt: LocalDateTime, + + //1명의 사용자는 여러 개의 게시글 작성이 가능 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + val user: Member +) \ No newline at end of file diff --git a/src/main/kotlin/study/post/repository/PostRepositories.kt b/src/main/kotlin/study/post/repository/PostRepositories.kt new file mode 100644 index 0000000..2063ce1 --- /dev/null +++ b/src/main/kotlin/study/post/repository/PostRepositories.kt @@ -0,0 +1,6 @@ +package study.post.repository + +import org.springframework.data.jpa.repository.JpaRepository +import study.post.entity.Post + +interface PostRepository: JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/study/post/service/PostSevice.kt b/src/main/kotlin/study/post/service/PostSevice.kt new file mode 100644 index 0000000..1e6ec7f --- /dev/null +++ b/src/main/kotlin/study/post/service/PostSevice.kt @@ -0,0 +1,29 @@ +package study.post.service + +import org.springframework.stereotype.Service +import study.member.entity.Member +import study.member.repository.MemberRepository +import study.post.dto.PostDtoRequest +import study.post.entity.Post +import study.post.repository.PostRepository + +@Service +class PostService( + private val postRepository: PostRepository, + private val memberRepository: MemberRepository, +) { + fun createPost(postDtoRequest: PostDtoRequest, loginId: String): String { + val member: Member = memberRepository.findByLoginId(loginId) + ?: throw IllegalArgumentException("사용자를 찾을 수 없습니다.") + +// val post = Post ( +// title = postDtoRequest.title, +// content = postDtoRequest.content, +// user = member +// ) + + //postRepository.save(post) + + return "게시글 작성이 완료되었습니다." + } +} \ No newline at end of file From 7573adab3ae648d3c1f0f25d3d1cdda6df8b585e Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Thu, 10 Apr 2025 19:47:52 +0900 Subject: [PATCH 06/15] post update --- .../study/post/controller/PostController.kt | 25 ++++++++++++++++ src/main/kotlin/study/post/dto/PostDtos.kt | 30 +++++++++++++++++-- .../kotlin/study/post/entity/PostEntities.kt | 26 ++++++---------- .../kotlin/study/post/service/PostSevice.kt | 26 ++++++---------- 4 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 src/main/kotlin/study/post/controller/PostController.kt diff --git a/src/main/kotlin/study/post/controller/PostController.kt b/src/main/kotlin/study/post/controller/PostController.kt new file mode 100644 index 0000000..aba5e03 --- /dev/null +++ b/src/main/kotlin/study/post/controller/PostController.kt @@ -0,0 +1,25 @@ +package study.post.controller + +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import study.common.dto.BaseResponse +import study.post.dto.PostDtoRequest +import study.post.service.PostService + +@RequestMapping("/api/member") +@RestController +class PostController ( + private val postService: PostService +) { + /** + * 게시글 작성 + */ + @PostMapping("/posting") + fun posting(@RequestBody @Valid postDtoRequest: PostDtoRequest): BaseResponse { + val result = postService.posting(postDtoRequest) + return BaseResponse(result) + } +} \ No newline at end of file diff --git a/src/main/kotlin/study/post/dto/PostDtos.kt b/src/main/kotlin/study/post/dto/PostDtos.kt index cac5e3e..944a3a0 100644 --- a/src/main/kotlin/study/post/dto/PostDtos.kt +++ b/src/main/kotlin/study/post/dto/PostDtos.kt @@ -1,6 +1,30 @@ package study.post.dto +import com.fasterxml.jackson.annotation.JsonProperty +import jakarta.validation.constraints.NotBlank +import study.post.entity.Post +import java.time.LocalDate +import java.time.LocalDateTime + data class PostDtoRequest( - val title: String, - val content: String, -) \ No newline at end of file + val id: Long? = null, + + @field:NotBlank //빈칸 허용 x + @JsonProperty("title") + private val _title : String?, + + @field:NotBlank + @JsonProperty("content") + private val _content : String?, + + private val writer : String = "익명의 사용자", + private val createDate: LocalDateTime = LocalDateTime.now() +) { + val title: String + get() = _title!!.toString() + val content: String + get() = _content!!.toString() + + fun toEntity(): Post = + Post(null, title, writer, content, createDate) +} \ No newline at end of file diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt index f6db8a0..37d86fe 100644 --- a/src/main/kotlin/study/post/entity/PostEntities.kt +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -1,31 +1,23 @@ package study.post.entity import jakarta.persistence.* -import org.springframework.data.annotation.CreatedDate -import org.springframework.data.jpa.domain.support.AuditingEntityListener -import study.member.entity.Member import java.time.LocalDateTime @Entity -@EntityListeners(AuditingEntityListener::class) -data class Post( +class Post( @Id - //자동 기본키 생성, DB에 위임 @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0, + var id: Long? = null, - @Column(nullable = false, length = 100) + //title, content 수정 가능, writer, date 는 수정 불가능 + @Column(nullable = false, length = 30, updatable = true) val title: String, - @Column(nullable = false, columnDefinition = "TEXT") + @Column(nullable = false, length = 500, updatable = true) val content: String, - @CreatedDate - @Column(updatable = false) - var createdAt: LocalDateTime, - - //1명의 사용자는 여러 개의 게시글 작성이 가능 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id") - val user: Member + @Column(nullable = false, length = 10, updatable = false) + val writer : String, + @Column(nullable = false, updatable = false) + val createDate: LocalDateTime ) \ No newline at end of file diff --git a/src/main/kotlin/study/post/service/PostSevice.kt b/src/main/kotlin/study/post/service/PostSevice.kt index 1e6ec7f..747d72a 100644 --- a/src/main/kotlin/study/post/service/PostSevice.kt +++ b/src/main/kotlin/study/post/service/PostSevice.kt @@ -1,29 +1,21 @@ package study.post.service +import jakarta.transaction.Transactional import org.springframework.stereotype.Service -import study.member.entity.Member -import study.member.repository.MemberRepository import study.post.dto.PostDtoRequest -import study.post.entity.Post import study.post.repository.PostRepository +@Transactional @Service class PostService( private val postRepository: PostRepository, - private val memberRepository: MemberRepository, ) { - fun createPost(postDtoRequest: PostDtoRequest, loginId: String): String { - val member: Member = memberRepository.findByLoginId(loginId) - ?: throw IllegalArgumentException("사용자를 찾을 수 없습니다.") - -// val post = Post ( -// title = postDtoRequest.title, -// content = postDtoRequest.content, -// user = member -// ) - - //postRepository.save(post) - - return "게시글 작성이 완료되었습니다." + /** + * 게시글 작성 + */ + fun posting(postDtoRequest: PostDtoRequest): String { + val post = postDtoRequest.toEntity() + postRepository.save(post) + return "게시글 작성 완료" } } \ No newline at end of file From a6093d45d1f4f60e1dd8099b6745e5b478e32e64 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Thu, 17 Apr 2025 05:27:49 +0900 Subject: [PATCH 07/15] =?UTF-8?q?=EC=84=B9=EC=85=98=206=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/authority/JwtTokenProvider.kt | 7 +++- .../study/common/authority/SecurityConfig.kt | 7 ++-- .../kotlin/study/common/dto/CustomUser.kt | 11 ++++++ .../service/CustomUserDetailsService.kt | 4 ++- .../kotlin/study/common/status/EnumStatus.kt | 2 +- .../member/controller/MemberController.kt | 36 +++++++++++++++++++ .../kotlin/study/member/dto/MemberDtos.kt | 12 +++++-- .../study/member/entity/MemberEntities.kt | 11 ++++++ .../study/member/service/MemberService.kt | 21 +++++++++++ .../kotlin/study/post/entity/PostEntities.kt | 1 + 10 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/study/common/dto/CustomUser.kt diff --git a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt index 20248e8..88c407b 100644 --- a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt +++ b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt @@ -12,6 +12,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.stereotype.Component +import study.common.dto.CustomUser import java.util.* const val EXPIRATION_MILLISECONDS: Long = 1000 * 60 * 30//60초 * 30개 -> 30분 @@ -39,6 +40,8 @@ class JwtTokenProvider { .builder() .subject(authentication.name) .claim("auth", authorities)//auth 라는 이름으로 권한을 담음 + //토큰 생성시 userId 정보도 기록 + .claim("userId", (authentication.principal as CustomUser).userId) .issuedAt(now)//토큰 발행 시간 .expiration(accessExpiration)//유효 시간 .signWith(key, Jwts.SIG.HS256)//사용한 알고리즘 @@ -55,13 +58,15 @@ class JwtTokenProvider { //auth 가 없으면 RuntimeException val auth = claims["auth"] ?: throw RuntimeException("잘못된 토큰입니다.") + val userId = claims["userId"] ?: throw RuntimeException("잘못된 토큰입니다.") //권한 정보 추출 val authorities: Collection = (auth as String) .split(",") .map {SimpleGrantedAuthority(it)} - val principal: UserDetails = User(claims.subject, "", authorities) + val principal: UserDetails = + CustomUser(userId.toString().toLong(),claims.subject, "", authorities) return UsernamePasswordAuthenticationToken(principal, "", authorities) } diff --git a/src/main/kotlin/study/common/authority/SecurityConfig.kt b/src/main/kotlin/study/common/authority/SecurityConfig.kt index c8ecfcb..fae22e0 100644 --- a/src/main/kotlin/study/common/authority/SecurityConfig.kt +++ b/src/main/kotlin/study/common/authority/SecurityConfig.kt @@ -24,10 +24,11 @@ class SecurityConfig ( //JWT 를 사용하기 때문에 Session 사용 X .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } //권한 관리 - //인증되지 않은 사용자만 "/api/member/signup" URL 호출 가능 - //그 외의 요청은 아무 권한 없이 모두 접근 가능 + //인증되지 않은 사용자만 "/api/member/signup, login" URL 호출 가능 + //그 외의 요청은 회원 권한이 있어야 가능 .authorizeHttpRequests { - it.requestMatchers("/api/member/signup").anonymous() + it.requestMatchers("/api/member/signup", "/api/member/login").anonymous() + .requestMatchers("/api/member/info/**").hasRole("MEMBER") .anyRequest().permitAll() } //뒤 필터를 실행하기 전에 앞 필터를 먼저 실행, 앞 필터가 통과되면 뒤 필터는 실행 X diff --git a/src/main/kotlin/study/common/dto/CustomUser.kt b/src/main/kotlin/study/common/dto/CustomUser.kt new file mode 100644 index 0000000..28f2feb --- /dev/null +++ b/src/main/kotlin/study/common/dto/CustomUser.kt @@ -0,0 +1,11 @@ +package study.common.dto + +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.User + +class CustomUser ( + val userId: Long, + userName: String, + password: String, + authorities: Collection +) : User(userName, password, authorities) \ No newline at end of file diff --git a/src/main/kotlin/study/common/service/CustomUserDetailsService.kt b/src/main/kotlin/study/common/service/CustomUserDetailsService.kt index 99f213e..d2fc23c 100644 --- a/src/main/kotlin/study/common/service/CustomUserDetailsService.kt +++ b/src/main/kotlin/study/common/service/CustomUserDetailsService.kt @@ -9,6 +9,7 @@ import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service +import study.common.dto.CustomUser @Service class CustomUserDetailsService ( @@ -21,7 +22,8 @@ class CustomUserDetailsService ( ?: throw UsernameNotFoundException("해당하는 유저를 찾을 수 없습니다.") //검색되는 정보가 없음 private fun createUserDetails(member: Member): UserDetails = - User( + CustomUser( + member.id!!, member.loginId, passwordEncoder.encode(member.password), member.memberRole!!.map { SimpleGrantedAuthority("ROLE_${it.role}")} diff --git a/src/main/kotlin/study/common/status/EnumStatus.kt b/src/main/kotlin/study/common/status/EnumStatus.kt index a300b7d..fcc4587 100644 --- a/src/main/kotlin/study/common/status/EnumStatus.kt +++ b/src/main/kotlin/study/common/status/EnumStatus.kt @@ -3,7 +3,7 @@ package study.common.status //기숙사 타입 enum class Dormitory(val desc: String) { - GOA( "고운A"), + GOA("고운A"), GOB("고운B"), GOC("고운C"), KYUNG11("경상11"), diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index 730222e..c6ba986 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -2,14 +2,20 @@ package study.member.controller import jakarta.validation.Valid +import org.springframework.security.core.context.SecurityContextHolder +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.RestController import study.common.authority.TokenInfo import study.common.dto.BaseResponse +import study.common.dto.CustomUser import study.member.dto.LoginDto import study.member.dto.MemberDtoRequest +import study.member.dto.MemberDtoResponse import study.member.service.MemberService //EndPoint: POST /api/member/signup @@ -37,4 +43,34 @@ class MemberController ( val tokenInfo = memberService.login(loginDto) return BaseResponse(data = tokenInfo) } + + /** + * 내 정보 조회 + */ + @GetMapping("/info") + fun searchMyInfo(): BaseResponse { + val userId = (SecurityContextHolder + .getContext() + .authentication + .principal as CustomUser) + .userId + val response = memberService.searchMyInfo(userId) + return BaseResponse(data = response) + } + + /** + * 내 정보 수정 + */ + @PutMapping("info") + fun saveMyInfo(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): + BaseResponse { + val userId = (SecurityContextHolder + .getContext() + .authentication + .principal as CustomUser) + .userId + memberDtoRequest.id = userId + val resultMsg: String = memberService.saveMyInfo(memberDtoRequest) + return BaseResponse(message= resultMsg) + } } \ No newline at end of file diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index 0597272..8d3cbcd 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -11,7 +11,7 @@ import study.member.entity.Member //회원가입시 입력받을 정보 data class MemberDtoRequest ( - val id: Long?, + var id: Long?, @field:NotBlank//빈 값을 받지 않음 @JsonProperty("loginId")//loginId와 _loginId 연결, loginId 사용 @@ -70,4 +70,12 @@ data class LoginDto ( get() = _loginId!! val password: String get() = _password!! -} \ No newline at end of file +} + +data class MemberDtoResponse ( + val id: Long, + val loginId: String, + val name: String, + val email: String, + val dormitory: String, +) \ No newline at end of file diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt index 7a689a2..1c06f9a 100644 --- a/src/main/kotlin/study/member/entity/MemberEntities.kt +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -4,6 +4,7 @@ package study.member.entity import jakarta.persistence.* import study.common.status.Dormitory import study.common.status.ROLE +import study.member.dto.MemberDtoResponse @Entity @Table( @@ -34,6 +35,16 @@ class Member( ) {//1 : N 연결 @OneToMany(fetch = FetchType.LAZY, mappedBy = "member") val memberRole: List? = null + + //DTO 변경 함수 + fun toDto(): MemberDtoResponse = + MemberDtoResponse( + id!!, + loginId, + name, + email, + dormitory.desc //dormitory 에 해당하는 값을 받음 + ) } @Entity diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index c1f513a..0112631 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -2,6 +2,7 @@ package study.member.service import jakarta.transaction.Transactional +import org.springframework.data.repository.findByIdOrNull import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.stereotype.Service @@ -11,6 +12,7 @@ import study.common.exception.InvalidInputException import study.common.status.ROLE import study.member.dto.LoginDto import study.member.dto.MemberDtoRequest +import study.member.dto.MemberDtoResponse import study.member.entity.Member import study.member.entity.MemberRole import study.member.repository.MemberRepository @@ -58,4 +60,23 @@ class MemberService( return jwtTokenProvider.createToken(authentication) } + + /** + * 내 정보 조회 + */ + fun searchMyInfo(id: Long): MemberDtoResponse { + //해당하는 id가 없으면 예외 처리 + val member = memberRepository.findByIdOrNull(id) + ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") + return member.toDto() + } + + /** + * 내 정보 수정 + */ + fun saveMyInfo(memberDtoRequest: MemberDtoRequest): String { + val member = memberDtoRequest.toEntity() + memberRepository.save(member) + return "정보 수정이 완료되었습니다." + } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt index 37d86fe..1b329ef 100644 --- a/src/main/kotlin/study/post/entity/PostEntities.kt +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -18,6 +18,7 @@ class Post( @Column(nullable = false, length = 10, updatable = false) val writer : String, + @Column(nullable = false, updatable = false) val createDate: LocalDateTime ) \ No newline at end of file From b9c32353f0a10837350a9020db65de38b4451453 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Thu, 17 Apr 2025 18:43:45 +0900 Subject: [PATCH 08/15] =?UTF-8?q?=EC=84=B9=EC=85=98=206=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/authority/JwtTokenProvider.kt | 3 +- .../study/common/authority/SecurityConfig.kt | 1 + .../member/controller/MemberController.kt | 4 +- .../study/post/controller/PostController.kt | 47 ++++++++++++++++--- src/main/kotlin/study/post/dto/PostDtos.kt | 23 +++++++-- .../kotlin/study/post/entity/PostEntities.kt | 19 +++++++- .../kotlin/study/post/service/PostSevice.kt | 39 +++++++++++++-- 7 files changed, 115 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt index 88c407b..8ff14cb 100644 --- a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt +++ b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt @@ -9,7 +9,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.stereotype.Component import study.common.dto.CustomUser @@ -43,7 +42,7 @@ class JwtTokenProvider { //토큰 생성시 userId 정보도 기록 .claim("userId", (authentication.principal as CustomUser).userId) .issuedAt(now)//토큰 발행 시간 - .expiration(accessExpiration)//유효 시간 + .expiration(accessExpiration)//토큰 유효 시간 .signWith(key, Jwts.SIG.HS256)//사용한 알고리즘 .compact() diff --git a/src/main/kotlin/study/common/authority/SecurityConfig.kt b/src/main/kotlin/study/common/authority/SecurityConfig.kt index fae22e0..57d9d3e 100644 --- a/src/main/kotlin/study/common/authority/SecurityConfig.kt +++ b/src/main/kotlin/study/common/authority/SecurityConfig.kt @@ -29,6 +29,7 @@ class SecurityConfig ( .authorizeHttpRequests { it.requestMatchers("/api/member/signup", "/api/member/login").anonymous() .requestMatchers("/api/member/info/**").hasRole("MEMBER") + .requestMatchers("api/post/posting/**").hasRole("MEMBER") .anyRequest().permitAll() } //뒤 필터를 실행하기 전에 앞 필터를 먼저 실행, 앞 필터가 통과되면 뒤 필터는 실행 X diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index c6ba986..a2c35a9 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -61,7 +61,7 @@ class MemberController ( /** * 내 정보 수정 */ - @PutMapping("info") + @PutMapping("/info") fun saveMyInfo(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): BaseResponse { val userId = (SecurityContextHolder @@ -71,6 +71,6 @@ class MemberController ( .userId memberDtoRequest.id = userId val resultMsg: String = memberService.saveMyInfo(memberDtoRequest) - return BaseResponse(message= resultMsg) + return BaseResponse(message = resultMsg) } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/controller/PostController.kt b/src/main/kotlin/study/post/controller/PostController.kt index aba5e03..928560c 100644 --- a/src/main/kotlin/study/post/controller/PostController.kt +++ b/src/main/kotlin/study/post/controller/PostController.kt @@ -1,17 +1,17 @@ package study.post.controller import jakarta.validation.Valid -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.bind.annotation.* import study.common.dto.BaseResponse +import study.common.dto.CustomUser import study.post.dto.PostDtoRequest +import study.post.dto.PostDtoResponse import study.post.service.PostService -@RequestMapping("/api/member") +@RequestMapping("/api/post") @RestController -class PostController ( +class PostController( private val postService: PostService ) { /** @@ -19,7 +19,40 @@ class PostController ( */ @PostMapping("/posting") fun posting(@RequestBody @Valid postDtoRequest: PostDtoRequest): BaseResponse { - val result = postService.posting(postDtoRequest) + val userId = (SecurityContextHolder + .getContext() + .authentication + .principal as CustomUser) + .userId + + val result = postService.posting(userId, postDtoRequest) return BaseResponse(result) } + + /** + * 게시글 작성자 조회 + */ + @GetMapping("/userInfo") + fun searchUser(): BaseResponse { + val userId = (SecurityContextHolder + .getContext() + .authentication + .principal as CustomUser) + .userId + val response = postService.searchUser(userId) + return BaseResponse(data = response) + } + + /** + * 게시글 작성자 수정 + */ + @PutMapping("/userInfo") + fun changeUserName(@RequestBody @Valid postDtoRequest: PostDtoRequest): + BaseResponse { + val userName = (SecurityContextHolder + .getContext()) + postDtoRequest._writer = userName.toString() + val resultMsg: String = postService.changeUserName(postDtoRequest) + return BaseResponse(message = resultMsg) + } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/dto/PostDtos.kt b/src/main/kotlin/study/post/dto/PostDtos.kt index 944a3a0..a8ee1f3 100644 --- a/src/main/kotlin/study/post/dto/PostDtos.kt +++ b/src/main/kotlin/study/post/dto/PostDtos.kt @@ -2,12 +2,12 @@ package study.post.dto import com.fasterxml.jackson.annotation.JsonProperty import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Pattern import study.post.entity.Post -import java.time.LocalDate import java.time.LocalDateTime data class PostDtoRequest( - val id: Long? = null, + var id: Long? = null, @field:NotBlank //빈칸 허용 x @JsonProperty("title") @@ -17,14 +17,27 @@ data class PostDtoRequest( @JsonProperty("content") private val _content : String?, - private val writer : String = "익명의 사용자", + @field:NotBlank + @JsonProperty("writer") + var _writer : String = "익명(${id})", + private val createDate: LocalDateTime = LocalDateTime.now() ) { val title: String get() = _title!!.toString() val content: String get() = _content!!.toString() + val writer: String + get() = _writer fun toEntity(): Post = - Post(null, title, writer, content, createDate) -} \ No newline at end of file + Post(null, title, content, writer, createDate) +} + +data class PostDtoResponse ( + val id: Long, + val title: String, + val content: String, + val writer: String, + val createDate: LocalDateTime +) \ No newline at end of file diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt index 1b329ef..697721b 100644 --- a/src/main/kotlin/study/post/entity/PostEntities.kt +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -1,9 +1,14 @@ package study.post.entity import jakarta.persistence.* +import study.post.dto.PostDtoResponse import java.time.LocalDateTime @Entity +@Table( + //loginId 중복X + uniqueConstraints = [UniqueConstraint(name = "uk_member_login_id", columnNames = ["loginId"])] +) class Post( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -17,8 +22,18 @@ class Post( val content: String, @Column(nullable = false, length = 10, updatable = false) - val writer : String, + var writer : String, @Column(nullable = false, updatable = false) val createDate: LocalDateTime -) \ No newline at end of file +) { + //DTO 변경 함수 + fun toDto(): PostDtoResponse = + PostDtoResponse( + id!!, + title, + content, + writer, + createDate + ) +} \ No newline at end of file diff --git a/src/main/kotlin/study/post/service/PostSevice.kt b/src/main/kotlin/study/post/service/PostSevice.kt index 747d72a..7ea990a 100644 --- a/src/main/kotlin/study/post/service/PostSevice.kt +++ b/src/main/kotlin/study/post/service/PostSevice.kt @@ -1,21 +1,54 @@ package study.post.service import jakarta.transaction.Transactional +import org.antlr.v4.runtime.Token +import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.stereotype.Service +import study.common.authority.JwtTokenProvider +import study.common.authority.TokenInfo +import study.common.exception.InvalidInputException +import study.member.dto.LoginDto +import study.member.repository.MemberRepository import study.post.dto.PostDtoRequest +import study.post.dto.PostDtoResponse import study.post.repository.PostRepository @Transactional @Service class PostService( + private val memberRepository: MemberRepository, private val postRepository: PostRepository, + private val authenticationManagerBuilder: AuthenticationManagerBuilder, + private val jwtTokenProvider: JwtTokenProvider, ) { /** - * 게시글 작성 + * 게시글 작성 -> 회원가입한 사용자만 작성 가능 */ - fun posting(postDtoRequest: PostDtoRequest): String { + fun posting(id: Long, postDtoRequest: PostDtoRequest): String { + memberRepository.findByIdOrNull(id) + ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") val post = postDtoRequest.toEntity() postRepository.save(post) - return "게시글 작성 완료" + return "게시글 작성이 완료되었습니다." + } + + /** + * 게시글 작성자 조회 + */ + fun searchUser(id: Long): PostDtoResponse { + val member = postRepository.findByIdOrNull(id) + ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") + return member.toDto() + } + + /** + * 작성자 이름을 토큰으로 찾은 사용자 이름으로 수정 + */ + fun changeUserName(postDtoRequest: PostDtoRequest): String { + val writer = postDtoRequest.toEntity() + postRepository.save(writer) + return "작성자 이름 수정이 완료되었습니다." } } \ No newline at end of file From 5f8ff6e05b61f5427d94c0e73af8f2f5e2a67ac4 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Mon, 5 May 2025 22:32:37 +0900 Subject: [PATCH 09/15] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/authority/JwtTokenProvider.kt | 1 + .../study/common/authority/SecurityConfig.kt | 2 +- .../study/post/controller/PostController.kt | 31 ++-------------- src/main/kotlin/study/post/dto/PostDtos.kt | 25 ++++--------- .../kotlin/study/post/entity/PostEntities.kt | 20 +++-------- .../kotlin/study/post/service/PostSevice.kt | 36 ++++--------------- 6 files changed, 21 insertions(+), 94 deletions(-) diff --git a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt index 8ff14cb..aa31969 100644 --- a/src/main/kotlin/study/common/authority/JwtTokenProvider.kt +++ b/src/main/kotlin/study/common/authority/JwtTokenProvider.kt @@ -31,6 +31,7 @@ class JwtTokenProvider { .authorities .joinToString(",", transform = GrantedAuthority::getAuthority) + //만료시간 설정 val now = Date() val accessExpiration = Date(now.time + EXPIRATION_MILLISECONDS) diff --git a/src/main/kotlin/study/common/authority/SecurityConfig.kt b/src/main/kotlin/study/common/authority/SecurityConfig.kt index 57d9d3e..beacc7e 100644 --- a/src/main/kotlin/study/common/authority/SecurityConfig.kt +++ b/src/main/kotlin/study/common/authority/SecurityConfig.kt @@ -29,7 +29,7 @@ class SecurityConfig ( .authorizeHttpRequests { it.requestMatchers("/api/member/signup", "/api/member/login").anonymous() .requestMatchers("/api/member/info/**").hasRole("MEMBER") - .requestMatchers("api/post/posting/**").hasRole("MEMBER") + .requestMatchers("api/post/posting").hasRole("MEMBER") .anyRequest().permitAll() } //뒤 필터를 실행하기 전에 앞 필터를 먼저 실행, 앞 필터가 통과되면 뒤 필터는 실행 X diff --git a/src/main/kotlin/study/post/controller/PostController.kt b/src/main/kotlin/study/post/controller/PostController.kt index 928560c..eaa25ab 100644 --- a/src/main/kotlin/study/post/controller/PostController.kt +++ b/src/main/kotlin/study/post/controller/PostController.kt @@ -6,7 +6,6 @@ import org.springframework.web.bind.annotation.* import study.common.dto.BaseResponse import study.common.dto.CustomUser import study.post.dto.PostDtoRequest -import study.post.dto.PostDtoResponse import study.post.service.PostService @RequestMapping("/api/post") @@ -22,37 +21,11 @@ class PostController( val userId = (SecurityContextHolder .getContext() .authentication - .principal as CustomUser) + .principal as CustomUser)//CustomUser 형식으로 userId 받음 .userId - val result = postService.posting(userId, postDtoRequest) + val result = postService.posting(postDtoRequest, userId) return BaseResponse(result) } - /** - * 게시글 작성자 조회 - */ - @GetMapping("/userInfo") - fun searchUser(): BaseResponse { - val userId = (SecurityContextHolder - .getContext() - .authentication - .principal as CustomUser) - .userId - val response = postService.searchUser(userId) - return BaseResponse(data = response) - } - - /** - * 게시글 작성자 수정 - */ - @PutMapping("/userInfo") - fun changeUserName(@RequestBody @Valid postDtoRequest: PostDtoRequest): - BaseResponse { - val userName = (SecurityContextHolder - .getContext()) - postDtoRequest._writer = userName.toString() - val resultMsg: String = postService.changeUserName(postDtoRequest) - return BaseResponse(message = resultMsg) - } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/dto/PostDtos.kt b/src/main/kotlin/study/post/dto/PostDtos.kt index a8ee1f3..8912f0d 100644 --- a/src/main/kotlin/study/post/dto/PostDtos.kt +++ b/src/main/kotlin/study/post/dto/PostDtos.kt @@ -2,7 +2,6 @@ package study.post.dto import com.fasterxml.jackson.annotation.JsonProperty import jakarta.validation.constraints.NotBlank -import jakarta.validation.constraints.Pattern import study.post.entity.Post import java.time.LocalDateTime @@ -17,27 +16,17 @@ data class PostDtoRequest( @JsonProperty("content") private val _content : String?, - @field:NotBlank - @JsonProperty("writer") - var _writer : String = "익명(${id})", - + //게시글 좋아요 수 + private val likes : Long = 0, private val createDate: LocalDateTime = LocalDateTime.now() ) { val title: String get() = _title!!.toString() val content: String get() = _content!!.toString() - val writer: String - get() = _writer - - fun toEntity(): Post = - Post(null, title, content, writer, createDate) -} -data class PostDtoResponse ( - val id: Long, - val title: String, - val content: String, - val writer: String, - val createDate: LocalDateTime -) \ No newline at end of file + //Member 의 name 을 writer 로 사용 + fun toEntity(writer: String): Post { + return Post(null, title, content, writer, likes, createDate) + } +} \ No newline at end of file diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt index 697721b..84cfa51 100644 --- a/src/main/kotlin/study/post/entity/PostEntities.kt +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -1,14 +1,9 @@ package study.post.entity import jakarta.persistence.* -import study.post.dto.PostDtoResponse import java.time.LocalDateTime @Entity -@Table( - //loginId 중복X - uniqueConstraints = [UniqueConstraint(name = "uk_member_login_id", columnNames = ["loginId"])] -) class Post( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -24,16 +19,9 @@ class Post( @Column(nullable = false, length = 10, updatable = false) var writer : String, + @Column(nullable = false, updatable = true) + var likes : Long, + @Column(nullable = false, updatable = false) val createDate: LocalDateTime -) { - //DTO 변경 함수 - fun toDto(): PostDtoResponse = - PostDtoResponse( - id!!, - title, - content, - writer, - createDate - ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/src/main/kotlin/study/post/service/PostSevice.kt b/src/main/kotlin/study/post/service/PostSevice.kt index 7ea990a..b6dda0c 100644 --- a/src/main/kotlin/study/post/service/PostSevice.kt +++ b/src/main/kotlin/study/post/service/PostSevice.kt @@ -1,18 +1,11 @@ package study.post.service import jakarta.transaction.Transactional -import org.antlr.v4.runtime.Token import org.springframework.data.repository.findByIdOrNull -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.stereotype.Service -import study.common.authority.JwtTokenProvider -import study.common.authority.TokenInfo import study.common.exception.InvalidInputException -import study.member.dto.LoginDto import study.member.repository.MemberRepository import study.post.dto.PostDtoRequest -import study.post.dto.PostDtoResponse import study.post.repository.PostRepository @Transactional @@ -20,35 +13,18 @@ import study.post.repository.PostRepository class PostService( private val memberRepository: MemberRepository, private val postRepository: PostRepository, - private val authenticationManagerBuilder: AuthenticationManagerBuilder, - private val jwtTokenProvider: JwtTokenProvider, ) { /** * 게시글 작성 -> 회원가입한 사용자만 작성 가능 */ - fun posting(id: Long, postDtoRequest: PostDtoRequest): String { - memberRepository.findByIdOrNull(id) + fun posting(postDtoRequest: PostDtoRequest, id: Long): String { + //관련 없는 토큰이 들어왔을 때, 토큰이 안 들어오면 에러 + val member = memberRepository.findByIdOrNull(id) ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") - val post = postDtoRequest.toEntity() + + //게시글 작성자 writer == member.name + val post = postDtoRequest.toEntity(member.name) postRepository.save(post) return "게시글 작성이 완료되었습니다." } - - /** - * 게시글 작성자 조회 - */ - fun searchUser(id: Long): PostDtoResponse { - val member = postRepository.findByIdOrNull(id) - ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") - return member.toDto() - } - - /** - * 작성자 이름을 토큰으로 찾은 사용자 이름으로 수정 - */ - fun changeUserName(postDtoRequest: PostDtoRequest): String { - val writer = postDtoRequest.toEntity() - postRepository.save(writer) - return "작성자 이름 수정이 완료되었습니다." - } } \ No newline at end of file From ac2ab067161540d8b100dbe7f78d0e519dec4337 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Tue, 6 May 2025 00:40:29 +0900 Subject: [PATCH 10/15] =?UTF-8?q?4=EC=A3=BC=EC=B0=A8=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/post/controller/PostController.kt | 19 +++++++++++++++ src/main/kotlin/study/post/dto/PostDtos.kt | 7 ++++-- .../kotlin/study/post/entity/PostEntities.kt | 8 +++---- .../study/post/repository/PostRepositories.kt | 4 +++- .../kotlin/study/post/service/PostSevice.kt | 23 ++++++++++++++++--- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/study/post/controller/PostController.kt b/src/main/kotlin/study/post/controller/PostController.kt index eaa25ab..594683a 100644 --- a/src/main/kotlin/study/post/controller/PostController.kt +++ b/src/main/kotlin/study/post/controller/PostController.kt @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.* import study.common.dto.BaseResponse import study.common.dto.CustomUser import study.post.dto.PostDtoRequest +import study.post.entity.Post import study.post.service.PostService @RequestMapping("/api/post") @@ -28,4 +29,22 @@ class PostController( return BaseResponse(result) } + /** + * 전체 게시글 조회 + */ + @GetMapping("/") + fun getAllPosts() : BaseResponse> { + val list = postService.getAllPosts() + return BaseResponse(data = list) + } + + /** + * 특정 게시글 조회 + */ + @GetMapping("/{postId}") + //@PathVariable: 클라이언트 측에서 url 에 인자를 전달하는 경우에 사용, url 경로에 변수를 넣어줌 + fun getPost(@PathVariable postId: Long) : BaseResponse { + val result = postService.getPost(postId) + return BaseResponse(data = result) + } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/dto/PostDtos.kt b/src/main/kotlin/study/post/dto/PostDtos.kt index 8912f0d..453167e 100644 --- a/src/main/kotlin/study/post/dto/PostDtos.kt +++ b/src/main/kotlin/study/post/dto/PostDtos.kt @@ -6,7 +6,7 @@ import study.post.entity.Post import java.time.LocalDateTime data class PostDtoRequest( - var id: Long? = null, + val id: Long? = null, @field:NotBlank //빈칸 허용 x @JsonProperty("title") @@ -18,6 +18,7 @@ data class PostDtoRequest( //게시글 좋아요 수 private val likes : Long = 0, + private val createDate: LocalDateTime = LocalDateTime.now() ) { val title: String @@ -27,6 +28,8 @@ data class PostDtoRequest( //Member 의 name 을 writer 로 사용 fun toEntity(writer: String): Post { - return Post(null, title, content, writer, likes, createDate) + return Post( + null, title, content, writer, likes, createDate + ) } } \ No newline at end of file diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt index 84cfa51..8256ca7 100644 --- a/src/main/kotlin/study/post/entity/PostEntities.kt +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -7,17 +7,17 @@ import java.time.LocalDateTime class Post( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null, + val id: Long? = null, //title, content 수정 가능, writer, date 는 수정 불가능 @Column(nullable = false, length = 30, updatable = true) - val title: String, + var title: String, @Column(nullable = false, length = 500, updatable = true) - val content: String, + var content: String, @Column(nullable = false, length = 10, updatable = false) - var writer : String, + val writer : String, @Column(nullable = false, updatable = true) var likes : Long, diff --git a/src/main/kotlin/study/post/repository/PostRepositories.kt b/src/main/kotlin/study/post/repository/PostRepositories.kt index 2063ce1..c282bc8 100644 --- a/src/main/kotlin/study/post/repository/PostRepositories.kt +++ b/src/main/kotlin/study/post/repository/PostRepositories.kt @@ -3,4 +3,6 @@ package study.post.repository import org.springframework.data.jpa.repository.JpaRepository import study.post.entity.Post -interface PostRepository: JpaRepository \ No newline at end of file +interface PostRepository: JpaRepository { + fun findPostById(postId: Long): Post? +} \ No newline at end of file diff --git a/src/main/kotlin/study/post/service/PostSevice.kt b/src/main/kotlin/study/post/service/PostSevice.kt index b6dda0c..de476fc 100644 --- a/src/main/kotlin/study/post/service/PostSevice.kt +++ b/src/main/kotlin/study/post/service/PostSevice.kt @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service import study.common.exception.InvalidInputException import study.member.repository.MemberRepository import study.post.dto.PostDtoRequest +import study.post.entity.Post import study.post.repository.PostRepository @Transactional @@ -17,14 +18,30 @@ class PostService( /** * 게시글 작성 -> 회원가입한 사용자만 작성 가능 */ - fun posting(postDtoRequest: PostDtoRequest, id: Long): String { + fun posting(postDtoRequest: PostDtoRequest, userId: Long): String { //관련 없는 토큰이 들어왔을 때, 토큰이 안 들어오면 에러 - val member = memberRepository.findByIdOrNull(id) - ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") + val member = memberRepository.findByIdOrNull(userId) + ?: throw InvalidInputException("id", "회원번호(${userId})가 존재하지 않는 사용자입니다.") //게시글 작성자 writer == member.name val post = postDtoRequest.toEntity(member.name) postRepository.save(post) return "게시글 작성이 완료되었습니다." } + + /** + * 전체 게시글 조회 + */ + fun getAllPosts(): MutableList { + return postRepository.findAll() + } + + /** + * 특정 게시글 조회 + */ + fun getPost(postId: Long): Post { + val post = postRepository.findPostById(postId) + ?: throw InvalidInputException("${postId}번 게시글은 존재하지 않습니다.") + return post + } } \ No newline at end of file From 1a65fc52b6ab7cf7f984db11c2f6422fe72d03a4 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Tue, 6 May 2025 01:05:49 +0900 Subject: [PATCH 11/15] =?UTF-8?q?dormitory=20->=20dormType=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/study/member/dto/MemberDtos.kt | 12 ++++++------ .../kotlin/study/member/entity/MemberEntities.kt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index 8d3cbcd..4ef52a9 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -38,8 +38,8 @@ data class MemberDtoRequest ( @field:NotBlank @field:ValidEnum(enumClass = Dormitory::class, message = "올바른 기숙사 타입을 선택해주세요.") - @JsonProperty("dormitory") - private val _dormitory: String?, + @JsonProperty("dormType") + private val _dormType: String?, ) {//Custom Getter val loginId: String get() = _loginId!! @@ -49,12 +49,12 @@ data class MemberDtoRequest ( get() = _name!! val email: String get() = _email!! - val dormitory: Dormitory//String?을 enum class 로 변환 - get() = Dormitory.valueOf(_dormitory!!) + val dormType: Dormitory//String?을 enum class 로 변환 + get() = Dormitory.valueOf(_dormType!!) //Entity 반환 fun toEntity(): Member = - Member(id, loginId, password, name, email, dormitory) + Member(id, loginId, password, name, email, dormType) } data class LoginDto ( @@ -77,5 +77,5 @@ data class MemberDtoResponse ( val loginId: String, val name: String, val email: String, - val dormitory: String, + val dormType: String, ) \ No newline at end of file diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt index 1c06f9a..6f27dc9 100644 --- a/src/main/kotlin/study/member/entity/MemberEntities.kt +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -31,7 +31,7 @@ class Member( @Column(nullable = false, length = 10) @Enumerated(EnumType.STRING)//db에 Dormitory의 이름(STRING)을 그대로 입력 - val dormitory: Dormitory, + val dormType: Dormitory, ) {//1 : N 연결 @OneToMany(fetch = FetchType.LAZY, mappedBy = "member") val memberRole: List? = null @@ -43,7 +43,7 @@ class Member( loginId, name, email, - dormitory.desc //dormitory 에 해당하는 값을 받음 + dormType.desc //dormitory 에 해당하는 값을 받음 ) } From 8db6ae8818964dfdac1ef3fd49d0b14d3b236596 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Tue, 6 May 2025 01:06:07 +0900 Subject: [PATCH 12/15] =?UTF-8?q?5=EC=A3=BC=EC=B0=A8=20=EA=B0=99=EC=9D=80?= =?UTF-8?q?=20=EA=B8=B0=EC=88=99=EC=82=AC=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/member/controller/MemberController.kt | 14 ++++++++++++++ .../study/member/repository/MemberRepositories.kt | 3 +++ .../kotlin/study/member/service/MemberService.kt | 11 +++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index a2c35a9..ad6e0e2 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -73,4 +73,18 @@ class MemberController ( val resultMsg: String = memberService.saveMyInfo(memberDtoRequest) return BaseResponse(message = resultMsg) } + + /** + * 같은 기숙사 조회 + */ + @GetMapping("/dorm/info") + fun getDormInfo(): BaseResponse> { + val userId = (SecurityContextHolder + .getContext() + .authentication + .principal as CustomUser) + .userId + val result = memberService.getDormInfo(userId) + return BaseResponse(data = result) + } } \ No newline at end of file diff --git a/src/main/kotlin/study/member/repository/MemberRepositories.kt b/src/main/kotlin/study/member/repository/MemberRepositories.kt index 850696d..3dbe6ef 100644 --- a/src/main/kotlin/study/member/repository/MemberRepositories.kt +++ b/src/main/kotlin/study/member/repository/MemberRepositories.kt @@ -2,12 +2,15 @@ package study.member.repository import org.springframework.data.jpa.repository.JpaRepository +import study.common.status.Dormitory import study.member.entity.Member import study.member.entity.MemberRole interface MemberRepository : JpaRepository {//JpaRepository 상속 //loginId로 찾기, ID 중복 검사를 위해 필요 fun findByLoginId(loginId: String): Member? + //같은 기숙사 타입 찾기 + fun findByDormType(dormType: Dormitory): MutableList } interface MemberRoleRepository : JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index 0112631..15f0fc6 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service import study.common.authority.JwtTokenProvider import study.common.authority.TokenInfo import study.common.exception.InvalidInputException +import study.common.status.Dormitory import study.common.status.ROLE import study.member.dto.LoginDto import study.member.dto.MemberDtoRequest @@ -79,4 +80,14 @@ class MemberService( memberRepository.save(member) return "정보 수정이 완료되었습니다." } + + /** + * 같은 기숙사 조회 -> id로 기준 기숙사 타입 결정 + * !!으로 null 이 아닌 경우에만 기숙사 타입 받음 + */ + fun getDormInfo(id: Long): List { + val dormType = memberRepository.findByIdOrNull(id)!!.dormType + val result = memberRepository.findByDormType(dormType) + return result.map { it.toDto() } + } } \ No newline at end of file From 13998d3bf0392d972a44ed84f88a8b8fd765f856 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Tue, 6 May 2025 01:30:22 +0900 Subject: [PATCH 13/15] =?UTF-8?q?5=EC=A3=BC=EC=B0=A8=20=EC=95=94=ED=98=B8?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 ++ src/main/kotlin/study/member/dto/MemberDtos.kt | 8 ++++++-- src/main/kotlin/study/member/entity/MemberEntities.kt | 2 +- src/main/kotlin/study/member/service/MemberService.kt | 11 ++++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6ea03f8..aa3e827 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,8 @@ dependencies { implementation("io.jsonwebtoken:jjwt-api:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") + //암호화 알고리즘 사용시 필요 + implementation("org.bouncycastle:bcprov-jdk15on:1.70") } noArg{ diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index 4ef52a9..c7a8259 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty import jakarta.validation.constraints.Email import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Pattern +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder import study.common.annotation.ValidEnum import study.common.status.Dormitory import study.member.entity.Member @@ -41,10 +42,13 @@ data class MemberDtoRequest ( @JsonProperty("dormType") private val _dormType: String?, ) {//Custom Getter + //암호화 기능 추가 + private val encoder = SCryptPasswordEncoder(16,8,1,8,8) + val loginId: String get() = _loginId!! - val password: String - get() = _password!! + private val password: String + get() = encoder.encode(_password) val name: String get() = _name!! val email: String diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt index 6f27dc9..25c102b 100644 --- a/src/main/kotlin/study/member/entity/MemberEntities.kt +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -36,7 +36,7 @@ class Member( @OneToMany(fetch = FetchType.LAZY, mappedBy = "member") val memberRole: List? = null - //DTO 변경 함수 + //DTO 변경 함수 -> 비밀번호 제외 fun toDto(): MemberDtoResponse = MemberDtoResponse( id!!, diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index 15f0fc6..13ba588 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -5,6 +5,7 @@ import jakarta.transaction.Transactional import org.springframework.data.repository.findByIdOrNull import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder import org.springframework.stereotype.Service import study.common.authority.JwtTokenProvider import study.common.authority.TokenInfo @@ -53,8 +54,16 @@ class MemberService( * 로그인 -> 토큰 발행 */ fun login(loginDto: LoginDto): TokenInfo { + val member = memberRepository.findByLoginId(loginDto.loginId) + ?: throw InvalidInputException("로그인 아이디 혹은 비밀번호가 틀렸습니다.") + val encoder = SCryptPasswordEncoder(16,8,1,8,8) + if(!encoder.matches(loginDto.password, member.password)) { + throw InvalidInputException("로그인 아이디 혹은 비밀번호가 틀렸습니다.") + } + + //loginDto 의 password 암호화 -> DB에 존재하는 내용으로 토큰 발행 val authenticationToken = - UsernamePasswordAuthenticationToken(loginDto.loginId, loginDto.password) + UsernamePasswordAuthenticationToken(loginDto.loginId, member.password) val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken) //DB에 있는 유저네임과 비교, 문제가 없으면 사용자에게 토큰 발행 From a585c91dd572b32ec4b77f27e1e0ab8e3e7e838c Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Fri, 9 May 2025 17:14:30 +0900 Subject: [PATCH 14/15] =?UTF-8?q?id=20->=20userId=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/common/service/CustomUserDetailsService.kt | 2 +- .../study/member/controller/MemberController.kt | 2 +- src/main/kotlin/study/member/dto/MemberDtos.kt | 6 +++--- .../kotlin/study/member/entity/MemberEntities.kt | 6 +++--- .../kotlin/study/member/service/MemberService.kt | 12 ++++++------ src/main/kotlin/study/post/dto/PostDtos.kt | 2 +- src/main/kotlin/study/post/entity/PostEntities.kt | 4 ++-- .../kotlin/study/post/repository/PostRepositories.kt | 2 +- src/main/kotlin/study/post/service/PostSevice.kt | 6 +++--- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/study/common/service/CustomUserDetailsService.kt b/src/main/kotlin/study/common/service/CustomUserDetailsService.kt index d2fc23c..4c39c7b 100644 --- a/src/main/kotlin/study/common/service/CustomUserDetailsService.kt +++ b/src/main/kotlin/study/common/service/CustomUserDetailsService.kt @@ -23,7 +23,7 @@ class CustomUserDetailsService ( private fun createUserDetails(member: Member): UserDetails = CustomUser( - member.id!!, + member.userId!!, member.loginId, passwordEncoder.encode(member.password), member.memberRole!!.map { SimpleGrantedAuthority("ROLE_${it.role}")} diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index ad6e0e2..fddbb20 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -69,7 +69,7 @@ class MemberController ( .authentication .principal as CustomUser) .userId - memberDtoRequest.id = userId + memberDtoRequest.userId = userId val resultMsg: String = memberService.saveMyInfo(memberDtoRequest) return BaseResponse(message = resultMsg) } diff --git a/src/main/kotlin/study/member/dto/MemberDtos.kt b/src/main/kotlin/study/member/dto/MemberDtos.kt index c7a8259..0c73b09 100644 --- a/src/main/kotlin/study/member/dto/MemberDtos.kt +++ b/src/main/kotlin/study/member/dto/MemberDtos.kt @@ -12,7 +12,7 @@ import study.member.entity.Member //회원가입시 입력받을 정보 data class MemberDtoRequest ( - var id: Long?, + var userId: Long?, @field:NotBlank//빈 값을 받지 않음 @JsonProperty("loginId")//loginId와 _loginId 연결, loginId 사용 @@ -58,7 +58,7 @@ data class MemberDtoRequest ( //Entity 반환 fun toEntity(): Member = - Member(id, loginId, password, name, email, dormType) + Member(userId, loginId, password, name, email, dormType) } data class LoginDto ( @@ -77,7 +77,7 @@ data class LoginDto ( } data class MemberDtoResponse ( - val id: Long, + val userId: Long, val loginId: String, val name: String, val email: String, diff --git a/src/main/kotlin/study/member/entity/MemberEntities.kt b/src/main/kotlin/study/member/entity/MemberEntities.kt index 25c102b..51b7128 100644 --- a/src/main/kotlin/study/member/entity/MemberEntities.kt +++ b/src/main/kotlin/study/member/entity/MemberEntities.kt @@ -14,7 +14,7 @@ import study.member.dto.MemberDtoResponse class Member( @Id @GeneratedValue(strategy = GenerationType.AUTO) - var id: Long? = null, + var userId: Long? = null, @Column(nullable = false, length = 30, updatable = false) //updatable = false -> 업데이트시 loginId는 제외, 변경X @@ -39,7 +39,7 @@ class Member( //DTO 변경 함수 -> 비밀번호 제외 fun toDto(): MemberDtoResponse = MemberDtoResponse( - id!!, + userId!!, loginId, name, email, @@ -51,7 +51,7 @@ class Member( class MemberRole( @Id @GeneratedValue(strategy = GenerationType.AUTO) - var id: Long? = null, + var userId: Long? = null, @Column(nullable = false, length = 30) @Enumerated(EnumType.STRING) diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index 13ba588..8a0eebc 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -74,10 +74,10 @@ class MemberService( /** * 내 정보 조회 */ - fun searchMyInfo(id: Long): MemberDtoResponse { + fun searchMyInfo(userId: Long): MemberDtoResponse { //해당하는 id가 없으면 예외 처리 - val member = memberRepository.findByIdOrNull(id) - ?: throw InvalidInputException("id", "회원번호(${id})가 존재하지 않는 사용자입니다.") + val member = memberRepository.findByIdOrNull(userId) + ?: throw InvalidInputException("userId", "회원번호(${userId}): 존재하지 않는 사용자입니다.") return member.toDto() } @@ -91,11 +91,11 @@ class MemberService( } /** - * 같은 기숙사 조회 -> id로 기준 기숙사 타입 결정 + * 같은 기숙사 조회 -> userId로 기준 기숙사 타입 결정 * !!으로 null 이 아닌 경우에만 기숙사 타입 받음 */ - fun getDormInfo(id: Long): List { - val dormType = memberRepository.findByIdOrNull(id)!!.dormType + fun getDormInfo(userId: Long): List { + val dormType = memberRepository.findByIdOrNull(userId)!!.dormType val result = memberRepository.findByDormType(dormType) return result.map { it.toDto() } } diff --git a/src/main/kotlin/study/post/dto/PostDtos.kt b/src/main/kotlin/study/post/dto/PostDtos.kt index 453167e..ffd06d6 100644 --- a/src/main/kotlin/study/post/dto/PostDtos.kt +++ b/src/main/kotlin/study/post/dto/PostDtos.kt @@ -6,7 +6,7 @@ import study.post.entity.Post import java.time.LocalDateTime data class PostDtoRequest( - val id: Long? = null, + val userId: Long? = null, @field:NotBlank //빈칸 허용 x @JsonProperty("title") diff --git a/src/main/kotlin/study/post/entity/PostEntities.kt b/src/main/kotlin/study/post/entity/PostEntities.kt index 8256ca7..00ad768 100644 --- a/src/main/kotlin/study/post/entity/PostEntities.kt +++ b/src/main/kotlin/study/post/entity/PostEntities.kt @@ -7,9 +7,9 @@ import java.time.LocalDateTime class Post( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long? = null, + val userId: Long? = null, - //title, content 수정 가능, writer, date 는 수정 불가능 + //title, content, like 수정 가능, writer, date 는 수정 불가능 @Column(nullable = false, length = 30, updatable = true) var title: String, diff --git a/src/main/kotlin/study/post/repository/PostRepositories.kt b/src/main/kotlin/study/post/repository/PostRepositories.kt index c282bc8..35d7b1a 100644 --- a/src/main/kotlin/study/post/repository/PostRepositories.kt +++ b/src/main/kotlin/study/post/repository/PostRepositories.kt @@ -4,5 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository import study.post.entity.Post interface PostRepository: JpaRepository { - fun findPostById(postId: Long): Post? + fun findPostByUserId(postId: Long): Post? } \ No newline at end of file diff --git a/src/main/kotlin/study/post/service/PostSevice.kt b/src/main/kotlin/study/post/service/PostSevice.kt index de476fc..b51ac12 100644 --- a/src/main/kotlin/study/post/service/PostSevice.kt +++ b/src/main/kotlin/study/post/service/PostSevice.kt @@ -21,7 +21,7 @@ class PostService( fun posting(postDtoRequest: PostDtoRequest, userId: Long): String { //관련 없는 토큰이 들어왔을 때, 토큰이 안 들어오면 에러 val member = memberRepository.findByIdOrNull(userId) - ?: throw InvalidInputException("id", "회원번호(${userId})가 존재하지 않는 사용자입니다.") + ?: throw InvalidInputException("id", "회원번호(${userId}): 존재하지 않는 사용자입니다.") //게시글 작성자 writer == member.name val post = postDtoRequest.toEntity(member.name) @@ -40,8 +40,8 @@ class PostService( * 특정 게시글 조회 */ fun getPost(postId: Long): Post { - val post = postRepository.findPostById(postId) - ?: throw InvalidInputException("${postId}번 게시글은 존재하지 않습니다.") + val post = postRepository.findPostByUserId(postId) + ?: throw InvalidInputException("게시글번호(${postId}): 존재하지 않는 게시글입니다.") return post } } \ No newline at end of file From 797b7c51e4c2287db8d7e2c17c340e740cb54901 Mon Sep 17 00:00:00 2001 From: Yuna_H Date: Fri, 9 May 2025 17:31:09 +0900 Subject: [PATCH 15/15] =?UTF-8?q?saveMyInfo=20->=20changeMyInfo=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/study/member/controller/MemberController.kt | 4 ++-- src/main/kotlin/study/member/service/MemberService.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/study/member/controller/MemberController.kt b/src/main/kotlin/study/member/controller/MemberController.kt index fddbb20..5bf4240 100644 --- a/src/main/kotlin/study/member/controller/MemberController.kt +++ b/src/main/kotlin/study/member/controller/MemberController.kt @@ -62,7 +62,7 @@ class MemberController ( * 내 정보 수정 */ @PutMapping("/info") - fun saveMyInfo(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): + fun changeMyInfo(@RequestBody @Valid memberDtoRequest: MemberDtoRequest): BaseResponse { val userId = (SecurityContextHolder .getContext() @@ -70,7 +70,7 @@ class MemberController ( .principal as CustomUser) .userId memberDtoRequest.userId = userId - val resultMsg: String = memberService.saveMyInfo(memberDtoRequest) + val resultMsg: String = memberService.changeMyInfo(memberDtoRequest) return BaseResponse(message = resultMsg) } diff --git a/src/main/kotlin/study/member/service/MemberService.kt b/src/main/kotlin/study/member/service/MemberService.kt index 8a0eebc..da44bd9 100644 --- a/src/main/kotlin/study/member/service/MemberService.kt +++ b/src/main/kotlin/study/member/service/MemberService.kt @@ -84,7 +84,7 @@ class MemberService( /** * 내 정보 수정 */ - fun saveMyInfo(memberDtoRequest: MemberDtoRequest): String { + fun changeMyInfo(memberDtoRequest: MemberDtoRequest): String { val member = memberDtoRequest.toEntity() memberRepository.save(member) return "정보 수정이 완료되었습니다."