kmbin92_2025081101 #1
25
build.gradle
25
build.gradle
@@ -1,3 +1,9 @@
|
||||
buildscript {
|
||||
ext {
|
||||
queryDslVersion = "5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.5.4'
|
||||
@@ -23,12 +29,6 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
queryDslVersion = "5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// 개발용 의존성 추가
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
@@ -36,6 +36,19 @@ dependencies {
|
||||
// PostgreSQL JDBC 드라이버
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
|
||||
// Spring Security 추가
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
|
||||
// Validation 추가
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
||||
// ModelMapper 추가
|
||||
implementation 'org.modelmapper:modelmapper:3.1.1'
|
||||
|
||||
// MyBatis 추가
|
||||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
14
ddl/schema.sql
Normal file
14
ddl/schema.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
create table member (
|
||||
status varchar(1) not null,
|
||||
created_at timestamp(6) not null,
|
||||
last_login_at timestamp(6),
|
||||
oid bigint generated by default as identity,
|
||||
updated_at timestamp(6) not null,
|
||||
role varchar(40) not null,
|
||||
password varchar(100) not null,
|
||||
user_id varchar(100) not null,
|
||||
refresh_token varchar(200),
|
||||
primary key (oid),
|
||||
constraint uk_member_user_id unique (user_id)
|
||||
);
|
@@ -2,8 +2,10 @@ package com.bio.bio_backend;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableJpaAuditing
|
||||
public class BioBackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@@ -1,34 +0,0 @@
|
||||
package com.bio.bio_backend.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.bio.bio_backend.entity.Test;
|
||||
import com.bio.bio_backend.repository.TestRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/test")
|
||||
public class TestController {
|
||||
|
||||
private final TestRepository repository;
|
||||
|
||||
public TestController(TestRepository repository){
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<Test> getAllUsers(){
|
||||
System.out.println("test10099!!");
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Test creatTest(@RequestBody Test test){
|
||||
return repository.save(test);
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
package com.bio.bio_backend.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Test {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
private String email;
|
||||
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
package com.bio.bio_backend.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.bio.bio_backend.entity.Test;
|
||||
|
||||
public interface TestRepository extends JpaRepository<Test,Long>{
|
||||
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
package com.bio.bio_backend.domain.user.member.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MemberController {
|
||||
|
||||
private final MemberService memberService;
|
||||
private final ModelMapper mapper;
|
||||
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
|
||||
|
||||
|
||||
@PostMapping("/join")
|
||||
public ResponseEntity<CreateMemberResponseDto> createMember(@RequestBody @Valid CreateMemberRequestDTO requestDto) {
|
||||
|
||||
// RequestMember를 MemberDTO로 변환
|
||||
MemberDTO member = new MemberDTO();
|
||||
member.setId(requestDto.getUserId());
|
||||
member.setPw(requestDto.getPassword());
|
||||
|
||||
int oid = memberService.createMember(member);
|
||||
|
||||
// 생성된 회원 정보를 조회하여 응답
|
||||
MemberDTO createdMember = memberService.selectMember(oid);
|
||||
CreateMemberResponseDto responseDto = mapper.map(createdMember, CreateMemberResponseDto.class);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
|
||||
}
|
||||
|
||||
// @PostMapping("/member/list")
|
||||
// public ResponseEntity<List<ResponseMember>> getMemberList(@RequestBody(required = false) Map<String, String> params) {
|
||||
|
||||
// if(params == null){
|
||||
// params = new HashMap<>();
|
||||
// }
|
||||
|
||||
// Iterable<MemberDTO> memberList = memberService.selectMemberList(params);
|
||||
|
||||
// List<ResponseMember> result = new ArrayList<>();
|
||||
|
||||
// memberList.forEach(m -> {
|
||||
// result.add(new ModelMapper().map(m, ResponseMember.class));
|
||||
// });
|
||||
|
||||
// return ResponseEntity.status(HttpStatus.OK).body(result);
|
||||
// }
|
||||
|
||||
// @GetMapping("/member/{seq}")
|
||||
// public ResponseEntity<ResponseMember> selectMember(@PathVariable("seq") int seq) {
|
||||
|
||||
// MemberDTO member = memberService.selectMember(seq);
|
||||
|
||||
// ResponseMember responseMember = mapper.map(member, ResponseMember.class);
|
||||
|
||||
// return ResponseEntity.status(HttpStatus.OK).body(responseMember);
|
||||
// }
|
||||
|
||||
// @PutMapping("/member")
|
||||
// public CustomApiResponse<Void> updateMember(@RequestBody @Valid CreateMemberRequestDTO requestMember, @AuthenticationPrincipal MemberDTO registrant) {
|
||||
// // 현재 JWT는 사용자 id 값을 통하여 생성, 회원정보 변경 시 JWT 재발급 여부 검토
|
||||
|
||||
// MemberDTO member = mapper.map(requestMember, MemberDTO.class);
|
||||
|
||||
// if (requestMember.getPassword() != null) {
|
||||
// member.setPw(bCryptPasswordEncoder.encode(requestMember.getPassword()));
|
||||
// }
|
||||
|
||||
// member.setRegSeq(registrant.getSeq());
|
||||
// memberService.updateMember(member);
|
||||
|
||||
// return CustomApiResponse.success(ApiResponseCode.USER_INFO_CHANGE, null);
|
||||
// }
|
||||
|
||||
// @DeleteMapping("/member")
|
||||
// public CustomApiResponse<Void> deleteMember(@RequestBody @Valid CreateMemberRequestDTO requestMember){
|
||||
|
||||
// MemberDTO member = mapper.map(requestMember, MemberDTO.class);
|
||||
|
||||
// memberService.deleteMember(member);
|
||||
|
||||
// return CustomApiResponse.success(ApiResponseCode.USER_DELETE_SUCCESSFUL, null);
|
||||
// }
|
||||
|
||||
// @PostMapping("/logout")
|
||||
// public CustomApiResponse<Void> logout(@AuthenticationPrincipal MemberDTO member) {
|
||||
|
||||
// String id = member.getId();
|
||||
|
||||
// try {
|
||||
// memberService.deleteRefreshToken(id);
|
||||
// } catch (Exception e) {
|
||||
// return CustomApiResponse.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null);
|
||||
// }
|
||||
|
||||
// return CustomApiResponse.success(ApiResponseCode.LOGOUT_SUCCESSFUL, null);
|
||||
// }
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.bio.bio_backend.domain.user.member.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class CreateMemberRequestDTO {
|
||||
|
||||
@NotBlank(message = "사용자 ID는 필수입니다")
|
||||
private String userId;
|
||||
|
||||
@NotBlank(message = "비밀번호는 필수입니다")
|
||||
private String password;
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package com.bio.bio_backend.domain.user.member.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class CreateMemberResponseDto {
|
||||
private String id;
|
||||
private String pw;
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
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 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;
|
||||
|
||||
@Data
|
||||
/**
|
||||
* 회원
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
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.global.entity.BaseEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Getter @Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Table(
|
||||
name = "member",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_member_user_id", columnNames = "user_id")
|
||||
}
|
||||
)
|
||||
public class Member extends BaseEntity {
|
||||
|
||||
@Column(name = "user_id", nullable = false, length = 100)
|
||||
private String userId;
|
||||
|
||||
@Column(name = "password", nullable = false, length = 100)
|
||||
private String password;
|
||||
|
||||
@Column(name = "role", nullable = false, length = 40)
|
||||
private String role;
|
||||
|
||||
@Column(name = "status", nullable = false, length = 1)
|
||||
private String status;
|
||||
|
||||
@Column(name = "refresh_token", length = 200)
|
||||
private String refreshToken;
|
||||
|
||||
@Column(name = "last_login_at")
|
||||
private LocalDateTime lastLoginAt;
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
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.MemberDTO;
|
||||
|
||||
@Mapper
|
||||
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(int seq);
|
||||
|
||||
int updateMember(MemberDTO member);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.bio.bio_backend.domain.user.member.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.bio.bio_backend.domain.user.member.entity.Member;
|
||||
|
||||
@Repository
|
||||
public interface MemberRepository extends JpaRepository<Member, Long> {
|
||||
|
||||
// 사용자 ID로 회원 조회
|
||||
Member findByUserId(String userId);
|
||||
|
||||
// 사용자 ID 존재 여부 확인
|
||||
boolean existsByUserId(String userId);
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package com.bio.bio_backend.domain.user.member.repository;
|
||||
|
||||
import com.bio.bio_backend.domain.user.member.entity.Member;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* QueryDSL을 활용한 커스텀 쿼리 메서드들을 정의하는 인터페이스
|
||||
* 복잡한 쿼리나 동적 쿼리가 필요한 경우 이 인터페이스를 구현하여 사용합니다.
|
||||
*/
|
||||
public interface MemberRepositoryCustom {
|
||||
|
||||
/**
|
||||
* 사용자 ID로 회원을 조회합니다.
|
||||
* QueryDSL을 사용하여 더 유연한 쿼리 작성이 가능합니다.
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return Optional<Member> 회원 정보 (없으면 empty)
|
||||
*/
|
||||
Optional<Member> findByUserIdCustom(String userId);
|
||||
|
||||
/**
|
||||
* 역할(Role)별로 회원 목록을 조회합니다.
|
||||
*
|
||||
* @param role 회원 역할
|
||||
* @return List<Member> 해당 역할을 가진 회원 목록
|
||||
*/
|
||||
List<Member> findByRole(String role);
|
||||
|
||||
/**
|
||||
* 상태(Status)별로 회원 목록을 조회합니다.
|
||||
*
|
||||
* @param status 회원 상태
|
||||
* @return List<Member> 해당 상태를 가진 회원 목록
|
||||
*/
|
||||
List<Member> findByStatus(String status);
|
||||
|
||||
/**
|
||||
* 사용자 ID와 상태로 회원을 조회합니다.
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @param status 회원 상태
|
||||
* @return Optional<Member> 회원 정보
|
||||
*/
|
||||
Optional<Member> findByUserIdAndStatus(String userId, String status);
|
||||
|
||||
/**
|
||||
* 검색 조건에 따른 회원 목록을 페이징하여 조회합니다.
|
||||
*
|
||||
* @param userId 사용자 ID (부분 검색)
|
||||
* @param role 회원 역할
|
||||
* @param status 회원 상태
|
||||
* @param pageable 페이징 정보
|
||||
* @return Page<Member> 페이징된 회원 목록
|
||||
*/
|
||||
Page<Member> findMembersByCondition(String userId, String role, String status, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 마지막 로그인 시간이 특정 시간 이후인 회원들을 조회합니다.
|
||||
*
|
||||
* @param lastLoginAfter 마지막 로그인 기준 시간
|
||||
* @return List<Member> 해당 조건을 만족하는 회원 목록
|
||||
*/
|
||||
List<Member> findActiveMembersByLastLogin(java.time.LocalDateTime lastLoginAfter);
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
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.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* QueryDSL을 활용하여 MemberRepositoryCustom 인터페이스를 구현하는 클래스
|
||||
* 복잡한 쿼리나 동적 쿼리를 QueryDSL로 작성하여 성능과 가독성을 향상시킵니다.
|
||||
*/
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class MemberRepositoryImpl implements MemberRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
|
||||
/**
|
||||
* QMember 인스턴스를 생성하여 쿼리에서 사용합니다.
|
||||
* QueryDSL의 Q클래스를 통해 타입 안전한 쿼리 작성이 가능합니다.
|
||||
*/
|
||||
private final QMember member = QMember.member;
|
||||
|
||||
@Override
|
||||
public Optional<Member> findByUserIdCustom(String userId) {
|
||||
// QueryDSL을 사용하여 사용자 ID로 회원을 조회합니다.
|
||||
// eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다.
|
||||
Member foundMember = queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.userId.eq(userId))
|
||||
.fetchOne();
|
||||
|
||||
return Optional.ofNullable(foundMember);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Member> findByRole(String role) {
|
||||
// 역할별로 회원을 조회합니다.
|
||||
// eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다.
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.role.eq(role))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Member> findByStatus(String status) {
|
||||
// 상태별로 회원을 조회합니다.
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.status.eq(status))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Member> findByUserIdAndStatus(String userId, String status) {
|
||||
// 사용자 ID와 상태를 모두 만족하는 회원을 조회합니다.
|
||||
// and() 메서드를 사용하여 여러 조건을 결합합니다.
|
||||
Member foundMember = queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.userId.eq(userId)
|
||||
.and(member.status.eq(status)))
|
||||
.fetchOne();
|
||||
|
||||
return Optional.ofNullable(foundMember);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Member> findMembersByCondition(String userId, String role, String status, Pageable pageable) {
|
||||
// BooleanBuilder를 사용하여 동적 쿼리를 구성합니다.
|
||||
// null이 아닌 조건만 쿼리에 포함시킵니다.
|
||||
BooleanBuilder builder = new BooleanBuilder();
|
||||
|
||||
// 사용자 ID가 제공된 경우 부분 검색 조건을 추가합니다.
|
||||
if (userId != null && !userId.trim().isEmpty()) {
|
||||
builder.and(member.userId.containsIgnoreCase(userId));
|
||||
}
|
||||
|
||||
// 역할이 제공된 경우 정확한 일치 조건을 추가합니다.
|
||||
if (role != null && !role.trim().isEmpty()) {
|
||||
builder.and(member.role.eq(role));
|
||||
}
|
||||
|
||||
// 상태가 제공된 경우 정확한 일치 조건을 추가합니다.
|
||||
if (status != null && !status.trim().isEmpty()) {
|
||||
builder.and(member.status.eq(status));
|
||||
}
|
||||
|
||||
// 전체 개수를 조회합니다.
|
||||
long total = queryFactory
|
||||
.selectFrom(member)
|
||||
.where(builder)
|
||||
.fetchCount();
|
||||
|
||||
// 페이징 조건을 적용하여 결과를 조회합니다.
|
||||
List<Member> content = queryFactory
|
||||
.selectFrom(member)
|
||||
.where(builder)
|
||||
.orderBy(member.createdAt.desc()) // 생성일 기준 내림차순 정렬
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.fetch();
|
||||
|
||||
// Page 객체를 생성하여 반환합니다.
|
||||
return new PageImpl<>(content, pageable, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Member> findActiveMembersByLastLogin(LocalDateTime lastLoginAfter) {
|
||||
// 마지막 로그인 시간이 특정 시간 이후인 활성 회원들을 조회합니다.
|
||||
// 여러 조건을 조합하여 복잡한 쿼리를 작성합니다.
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.status.eq("A") // 활성 상태
|
||||
.and(member.lastLoginAt.isNotNull()) // 마지막 로그인 시간이 존재
|
||||
.and(member.lastLoginAt.after(lastLoginAfter))) // 특정 시간 이후
|
||||
.orderBy(member.lastLoginAt.desc()) // 마지막 로그인 시간 기준 내림차순 정렬
|
||||
.fetch();
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package com.bio.bio_backend.domain.user.member.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
|
||||
|
||||
public interface MemberService extends UserDetailsService {
|
||||
|
||||
UserDetails loadUserByUsername(String id);
|
||||
|
||||
int createMember(MemberDTO memberDTO);
|
||||
|
||||
void updateRefreshToken(MemberDTO memberDTO);
|
||||
|
||||
String getRefreshToken(String id);
|
||||
|
||||
int deleteRefreshToken(String id);
|
||||
|
||||
List<MemberDTO> selectMemberList(Map<String, String> params);
|
||||
|
||||
MemberDTO selectMember(int seq);
|
||||
|
||||
int updateMember(MemberDTO member);
|
||||
|
||||
int deleteMember(MemberDTO member);
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
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.mapper.MemberMapper;
|
||||
import com.bio.bio_backend.domain.user.member.repository.MemberRepository;
|
||||
import com.bio.bio_backend.global.constants.MemberConstants;
|
||||
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 java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MemberServiceImpl implements MemberService {
|
||||
|
||||
private final MemberMapper memberMapper;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createMember(MemberDTO memberDTO) {
|
||||
// JPA Entity를 사용하여 회원 생성
|
||||
Member member = Member.builder()
|
||||
.userId(memberDTO.getId())
|
||||
.password(bCryptPasswordEncoder.encode(memberDTO.getPw()))
|
||||
.role(MemberConstants.ROLE_MEMBER)
|
||||
.status(MemberConstants.MEMBER_ACTIVE)
|
||||
.build();
|
||||
|
||||
// JPA 레파지토리를 통해 저장
|
||||
Member savedMember = memberRepository.save(member);
|
||||
|
||||
// 저장된 회원의 oid를 반환
|
||||
return savedMember.getOid().intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRefreshToken(MemberDTO memberDTO) {
|
||||
memberMapper.updateRefreshToken(memberDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefreshToken(String id) {
|
||||
return memberMapper.getRefreshToken(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteRefreshToken(String id) {
|
||||
return memberMapper.deleteRefreshToken(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MemberDTO> selectMemberList(Map<String, String> params) {
|
||||
return memberMapper.selectMemberList(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemberDTO selectMember(int seq) {
|
||||
return memberMapper.selectMemberBySeq(seq);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateMember(MemberDTO member) {
|
||||
return memberMapper.updateMember(member);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteMember(MemberDTO member) {
|
||||
|
||||
member.setStatus(MemberConstants.MEMBER_INACTIVE);
|
||||
|
||||
log.info(member.toString());
|
||||
|
||||
return memberMapper.updateMember(member);
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.qsl.qsl_tutorial.base;
|
||||
package com.bio.bio_backend.global.aop;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
@@ -6,24 +6,51 @@ import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
/**
|
||||
* Repository 계층의 메서드 호출을 로깅하는 AOP(Aspect-Oriented Programming) 클래스
|
||||
* 모든 Repository 인터페이스의 메서드 호출 시점과 실행 시간을 로그로 기록합니다.
|
||||
*/
|
||||
@Aspect // AOP 기능을 활성화하는 어노테이션
|
||||
@Component // Spring Bean으로 등록하는 어노테이션
|
||||
@Slf4j // Lombok의 로깅 기능을 제공하는 어노테이션
|
||||
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 {
|
||||
// 메서드 실행 시작 시간을 기록
|
||||
long t0 = System.currentTimeMillis();
|
||||
|
||||
// 실행될 메서드의 클래스명과 메서드명을 추출
|
||||
String type = pjp.getSignature().getDeclaringTypeName();
|
||||
String method = pjp.getSignature().getName();
|
||||
|
||||
// 메서드 호출 시 전달되는 매개변수들을 추출
|
||||
Object[] args = pjp.getArgs();
|
||||
|
||||
// 메서드 호출 시작을 로그로 기록
|
||||
log.info("[QUERY CALL] {}.{}(args={})", type, method, java.util.Arrays.toString(args));
|
||||
|
||||
try {
|
||||
// 원본 메서드를 실행
|
||||
Object result = pjp.proceed();
|
||||
|
||||
// 메서드 실행 완료를 로그로 기록 (실행 시간 포함)
|
||||
log.info("[QUERY DONE] {}.{}() in {}ms", type, method, (System.currentTimeMillis() - t0));
|
||||
|
||||
// 원본 메서드의 결과를 반환
|
||||
return result;
|
||||
} catch (Throwable ex) {
|
||||
// 메서드 실행 중 예외 발생 시 로그로 기록
|
||||
log.warn("[QUERY FAIL] {}.{}() -> {}", type, method, ex.toString());
|
||||
|
||||
// 예외를 다시 던져서 원래의 예외 처리 흐름을 유지
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,14 @@
|
||||
package com.qsl.qsl_tutorial.base;
|
||||
package com.bio.bio_backend.global.config;
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Bean
|
||||
public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package com.bio.bio_backend.global.config;
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* QueryDSL 설정을 위한 Configuration 클래스
|
||||
* JPAQueryFactory Bean을 등록하여 QueryDSL을 사용할 수 있도록 합니다.
|
||||
*/
|
||||
@Configuration
|
||||
public class QuerydslConfig {
|
||||
|
||||
/**
|
||||
* JPA EntityManager를 주입받습니다.
|
||||
* @PersistenceContext 어노테이션을 사용하여 Spring이 관리하는 EntityManager를 주입받습니다.
|
||||
*/
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
/**
|
||||
* JPAQueryFactory Bean을 생성하여 등록합니다.
|
||||
* 이 Bean은 QueryDSL을 사용하는 Repository에서 주입받아 사용됩니다.
|
||||
*
|
||||
* @return JPAQueryFactory 인스턴스
|
||||
*/
|
||||
@Bean
|
||||
public JPAQueryFactory jpaQueryFactory() {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
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; // 비밀번호 최대 길이
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package com.bio.bio_backend.global.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 모든 엔티티가 상속받는 기본 엔티티 클래스
|
||||
* 공통 필드들을 정의하고 JPA Auditing을 지원합니다.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public abstract class BaseEntity {
|
||||
|
||||
/**
|
||||
* 엔티티의 고유 식별자 (Primary Key)
|
||||
* 자동 증가하는 Long 타입으로 설정
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@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;
|
||||
|
||||
/**
|
||||
* 엔티티 저장 전에 실행되는 메서드
|
||||
* 생성 시간과 수정 시간을 자동으로 설정
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
this.createdAt = now;
|
||||
this.updatedAt = now;
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티 수정 전에 실행되는 메서드
|
||||
* 수정 시간을 자동으로 설정
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user