From bdb5ba7011ca5d369023c70a1e6b7012e99bcbaa Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 10 Dec 2025 17:30:54 +0900 Subject: [PATCH] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/AuthFailureEventListener.java | 31 +++ .../kamcoback/members/AdminApiController.java | 135 +++++------ .../cd/kamcoback/members/AuthController.java | 107 +++++---- .../members/MembersApiController.java | 64 ++--- .../cd/kamcoback/members/dto/MembersDto.java | 92 +++----- .../members/service/AdminService.java | 58 +---- .../members/service/AuthService.java | 35 +++ .../members/service/MembersService.java | 40 ++-- .../postgres/core/MembersCoreService.java | 219 ++++++++---------- .../postgres/entity/MemberEntity.java | 10 + .../members/MembersRepositoryCustom.java | 11 +- .../members/MembersRepositoryImpl.java | 170 +++++++------- 12 files changed, 470 insertions(+), 502 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/auth/AuthFailureEventListener.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/members/service/AuthService.java diff --git a/src/main/java/com/kamco/cd/kamcoback/auth/AuthFailureEventListener.java b/src/main/java/com/kamco/cd/kamcoback/auth/AuthFailureEventListener.java new file mode 100644 index 00000000..75486d9e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/auth/AuthFailureEventListener.java @@ -0,0 +1,31 @@ +package com.kamco.cd.kamcoback.auth; + +import com.kamco.cd.kamcoback.members.service.AuthService; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationListener; +import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AuthFailureEventListener implements ApplicationListener { + + private final AuthService authService; + + @Override + public void onApplicationEvent(AbstractAuthenticationFailureEvent event) { + // 로그인 시도에 사용된 (username) + Object principal = event.getAuthentication().getPrincipal(); + + if (principal instanceof String username) { + // 로그인 실패 카운트 증가 로직 호출 + authService.loginFail(UUID.fromString(username)); + } + } + + @Override + public boolean supportsAsyncExecution() { + return ApplicationListener.super.supportsAsyncExecution(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/members/AdminApiController.java b/src/main/java/com/kamco/cd/kamcoback/members/AdminApiController.java index 6839e605..4ed55cda 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/AdminApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/AdminApiController.java @@ -13,7 +13,6 @@ import jakarta.validation.Valid; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -31,101 +30,81 @@ public class AdminApiController { @Operation(summary = "관리자 계정 등록", description = "관리자 계정 등록") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/join") public ApiResponseDto saveMember( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "관리자 계정 등록", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = MembersDto.AddReq.class))) - @RequestBody - @Valid - MembersDto.AddReq addReq) { + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "관리자 계정 등록", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = MembersDto.AddReq.class))) + @RequestBody + @Valid + MembersDto.AddReq addReq) { return ApiResponseDto.createOK(adminService.saveMember(addReq)); } @Operation(summary = "관리자 계정 수정", description = "관리자 계정 수정") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "수정 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "수정 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PutMapping("/{uuid}") public ApiResponseDto updateMembers( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "관리자 계정 수정", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = MembersDto.UpdateReq.class))) - @PathVariable - UUID uuid, - @RequestBody MembersDto.UpdateReq updateReq) { + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "관리자 계정 수정", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = MembersDto.UpdateReq.class))) + @PathVariable + UUID uuid, + @RequestBody @Valid MembersDto.UpdateReq updateReq) { adminService.updateMembers(uuid, updateReq); return ApiResponseDto.createOK(UUID.randomUUID()); } - @Operation(summary = "회원 탈퇴", description = "회원 탈퇴") + @Operation(summary = "관리자 계정 미사용 처리", description = "관리자 계정 미사용 처리") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "회원 탈퇴", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = UUID.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "관리자 계정 미사용 처리", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = UUID.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @DeleteMapping("/delete/{uuid}") public ApiResponseDto deleteAccount(@PathVariable UUID uuid) { adminService.deleteAccount(uuid); return ApiResponseDto.createOK(uuid); } - - @Operation(summary = "비밀번호 초기화", description = "비밀번호 초기화") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "비밀번호 초기화", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PatchMapping("/{memberId}/password") - public ApiResponseDto resetPassword(@PathVariable Long memberId) { - adminService.resetPassword(memberId); - return ApiResponseDto.createOK(memberId); - } } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java b/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java index 2fe356b8..d14a9a4a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/AuthController.java @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.auth.JwtTokenProvider; import com.kamco.cd.kamcoback.auth.RefreshTokenService; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.members.dto.SignInRequest; +import com.kamco.cd.kamcoback.members.service.AuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -13,6 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import java.nio.file.AccessDeniedException; import java.time.Duration; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; @@ -36,6 +38,7 @@ public class AuthController { private final AuthenticationManager authenticationManager; private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenService refreshTokenService; + private final AuthService authService; @Value("${token.refresh-cookie-name}") private String refreshCookieName; @@ -47,45 +50,49 @@ public class AuthController { @Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.") @ApiResponses({ @ApiResponse( - responseCode = "200", - description = "로그인 성공", - content = @Content(schema = @Schema(implementation = TokenResponse.class))), + responseCode = "200", + description = "로그인 성공", + content = @Content(schema = @Schema(implementation = TokenResponse.class))), @ApiResponse( - responseCode = "401", - description = "ID 또는 비밀번호 불일치", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + responseCode = "401", + description = "ID 또는 비밀번호 불일치", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) public ApiResponseDto signin( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "로그인 요청 정보", - required = true) - @RequestBody - SignInRequest request, - HttpServletResponse response) { + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "로그인 요청 정보", + required = true) + @RequestBody + SignInRequest request, + HttpServletResponse response) { Authentication authentication = - authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); String username = authentication.getName(); // UserDetailsService 에서 사용한 username + // 로그인 시간 저장 + authService.saveLogin(UUID.fromString(username)); + String accessToken = jwtTokenProvider.createAccessToken(username); String refreshToken = jwtTokenProvider.createRefreshToken(username); // Redis에 RefreshToken 저장 (TTL = 7일) refreshTokenService.save( - username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); + username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); // HttpOnly + Secure 쿠키에 RefreshToken 저장 ResponseCookie cookie = - ResponseCookie.from(refreshCookieName, refreshToken) - .httpOnly(true) - .secure(refreshCookieSecure) - .path("/") - .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) - .sameSite("Strict") - .build(); + ResponseCookie.from(refreshCookieName, refreshToken) + .httpOnly(true) + .secure(refreshCookieSecure) + .path("/") + .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) + .sameSite("Strict") + .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + return ApiResponseDto.ok(new TokenResponse(accessToken, refreshToken)); } @@ -93,16 +100,16 @@ public class AuthController { @Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.") @ApiResponses({ @ApiResponse( - responseCode = "200", - description = "재발급 성공", - content = @Content(schema = @Schema(implementation = TokenResponse.class))), + responseCode = "200", + description = "재발급 성공", + content = @Content(schema = @Schema(implementation = TokenResponse.class))), @ApiResponse( - responseCode = "401", - description = "만료되었거나 유효하지 않은 리프레시 토큰", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + responseCode = "401", + description = "만료되었거나 유효하지 않은 리프레시 토큰", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) public ResponseEntity refresh(String refreshToken, HttpServletResponse response) - throws AccessDeniedException { + throws AccessDeniedException { if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) { throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); } @@ -120,17 +127,17 @@ public class AuthController { // Redis 갱신 refreshTokenService.save( - username, newRefreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); + username, newRefreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); // 쿠키 갱신 ResponseCookie cookie = - ResponseCookie.from(refreshCookieName, newRefreshToken) - .httpOnly(true) - .secure(refreshCookieSecure) - .path("/") - .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) - .sameSite("Strict") - .build(); + ResponseCookie.from(refreshCookieName, newRefreshToken) + .httpOnly(true) + .secure(refreshCookieSecure) + .path("/") + .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) + .sameSite("Strict") + .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); return ResponseEntity.ok(new TokenResponse(newAccessToken, newRefreshToken)); @@ -140,12 +147,12 @@ public class AuthController { @Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.") @ApiResponses({ @ApiResponse( - responseCode = "200", - description = "로그아웃 성공", - content = @Content(schema = @Schema(implementation = Void.class))) + responseCode = "200", + description = "로그아웃 성공", + content = @Content(schema = @Schema(implementation = Void.class))) }) public ApiResponseDto> logout( - Authentication authentication, HttpServletResponse response) { + Authentication authentication, HttpServletResponse response) { if (authentication != null) { String username = authentication.getName(); // Redis에서 RefreshToken 삭제 @@ -154,17 +161,19 @@ public class AuthController { // 쿠키 삭제 (Max-Age=0) ResponseCookie cookie = - ResponseCookie.from(refreshCookieName, "") - .httpOnly(true) - .secure(refreshCookieSecure) - .path("/") - .maxAge(0) - .sameSite("Strict") - .build(); + ResponseCookie.from(refreshCookieName, "") + .httpOnly(true) + .secure(refreshCookieSecure) + .path("/") + .maxAge(0) + .sameSite("Strict") + .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); return ApiResponseDto.createOK(ResponseEntity.noContent().build()); } - public record TokenResponse(String accessToken, String refreshToken) {} + public record TokenResponse(String accessToken, String refreshToken) { + + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/MembersApiController.java b/src/main/java/com/kamco/cd/kamcoback/members/MembersApiController.java index 08114c6f..bfcdc221 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/MembersApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/MembersApiController.java @@ -10,13 +10,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.UUID; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,41 +31,41 @@ public class MembersApiController { @Operation(summary = "회원정보 목록", description = "회원정보 조회") @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "검색 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Page.class))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @GetMapping public ApiResponseDto> getMemberList( - @ParameterObject MembersDto.SearchReq searchReq) { + @ParameterObject MembersDto.SearchReq searchReq) { return ApiResponseDto.ok(membersService.findByMembers(searchReq)); } - @Operation(summary = "회원정보 수정", description = "회원정보 수정") + + @Operation(summary = "비밀번호 초기화", description = "비밀번호 초기화") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "회원정보 수정", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = UUID.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PutMapping("/{uuid}") - public ApiResponseDto updateMember( - @PathVariable UUID uuid, @RequestBody MembersDto.UpdateReq updateReq) { - // membersService.updateMember(uuid, updateReq); - return ApiResponseDto.createOK(uuid); + value = { + @ApiResponse( + responseCode = "201", + description = "비밀번호 초기화", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PatchMapping("/{memberId}/password") + public ApiResponseDto resetPassword(@PathVariable Long memberId, @RequestBody @Valid MembersDto.InitReq initReq) { + membersService.resetPassword(memberId, initReq); + return ApiResponseDto.createOK(memberId); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java b/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java index b42c1d5e..af052366 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java @@ -1,7 +1,5 @@ package com.kamco.cd.kamcoback.members.dto; -import com.kamco.cd.kamcoback.common.enums.RoleType; -import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -23,33 +21,47 @@ public class MembersDto { private Long id; private UUID uuid; - private String employeeNo; + private String userRole; private String name; - private String email; + private String userId; + private String employeeNo; + private String tempPassword; private String status; - private String roleName; - @JsonFormatDttm private ZonedDateTime createdDttm; - @JsonFormatDttm private ZonedDateTime updatedDttm; + @JsonFormatDttm + private ZonedDateTime createdDttm; + @JsonFormatDttm + private ZonedDateTime updatedDttm; + @JsonFormatDttm + private ZonedDateTime firstLoginDttm; + @JsonFormatDttm + private ZonedDateTime lastLoginDttm; public Basic( - Long id, - UUID uuid, - String employeeNo, - String name, - String email, - String status, - String roleName, - ZonedDateTime createdDttm, - ZonedDateTime updatedDttm) { + Long id, + UUID uuid, + String userRole, + String name, + String userId, + String employeeNo, + String tempPassword, + String status, + ZonedDateTime createdDttm, + ZonedDateTime updatedDttm, + ZonedDateTime firstLoginDttm, + ZonedDateTime lastLoginDttm + ) { this.id = id; this.uuid = uuid; - this.employeeNo = employeeNo; + this.userRole = userRole; this.name = name; - this.email = email; + this.userId = userId; + this.employeeNo = employeeNo; + this.tempPassword = tempPassword; this.status = status; - this.roleName = roleName; this.createdDttm = createdDttm; this.updatedDttm = updatedDttm; + this.firstLoginDttm = firstLoginDttm; + this.lastLoginDttm = lastLoginDttm; } } @@ -59,21 +71,12 @@ public class MembersDto { @AllArgsConstructor public static class SearchReq { - @Schema(description = "이름(name), 이메일(email), 사번(employeeNo)", example = "name") - private String field; + @Schema(description = "전체, 관리자(ROLE_ADMIN), 라벨러(ROLE_LABELER), 검수자(ROLE_REVIEWER)", example = "") + private String userRole; @Schema(description = "키워드", example = "홍길동") private String keyword; - @Schema(description = "라벨러 포함 여부", example = "true") - private boolean labeler = true; - - @Schema(description = "검수자 포함 여부", example = "true") - private boolean reviewer = true; - - @Schema(description = "운영자 포함 여부", example = "true") - private boolean admin = true; - // 페이징 파라미터 @Schema(description = "페이지 번호 (0부터 시작) ", example = "0") private int page = 0; @@ -112,7 +115,7 @@ public class MembersDto { private String employeeNo; public AddReq( - String userRole, String name, String userId, String tempPassword, String employeeNo) { + String userRole, String name, String userId, String tempPassword, String employeeNo) { this.userRole = userRole; this.name = name; this.userId = userId; @@ -146,29 +149,10 @@ public class MembersDto { @Getter @Setter - public static class RolesDto { + public static class InitReq { - @Schema(description = "UUID", example = "4e89e487-c828-4a34-a7fc-0d5b0e3b53b5") - private UUID uuid; - - @Schema(description = "역할 ROLE_ADMIN, ROLE_LABELER, ROLE_REVIEWER", example = "ROLE_ADMIN") - @EnumValid(enumClass = RoleType.class) - private String roleName; - - public RolesDto(UUID uuid, String roleName) { - this.uuid = uuid; - this.roleName = roleName; - } - } - - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class StatusDto { - - @Schema(description = "변경할 상태값 ACTIVE, INACTIVE, ARCHIVED", example = "ACTIVE") - @NotBlank - private String status; + @Schema(description = "패스워드", example = "") + @Size(max = 255) + private String password; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/service/AdminService.java b/src/main/java/com/kamco/cd/kamcoback/members/service/AdminService.java index 948d2454..da6905d7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/service/AdminService.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/service/AdminService.java @@ -1,11 +1,9 @@ package com.kamco.cd.kamcoback.members.service; -import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator; import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.postgres.core.MembersCoreService; import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.mindrot.jbcrypt.BCrypt; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,69 +15,35 @@ public class AdminService { private final MembersCoreService membersCoreService; /** - * 회원가입 + * 관리자 계정 등록 * * @param addReq * @return */ @Transactional public Long saveMember(MembersDto.AddReq addReq) { - // salt 생성, 사번이 salt - String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getUserId().trim()); - - // 패스워드 암호화, 초기 패스워드 고정 - String hashedPassword = BCrypt.hashpw(addReq.getTempPassword(), salt); - addReq.setTempPassword(hashedPassword); return membersCoreService.saveMembers(addReq); } + /** + * 관리자 계정 수정 + * + * @param uuid + * @param updateReq + */ + @Transactional public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { membersCoreService.updateMembers(uuid, updateReq); } - /** - * 역할 추가 - * - * @param rolesDto - */ - @Transactional - public void saveRoles(MembersDto.RolesDto rolesDto) { - // membersCoreService.saveRoles(rolesDto); - } /** - * 역할 삭제 - * - * @param rolesDto - */ - public void deleteRoles(MembersDto.RolesDto rolesDto) { - // membersCoreService.deleteRoles(rolesDto); - } - - /** - * 역할 수정 - * - * @param statusDto - */ - public void updateStatus(UUID uuid, MembersDto.StatusDto statusDto) { - // membersCoreService.updateStatus(uuid, statusDto); - } - - /** - * 회원 탈퇴 + * 관리자 계정 미사용 처리 * * @param uuid */ + @Transactional public void deleteAccount(UUID uuid) { - // membersCoreService.deleteAccount(uuid); - } - - /** - * 패스워드 초기화 - * - * @param id - */ - public void resetPassword(Long id) { - // membersCoreService.resetPassword(id); + membersCoreService.deleteAccount(uuid); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/service/AuthService.java b/src/main/java/com/kamco/cd/kamcoback/members/service/AuthService.java new file mode 100644 index 00000000..49ce1bce --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/members/service/AuthService.java @@ -0,0 +1,35 @@ +package com.kamco.cd.kamcoback.members.service; + +import com.kamco.cd.kamcoback.postgres.core.MembersCoreService; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuthService { + + private final MembersCoreService membersCoreService; + + /** + * 로그인 일시 저장 + * + * @param uuid + */ + @Transactional + public void saveLogin(UUID uuid) { + membersCoreService.saveLogin(uuid); + } + + /** + * 로그인 실패 저장 + * + * @param uuid + */ + @Transactional + public void loginFail(UUID uuid) { + membersCoreService.loginFail(uuid); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/members/service/MembersService.java b/src/main/java/com/kamco/cd/kamcoback/members/service/MembersService.java index fe85e06c..945d4868 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/service/MembersService.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/service/MembersService.java @@ -1,11 +1,13 @@ package com.kamco.cd.kamcoback.members.service; +import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic; import com.kamco.cd.kamcoback.postgres.core.MembersCoreService; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,38 +25,22 @@ public class MembersService { * @return */ public Page findByMembers(MembersDto.SearchReq searchReq) { - return null; // membersCoreService.findByMembers(searchReq); + return membersCoreService.findByMembers(searchReq); } /** - * 회원정보 수정 + * 패스워드 사용자 변경 * - * @param uuid - * @param updateReq + * @param id + * @param initReq */ - // public void updateMember(UUID uuid, MembersDto.UpdateReq updateReq) { - // - // if (StringUtils.isNotBlank(updateReq.getPassword())) { - // - // if (!this.isValidPassword(updateReq.getPassword())) { - // throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); - // } - // - // if (StringUtils.isBlank(updateReq.getEmployeeNo())) { - // throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); - // } - // - // // salt 생성, 사번이 salt - // String salt = - // BCryptSaltGenerator.generateSaltWithEmployeeNo(updateReq.getEmployeeNo().trim()); - // - // // 패스워드 암호화, 초기 패스워드 고정 - // String hashedPassword = BCrypt.hashpw(updateReq.getPassword(), salt); - // updateReq.setPassword(hashedPassword); - // } - // - // membersCoreService.updateMembers(uuid, updateReq); - // } + @Transactional + public void resetPassword(Long id, MembersDto.InitReq initReq) { + if (!isValidPassword(initReq.getPassword())) { + throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); + } + membersCoreService.resetPassword(id, initReq); + } /** * 대문자 1개 이상 소문자 1개 이상 숫자 1개 이상 특수문자(!@#$) 1개 이상 diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java index d6acd8a9..ca2102f1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java @@ -1,16 +1,20 @@ package com.kamco.cd.kamcoback.postgres.core; +import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator; import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto.AddReq; +import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic; import com.kamco.cd.kamcoback.members.exception.MemberException.DuplicateMemberException; import com.kamco.cd.kamcoback.members.exception.MemberException.DuplicateMemberException.Field; import com.kamco.cd.kamcoback.members.exception.MemberException.MemberNotFoundException; import com.kamco.cd.kamcoback.postgres.entity.MemberEntity; -import com.kamco.cd.kamcoback.postgres.repository.members.MembersArchivedRepository; import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository; +import java.time.ZonedDateTime; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @Service @@ -18,10 +22,9 @@ import org.springframework.stereotype.Service; public class MembersCoreService { private final MembersRepository membersRepository; - private final MembersArchivedRepository memberArchivedRepository; /** - * 회원가입 + * 관리자 계정 등록 * * @param addReq * @return @@ -30,11 +33,17 @@ public class MembersCoreService { if (membersRepository.existsByUserId(addReq.getUserId())) { throw new DuplicateMemberException(Field.USER_ID, addReq.getUserId()); } + + // salt 생성, 사번이 salt + String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getUserId().trim()); + // 패스워드 암호화, 초기 패스워드 고정 + String hashedPassword = BCrypt.hashpw(addReq.getTempPassword(), salt); + MemberEntity memberEntity = new MemberEntity(); memberEntity.setUserId(addReq.getUserId()); memberEntity.setUserRole(addReq.getUserRole()); - memberEntity.setTempPassword(addReq.getTempPassword()); - memberEntity.setPassword(addReq.getTempPassword()); + memberEntity.setTempPassword(addReq.getTempPassword()); // 임시 패스워드는 암호화 하지 않음 + memberEntity.setPassword(hashedPassword); memberEntity.setName(addReq.getName()); memberEntity.setEmployeeNo(addReq.getEmployeeNo()); @@ -42,22 +51,22 @@ public class MembersCoreService { } /** - * 회원정보 수정 + * 관리자 계정 수정 * * @param uuid * @param updateReq */ public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); + membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); if (StringUtils.isNotBlank(updateReq.getName())) { memberEntity.setName(updateReq.getName()); } + // 임시 패스워드는 암호화 하지 않음 if (StringUtils.isNotBlank(updateReq.getTempPassword())) { memberEntity.setTempPassword(updateReq.getTempPassword()); - memberEntity.setPassword(updateReq.getTempPassword()); } if (StringUtils.isNotBlank(memberEntity.getEmployeeNo())) { @@ -66,123 +75,42 @@ public class MembersCoreService { membersRepository.save(memberEntity); } - // - // /** - // * 역할 추가 - // * - // * @param rolesDto - // */ - // public void saveRoles(MembersDto.RolesDto rolesDto) { - // - // MemberEntity memberEntity = - // membersRepository - // .findByUUID(rolesDto.getUuid()) - // .orElseThrow(() -> new MemberNotFoundException()); - // - // if (memberRoleRepository.findByUuidAndRoleName(rolesDto)) { - // throw new MemberException.DuplicateMemberException( - // MemberException.DuplicateMemberException.Field.DEFAULT, "중복된 역할이 있습니다."); - // } - // - // MemberRoleEntityId memberRoleEntityId = new MemberRoleEntityId(); - // memberRoleEntityId.setMemberUuid(rolesDto.getUuid()); - // memberRoleEntityId.setRoleName(rolesDto.getRoleName()); - // - // MemberRoleEntity memberRoleEntity = new MemberRoleEntity(); - // memberRoleEntity.setId(memberRoleEntityId); - // memberRoleEntity.setMemberUuid(memberEntity); - // memberRoleEntity.setCreatedDttm(ZonedDateTime.now()); - // memberRoleRepository.save(memberRoleEntity); - // } - // - // /** - // * 역할 삭제 - // * - // * @param rolesDto - // */ - // public void deleteRoles(MembersDto.RolesDto rolesDto) { - // MemberEntity memberEntity = - // membersRepository - // .findByUUID(rolesDto.getUuid()) - // .orElseThrow(() -> new MemberNotFoundException()); - // - // MemberRoleEntityId memberRoleEntityId = new MemberRoleEntityId(); - // memberRoleEntityId.setMemberUuid(rolesDto.getUuid()); - // memberRoleEntityId.setRoleName(rolesDto.getRoleName()); - // - // MemberRoleEntity memberRoleEntity = new MemberRoleEntity(); - // memberRoleEntity.setId(memberRoleEntityId); - // memberRoleEntity.setMemberUuid(memberEntity); - // - // memberRoleRepository.delete(memberRoleEntity); - // } - // - // /** - // * 상태 수정 - // * - // * @param statusDto - // */ - // public void updateStatus(UUID uuid, MembersDto.StatusDto statusDto) { - // MemberEntity memberEntity = - // membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); - // - // memberEntity.setStatus(statusDto.getStatus()); - // memberEntity.setUpdatedDttm(ZonedDateTime.now()); - // membersRepository.save(memberEntity); - // } - // - // /** - // * 회원 탈퇴 - // * - // * @param uuid - // */ - // public void deleteAccount(UUID uuid) { - // MemberEntity memberEntity = - // membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); - // - // MemberArchivedEntityId memberArchivedEntityId = new MemberArchivedEntityId(); - // memberArchivedEntityId.setUserId(memberEntity.getId()); - // memberArchivedEntityId.setUuid(memberEntity.getUuid()); - // - // MemberArchivedEntity memberArchivedEntity = new MemberArchivedEntity(); - // memberArchivedEntity.setId(memberArchivedEntityId); - // memberArchivedEntity.setEmployeeNo(memberEntity.getEmployeeNo()); - // memberArchivedEntity.setName(memberEntity.getName()); - // memberArchivedEntity.setPassword(memberEntity.getPassword()); - // memberArchivedEntity.setEmail(memberEntity.getEmail()); - // memberArchivedEntity.setStatus(memberEntity.getStatus()); - // memberArchivedEntity.setCreatedDttm(memberEntity.getCreatedDttm()); - // memberArchivedEntity.setArchivedDttm(ZonedDateTime.now()); - // memberArchivedRepository.save(memberArchivedEntity); - // - // memberEntity.setStatus("ARCHIVED"); - // memberEntity.setName("**********"); - // memberEntity.setEmployeeNo("**********"); - // memberEntity.setPassword("**********"); - // memberEntity.setEmail("**********"); - // memberEntity.setUpdatedDttm(ZonedDateTime.now()); - // membersRepository.save(memberEntity); - // } - // - // /** - // * 패스워드 초기화 - // * - // * @param id - // */ - // public void resetPassword(Long id) { - // MemberEntity memberEntity = - // membersRepository.findById(id).orElseThrow(() -> new MemberNotFoundException()); - // - // String salt = - // BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim()); - // // 패스워드 암호화, 초기 패스워드 고정 - // String hashedPassword = BCrypt.hashpw(password, salt); - // - // memberEntity.setPassword(hashedPassword); - // memberEntity.setStatus("INACTIVE"); - // memberEntity.setUpdatedDttm(ZonedDateTime.now()); - // membersRepository.save(memberEntity); - // } + + + /** + * 관리자 계정 미사용 처리 + * + * @param uuid + */ + public void deleteAccount(UUID uuid) { + MemberEntity memberEntity = + membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); + + memberEntity.setStatus("INACTIVE"); + memberEntity.setUpdatedDttm(ZonedDateTime.now()); + membersRepository.save(memberEntity); + } + + + /** + * 패스워드 변경 + * + * @param id + */ + public void resetPassword(Long id, MembersDto.InitReq initReq) { + MemberEntity memberEntity = + membersRepository.findById(id).orElseThrow(() -> new MemberNotFoundException()); + + String salt = + BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim()); + // 패스워드 암호화 + String hashedPassword = BCrypt.hashpw(initReq.getPassword(), salt); + + memberEntity.setPassword(hashedPassword); + memberEntity.setStatus("ACTIVE"); + memberEntity.setUpdatedDttm(ZonedDateTime.now()); + membersRepository.save(memberEntity); + } // /** @@ -191,7 +119,42 @@ public class MembersCoreService { * @param searchReq * @return */ - // public Page findByMembers(MembersDto.SearchReq searchReq) { - // return membersRepository.findByMembers(searchReq); - // } + public Page findByMembers(MembersDto.SearchReq searchReq) { + return membersRepository.findByMembers(searchReq); + } + + /** + * 최초 로그인 저장 마지막 로그인 저장 + * + * @param uuid + */ + public void saveLogin(UUID uuid) { + MemberEntity memberEntity = + membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); + + if (memberEntity.getFirstLoginDttm() == null) { + memberEntity.setFirstLoginDttm(ZonedDateTime.now()); + } + memberEntity.setLastLoginDttm(ZonedDateTime.now()); + memberEntity.setLoginFailCount(0); + membersRepository.save(memberEntity); + } + + /** + * 로그인 실패시 상태 저장 + * + * @param uuid + */ + public void loginFail(UUID uuid) { + MemberEntity memberEntity = + membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); + + int failCnt = memberEntity.getLoginFailCount() + 1; + if (failCnt >= 5) { + memberEntity.setStatus("INACTIVE"); + } + + memberEntity.setLoginFailCount(failCnt); + membersRepository.save(memberEntity); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MemberEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MemberEntity.java index f9cf9ef8..5a4879b0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MemberEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MemberEntity.java @@ -73,4 +73,14 @@ public class MemberEntity { @ColumnDefault("now()") @Column(name = "updated_dttm") private ZonedDateTime updatedDttm = ZonedDateTime.now(); + + @Column(name = "first_login_dttm") + private ZonedDateTime firstLoginDttm; + + @Column(name = "last_login_dttm") + private ZonedDateTime lastLoginDttm; + + @Column(name = "login_fail_count") + @ColumnDefault("0") + private Integer loginFailCount = 0; } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryCustom.java index 17f9ee43..bcd23223 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryCustom.java @@ -1,20 +1,19 @@ package com.kamco.cd.kamcoback.postgres.repository.members; +import com.kamco.cd.kamcoback.members.dto.MembersDto; +import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic; import com.kamco.cd.kamcoback.postgres.entity.MemberEntity; import java.util.Optional; import java.util.UUID; +import org.springframework.data.domain.Page; public interface MembersRepositoryCustom { boolean existsByUserId(String userId); - Optional findByUserId(String employeeNo); + Optional findByUserId(String userId); Optional findByUUID(UUID uuid); - // - // Page findByMembers(MembersDto.SearchReq searchReq); - // - // - // Optional findByEmployeeNo(String employeeNo); + Page findByMembers(MembersDto.SearchReq searchReq); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java index c428c639..7e84baec 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java @@ -1,11 +1,20 @@ package com.kamco.cd.kamcoback.postgres.repository.members; +import com.kamco.cd.kamcoback.members.dto.MembersDto; +import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic; import com.kamco.cd.kamcoback.postgres.entity.MemberEntity; import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; @Repository @@ -24,96 +33,95 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom { @Override public boolean existsByUserId(String userId) { return queryFactory - .selectOne() - .from(memberEntity) - .where(memberEntity.userId.eq(userId)) - .fetchFirst() - != null; + .selectOne() + .from(memberEntity) + .where(memberEntity.userId.eq(userId)) + .fetchFirst() + != null; } + /** + * 사용자 조회 user id + * + * @param userId + * @return + */ @Override public Optional findByUserId(String userId) { return Optional.ofNullable( - queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne()); + queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne()); } - // /** - // * 회원정보 목록 조회 - // * - // * @param searchReq - // * @return - // */ - // @Override - // public Page findByMembers(MembersDto.SearchReq searchReq) { - // Pageable pageable = searchReq.toPageable(); - // BooleanBuilder builder = new BooleanBuilder(); - // BooleanBuilder leftBuilder = new BooleanBuilder(); - // - // if (StringUtils.isNotBlank(searchReq.getField())) { - // switch (searchReq.getField()) { - // case "name" -> - // builder.and(memberEntity.name.containsIgnoreCase(searchReq.getKeyword().trim())); - // } - // } - // - // List roles = new ArrayList<>(); - // // 라벨러 - // if (searchReq.isLabeler()) { - // roles.add(RoleType.ROLE_LABELER.getId()); - // } - // - // // 시스템 전체 관리자 - // if (searchReq.isAdmin()) { - // roles.add(RoleType.ROLE_ADMIN.getId()); - // } - // - // // 검수자 - // if (searchReq.isReviewer()) { - // roles.add(RoleType.ROLE_REVIEWER.getId()); - // } - // - // // 역할 in 조건 추가 - // if (!roles.isEmpty()) { - // leftBuilder.and(memberRoleEntity.id.roleName.in(roles)); - // } - // - // List content = - // queryFactory - // .select( - // Projections.constructor( - // MembersDto.Basic.class, - // memberEntity.id, - // memberEntity.uuid, - // memberEntity.employeeNo, - // memberEntity.name, - // null, - // memberEntity.status, - // memberRoleEntity.id.roleName, - // memberEntity.createdDttm, - // memberEntity.updatedDttm)) - // .from(memberEntity) - // .leftJoin(memberRoleEntity) - // .on(memberRoleEntity.memberUuid.uuid.eq(memberEntity.uuid).and(leftBuilder)) - // .where(builder) - // .offset(pageable.getOffset()) - // .limit(pageable.getPageSize()) - // .orderBy(memberEntity.createdDttm.desc()) - // .fetch(); - // - // long total = - // queryFactory - // .select(memberEntity) - // .from(memberEntity) - // .leftJoin(memberRoleEntity) - // .on(memberRoleEntity.memberUuid.uuid.eq(memberEntity.uuid).and(leftBuilder)) - // .fetchCount(); - // - // return new PageImpl<>(content, pageable, total); - // } - // + /** + * 회원정보 목록 조회 + * + * @param searchReq + * @return + */ + @Override + public Page findByMembers(MembersDto.SearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + BooleanBuilder builder = new BooleanBuilder(); + + // 검색어 + if (StringUtils.isNotBlank(searchReq.getKeyword())) { + String contains = "%" + searchReq.getKeyword() + "%"; + + builder.and( + memberEntity.name.likeIgnoreCase(contains) + .or(memberEntity.userId.likeIgnoreCase(contains)) + .or(memberEntity.employeeNo.likeIgnoreCase(contains)) + ); + } + + // 권한 + if (StringUtils.isNotBlank(searchReq.getUserRole())) { + builder.and(memberEntity.userRole.eq(searchReq.getUserRole())); + } + + List content = + queryFactory + .select( + Projections.constructor( + MembersDto.Basic.class, + memberEntity.id, + memberEntity.uuid, + memberEntity.userRole, + memberEntity.name, + memberEntity.userId, + memberEntity.employeeNo, + memberEntity.tempPassword, + memberEntity.status, + memberEntity.createdDttm, + memberEntity.updatedDttm, + memberEntity.firstLoginDttm, + memberEntity.lastLoginDttm + )) + .from(memberEntity) + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(memberEntity.createdDttm.desc()) + .fetch(); + + long total = + queryFactory + .select(memberEntity) + .from(memberEntity) + .fetchCount(); + + return new PageImpl<>(content, pageable, total); + } + + /** + * 사용자 ID 조회 UUID + * + * @param uuid + * @return + */ @Override public Optional findByUUID(UUID uuid) { return Optional.ofNullable( - queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne()); + queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne()); } }