[회원 목록 조회 기능 개선] 회원 목록 조회 API에 페이지네이션 기능을 추가하고, 검색 조건을 위한 DTO 및 관련 메서드를 구현하여 효율적인 데이터 조회를 지원. README.md에 DTO 네이밍 규칙을 추가하여 코드 일관성을 강화.
This commit is contained in:
		
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							@@ -353,7 +353,33 @@ public class Member extends BaseEntity {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 11. 데이터베이스 스키마
 | 
			
		||||
### 11. DTO 네이밍 규칙
 | 
			
		||||
 | 
			
		||||
#### 기본 원칙
 | 
			
		||||
 | 
			
		||||
- **API 계층**: `Dto` 접미사 유지
 | 
			
		||||
- **Service 계층**: 역할에 따라 `Dto` 접미사 결정
 | 
			
		||||
 | 
			
		||||
#### 사용 예시
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
// API 계층 (Dto 유지)
 | 
			
		||||
CreateMemberRequestDto, GetMemberResponseDto
 | 
			
		||||
 | 
			
		||||
// Service 계층 - 비즈니스 핵심 (Dto 유지)
 | 
			
		||||
MemberDto
 | 
			
		||||
 | 
			
		||||
// Service 계층 - 내부 전달 (Dto 제거)
 | 
			
		||||
MemberSearchCondition
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 핵심 규칙
 | 
			
		||||
 | 
			
		||||
- **API 노출**: `Dto` 유지
 | 
			
		||||
- **비즈니스 핵심**: `Dto` 유지
 | 
			
		||||
- **내부 전달**: `Dto` 제거
 | 
			
		||||
 | 
			
		||||
### 12. 데이터베이스 스키마
 | 
			
		||||
 | 
			
		||||
**데이터베이스 테이블 구조는 `ddl/schema_entity.sql`에 정의되어 있습니다.**
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,11 @@ import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberSearchCondition;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMembersRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMembersPagedRequestDto;
 | 
			
		||||
import com.bio.bio_backend.global.dto.PagedResult;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.mapper.MemberMapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -26,7 +31,7 @@ import com.bio.bio_backend.global.utils.SecurityUtils;
 | 
			
		||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -60,21 +65,33 @@ public class MemberController {
 | 
			
		||||
    @LogExecution("회원 목록 조회")
 | 
			
		||||
    @Operation(summary = "회원 목록 조회", description = "활성화된 모든 회원의 목록을 조회합니다.")
 | 
			
		||||
    @ApiResponses({
 | 
			
		||||
        @ApiResponse(responseCode = "200", description = "회원 목록 조회 성공")
 | 
			
		||||
        @ApiResponse(responseCode = "200", description = "회원 목록 조회 성공"),
 | 
			
		||||
        @ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
 | 
			
		||||
    })
 | 
			
		||||
    @GetMapping
 | 
			
		||||
    public ResponseEntity<ApiResponseDto<List<GetMemberResponseDto>>> getMembers() {
 | 
			
		||||
        try {
 | 
			
		||||
            List<GetMemberResponseDto> members = memberService.selectMemberListForDisplay(new HashMap<>());
 | 
			
		||||
    public ResponseEntity<ApiResponseDto<List<GetMemberResponseDto>>> getMembers(@ModelAttribute GetMembersRequestDto requestDto) {
 | 
			
		||||
        MemberSearchCondition condition = memberMapper.toSearchCondition(requestDto);
 | 
			
		||||
        List<GetMemberResponseDto> members = memberService.getMembers(condition);
 | 
			
		||||
        ApiResponseDto<List<GetMemberResponseDto>> apiResponse = ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS, members);
 | 
			
		||||
            log.info("전체 회원 목록 조회 완료: {}명", members.size());
 | 
			
		||||
        log.info("회원 목록 조회 완료: {}명", members.size());
 | 
			
		||||
 | 
			
		||||
        return ResponseEntity.ok(apiResponse);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("회원 목록 조회 중 오류 발생: {}", e.getMessage());
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
 | 
			
		||||
                    .body(ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @LogExecution("회원 목록 조회 (페이지네이션)")
 | 
			
		||||
    @Operation(summary = "회원 목록 조회 (페이지네이션)", description = "페이지네이션을 적용한 회원 목록을 조회합니다.")
 | 
			
		||||
    @ApiResponses({
 | 
			
		||||
        @ApiResponse(responseCode = "200", description = "회원 목록 조회 성공"),
 | 
			
		||||
        @ApiResponse(responseCode = "400", description = "잘못된 요청 파라미터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
 | 
			
		||||
    })
 | 
			
		||||
    @GetMapping("/paged")
 | 
			
		||||
    public ResponseEntity<ApiResponseDto<PagedResult<GetMemberResponseDto>>> getMembersPaged(@ModelAttribute GetMembersPagedRequestDto requestDto) {
 | 
			
		||||
        MemberSearchCondition condition = memberMapper.toSearchCondition(requestDto);
 | 
			
		||||
        PagedResult<GetMemberResponseDto> pagedMembers = memberService.getMembersPaged(condition, requestDto.getPage(), requestDto.getSize());
 | 
			
		||||
        ApiResponseDto<PagedResult<GetMemberResponseDto>> apiResponse = ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS, pagedMembers);
 | 
			
		||||
        log.info("페이지네이션된 회원 목록 조회 완료: 페이지={}, 크기={}, 전체={}명", requestDto.getPage(), requestDto.getSize(), pagedMembers.getTotalElements());
 | 
			
		||||
 | 
			
		||||
        return ResponseEntity.ok(apiResponse);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @LogExecution("로그아웃")
 | 
			
		||||
@@ -84,7 +101,6 @@ public class MemberController {
 | 
			
		||||
    })
 | 
			
		||||
    @PostMapping("/logout")
 | 
			
		||||
    public ResponseEntity<ApiResponseDto<Void>> logout(HttpServletResponse response) {
 | 
			
		||||
        try {
 | 
			
		||||
        String userId = SecurityUtils.getCurrentUserId();
 | 
			
		||||
        memberService.deleteRefreshToken(userId);
 | 
			
		||||
        // 모든 토큰 쿠키 삭제
 | 
			
		||||
@@ -92,10 +108,5 @@ public class MemberController {
 | 
			
		||||
        log.info("사용자 로그아웃 완료: {}", userId);
 | 
			
		||||
 | 
			
		||||
        return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("로그아웃 처리 중 오류 발생: {}", e.getMessage());
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
 | 
			
		||||
                    .body(ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.global.dto.BasePagedRequestDto;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 회원 목록 조회 요청용 DTO (검색 조건 + 페이지네이션 포함)
 | 
			
		||||
 * GET /members/paged
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class GetMembersPagedRequestDto extends BasePagedRequestDto {
 | 
			
		||||
    
 | 
			
		||||
    private String userId;
 | 
			
		||||
    private String name;
 | 
			
		||||
    private String email;
 | 
			
		||||
    private String searchKeyword;
 | 
			
		||||
    private String createdDateFrom;
 | 
			
		||||
    private String createdDateTo;
 | 
			
		||||
    private String lastLoginFrom;
 | 
			
		||||
    private String lastLoginTo;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 회원 목록 조회 요청용 DTO (검색 조건만 포함)
 | 
			
		||||
 * GET /members
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
public class GetMembersRequestDto {
 | 
			
		||||
    
 | 
			
		||||
    private String userId;
 | 
			
		||||
    private String name;
 | 
			
		||||
    private String email;
 | 
			
		||||
    private String searchKeyword;
 | 
			
		||||
    private String createdDateFrom;
 | 
			
		||||
    private String createdDateTo;
 | 
			
		||||
    private String lastLoginFrom;
 | 
			
		||||
    private String lastLoginTo;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 회원 검색 조건
 | 
			
		||||
 */
 | 
			
		||||
@Builder
 | 
			
		||||
@Getter
 | 
			
		||||
@Setter
 | 
			
		||||
public class MemberSearchCondition {
 | 
			
		||||
    
 | 
			
		||||
    private String userId;
 | 
			
		||||
    private String name;
 | 
			
		||||
    private String email;
 | 
			
		||||
    private String searchKeyword;
 | 
			
		||||
    private String createdDateFrom;
 | 
			
		||||
    private String createdDateTo;
 | 
			
		||||
    private String lastLoginFrom;
 | 
			
		||||
    private String lastLoginTo;
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,9 @@ import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.LoginResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberSearchCondition;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMembersRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMembersPagedRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.global.annotation.IgnoreBaseEntityMapping;
 | 
			
		||||
@@ -68,4 +71,14 @@ public interface MemberMapper {
 | 
			
		||||
     * Member 엔티티 리스트를 GetMemberResponseDto 리스트로 변환 (민감한 정보 제외)
 | 
			
		||||
     */
 | 
			
		||||
    List<GetMemberResponseDto> toGetMemberResponseDtoList(List<Member> members);
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * GetMembersRequestDto를 MemberSearchCondition으로 변환
 | 
			
		||||
     */
 | 
			
		||||
    MemberSearchCondition toSearchCondition(GetMembersRequestDto request);
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * GetMembersPagedRequestDto를 MemberSearchCondition으로 변환
 | 
			
		||||
     */
 | 
			
		||||
    MemberSearchCondition toSearchCondition(GetMembersPagedRequestDto request);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.repository;
 | 
			
		||||
 | 
			
		||||
import org.springframework.data.domain.Page;
 | 
			
		||||
import org.springframework.data.domain.Pageable;
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
@@ -14,4 +16,7 @@ public interface MemberRepository extends JpaRepository<Member, Long>, MemberRep
 | 
			
		||||
    
 | 
			
		||||
    // 활성화된 회원 목록 조회
 | 
			
		||||
    List<Member> findByUseFlagTrue();
 | 
			
		||||
    
 | 
			
		||||
    // 활성화된 회원 목록 조회 (페이지네이션)
 | 
			
		||||
    Page<Member> findByUseFlagTrue(Pageable pageable);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.repository;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberSearchCondition;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import org.springframework.data.domain.Page;
 | 
			
		||||
import org.springframework.data.domain.Pageable;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
public interface MemberRepositoryCustom {
 | 
			
		||||
@@ -12,4 +16,21 @@ public interface MemberRepositoryCustom {
 | 
			
		||||
     * @return Optional<Member> 회원 정보 (없으면 empty)
 | 
			
		||||
     */
 | 
			
		||||
    Optional<Member> findActiveMemberByUserId(String userId);
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * QueryDSL을 사용한 회원 목록 조회 (검색 조건 + 페이징)
 | 
			
		||||
     *
 | 
			
		||||
     * @param condition 검색 조건
 | 
			
		||||
     * @param pageable 페이징 정보
 | 
			
		||||
     * @return Page<Member> 검색된 회원 목록
 | 
			
		||||
     */
 | 
			
		||||
    Page<Member> findMembers(MemberSearchCondition condition, Pageable pageable);
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * QueryDSL을 사용한 회원 목록 조회 (검색 조건)
 | 
			
		||||
     *
 | 
			
		||||
     * @param condition 검색 조건
 | 
			
		||||
     * @return List<Member> 검색된 회원 목록
 | 
			
		||||
     */
 | 
			
		||||
    List<Member> findMembers(MemberSearchCondition condition);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,20 @@
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.repository;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberSearchCondition;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.QMember;
 | 
			
		||||
import com.querydsl.core.BooleanBuilder;
 | 
			
		||||
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 org.springframework.util.StringUtils;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -30,4 +39,91 @@ public class MemberRepositoryImpl implements MemberRepositoryCustom {
 | 
			
		||||
 | 
			
		||||
        return Optional.ofNullable(foundMember);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Page<Member> findMembers(MemberSearchCondition condition, Pageable pageable) {
 | 
			
		||||
        BooleanBuilder builder = buildSearchConditions(condition);
 | 
			
		||||
        
 | 
			
		||||
        List<Member> members = queryFactory
 | 
			
		||||
                .selectFrom(member)
 | 
			
		||||
                .where(builder)
 | 
			
		||||
                .orderBy(member.createdAt.desc())
 | 
			
		||||
                .offset(pageable.getOffset())
 | 
			
		||||
                .limit(pageable.getPageSize())
 | 
			
		||||
                .fetch();
 | 
			
		||||
        
 | 
			
		||||
        Long total = queryFactory
 | 
			
		||||
                .select(member.count())
 | 
			
		||||
                .from(member)
 | 
			
		||||
                .where(builder)
 | 
			
		||||
                .fetchOne();
 | 
			
		||||
        
 | 
			
		||||
        return new PageImpl<>(members, pageable, total);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Member> findMembers(MemberSearchCondition condition) {
 | 
			
		||||
        return queryFactory
 | 
			
		||||
                .selectFrom(member)
 | 
			
		||||
                .where(buildSearchConditions(condition))
 | 
			
		||||
                .orderBy(member.createdAt.desc())
 | 
			
		||||
                .fetch();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 검색 조건들을 조합하여 BooleanBuilder 생성
 | 
			
		||||
     */
 | 
			
		||||
    private BooleanBuilder buildSearchConditions(MemberSearchCondition condition) {
 | 
			
		||||
        BooleanBuilder builder = new BooleanBuilder();
 | 
			
		||||
        
 | 
			
		||||
        // 기본 조건: useFlag = true (활성화된 회원만 조회)
 | 
			
		||||
        builder.and(member.useFlag.eq(true));
 | 
			
		||||
        
 | 
			
		||||
        // 사용자 ID (정확 일치)
 | 
			
		||||
        if (StringUtils.hasText(condition.getUserId())) {
 | 
			
		||||
            builder.and(member.userId.eq(condition.getUserId()));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 이름 (부분 일치)
 | 
			
		||||
        if (StringUtils.hasText(condition.getName())) {
 | 
			
		||||
            builder.and(member.name.containsIgnoreCase(condition.getName()));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 이메일 (부분 일치)
 | 
			
		||||
        if (StringUtils.hasText(condition.getEmail())) {
 | 
			
		||||
            builder.and(member.email.containsIgnoreCase(condition.getEmail()));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 통합 검색 키워드 (이름, 이메일, 사용자ID에서 검색)
 | 
			
		||||
        if (StringUtils.hasText(condition.getSearchKeyword())) {
 | 
			
		||||
            String keyword = condition.getSearchKeyword();
 | 
			
		||||
            builder.and(member.name.containsIgnoreCase(keyword)
 | 
			
		||||
                    .or(member.email.containsIgnoreCase(keyword))
 | 
			
		||||
                    .or(member.userId.containsIgnoreCase(keyword)));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 생성일 범위
 | 
			
		||||
        if (StringUtils.hasText(condition.getCreatedDateFrom())) {
 | 
			
		||||
            LocalDate fromDate = LocalDate.parse(condition.getCreatedDateFrom(), DateTimeFormatter.ISO_LOCAL_DATE);
 | 
			
		||||
            builder.and(member.createdAt.goe(fromDate.atStartOfDay()));
 | 
			
		||||
        }
 | 
			
		||||
        if (StringUtils.hasText(condition.getCreatedDateTo())) {
 | 
			
		||||
            LocalDate toDate = LocalDate.parse(condition.getCreatedDateTo(), DateTimeFormatter.ISO_LOCAL_DATE);
 | 
			
		||||
            builder.and(member.createdAt.loe(toDate.atTime(23, 59, 59)));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 마지막 로그인 범위
 | 
			
		||||
        if (StringUtils.hasText(condition.getLastLoginFrom())) {
 | 
			
		||||
            LocalDate fromDate = LocalDate.parse(condition.getLastLoginFrom(), DateTimeFormatter.ISO_LOCAL_DATE);
 | 
			
		||||
            builder.and(member.lastLoginAt.goe(fromDate.atStartOfDay()));
 | 
			
		||||
        }
 | 
			
		||||
        if (StringUtils.hasText(condition.getLastLoginTo())) {
 | 
			
		||||
            LocalDate toDate = LocalDate.parse(condition.getLastLoginTo(), DateTimeFormatter.ISO_LOCAL_DATE);
 | 
			
		||||
            builder.and(member.lastLoginAt.loe(toDate.atTime(23, 59, 59)));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return builder;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,10 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberSearchCondition;
 | 
			
		||||
import com.bio.bio_backend.global.dto.PagedResult;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
public interface MemberService extends UserDetailsService {
 | 
			
		||||
 | 
			
		||||
@@ -21,12 +22,19 @@ public interface MemberService extends UserDetailsService {
 | 
			
		||||
 | 
			
		||||
    void updateMember(MemberDto member);
 | 
			
		||||
    
 | 
			
		||||
    List<MemberDto> selectMemberList(Map<String, String> params);
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 회원 목록 조회 (민감한 정보 제외)
 | 
			
		||||
     * @param params 검색 파라미터
 | 
			
		||||
     * 검색 조건에 따른 회원 목록 조회
 | 
			
		||||
     * @param condition 검색 조건
 | 
			
		||||
     * @return GetMemberResponseDto 리스트
 | 
			
		||||
     */
 | 
			
		||||
    List<GetMemberResponseDto> selectMemberListForDisplay(Map<String, String> params);
 | 
			
		||||
    List<GetMemberResponseDto> getMembers(MemberSearchCondition condition);
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 페이지네이션된 회원 목록 조회
 | 
			
		||||
     * @param condition 검색 조건
 | 
			
		||||
     * @param page 페이지 번호
 | 
			
		||||
     * @param size 페이지 크기
 | 
			
		||||
     * @return PagedResult<GetMemberResponseDto> 페이지네이션된 회원 목록
 | 
			
		||||
     */
 | 
			
		||||
    PagedResult<GetMemberResponseDto> getMembersPaged(MemberSearchCondition condition, int page, int size);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ package com.bio.bio_backend.domain.base.member.service;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.GetMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberSearchCondition;
 | 
			
		||||
import com.bio.bio_backend.global.dto.PagedResult;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.mapper.MemberMapper;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.repository.MemberRepository;
 | 
			
		||||
@@ -13,12 +15,15 @@ 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.data.domain.Page;
 | 
			
		||||
import org.springframework.data.domain.PageRequest;
 | 
			
		||||
import org.springframework.data.domain.Pageable;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@@ -91,16 +96,18 @@ public class MemberServiceImpl implements MemberService {
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<MemberDto> selectMemberList(Map<String, String> params) {
 | 
			
		||||
        List<Member> members = memberRepository.findByUseFlagTrue();
 | 
			
		||||
        
 | 
			
		||||
        return memberMapper.toMemberDtoList(members);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<GetMemberResponseDto> selectMemberListForDisplay(Map<String, String> params) {
 | 
			
		||||
        List<Member> members = memberRepository.findByUseFlagTrue();
 | 
			
		||||
    public List<GetMemberResponseDto> getMembers(MemberSearchCondition condition) {
 | 
			
		||||
        List<Member> members = memberRepository.findMembers(condition);
 | 
			
		||||
        
 | 
			
		||||
        return memberMapper.toGetMemberResponseDtoList(members);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public PagedResult<GetMemberResponseDto> getMembersPaged(MemberSearchCondition condition, int page, int size) {
 | 
			
		||||
        Pageable pageable = PageRequest.of(page, size);
 | 
			
		||||
        Page<Member> memberPage = memberRepository.findMembers(condition, pageable);
 | 
			
		||||
        List<GetMemberResponseDto> members = memberMapper.toGetMemberResponseDtoList(memberPage.getContent());
 | 
			
		||||
 | 
			
		||||
        return PagedResult.of(memberPage, members);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package com.bio.bio_backend.global.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.Min;
 | 
			
		||||
import jakarta.validation.constraints.Max;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 기본 페이징 요청 DTO
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@Builder
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class BasePagedRequestDto {
 | 
			
		||||
    
 | 
			
		||||
    @Min(value = 0, message = "페이지 번호는 0 이상이어야 합니다")
 | 
			
		||||
    @Builder.Default
 | 
			
		||||
    private int page = 0;
 | 
			
		||||
    
 | 
			
		||||
    @Min(value = 1, message = "페이지 크기는 1 이상이어야 합니다")
 | 
			
		||||
    @Max(value = 100, message = "페이지 크기는 100 이하여야 합니다")
 | 
			
		||||
    @Builder.Default
 | 
			
		||||
    private int size = 10;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
package com.bio.bio_backend.global.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import lombok.experimental.SuperBuilder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 기본 페이징 응답 DTO
 | 
			
		||||
 * 모든 페이징 응답에서 공통으로 사용되는 필드들을 포함
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@SuperBuilder
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
public class BasePagedResponseDto {
 | 
			
		||||
    
 | 
			
		||||
    private int currentPage;        // 현재 페이지
 | 
			
		||||
    private int totalPages;         // 전체 페이지 수
 | 
			
		||||
    private long totalElements;     // 전체 요소 수
 | 
			
		||||
    private int size;              // 페이지 크기
 | 
			
		||||
    private boolean hasNext;       // 다음 페이지 존재 여부
 | 
			
		||||
    private boolean hasPrevious;   // 이전 페이지 존재 여부
 | 
			
		||||
    private boolean isFirst;       // 첫 번째 페이지 여부
 | 
			
		||||
    private boolean isLast;        // 마지막 페이지 여부
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
package com.bio.bio_backend.global.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.springframework.data.domain.Page;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 페이지네이션된 결과를 담는 제네릭 클래스
 | 
			
		||||
 * @param <T> 페이지네이션할 데이터의 타입
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class PagedResult<T> extends BasePagedResponseDto {
 | 
			
		||||
    
 | 
			
		||||
    private List<T> content;
 | 
			
		||||
    
 | 
			
		||||
    public static <T> PagedResult<T> of(Page<?> page, List<T> content) {
 | 
			
		||||
        PagedResult<T> result = new PagedResult<>();
 | 
			
		||||
        result.setContent(content);
 | 
			
		||||
        result.setCurrentPage(page.getNumber());
 | 
			
		||||
        result.setTotalPages(page.getTotalPages());
 | 
			
		||||
        result.setTotalElements(page.getTotalElements());
 | 
			
		||||
        result.setSize(page.getSize());
 | 
			
		||||
        result.setHasNext(page.hasNext());
 | 
			
		||||
        result.setHasPrevious(page.hasPrevious());
 | 
			
		||||
        result.setFirst(page.isFirst());
 | 
			
		||||
        result.setLast(page.isLast());
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user