[회원 관리 기능 개선 및 MapStruct 도입] - 회원 엔티티 및 DTO 구조 변경, MapStruct를 통한 변환 로직 추가, 사용자 ID 중복 체크 기능 구현, README 업데이트, Gradle 의존성 수정
This commit is contained in:
56
README.md
56
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` (성능 최적화)
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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),
|
||||
|
@@ -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<String> createMember1() {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body("test");
|
||||
}
|
||||
@PostMapping("/members")
|
||||
public ResponseEntity<CreateMemberResponseDto> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) {
|
||||
MemberDto member = memberMapper.toMemberDto(requestDto);
|
||||
MemberDto createdMember = memberService.createMember(member);
|
||||
CreateMemberResponseDto responseDto = memberMapper.toCreateMemberResponseDto(createdMember);
|
||||
|
||||
@PostMapping("/join")
|
||||
public ResponseEntity<Long> 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")
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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<? extends GrantedAuthority> getAuthorities() {
|
||||
|
||||
Set<GrantedAuthority> 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<? extends GrantedAuthority> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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<MemberDto> selectMemberList(Map<String, String> 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<MemberDto> toMemberDtoList(List<Member> members);
|
||||
|
||||
/**
|
||||
* MemberDto를 CreateMemberResponseDto로 변환
|
||||
*/
|
||||
CreateMemberResponseDto toCreateMemberResponseDto(MemberDto memberDto);
|
||||
}
|
||||
|
@@ -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<Member, Long> {
|
||||
|
||||
// 사용자 ID로 회원 조회
|
||||
Member findByUserId(String userId);
|
||||
// 사용자 ID로 회원 조회 (Optional 반환)
|
||||
Optional<Member> findByUserId(String userId);
|
||||
|
||||
// 사용자 ID 존재 여부 확인
|
||||
boolean existsByUserId(String userId);
|
||||
|
@@ -31,32 +31,32 @@ public interface MemberRepositoryCustom {
|
||||
List<Member> findByRole(String role);
|
||||
|
||||
/**
|
||||
* 상태(Status)별로 회원 목록을 조회합니다.
|
||||
* 사용 여부별로 회원 목록을 조회합니다.
|
||||
*
|
||||
* @param status 회원 상태
|
||||
* @return List<Member> 해당 상태를 가진 회원 목록
|
||||
* @param useFlag 사용 여부
|
||||
* @return List<Member> 해당 사용 여부를 가진 회원 목록
|
||||
*/
|
||||
List<Member> findByStatus(String status);
|
||||
List<Member> findByUseFlag(Boolean useFlag);
|
||||
|
||||
/**
|
||||
* 사용자 ID와 상태로 회원을 조회합니다.
|
||||
* 사용자 ID와 사용 여부로 회원을 조회합니다.
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @param status 회원 상태
|
||||
* @param useFlag 사용 여부
|
||||
* @return Optional<Member> 회원 정보
|
||||
*/
|
||||
Optional<Member> findByUserIdAndStatus(String userId, String status);
|
||||
Optional<Member> findByUserIdAndUseFlag(String userId, Boolean useFlag);
|
||||
|
||||
/**
|
||||
* 검색 조건에 따른 회원 목록을 페이징하여 조회합니다.
|
||||
*
|
||||
* @param userId 사용자 ID (부분 검색)
|
||||
* @param role 회원 역할
|
||||
* @param status 회원 상태
|
||||
* @param useFlag 사용 여부
|
||||
* @param pageable 페이징 정보
|
||||
* @return Page<Member> 페이징된 회원 목록
|
||||
*/
|
||||
Page<Member> findMembersByCondition(String userId, String role, String status, Pageable pageable);
|
||||
Page<Member> findMembersByCondition(String userId, String role, Boolean useFlag, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 마지막 로그인 시간이 특정 시간 이후인 회원들을 조회합니다.
|
||||
|
@@ -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<Member> 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<Member> findByStatus(String status) {
|
||||
// 상태별로 회원을 조회합니다.
|
||||
public List<Member> findByUseFlag(Boolean useFlag) {
|
||||
// 사용 여부별로 회원을 조회합니다.
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.status.eq(status))
|
||||
.where(member.useFlag.eq(useFlag))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Member> findByUserIdAndStatus(String userId, String status) {
|
||||
// 사용자 ID와 상태를 모두 만족하는 회원을 조회합니다.
|
||||
public Optional<Member> 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<Member> findMembersByCondition(String userId, String role, String status, Pageable pageable) {
|
||||
public Page<Member> 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()) // 마지막 로그인 시간 기준 내림차순 정렬
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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<MemberDto> selectMemberList(Map<String, String> params) {
|
||||
return memberMapper.selectMemberList(params);
|
||||
// JPA를 사용하여 회원 목록 조회 (간단한 구현)
|
||||
List<Member> 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 반환
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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; // 비밀번호 최대 길이
|
||||
}
|
@@ -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();
|
||||
|
@@ -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<Void> handleExpiredJwtException(JsonProcessingException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JSON_PROCESSING_EXCEPTION, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(UserDuplicateException.class)
|
||||
public CustomApiResponse<Void> handleUserDuplicateException(UserDuplicateException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.USER_ID_DUPLICATE, null);
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
|
@@ -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;
|
||||
|
@@ -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"),
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user