From c50e8d835b6eadd40f18a1bbff7a02eeb1572c74 Mon Sep 17 00:00:00 2001 From: sohot8653 Date: Tue, 12 Aug 2025 15:00:12 +0900 Subject: [PATCH] =?UTF-8?q?[=ED=9A=8C=EC=9B=90=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20MapStruct?= =?UTF-8?q?=20=EB=8F=84=EC=9E=85]=20-=20=ED=9A=8C=EC=9B=90=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EB=B0=8F=20DTO=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20MapStruct=EB=A5=BC=20=ED=86=B5=ED=95=9C?= =?UTF-8?q?=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?,=20=EC=82=AC=EC=9A=A9=EC=9E=90=20ID=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84,?= =?UTF-8?q?=20README=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8,=20Gradle=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 56 +++++- build.gradle | 7 +- ddl/schema.sql | 8 +- .../member/controller/MemberController.java | 30 +--- .../member/dto/CreateMemberRequestDto.java | 20 ++- .../member/dto/CreateMemberResponseDto.java | 20 ++- .../user/member/dto/LoginRequestDto.java | 22 ++- .../user/member/dto/LoginResponseDto.java | 25 ++- .../domain/user/member/dto/MemberDto.java | 168 ++++++------------ .../domain/user/member/entity/Member.java | 31 ++-- .../domain/user/member/enums/MemberRole.java | 39 ++++ .../exception/UserDuplicateException.java | 12 ++ .../user/member/mapper/MemberMapper.java | 65 ++++--- .../member/repository/MemberRepository.java | 5 +- .../repository/MemberRepositoryCustom.java | 18 +- .../repository/MemberRepositoryImpl.java | 29 +-- .../user/member/service/MemberService.java | 2 +- .../member/service/MemberServiceImpl.java | 124 ++++++++----- .../global/aop/RepositoryLoggingAspect.java | 17 +- .../bio_backend/global/config/AppConfig.java | 12 -- .../global/constants/MemberConstants.java | 38 ---- .../bio_backend/global/entity/BaseEntity.java | 34 +--- .../exception/GlobalExceptionHandler.java | 6 + .../security/JwtAuthenticationFilter.java | 17 +- .../global/security/WebSecurity.java | 5 +- .../global/utils/ApiResponseCode.java | 3 + src/main/resources/application.properties | 3 + 27 files changed, 429 insertions(+), 387 deletions(-) create mode 100644 src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java create mode 100644 src/main/java/com/bio/bio_backend/domain/user/member/exception/UserDuplicateException.java delete mode 100644 src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java diff --git a/README.md b/README.md index 217ac91..dc03b06 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,56 @@ -# bio_backend +# Bio Backend +## 기술 스택 + +- **Framework**: Spring Boot +- **Database**: PostgreSQL +- **ORM**: Spring Data JPA + QueryDSL +- **Security**: Spring Security + JWT +- **Build Tool**: Gradle +- **Container**: Docker + Kubernetes + +## 개발 가이드 + +### 1. 프로젝트 구조 + +``` +src/main/java/com/bio/bio_backend/ +├── domain/ # 도메인별 패키지 +│ └── user/ +│ └── member/ # 회원 도메인 +│ ├── controller/ # API 엔드포인트 +│ ├── service/ # 비즈니스 로직 +│ ├── repository/ # 데이터 접근 +│ ├── entity/ # JPA 엔티티 +│ └── dto/ # 데이터 전송 객체 +├── global/ # 공통 설정 +│ ├── config/ # 설정 클래스 +│ ├── security/ # 보안 설정 +│ ├── exception/ # 예외 처리 +│ └── utils/ # 유틸리티 +└── BioBackendApplication.java +``` + +### 2. 트랜잭션 관리 + +#### 기본 설정 + +```java +@Service +@Transactional(readOnly = true) // 클래스 레벨: 읽기 전용 기본값 +public class MemberServiceImpl { + + // 읽기 전용 메서드 (별도 어노테이션 불필요) + public MemberDto selectMember(long seq) { ... } + + // 쓰기 작업 메서드 (개별 @Transactional 적용) + @Transactional + public MemberDto createMember(MemberDto dto) { ... } +} +``` + +#### 핵심 규칙 + +- **클래스 레벨**: `@Transactional(readOnly = true)` 기본 설정 +- **메서드별**: 데이터 수정 시에만 `@Transactional` 개별 적용 +- **설정**: `spring.jpa.open-in-view=false` (성능 최적화) diff --git a/build.gradle b/build.gradle index 5a79fc6..65e05a7 100644 --- a/build.gradle +++ b/build.gradle @@ -43,8 +43,9 @@ dependencies { // Validation 추가 implementation 'org.springframework.boot:spring-boot-starter-validation' - // ModelMapper 추가 - implementation 'org.modelmapper:modelmapper:3.0.0' + // MapStruct 추가 (안정적인 버전으로 수정) + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' // MyBatis 추가 implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' @@ -54,6 +55,8 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // Lombok과 MapStruct 함께 사용을 위한 바인딩 + annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/ddl/schema.sql b/ddl/schema.sql index 6850d73..d298e83 100644 --- a/ddl/schema.sql +++ b/ddl/schema.sql @@ -1,11 +1,13 @@ - create table member ( - status varchar(1) not null, + create table st_member ( + use_flag boolean not null, created_at timestamp(6) not null, + created_oid bigint, last_login_at timestamp(6), oid bigint not null, updated_at timestamp(6) not null, - role varchar(40) not null, + updated_oid bigint, + role varchar(40) not null check (role in ('MEMBER','ADMIN','USER','SYSTEM_ADMIN')), password varchar(100) not null, user_id varchar(100) not null, refresh_token varchar(200), diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java b/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java index 3cb09b1..8394e2a 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java @@ -1,6 +1,5 @@ package com.bio.bio_backend.domain.user.member.controller; -import org.modelmapper.ModelMapper; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -14,6 +13,7 @@ import com.bio.bio_backend.domain.user.member.dto.MemberDto; import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDto; import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto; import com.bio.bio_backend.domain.user.member.service.MemberService; +import com.bio.bio_backend.domain.user.member.mapper.MemberMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,30 +23,16 @@ import lombok.extern.slf4j.Slf4j; public class MemberController { private final MemberService memberService; - private final ModelMapper mapper; + private final MemberMapper memberMapper; private final BCryptPasswordEncoder bCryptPasswordEncoder; - - @GetMapping("/join") - public ResponseEntity createMember1() { - return ResponseEntity.status(HttpStatus.CREATED).body("test"); - } + @PostMapping("/members") + public ResponseEntity createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) { + MemberDto member = memberMapper.toMemberDto(requestDto); + MemberDto createdMember = memberService.createMember(member); + CreateMemberResponseDto responseDto = memberMapper.toCreateMemberResponseDto(createdMember); - @PostMapping("/join") - public ResponseEntity createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) { - - // RequestMember를 MemberDTO로 변환 - MemberDto member = new MemberDto(); - member.setId(requestDto.getUserId()); - member.setPw(requestDto.getPassword()); - - long oid = memberService.createMember(member); - - // 생성된 회원 정보를 조회하여 응답 - //MemberDto createdMember = memberService.selectMember(oid); - //CreateMemberResponseDto responseDto = mapper.map(createdMember, CreateMemberResponseDto.class); - - return ResponseEntity.status(HttpStatus.CREATED).body(oid); + return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); } // @PostMapping("/member/list") diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java index f988f22..f424b00 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java @@ -1,17 +1,21 @@ package com.bio.bio_backend.domain.user.member.dto; -import com.fasterxml.jackson.annotation.JsonInclude; -import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; @Data -@JsonInclude(JsonInclude.Include.NON_NULL) +@Builder +@NoArgsConstructor +@AllArgsConstructor public class CreateMemberRequestDto { - - @NotBlank(message = "사용자 ID는 필수입니다") + + @NotBlank(message = "아이디는 필수입니다.") private String userId; - - @NotBlank(message = "비밀번호는 필수입니다") + + @NotBlank(message = "비밀번호는 필수입니다.") private String password; - } diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java index 187594b..ac45060 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java @@ -1,11 +1,23 @@ package com.bio.bio_backend.domain.user.member.dto; -import com.fasterxml.jackson.annotation.JsonInclude; +import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; @Data -@JsonInclude(JsonInclude.Include.NON_NULL) +@Builder +@NoArgsConstructor +@AllArgsConstructor public class CreateMemberResponseDto { - private String id; - private String pw; + + private Long oid; + private String userId; + private MemberRole role; + private Boolean useFlag; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; } diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java index 5185387..b5017d0 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java @@ -1,15 +1,21 @@ package com.bio.bio_backend.domain.user.member.dto; -import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; @Data +@Builder +@NoArgsConstructor +@AllArgsConstructor public class LoginRequestDto { - @NotNull(message = "ID cannot be null") - private String id; - - @NotNull(message = "Password cannot be null") - private String pw; - - private int loginLogFlag; + + @NotBlank(message = "아이디는 필수입니다.") + private String userId; + + @NotBlank(message = "비밀번호는 필수입니다.") + private String password; } diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java index 6180095..e3a242c 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java @@ -1,24 +1,19 @@ package com.bio.bio_backend.domain.user.member.dto; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; -import java.sql.Date; -import java.sql.Timestamp; +import java.time.LocalDateTime; @Data -@JsonInclude(JsonInclude.Include.NON_NULL) +@Builder +@NoArgsConstructor +@AllArgsConstructor public class LoginResponseDto { - private String id; - + + private String userId; private String role; - - private String status; - - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") - private Date regAt; - - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") - private Timestamp lastLoginAt; + private LocalDateTime lastLoginAt; } diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java b/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java index 7bed5c0..74151dc 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java @@ -1,127 +1,61 @@ package com.bio.bio_backend.domain.user.member.dto; -import java.sql.Timestamp; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - +import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import com.bio.bio_backend.global.constants.MemberConstants; -import lombok.Data; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; @Data -/** - * 회원 - */ +@Builder +@NoArgsConstructor +@AllArgsConstructor public class MemberDto implements UserDetails { - /** - * 시퀀스 (PK) - */ - private int seq; - - /** - * ID - */ - private String id; - - /** - * Password - */ - private String pw; - - /** - * 권한 - */ - private String role; - - /** - * 회원 상태 - */ - private String status; - - /** - * 가입 일시 - */ - private Timestamp regAt; - - /** - * 등록자 - */ - private int regSeq; - - /** - * 수정 일시 - */ - private Timestamp udtAt; - - /** - * 수정자 - */ - private int udtSeq; - - /** - * 최근 로그인 일시 - */ - private Timestamp lastLoginAt; - - /** - * Refresh Token - */ - private String refreshToken; - - @Override - public Collection getAuthorities() { - - Set roles = new HashSet<>(); - String auth = ""; - - - if(role.equals("SYSTEM_ADMIN")){ - auth = MemberConstants.ROLE_SYSTEM_ADMIN + "," + - MemberConstants.ROLE_ADMIN + "," + MemberConstants.ROLE_MEMBER; - }else if(role.equals("ADMIN")){ - auth = MemberConstants.ROLE_ADMIN + "," + MemberConstants.ROLE_MEMBER; - }else { - auth = MemberConstants.ROLE_MEMBER; - } - - for (String x : auth.split(",")) { - roles.add(new SimpleGrantedAuthority(x)); - } - - return roles; - } - - @Override - public String getPassword() { - return pw; - } - - @Override - public String getUsername() { - return id; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } + + private Long oid; + private String userId; + private String password; + private MemberRole role; + private Boolean useFlag; + private String refreshToken; + private LocalDateTime lastLoginAt; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + @Override + public Collection getAuthorities() { + return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + this.role.getValue())); + } + + @Override + public String getUsername() { + return this.userId; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return this.useFlag != null && this.useFlag; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return this.useFlag != null && this.useFlag; + } } diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java b/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java index 77ba685..29e4367 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java @@ -1,25 +1,23 @@ package com.bio.bio_backend.domain.user.member.entity; -import java.time.LocalDateTime; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; +import com.bio.bio_backend.domain.user.member.enums.MemberRole; import com.bio.bio_backend.global.entity.BaseEntity; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import java.time.LocalDateTime; + @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @Table( - name = "member", + name = "st_member", uniqueConstraints = { @UniqueConstraint(name = "uk_member_user_id", columnNames = "user_id") } @@ -32,15 +30,28 @@ public class Member extends BaseEntity { @Column(name = "password", nullable = false, length = 100) private String password; + @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false, length = 40) - private String role; + private MemberRole role; - @Column(name = "status", nullable = false, length = 1) - private String status; + @Column(name = "use_flag", nullable = false) + @Builder.Default + private Boolean useFlag = true; @Column(name = "refresh_token", length = 200) private String refreshToken; @Column(name = "last_login_at") private LocalDateTime lastLoginAt; + + /** + * 엔티티 저장 후 실행되는 메서드 + * createdOid와 updatedOid를 자기 자신의 oid로 설정 + */ + @PostPersist + protected void onPostPersist() { + if (this.getCreatedOid() == null) { + this.setCreatedOid(this.getOid()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java b/src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java new file mode 100644 index 0000000..53014a7 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java @@ -0,0 +1,39 @@ +package com.bio.bio_backend.domain.user.member.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 회원 역할을 정의하는 Enum + */ +@Getter +@RequiredArgsConstructor +public enum MemberRole { + + MEMBER("MEMBER", "일반 회원"), + ADMIN("ADMIN", "관리자"), + USER("USER", "사용자"), + SYSTEM_ADMIN("SYSTEM_ADMIN", "시스템 관리자"); + + private final String value; + private final String description; + + /** + * 문자열 값으로부터 MemberRole을 찾는 메서드 + */ + public static MemberRole fromValue(String value) { + for (MemberRole role : values()) { + if (role.value.equals(value)) { + return role; + } + } + throw new IllegalArgumentException("Unknown MemberRole value: " + value); + } + + /** + * 기본 역할 반환 + */ + public static MemberRole getDefault() { + return MEMBER; + } +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/exception/UserDuplicateException.java b/src/main/java/com/bio/bio_backend/domain/user/member/exception/UserDuplicateException.java new file mode 100644 index 0000000..c399d48 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/user/member/exception/UserDuplicateException.java @@ -0,0 +1,12 @@ +package com.bio.bio_backend.domain.user.member.exception; + +public class UserDuplicateException extends RuntimeException { + + public UserDuplicateException(String message) { + super(message); + } + + public UserDuplicateException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java b/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java index 200fbc2..7b90c00 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java @@ -1,27 +1,50 @@ package com.bio.bio_backend.domain.user.member.mapper; -import java.util.List; -import java.util.Map; - -import org.apache.ibatis.annotations.Mapper; - +import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDto; +import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto; import com.bio.bio_backend.domain.user.member.dto.MemberDto; +import com.bio.bio_backend.domain.user.member.entity.Member; +import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; +import java.util.List; -@Mapper +@Mapper(componentModel = "spring") public interface MemberMapper { - int createMember(MemberDto memberDTO); - - MemberDto loadUserByUsername(String id); - - void updateRefreshToken(MemberDto memberDTO); - - String getRefreshToken(String id); - - int deleteRefreshToken(String id); - - List selectMemberList(Map params); - - MemberDto selectMemberBySeq(long seq); - - int updateMember(MemberDto member); + + MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class); + + /** + * CreateMemberRequestDto를 MemberDto로 변환 + * 기본값 설정: role = MemberRole.MEMBER, useFlag = true + */ + @Mapping(target = "oid", ignore = true) + @Mapping(target = "role", expression = "java(com.bio.bio_backend.domain.user.member.enums.MemberRole.getDefault())") + @Mapping(target = "useFlag", constant = "true") + @Mapping(target = "refreshToken", ignore = true) + @Mapping(target = "lastLoginAt", ignore = true) + @Mapping(target = "createdAt", ignore = true) + @Mapping(target = "updatedAt", ignore = true) + MemberDto toMemberDto(CreateMemberRequestDto requestDto); + + /** + * Member 엔티티를 MemberDto로 변환 + */ + MemberDto toMemberDto(Member member); + + /** + * MemberDto를 Member 엔티티로 변환 + */ + Member toMember(MemberDto memberDto); + + /** + * Member 엔티티 리스트를 MemberDto 리스트로 변환 + */ + List toMemberDtoList(List members); + + /** + * MemberDto를 CreateMemberResponseDto로 변환 + */ + CreateMemberResponseDto toCreateMemberResponseDto(MemberDto memberDto); } diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java index 8ff56f2..c7081eb 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java @@ -4,12 +4,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.bio.bio_backend.domain.user.member.entity.Member; +import java.util.Optional; @Repository public interface MemberRepository extends JpaRepository { - // 사용자 ID로 회원 조회 - Member findByUserId(String userId); + // 사용자 ID로 회원 조회 (Optional 반환) + Optional findByUserId(String userId); // 사용자 ID 존재 여부 확인 boolean existsByUserId(String userId); diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java index c553ea7..52f5d57 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java @@ -31,32 +31,32 @@ public interface MemberRepositoryCustom { List findByRole(String role); /** - * 상태(Status)별로 회원 목록을 조회합니다. + * 사용 여부별로 회원 목록을 조회합니다. * - * @param status 회원 상태 - * @return List 해당 상태를 가진 회원 목록 + * @param useFlag 사용 여부 + * @return List 해당 사용 여부를 가진 회원 목록 */ - List findByStatus(String status); + List findByUseFlag(Boolean useFlag); /** - * 사용자 ID와 상태로 회원을 조회합니다. + * 사용자 ID와 사용 여부로 회원을 조회합니다. * * @param userId 사용자 ID - * @param status 회원 상태 + * @param useFlag 사용 여부 * @return Optional 회원 정보 */ - Optional findByUserIdAndStatus(String userId, String status); + Optional findByUserIdAndUseFlag(String userId, Boolean useFlag); /** * 검색 조건에 따른 회원 목록을 페이징하여 조회합니다. * * @param userId 사용자 ID (부분 검색) * @param role 회원 역할 - * @param status 회원 상태 + * @param useFlag 사용 여부 * @param pageable 페이징 정보 * @return Page 페이징된 회원 목록 */ - Page findMembersByCondition(String userId, String role, String status, Pageable pageable); + Page findMembersByCondition(String userId, String role, Boolean useFlag, Pageable pageable); /** * 마지막 로그인 시간이 특정 시간 이후인 회원들을 조회합니다. diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java index 914c419..58e706a 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java @@ -2,6 +2,7 @@ package com.bio.bio_backend.domain.user.member.repository; import com.bio.bio_backend.domain.user.member.entity.Member; import com.bio.bio_backend.domain.user.member.entity.QMember; +import com.bio.bio_backend.domain.user.member.enums.MemberRole; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -46,37 +47,37 @@ public class MemberRepositoryImpl implements MemberRepositoryCustom { @Override public List findByRole(String role) { // 역할별로 회원을 조회합니다. - // eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다. + // String을 MemberRole enum으로 변환하여 비교합니다. return queryFactory .selectFrom(member) - .where(member.role.eq(role)) + .where(member.role.eq(MemberRole.fromValue(role))) .fetch(); } @Override - public List findByStatus(String status) { - // 상태별로 회원을 조회합니다. + public List findByUseFlag(Boolean useFlag) { + // 사용 여부별로 회원을 조회합니다. return queryFactory .selectFrom(member) - .where(member.status.eq(status)) + .where(member.useFlag.eq(useFlag)) .fetch(); } @Override - public Optional findByUserIdAndStatus(String userId, String status) { - // 사용자 ID와 상태를 모두 만족하는 회원을 조회합니다. + public Optional findByUserIdAndUseFlag(String userId, Boolean useFlag) { + // 사용자 ID와 사용 여부를 모두 만족하는 회원을 조회합니다. // and() 메서드를 사용하여 여러 조건을 결합합니다. Member foundMember = queryFactory .selectFrom(member) .where(member.userId.eq(userId) - .and(member.status.eq(status))) + .and(member.useFlag.eq(useFlag))) .fetchOne(); return Optional.ofNullable(foundMember); } @Override - public Page findMembersByCondition(String userId, String role, String status, Pageable pageable) { + public Page findMembersByCondition(String userId, String role, Boolean useFlag, Pageable pageable) { // BooleanBuilder를 사용하여 동적 쿼리를 구성합니다. // null이 아닌 조건만 쿼리에 포함시킵니다. BooleanBuilder builder = new BooleanBuilder(); @@ -88,12 +89,12 @@ public class MemberRepositoryImpl implements MemberRepositoryCustom { // 역할이 제공된 경우 정확한 일치 조건을 추가합니다. if (role != null && !role.trim().isEmpty()) { - builder.and(member.role.eq(role)); + builder.and(member.role.eq(MemberRole.fromValue(role))); } - // 상태가 제공된 경우 정확한 일치 조건을 추가합니다. - if (status != null && !status.trim().isEmpty()) { - builder.and(member.status.eq(status)); + // 사용 여부가 제공된 경우 정확한 일치 조건을 추가합니다. + if (useFlag != null) { + builder.and(member.useFlag.eq(useFlag)); } // 전체 개수를 조회합니다. @@ -121,7 +122,7 @@ public class MemberRepositoryImpl implements MemberRepositoryCustom { // 여러 조건을 조합하여 복잡한 쿼리를 작성합니다. return queryFactory .selectFrom(member) - .where(member.status.eq("A") // 활성 상태 + .where(member.useFlag.eq(true) // 사용 중인 상태 .and(member.lastLoginAt.isNotNull()) // 마지막 로그인 시간이 존재 .and(member.lastLoginAt.after(lastLoginAfter))) // 특정 시간 이후 .orderBy(member.lastLoginAt.desc()) // 마지막 로그인 시간 기준 내림차순 정렬 diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java index 57e13e5..7377ba7 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java @@ -12,7 +12,7 @@ public interface MemberService extends UserDetailsService { UserDetails loadUserByUsername(String id); - long createMember(MemberDto memberDTO); + MemberDto createMember(MemberDto memberDTO); void updateRefreshToken(MemberDto memberDTO); diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java index 8d503e0..40b6451 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java +++ b/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java @@ -2,15 +2,17 @@ package com.bio.bio_backend.domain.user.member.service; import com.bio.bio_backend.domain.user.member.dto.MemberDto; import com.bio.bio_backend.domain.user.member.entity.Member; +import com.bio.bio_backend.domain.user.member.enums.MemberRole; import com.bio.bio_backend.domain.user.member.mapper.MemberMapper; import com.bio.bio_backend.domain.user.member.repository.MemberRepository; -import com.bio.bio_backend.global.constants.MemberConstants; +import com.bio.bio_backend.domain.user.member.exception.UserDuplicateException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; @@ -18,59 +20,81 @@ import java.util.Map; @Service @RequiredArgsConstructor @Slf4j +@Transactional(readOnly = true) public class MemberServiceImpl implements MemberService { - private final MemberMapper memberMapper; + private final MemberMapper memberMapper; // MapStruct Mapper 사용 private final MemberRepository memberRepository; private final BCryptPasswordEncoder bCryptPasswordEncoder; @Override public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException { - - MemberDto member = memberMapper.loadUserByUsername(id); - - if (member == null) { - throw new UsernameNotFoundException("User not found with id : " + id); - } - - return member; + // JPA 레파지토리를 사용하여 회원 조회 + Member member = memberRepository.findByUserId(id) + .orElseThrow(() -> new UsernameNotFoundException("User not found with id : " + id)); + + // MapStruct를 사용하여 Member 엔티티를 MemberDto로 변환 + MemberDto memberDto = memberMapper.toMemberDto(member); + + return memberDto; } @Override - public long createMember(MemberDto memberDTO) { - // JPA Entity를 사용하여 회원 생성 + @Transactional + public MemberDto createMember(MemberDto memberDTO) { + // userId 중복 체크 + if (memberRepository.existsByUserId(memberDTO.getUserId())) { + throw new UserDuplicateException("User ID already exists"); + } + Member member = Member.builder() - .userId(memberDTO.getId()) - .password(bCryptPasswordEncoder.encode(memberDTO.getPw())) - .role(MemberConstants.ROLE_MEMBER) - .status(MemberConstants.MEMBER_ACTIVE) + .userId(memberDTO.getUserId()) + .password(bCryptPasswordEncoder.encode(memberDTO.getPassword())) + .role(MemberRole.getDefault()) .build(); - // JPA 레파지토리를 통해 저장 Member savedMember = memberRepository.save(member); - // 저장된 회원의 oid를 반환 - return savedMember.getOid(); + return memberMapper.toMemberDto(savedMember); } @Override + @Transactional public void updateRefreshToken(MemberDto memberDTO) { - memberMapper.updateRefreshToken(memberDTO); + // JPA를 사용하여 refresh token 업데이트 + Member member = memberRepository.findByUserId(memberDTO.getUserId()) + .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. id: " + memberDTO.getUserId())); + + member.setRefreshToken(memberDTO.getRefreshToken()); + memberRepository.save(member); } @Override public String getRefreshToken(String id) { - return memberMapper.getRefreshToken(id); + Member member = memberRepository.findByUserId(id) + .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. id: " + id)); + + return member.getRefreshToken(); } @Override + @Transactional public int deleteRefreshToken(String id) { - return memberMapper.deleteRefreshToken(id); + Member member = memberRepository.findByUserId(id) + .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. id: " + id)); + + member.setRefreshToken(null); + memberRepository.save(member); + return 1; // 성공 시 1 반환 } @Override public List selectMemberList(Map params) { - return memberMapper.selectMemberList(params); + // JPA를 사용하여 회원 목록 조회 (간단한 구현) + List members = memberRepository.findAll(); + + // MapStruct를 사용하여 Member 엔티티 리스트를 MemberDto 리스트로 변환 + return memberMapper.toMemberDtoList(members); } @Override @@ -79,35 +103,39 @@ public class MemberServiceImpl implements MemberService { Member member = memberRepository.findById(seq) .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. seq: " + seq)); - // Member 엔티티를 MemberDto로 변환하여 반환 - MemberDto memberDto = new MemberDto(); - memberDto.setSeq(member.getOid().intValue()); // Long을 int로 안전하게 변환 - memberDto.setId(member.getUserId()); - memberDto.setPw(member.getPassword()); - memberDto.setRole(member.getRole()); - memberDto.setStatus(member.getStatus()); - memberDto.setRegAt(java.sql.Timestamp.valueOf(member.getCreatedAt())); // LocalDateTime을 Timestamp로 변환 - memberDto.setUdtAt(java.sql.Timestamp.valueOf(member.getUpdatedAt())); - if (member.getLastLoginAt() != null) { - memberDto.setLastLoginAt(java.sql.Timestamp.valueOf(member.getLastLoginAt())); - } - memberDto.setRefreshToken(member.getRefreshToken()); + // MapStruct를 사용하여 Member 엔티티를 MemberDto로 자동 변환 + return memberMapper.toMemberDto(member); + } + + @Override + @Transactional + public int updateMember(MemberDto memberDto) { + Member member = memberRepository.findByUserId(memberDto.getUserId()) + .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. id: " + memberDto.getUserId())); - return memberDto; + // 비밀번호가 변경된 경우 암호화 + if (memberDto.getPassword() != null && !memberDto.getPassword().isEmpty()) { + member.setPassword(bCryptPasswordEncoder.encode(memberDto.getPassword())); + } + + member.setRole(memberDto.getRole()); + member.setUseFlag(memberDto.getUseFlag()); + + memberRepository.save(member); + return 1; // 성공 시 1 반환 } @Override - public int updateMember(MemberDto member) { - return memberMapper.updateMember(member); - } + @Transactional + public int deleteMember(MemberDto memberDto) { + Member member = memberRepository.findByUserId(memberDto.getUserId()) + .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. id: " + memberDto.getUserId())); - @Override - public int deleteMember(MemberDto member) { - - member.setStatus(MemberConstants.MEMBER_INACTIVE); - - log.info(member.toString()); - - return memberMapper.updateMember(member); + member.setUseFlag(false); + + log.info("회원 삭제 처리: {}", member.toString()); + + memberRepository.save(member); + return 1; // 성공 시 1 반환 } } diff --git a/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java b/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java index 71d0600..9bc7395 100644 --- a/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java +++ b/src/main/java/com/bio/bio_backend/global/aop/RepositoryLoggingAspect.java @@ -6,21 +6,13 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; -/** - * Repository 계층의 메서드 호출을 로깅하는 AOP(Aspect-Oriented Programming) 클래스 - * 모든 Repository 인터페이스의 메서드 호출 시점과 실행 시간을 로그로 기록합니다. - */ -@Aspect // AOP 기능을 활성화하는 어노테이션 -@Component // Spring Bean으로 등록하는 어노테이션 -@Slf4j // Lombok의 로깅 기능을 제공하는 어노테이션 +@Aspect +@Component +@Slf4j public class RepositoryLoggingAspect { /** * Repository 계층의 모든 메서드 호출을 가로채서 로깅하는 Around 어드바이스 - * - * @param pjp ProceedingJoinPoint - 실행될 메서드의 정보를 담고 있는 객체 - * @return Object - 원본 메서드의 실행 결과 - * @throws Throwable - 원본 메서드에서 발생할 수 있는 예외 */ @Around("execution(* org.springframework.data.repository.Repository+.*(..))") public Object logQueryCall(ProceedingJoinPoint pjp) throws Throwable { @@ -47,10 +39,7 @@ public class RepositoryLoggingAspect { // 원본 메서드의 결과를 반환 return result; } catch (Throwable ex) { - // 메서드 실행 중 예외 발생 시 로그로 기록 log.warn("[QUERY FAIL] {}.{}() -> {}", type, method, ex.toString()); - - // 예외를 다시 던져서 원래의 예외 처리 흐름을 유지 throw ex; } } diff --git a/src/main/java/com/bio/bio_backend/global/config/AppConfig.java b/src/main/java/com/bio/bio_backend/global/config/AppConfig.java index 098924a..0b22fb7 100644 --- a/src/main/java/com/bio/bio_backend/global/config/AppConfig.java +++ b/src/main/java/com/bio/bio_backend/global/config/AppConfig.java @@ -1,15 +1,8 @@ package com.bio.bio_backend.global.config; -import org.modelmapper.ModelMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.web.cors.CorsConfiguration; - -import org.springframework.context.annotation.Bean; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration public class AppConfig { @@ -18,9 +11,4 @@ public class AppConfig { public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } - - @Bean - public ModelMapper modelMapper() { - return new ModelMapper(); - } } \ No newline at end of file diff --git a/src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java b/src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java deleted file mode 100644 index 99e4d98..0000000 --- a/src/main/java/com/bio/bio_backend/global/constants/MemberConstants.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.bio.bio_backend.global.constants; - -/** - * Member 엔티티에서 사용하는 상수들을 정의하는 클래스 - * 역할(Role)과 상태(Status) 등의 상수값을 관리합니다. - */ -public class MemberConstants { - - /** - * 회원 역할 상수 - */ - public static final String ROLE_MEMBER = "MEMBER"; // 일반 회원 - public static final String ROLE_ADMIN = "ADMIN"; // 관리자 - public static final String ROLE_USER = "USER"; // 사용자 - public static final String ROLE_SYSTEM_ADMIN = "SYSTEM_ADMIN"; // 시스템 관리자 - - /** - * 회원 상태 상수 - */ - public static final String MEMBER_ACTIVE = "A"; // 활성 상태 (Active) - public static final String MEMBER_INACTIVE = "I"; // 비활성 상태 (Inactive) - public static final String MEMBER_SUSPENDED = "S"; // 정지 상태 (Suspended) - public static final String MEMBER_DELETED = "D"; // 삭제 상태 (Deleted) - - /** - * 기본값 상수 - */ - public static final String DEFAULT_ROLE = ROLE_MEMBER; // 기본 역할 - public static final String DEFAULT_STATUS = MEMBER_ACTIVE; // 기본 상태 - - /** - * 유효성 검사 상수 - */ - public static final int MIN_USER_ID_LENGTH = 4; // 사용자 ID 최소 길이 - public static final int MAX_USER_ID_LENGTH = 20; // 사용자 ID 최대 길이 - public static final int MIN_PASSWORD_LENGTH = 8; // 비밀번호 최소 길이 - public static final int MAX_PASSWORD_LENGTH = 100; // 비밀번호 최대 길이 -} diff --git a/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java b/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java index 2935204..86ed367 100644 --- a/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java +++ b/src/main/java/com/bio/bio_backend/global/entity/BaseEntity.java @@ -3,10 +3,10 @@ package com.bio.bio_backend.global.entity; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.GenericGenerator; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import com.bio.bio_backend.global.utils.OidUtil; import java.time.LocalDateTime; @@ -20,50 +20,32 @@ import java.time.LocalDateTime; @EntityListeners(AuditingEntityListener.class) public abstract class BaseEntity { - /** - * 엔티티의 고유 식별자 (Primary Key) - * 자동 증가하는 Long 타입으로 설정 - */ @Id - @GeneratedValue(generator = "customOidGenerator") - @GenericGenerator( - name = "customOidGenerator", - type = com.bio.bio_backend.global.utils.CustomIdGenerator.class - ) @Column(name = "oid", nullable = false) private Long oid; - /** - * 엔티티 생성 시간 - * JPA Auditing을 통해 자동으로 설정됨 - */ @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; - /** - * 엔티티 수정 시간 - * JPA Auditing을 통해 자동으로 설정됨 - */ @LastModifiedDate @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - /** - * 엔티티 저장 전에 실행되는 메서드 - * 생성 시간과 수정 시간을 자동으로 설정 - */ + @Column(name = "created_oid", updatable = false) + private Long createdOid; + + @Column(name = "updated_oid") + private Long updatedOid; + @PrePersist protected void onCreate() { LocalDateTime now = LocalDateTime.now(); + this.oid = OidUtil.generateOid(); this.createdAt = now; this.updatedAt = now; } - /** - * 엔티티 수정 전에 실행되는 메서드 - * 수정 시간을 자동으로 설정 - */ @PreUpdate protected void onUpdate() { this.updatedAt = LocalDateTime.now(); diff --git a/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java b/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java index 047b77e..558197b 100644 --- a/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/bio/bio_backend/global/exception/GlobalExceptionHandler.java @@ -16,6 +16,7 @@ import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.security.SignatureException; import com.bio.bio_backend.global.dto.CustomApiResponse; import com.bio.bio_backend.global.utils.ApiResponseCode; +import com.bio.bio_backend.domain.user.member.exception.UserDuplicateException; @RestControllerAdvice public class GlobalExceptionHandler { @@ -71,4 +72,9 @@ public class GlobalExceptionHandler { public CustomApiResponse handleExpiredJwtException(JsonProcessingException e) { return CustomApiResponse.fail(ApiResponseCode.JSON_PROCESSING_EXCEPTION, null); } + + @ExceptionHandler(UserDuplicateException.class) + public CustomApiResponse handleUserDuplicateException(UserDuplicateException e) { + return CustomApiResponse.fail(ApiResponseCode.USER_ID_DUPLICATE, null); + } } diff --git a/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java b/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java index 90ee39f..84df2b9 100644 --- a/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java @@ -16,7 +16,6 @@ import com.bio.bio_backend.global.utils.JwtUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.modelmapper.ModelMapper; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -42,7 +41,6 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte private final AuthenticationManager authenticationManager; private final MemberService memberService; - private final ModelMapper modelMapper; private final JwtUtils jwtUtils; private final Environment env; @@ -52,10 +50,10 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - LoginRequestDto req = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class); + LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class); // UsernamePasswordAuthenticationToken authToken; - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(req.getId(), req.getPw()); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(requestDto.getUserId(), requestDto.getPassword()); /* if (req.getLoginLogFlag() == 1) { @@ -76,15 +74,15 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte MemberDto member = (MemberDto) userDetails; - String accessToken = jwtUtils.generateToken(userDetails.getUsername(), member.getRole(), + String accessToken = jwtUtils.generateToken(member.getUserId(), member.getRole().getValue(), Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access")))); - String refreshToken = jwtUtils.generateToken(userDetails.getUsername(), member.getRole(), + String refreshToken = jwtUtils.generateToken(member.getUserId(), member.getRole().getValue(), Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh")))); member.setRefreshToken(refreshToken); - member.setLastLoginAt(Timestamp.valueOf(LocalDateTime.now())); + member.setLastLoginAt(LocalDateTime.now()); memberService.updateRefreshToken(member); @@ -109,7 +107,10 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte context.setAuthentication(authResult); contextHolder.setContext(context); - LoginResponseDto memberData = modelMapper.map(member, LoginResponseDto.class); + LoginResponseDto memberData = new LoginResponseDto(); + memberData.setUserId(member.getUserId()); + memberData.setRole(member.getRole().getValue()); + memberData.setLastLoginAt(member.getLastLoginAt()); // login 성공 메시지 전송 response.setStatus(HttpStatus.OK.value()); diff --git a/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java b/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java index 03a8620..44c7a09 100644 --- a/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java +++ b/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java @@ -1,6 +1,5 @@ package com.bio.bio_backend.global.security; -import org.modelmapper.ModelMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -19,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.Filter; //import com.bio.bio_backend.domain.common.service.AccessLogService; import com.bio.bio_backend.domain.user.member.service.MemberService; -import com.bio.bio_backend.global.constants.MemberConstants; import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler; import com.bio.bio_backend.global.exception.JwtAccessDeniedHandler; import com.bio.bio_backend.global.exception.JwtAuthenticationEntryPoint; @@ -38,7 +36,6 @@ public class WebSecurity { private final JwtUtils jwtUtils; // private final AccessLogService accessLogService; private final CorsFilter corsFilter; - private final ModelMapper mapper; private final ObjectMapper objectMapper; private final HttpUtils httpUtils; private final Environment env; @@ -49,7 +46,7 @@ public class WebSecurity { private JwtAuthenticationFilter getJwtAuthenticationFilter(AuthenticationManager authenticationManager) throws Exception { - JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager, memberService, mapper, jwtUtils, env); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager, memberService, jwtUtils, env); filter.setFilterProcessesUrl("/login"); // 로그인 EndPoint filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper)); return filter; diff --git a/src/main/java/com/bio/bio_backend/global/utils/ApiResponseCode.java b/src/main/java/com/bio/bio_backend/global/utils/ApiResponseCode.java index 4ca0e4c..e819ef6 100644 --- a/src/main/java/com/bio/bio_backend/global/utils/ApiResponseCode.java +++ b/src/main/java/com/bio/bio_backend/global/utils/ApiResponseCode.java @@ -28,6 +28,9 @@ public enum ApiResponseCode { AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED.value(), "Password is invalid"), + // 409 Conflict + USER_ID_DUPLICATE(HttpStatus.CONFLICT.value(), "User ID already exists"), + /*auth*/ // 401 Unauthorized JWT_SIGNATURE_MISMATCH(HttpStatus.UNAUTHORIZED.value(), "JWT signature does not match. Authentication failed"), diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d07b4c6..cfa157e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -35,6 +35,9 @@ logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.orm.jdbc.bind=TRACE logging.level.org.springframework.data.jpa=DEBUG +# Open Session in View 설정 (권장: false) +spring.jpa.open-in-view=false + # P6Spy decorator.datasource.p6spy.enable-logging=true decorator.datasource.p6spy.log-format=%(sqlSingleLine)