Spring Projcect/학습 관리 시스템 & 백오피스 구축

Chapter 11. 회원 상세 및 상태 처리

계란💕 2022. 8. 21. 20:11

11.1 회원 상세 목록 구현

 

  Ex) 아이디 눌렀을 때 상세 화면으로 이동 - 모자이크 부분은 이메일

  Note) 실행 결과

 

 

  Ex) 상세 페이지 만들기

    - detail

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>

    <style>
    </style>
</head>
<body>
   <h1>관리자 회원 관리 - 회원 상세 정보</h1>
<!--   <div th:replace="/fragments/layout.html :: fragment-body-menu" ></div>-->
   <div>
       <a href="/admin/main.do">관리자 메인</a>
       |
       <a href="/admin/member/list.do">회원 관리</a>
       |
       <a href="#">카테고리 관리</a>
       |
       <a href="#">강의 관리</a>
       |
       <a href="/member/logout">로그 아웃</a>
       <br>
       <hr/>
    </div>
   
    <div class="detail">
    </div>

</body>
</html>

 

    - param 에 추가

String userId;

 

    - detail 수정 - 모자이크는 이메일

 

    - adminMemberCont  메서드

<hide/>
@GetMapping("/admin/member/detail.do")
public String detail(Model model, MemberParam parameter){

    parameter.init();   // 유효한 값이 되도록 만든다.
    return "admin/member/detail";
}

 

  Note) 실행 결과

 

      - 회원관리 탭 누르면?

 

 

  Ex)

    - detail()

MemberDto detail(String userId);

 

    - 목록을 가져올 때 마이바티스 쿼리 매퍼를 이용해서 가져왔다. '페이징'이 복잡, 쿼리가 복잡하면 마이바티스 사용

    - detail 파일은 JPA가 빠르고 편하다. 심플한 쿼리 => JPA

 

 

    - Dto에 메서드 of() 추가

<hide/>
public static MemberDto of(Member member){
    MemberDto memberDto = MemberDto.builder()
                                    .userId(member.getUserId())
                                    .userName(member.getUserName())
                                    .phone(member.getPhone())

            .regDt(member.getRegDt())
            .emailAuthYn(member.isEmailAuthYn())
            .emailAuthDt(member.getEmailAuthDt())
            .emailAuthKey(member.getEmailAuthKey())

            .resetPasswordKey(member.getResetPasswordKey())
            .resetPasswordLimitDt(member.getResetPasswordLimitDt())
            .adminYn(member.isAdminYn())
            .build();

    return  memberDto;
}

 

    - 임플  -detail (멤버의 상세 정보를 가져오는 메서드)

<hide/>
@Override
public MemberDto detail(String userId) {

    Optional<Member> optionalMember = memberRepository.findById(userId);
    if(!optionalMember.isPresent()){
        return null;
    }

    // optional 멤버 존재할 때만
    Member member = optionalMember.get();
    return MemberDto.of(member);
}

 

    - 컨트롤러

<hide/>
@GetMapping("/admin/member/detail.do")
public String detail(Model model, MemberParam parameter){

    parameter.init();   // 유효한 값이 되도록 만든다.
    MemberDto member = memberService.detail(parameter.getUserId());
    model.addAttribute("member", member);
    return "admin/member/detail";
}

 

 

 =================오류 =======================

 

 

    ================= 오류 ==================

    - 오류: SQL문을 건드리지 않았는데 다음과 같은 오류가 났다.

    - 원인: DTO 클래스를 빌드 하기 위해서 @NoArgs, @AllArgs 를 붙여야 한다.

 

  Note) 실행 결과

   - 이제 이 데이터를 표로 만들자

 

  Ex) 테이블로 만들기

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>

    <style>
    </style>
</head>
<body>
   <h1>관리자 회원 관리 - 회원 상세 정보</h1>
<!--   <div th:replace="/fragments/layout.html :: fragment-body-menu" ></div>-->
   <div>
       <a href="/admin/main.do">관리자 메인</a>
       |
       <a href="/admin/member/list.do">회원 관리</a>
       |
       <a href="#">카테고리 관리</a>
       |
       <a href="#">강의 관리</a>
       |
       <a href="/member/logout">로그 아웃</a>
       <br>
       <hr/>
    </div>
    <div class="detail">
        <table>
            <tbody>
                <tr>
                    <th>아이디</th>
                    <td>
                        <p th:text="${member.userId}"></p>
                    </td>
                </tr>

                <tr>
                    <th>이름</th>
                    <td>
                        <p th:text="${member.userName}"></p>
                    </td>
                </tr>
                <tr>
                    <th>연락처</th>
                    <td>
                        <p th:text="${member.phone}"></p>
                    </td>
                </tr>
                <tr>
                    <th>가입일</th>
                    <td>
                        <p th:text="${member.regDt}"></p>
                    </td>
                </tr>
                <tr>
                    <th>이메일 인증</th>
                    <td>
                        <p th:text="${member.isEmailAuthYn()}"></p>
                    </td>
                </tr>
                <tr>
                    <th>관리자 여부</th>
                    <td>
                        <p th:text="${member.isAdminYn()}"></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

 

  Note) 실행 결과

 

  Ex) CSS - 표 테두리

<hide/>
<style>
   .detail table{
            width: 100%;
            border-collapse: collapse;
        }
    .detail table th, .detail table td{
        border: solid 1px #000;
    }
</style>

  Note) 실행 결과

 

 

11.2 회원 상태 및 비밀번호 초기화 구현

 

  Ex) 회원이 로그인 가능한지 아닌지 파악

 

     - 엔티티

private String userStatus;

 

    - 멤버코드 인터페이스

<hide?>
package com.zerobase.fastlms.member.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.time.LocalDateTime;
public interface MemberCode {

    String MEMBER_STATUS_ING = "ING";
    String MEMBER_STATUS_STOP = "STOP";
   
}

 

      - 방금 생성한 변수가 테이블에 나오게 하려면?

 

 

    - 임플 - register()에 추가한다. -  회원 가입 중일 때는 멤버 코드가 요청 중이다.

      -> emailAuth() 에서 이메일 인증을 한 다음에 회원이 되도록 구현

<hide/>
  Member member = Member.builder()
                        .userId(parameter.getUserId())
                        .userName(parameter.getUserName())
                        .phone(parameter.getPhone())
                        .password(encPassword)
                        .regDt(LocalDateTime.now())
                        .emailAuthYn(false)
                        .emailAuthKey(uuid)
                        .userStatus(Member.MEMBER_STATUS_REQ)
                        .build();

 

    - emailAuth() => ING를 넣어줘야 이용 가능한 회원이 된다.

<hide/>
@Override
public boolean emailAuth(String uuid) { // 이메일 인증
    Optional<Member> optionalMember = memberRepository.findByEmailAuthKey(uuid);    // 있으면 Optional<Member> 가 리턴된다.

    if(!optionalMember.isPresent()){
        return false;
    }
    Member member = optionalMember.get();   // 존재하는 경우 멤버

    // 이미 활성화됐기 때문에 또 활성화할 필요없다.
    if(member.isEmailAuthYn()){
        return false;
    }
    member.setUserStatus(MemberCode.MEMBER_STATUS_ING);
    member.setEmailAuthYn(true);
    member.setEmailAuthDt(LocalDateTime.now());
    memberRepository.save(member);
    return true;
}

 

    - 임플 클래스 loadUserBuUsername

<hide/>
if(Member.MEMBER_STATUS_REQ.equals(member.getUserStatus())){
        throw new MemberNotEmailAuthException("");
}
if(Member.MEMBER_STATUS_STOP.equals(member.getUserStatus())){
    throw new MemberStopUserException("정지된 회원입니다.");
}

 

    - 새로운 예외 클래스

<hide/>
package com.zerobase.fastlms.member.exception;
public class MemberStopUserException extends RuntimeException{
    public MemberStopUserException(String error) {
        super(error);
   }
}

 

 

  Ex)  Status 테스트

 

    - 회원가입 후, db

 

    - 이메일 활성화하지 않고 로그인 하면?

 

    - 활성화 하고 나면 상태가 ING로 바뀐다.

 

 

  Ex) 회원 정지

 

    - DTO 클래스에도 추가해야 detail페이지에서 userStatus를 사용 가능하다.

String userStatus;

 

   - of()

<hide/>
 public static MemberDto of(Member member){
        MemberDto memberDto = MemberDto.builder()
                                        .userId(member.getUserId())
                                        .userName(member.getUserName())
                                        .phone(member.getPhone())

                                        .regDt(member.getRegDt())
                                        .emailAuthYn(member.isEmailAuthYn())
                                        .emailAuthDt(member.getEmailAuthDt())
                                        .emailAuthKey(member.getEmailAuthKey())

                                        .resetPasswordKey(member.getResetPasswordKey())
                                        .resetPasswordLimitDt(member.getResetPasswordLimitDt())
                                        .adminYn(member.isAdminYn())
                                        .userStatus(member.getUserStatus())
                                        .build();

        return  memberDto;
    }

 

  Note) 방금 가입하면 회원 상태 정보에 데이터가 들어가서 볼 수 있다.

 

 

   Ex) 회원 상태 변경하기

 

    - detail

<hide/>
<tr>
    <th>회원 상태</th>
    <td>
        <p>
            현재 상태: <span th:text="${member.userStatus}"></span>
        </p>
        <select name="userStatus">
            <option value="REQ">가입 승인 중</option>
            <option value="ING">정상 이용 중</option>
            <option value="STOP">정지 중</option>
        </select>
        <p th:text="${member.userStatus}"></p>
    </td>
</tr>

 

    - 관리자가 옵션을 선택해서 변경 가능하도록 구현한다.

 

 

    - 상태 변경 버튼을 누르면 서버로 전송되게끔 <form> 으로 감싼다.

      -> 상태만 전송하면 안되니까 <input> 으로 아이디도 함께 보낸다.

<hide/>
<form method="post" action="/admin/member/status.do">
    <input type="hidden" th:value="${member.userId}"/>
    <select name="userStatus">
        <option value="REQ">가입 승인 중</option>
        <option value="ING">정상 이용 중</option>
        <option value="STOP">정지 중</option>
    </select>
    <button type="submit">상태 변경</button>
</form>

 

    - 컨트롤러에서 주소를 만든다.

 

    - MemberStatusInput' 

<hide/>
package com.zerobase.fastlms.member.model;
import lombok.Data;
import lombok.ToString;
@Data
public class MemberStatusInput {

    String userId;
    String userStatus;
}

 

    - 컨트롤러

      -> redirect 로 주소를 직접 이동시킨다. 페이지 직접 이동

<hide/>
@PostMapping("/admin/member/status.do")
public String status(Model model, MemberStatusInput parameter){
    boolean result = memberService.updateStatus(parameter.getUserId(), parameter.getUserStatus());
    return "redirect:/admin/member/detail.do?userId?=" + parameter.getUserId();
}

 

    - 멤버서비스: updateStatus()

/**
 * 회원 상태 변경
 */

boolean updateStatus(String userId, String userStatus);

 

    - 임플 클래스에 위 메서드 구현

 

 

    - 기존 화면

    - 정지 중 선택

 

 

    - 상태 변경 클릭

 

========================오류 ========================

    - 오류:  상태변경을 클릭하면 로그인 페이지로 이동한다.

    - 원인: impl클래스에서 updateStatus()의 반환값을 false로 뒀다 틀림, .. 말고도 다른 원인이 있다.

      -> detail 파일의 <input>태그를 잘못 작성했다. 아래처럼 수정하면 된다.

<input th:value="${member.userId}" type="hidden" name="userId"/>

 

 

  Ex) 비밀번호 초기화

    - 사용자 아이디를 가지고 비밀번호를 초기화 하도록한다.

<hide/>
<tr>
    <th>비밀번호 초기화</th>
    <td>
        <div>
            <form method="post" action="/admin/member/password.do">
                <input th:value="${member.userId}" type="hidden" name="userId"/>
                <input type="text" name="password"/>
                <button type="submit">비밀번호 초기화</button>
            </form>
        </div>
    </td>
</tr>

 

   - 멤버 컨트롤러

<hide/>
@PostMapping("/admin/member/password.do")
public String password(Model model, MemberStatusInput parameter){
    boolean result = memberService.updatePassword(parameter.getUserId(), parameter.getUserStatus());
    return "redirect:/admin/member/detail.do?userId=" + parameter.getUserId();
}

 

    - admin에 모델 패키지를 하나 만든다. (멤버에도 이름이 같은 패키지가 있다.)

<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class MemberInput {

    String userId;
    String userStatus;
    String password;
}

 

     - 컨트롤러

<hide/>
@PostMapping("/admin/member/password.do")
public String password(Model model, MemberInput parameter){

    boolean result = memberService.updatePassword(parameter.getUserId(), parameter.getPassword());
    return "redirect:/admin/member/detail.do?userId=" + parameter.getUserId();
}

 

    - updatePassword()

<hide/>
@Override
public boolean updatePassword(String userId, String password) {

    Optional<Member> optionalMember = memberRepository.findById(userId);
    if(!optionalMember.isPresent()){
        throw new UsernameNotFoundException("회원 정보가 존재하지 않습니다.");
    }

    Member member = optionalMember.get();
    String encPassword = BCrypt.hashpw(password, BCrypt.gensalt());
    member.setPassword(encPassword);
    memberRepository.save(member);
    return true;
}

    - 비번 초기화

 

  Note) 실행 결과 - 정상