Skip to content

Commit

Permalink
Merge PR(#92) from feature/member-inactive-#84 휴면 회원 처리 및 활성화 기능
Browse files Browse the repository at this point in the history
  • Loading branch information
woody35545 authored May 9, 2024
2 parents 44a8739 + 070c3c9 commit 6a15de5
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.t3t.frontserver.auth.model.request.LoginRequestDto;
import com.t3t.frontserver.auth.service.LoginService;
import com.t3t.frontserver.member.model.constant.MemberStatus;
import com.t3t.frontserver.member.model.response.MemberInfoResponse;
import com.t3t.frontserver.member.service.MemberService;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
Expand All @@ -26,43 +30,58 @@
import java.util.Base64;
import java.util.Map;


@Slf4j
@Controller
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
private final MemberService memberService;


/**
* 로그인 페이지 뷰 반환
*
* @return 로그인 페이지 뷰
* @author joohyun1996(이주현)
*/
@GetMapping("/login")
public String loginPage(Model model){
public String loginPage(Model model) {
model.addAttribute("loginRequestDto", new LoginRequestDto());
return "main/page/login";
}

/**
* 로그인 요청 처리
*
* @param loginRequestDto,redirectAttributes,resp
* @return 성공시 : redirect:/, 실패시 : redirect:/login
* @throws JsonProcessingException
* @author joohyun1996(이주현)
*/
@PostMapping("/login")
public String doLogin(@ModelAttribute @Valid LoginRequestDto loginRequestDto,
RedirectAttributes redirectAttributes,
HttpServletResponse resp) throws JsonProcessingException {
RedirectAttributes redirectAttributes,
Model model,
HttpServletResponse resp) throws JsonProcessingException {
try {
ResponseEntity responseEntity = loginService.login(loginRequestDto);
String access = responseEntity.getHeaders().getFirst(HttpHeaders.AUTHORIZATION).trim().split(" ")[1];

MemberInfoResponse memberInfoResponse = memberService.getMemberInfoResponseById(getMemberId(access));

if (memberInfoResponse.getStatus().equals(MemberStatus.INACTIVE)) {
model.addAttribute("memberId", memberInfoResponse.getMemberId());
model.addAttribute("memberLatestLogin", memberInfoResponse.getLatestLogin());
model.addAttribute("memberName", memberInfoResponse.getName());
return "main/page/activateMemberIssue";
}

Cookie cookie = new Cookie("t3t", access);
cookie.setHttpOnly(true);
cookie.setMaxAge(-1);

resp.addCookie(cookie);

// contextholder에 추가
SecurityContextHolder.getContext().setAuthentication(getAuthentication(access));

Expand All @@ -75,6 +94,7 @@ public String doLogin(@ModelAttribute @Valid LoginRequestDto loginRequestDto,

/**
* Security Context Holder를 사용하기 위해 임의의 CustomUserDetails를 넣고 반환해주는 메소드
*
* @param token
* @return Authentication
* @throws JsonProcessingException
Expand All @@ -92,4 +112,15 @@ public UsernamePasswordAuthenticationToken getAuthentication(String token) throw
UserDetails userDetails = User.withUsername(member).password("").roles(role).build();
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}

public long getMemberId(String token) throws JsonProcessingException {
byte[] decodedPayload = Base64.getDecoder().decode(token.split("\\.")[1]);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(new String(decodedPayload),
new TypeReference<Map<String, Object>>() {
});

log.info("memberId : {}", map.get("username"));
return Long.parseLong(map.get("username").toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,32 @@ public void withdrawMember(long memberId) {
}
}

/**
* 회원 휴면 계정 활성화 코드 발급
*
* @param memberId 대상 회원 식별자
* @author woody35545(구건모)
*/
public void issueMemberActivationCode(long memberId) {
try {
memberApiClient.issueMemberActivationCertCode(memberId);
} catch (FeignException e) {
throw new MemberApiClientException("회원 활성화 코드 발급에 실패하였습니다. " + FeignClientUtils.getMessageFromFeignException(e));
}
}

/**
* 회원 휴면 계정 활성화 코드 검증
*
* @param memberId 대상 회원 식별자
* @param code 인증 코드
* @author woody35545(구건모)
*/
public void verifyMemberActivationCode(long memberId, String code) {
try {
memberApiClient.verifyMemberActivationCertCode(memberId, code);
} catch (FeignException e) {
throw new MemberApiClientException("회원 활성화 코드 검증에 실패하였습니다. " + FeignClientUtils.getMessageFromFeignException(e));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,27 @@ public interface MemberApiClient {

/**
* 회원 탈퇴 API
*
* @param memberId 탈퇴할 회원 식별자
* @author woody35545(구건모)
*/
@DeleteMapping("/t3t/bookstore/members/{memberId}")
BaseResponse<Void> withdrawMember(@PathVariable("memberId") long memberId);

/**
* 회원 휴면 계정 활성화 인증 코드 발급 API
*
* @author wooody35545(구건모)
*/
@PostMapping("/t3t/bookstore/members/{memberId}/codes?type=issue")
BaseResponse<Void> issueMemberActivationCertCode(@PathVariable("memberId") Long memberId);

/**
* 회원 휴면 계정 활성화 인증 코드 검증 API
*
* @author wooody35545(구건모)
*/
@PostMapping("/t3t/bookstore/members/{memberId}/codes?type=verify")
BaseResponse<Void> verifyMemberActivationCertCode(@PathVariable("memberId") Long memberId, @RequestParam("value") String code);

}
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,40 @@ public String deleteMember(RedirectAttributes redirectAttributes, HttpServletRes

return "redirect:/message";
}

/**
* 휴면 회원 활성화 인증 코드 발급
* @author woody35545(구건모)
*/
@PostMapping("/member/activation/issue")
public String issueMemberActivationCertCode(@RequestParam("memberId") long memberId,
@RequestParam("memberLatestLogin") String memberLatestLogin,
@RequestParam("memberName") String memberName,
Model model) {
memberService.issueMemberActivationCode(memberId);

model.addAttribute("memberLatestLogin", memberLatestLogin);
model.addAttribute("memberId", memberId);
model.addAttribute("memberName", memberName);

return "main/page/activateMemberVerify";
}

/**
* 휴면 회원 활성화 인증 코드 검증
*
* @param memberId 회원 식별자
* @param code 인증 코드
* @author woody35545(구건모)
*/
@PostMapping("/member/activation/verify")
public String verifyMemberActivationCertCode(@RequestParam("memberId") long memberId,
@RequestParam("activationCode") String code,
RedirectAttributes redirectAttributes) {

memberService.verifyMemberActivationCode(memberId, code);

redirectAttributes.addAttribute("message", "회원 활성화가 완료되었습니다. 다시 로그인 해주세요.");
return "redirect:/message";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.util.List;


@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService {
Expand Down Expand Up @@ -72,4 +71,25 @@ public void modifyPassword(long memberId, MemberPasswordModifyRequest request) {
public void withdrawMember(long memberId) {
memberAdaptor.withdrawMember(memberId);
}

/**
* 휴면 회원 활성화 인증 코드 발급
*
* @param memberId 회원 식별자
* @author woody35545(구건모)
*/
public void issueMemberActivationCode(long memberId) {
memberAdaptor.issueMemberActivationCode(memberId);
}

/**
* 휴면 회원 활성화 인증 코드 검증
*
* @param memberId 회원 식별자
* @param activationCode 활성화 코드
* @author woody35545(구건모)
*/
public void verifyMemberActivationCode(long memberId, String activationCode) {
memberAdaptor.verifyMemberActivationCode(memberId, activationCode);
}
}
27 changes: 27 additions & 0 deletions src/main/resources/templates/main/page/activateMemberIssue.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="main/layout/layout">

<th:block layout:fragment="content">
<div class="container mt-3 mb-5">
<h1>휴면 계정 안내</h1>
<p><span th:text="${memberName}"></span> 회원님의 계정은 3개월 이상 로그인하지 않아서 휴면 처리되었습니다.</p>

<div class="alert alert-warning" role="alert">
마지막 로그인 일시: <span th:text="${memberLatestLogin}"></span>
</div>

<p>계속 서비스를 이용하시려면 <b>[휴면 해제하기]</b> 를 눌러 휴면 해제를 진행해주세요.</p>

<div class="md-5">
<form action="/member/activation/issue" method="post">
<input type="hidden" name="memberId" th:value="${memberId}">
<input type="hidden" name="memberLatestLogin" th:value="${memberLatestLogin}">
<input type="hidden" name="memberName" th:value="${memberName}">

<button type="submit" class="btn btn-primary">휴면 해제하기</button>
</form>
</div>

</div>
</th:block>
65 changes: 65 additions & 0 deletions src/main/resources/templates/main/page/activateMemberVerify.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="main/layout/layout">


<th:block layout:fragment="content">
<div class="container mt-3 mb-5">
<h1>휴면 계정 안내</h1>
<p><span th:text="${memberName}"></span> 회원님의 계정은 3개월 이상 로그인하지 않아서 휴면 처리되었습니다.</p>

<div class="alert alert-warning" role="alert">
마지막 로그인 일시: <span th:text="${memberLatestLogin}"></span>
</div>

<p>인증 번호가 발송되었습니다. 인증 번호를 입력 후 <b>[휴면 해제하기]</b> 를 눌러 휴면 해제를 완료해주세요.</p>

<div class="md-5">
<form id="verifyActivationCodeForm" action="/member/activation/verify" method="post">
<div class="d-flex">
<input type="hidden" name="memberId" th:value="${memberId}">
<input id='activationCode'
type="text"
class="form-control col-md-2"
name="activationCode"
placeholder="인증 번호 입력">

<button id='activationCodeVerifyButton'
type="submit"
class="btn btn-outline-primary col-md-2 ml-2">
휴면 해제하기
</button>
</div>
</form>
</div>

</div>

<script th:inline="javascript">
startTimer();

function startTimer() {
let timeLeft = 300;
let timerInterval = setInterval(function () {
let minutes = Math.floor(timeLeft / 60);
let seconds = timeLeft % 60;

let formattedTime = padNumber(minutes) + ":" + padNumber(seconds);
document.getElementById("activationCode").placeholder = "남은 시간: " + formattedTime;

timeLeft--;

if (timeLeft < 0) {
clearInterval(timerInterval);
document.getElementById("activationCode").placeholder = "시간이 초과되었습니다.";
}
}, 1000);
}

function padNumber(number) {
return (number < 10 ? "0" : "") + number;
}

</script>
</th:block>
</html>

0 comments on commit 6a15de5

Please sign in to comment.