회원 정보 페이지 : 내 강좌 목록, 내 정보 변경 가능하도록!
@GetMapping("/member/info")
public String memberInfo(Model model, Principal principal) {
String userId = principal.getName();
MemberDto detail = memberService.detail(userId);
model.addAttribute("detail", detail);
return "member/info";
}
@GetMapping("/member/password")
public String password(Model model, Principal principal) {
String userId = principal.getName();
MemberDto detail = memberService.detail(userId);
model.addAttribute("detail", detail);
return "member/password";
}
@GetMapping("/member/takecourse")
public String takecourse(Model model, Principal principal) {
String userId = principal.getName();
MemberDto detail = memberService.detail(userId);
model.addAttribute("detail", detail);
return "member/takecourse";
}
<div>
<a href="/member/info">회원 정보 수정</a>
|
<a href="/member/password">비밀번호 변경</a>
|
<a href="/member/takecourse">내 수강 목록</a>
<hr>
</div>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>마이페이지</title>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script>
<script>
$(document).ready(function () {
$('#submitForm').on('submit', function() {
var $thisForm = $(this);
var newPassword = $thisForm.find('input[name=newPassword]').val();
var newRePassword = $thisForm.find('input[name=newRePassword]').val();
if (newPassword !== newRePassword) {
alert('신규 비밀번호와 신규 확인 비밀번호가 일치하지 않습니다.');
return false;
}
});
});
</script>
</head>
<body>
<h1>마이페이지</h1>
<div th:replace="/fragments/layout.html :: fragment-body-menu"></div>
<div>
<a href="/member/info">회원 정보 수정</a>
|
<a href="/member/password">비밀번호 변경</a>
|
<a href="/member/takecourse">내 수강 목록</a>
<hr>
</div>
<div>
<form id="submitForm" method="post">
<div>
<input type="password" name="password" placeholder="현재 비밀번호 입력" required>
</div>
<div>
<input type="password" name="newPassword" placeholder="신규 비밀번호 입력" required>
</div>
<div>
<input type="password" name="newRePassword" placeholder="신규 확인 비밀번호 입력" required>
</div>
<div>
<button type="submit">비밀번호 변경</button>
</div>
</form>
</div>
</body>
</html>
@PostMapping("/member/password")
public String passwordSubmit(Model model
, MemberInput parameter
, Principal principal) {
String userId = principal.getName();
parameter.setUserId(userId);
ServiceResult result = memberService.updateMemberPassword(parameter);
if (!result.isResult()) {
model.addAttribute("message", result.getMessage());
return "common/error";
}
return "redirect:/member/info";
}
private String newPassword;
ServiceResult updateMemberPassword(MemberInput parameter);
@Override
public ServiceResult updateMemberPassword(MemberInput parameter) {
String userId = parameter.getUserId();
Optional<Member> optionalMember = memberRepository.findById(userId);
if (!optionalMember.isPresent()) {
return new ServiceResult(false,"회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get();
if (!BCrypt.checkpw(parameter.getPassword(), member.getPassword())) {
return new ServiceResult(false,"비밀번호가 일치하지 않습니다.");
}
String encPassword = BCrypt.hashpw(parameter.getNewPassword(), BCrypt.gensalt());
member.setPassword(encPassword);
memberRepository.save(member);
return new ServiceResult(true);
}
신규 비밀번호와 신규 확인 비밀번호 값이 일치하지 않으면 알림창
id가 존재하지 않으면 "회원 정보가 존재하지 않습니다" 에러 페이지로 이동
현재 비밀번호와 페이지에 적은 현재 비밀번호 칸의 입력값이 일치하지 않으면 "비밀번호가 일치하지 않습니다" 에러 페이지로 이동
readonly
<style>
.btn {
margin-top: 20px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script>
<script>
$(function () {
$('#updateForm').on('submit', function () {
if (!confirm('회원 정보를 수정 하시겠습니까?')) {
return false;
}
});
});
</script>
<div>
<form id="updateForm" method="post">
<table>
<tbody>
<tr>
<th>아이디(이메일)</th>
<td>
<p th:text="${detail.userId}">아이디</p>
</td>
</tr>
<tr>
<th>이름</th>
<td>
<p th:text="${detail.userName}">이름</p>
</td>
</tr>
<tr>
<th>전화번호</th>
<td>
<input name="phone" type="text" th:value="${detail.phone}">
<p th:text="${detail.phone}">전화번호</p>
</td>
</tr>
<tr>
<th>가입일</th>
<td>
<p th:text="${detail.regDtText}">가입일</p>
</td>
</tr>
<tr>
<th>회원정보 수정일</th>
<td>
<p th:text="${detail.uptDtText}">가입일</p>
</td>
</tr>
</tbody>
</table>
<div>
<button class="btn" type="submit">수정</button>
</div>
</form>
</div>
.uptDt(member.getUptDt())
public String getRegDtText() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss");
return regDt != null ? regDt.format(formatter) : "";
}
public String getUptDtText() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss");
return uptDt != null ? uptDt.format(formatter) : "";
}
전화번호만 수정 가능
@PostMapping("/member/info")
public String memberInfoSubmit(Model model
, MemberInput parameter
, Principal principal) {
String userId = principal.getName();
parameter.setUserId(userId);
ServiceResult result = memberService.updateMember(parameter);
if (!result.isResult()) {
model.addAttribute("message", result.getMessage());
return "common/error";
}
return "redirect:/member/info";
}
private LocalDateTime uptDt; // 회원정보 수정 날짜
ServiceResult updateMember(MemberInput parameter);
@Override
public ServiceResult updateMember(MemberInput parameter) {
String userId = parameter.getUserId();
Optional<Member> optionalMember = memberRepository.findById(userId);
if (!optionalMember.isPresent()) {
return new ServiceResult(false, "회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get();
member.setPhone(parameter.getPhone());
member.setUptDt(LocalDateTime.now());
memberRepository.save(member);
return new ServiceResult();
}
public ServiceResult() {
result=true;
}
처음에 했어야하는데..
못해서 지금..😅
우편번호, 주소, 상세주소
private String zipcode;
private String addr;
private String addrDetail;
private String zipcode;
private String addr;
private String addrDetail;
.zipcode(member.getZipcode())
.addr(member.getAddr())
.addrDetail(member.getAddDetail())
MemberInput의 이름은 info의 이름과 같아야 controller가 MemberInput에 매핑을 시켜주는 것임! 틀리면 안됨..
private String zipcode;
private String addr;
private String addrDetail;
<tr>
<th>주소</th>
<td>
<div>
<input type="text" name="zipcode" th:value="${detail.zipcode}" readonly>
<button type="button"> 우편번호 찾기 </button>
</div>
<div>
<input type="text" name="addr" th:value="${detail.addr}" readonly placeholder="주소 입력">
<input type="text" name="addrDetail" th:value="${detail.addrDetail}" placeholder="상세 주소 입력">
</div>
</td>
</tr>
member.setZipcode(parameter.getZipcode());
member.setAddr(parameter.getAddr());
member.setAddrDetail(parameter.getAddrDetail());
상세주소를 제외한 우편번호와 주소는 readonly로 직접 입력이 불가능
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
//본 예제에서는 도로명 주소 표기 방식에 대한 법령에 따라, 내려오는 데이터를 조합하여 올바른 주소를 구성하는 방법을 설명합니다.
var element_layer = document.getElementById('layer');
function closeDaumPostcode() {
// iframe을 넣은 element를 안보이게 한다.
element_layer.style.display = 'none';
}
function execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
// 조합된 참고항목을 해당 필드에 넣는다.
//document.getElementById("sample2_extraAddress").value = extraAddr;
} else {
//document.getElementById("sample2_extraAddress").value = '';
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('zipcode').value = data.zonecode;
document.getElementById("addr").value = addr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("addrDetail").focus();
// iframe을 넣은 element를 안보이게 한다.
// (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
element_layer.style.display = 'none';
},
width : '100%',
height : '100%',
maxSuggestItems : 5
}).embed(element_layer);
// iframe을 넣은 element를 보이게 한다.
element_layer.style.display = 'block';
// iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
initLayerPosition();
}
// 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
// resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
// 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
function initLayerPosition(){
var width = 300; //우편번호서비스가 들어갈 element의 width
var height = 400; //우편번호서비스가 들어갈 element의 height
var borderWidth = 5; //샘플에서 사용하는 border의 두께
// 위에서 선언한 값들을 실제 element에 넣는다.
element_layer.style.width = width + 'px';
element_layer.style.height = height + 'px';
element_layer.style.border = borderWidth + 'px solid';
// 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
}
</script>
api를 info.html
에 추가
<tr>
<th>주소</th>
<td>
<div>
<input type="text" id="zipcode" name="zipcode" th:value="${detail.zipcode}" readonly placeholder="우편 번호 입력">
<button onclick="execDaumPostcode()" type="button"> 우편번호 찾기 </button>
</div>
<div>
<input type="text" id="addr" name="addr" th:value="${detail.addr}" readonly placeholder="주소 입력">
<input type="text" id="addrDetail" name="addrDetail" th:value="${detail.addrDetail}" placeholder="상세 주소 입력">
</div>
</td>
</tr>
@GetMapping("/member/takecourse")
public String takecourse(Model model, Principal principal) {
String userId = principal.getName();
// 나의 수강 목록이 여러개일수도 있으니까 List로 가져오기
List<TakeCourseDto> list = takeCourseService.myCourse(userId);
model.addAttribute("list", list);
return "member/takecourse";
}
List<TakeCourseDto> myCourse(String userId);
@Override
public List<TakeCourseDto> myCourse(String userId) {
TakeCourseParam parameter = new TakeCourseParam();
parameter.setUserId(userId);
List<TakeCourseDto> list = takeCourseMapper.selectListMyCourse(parameter);
return list;
}
String userId;
<select id="selectListMyCourse"
resultType="com.kdew.dewlms.course.dto.TakeCourseDto">
SELECT tc.*, c.subject
FROM take_course tc
JOIN course c ON tc.course_id = c.id
WHERE tc.user_id = #{userId}
ORDER BY reg_dt DESC
limit #{pageStart}, #{pageEnd}
</select>
List<TakeCourseDto> selectListMyCourse(TakeCourseParam parameter);
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>마이페이지</title>
<style>
.list table {
margin-top: 20px;
width: 100%;
border-collapse: collapse;
}
.list table th, .list table td {
border: solid 1px #000;
}
p.nothing {
text-align: center;
padding: 100px;
}
</style>
</head>
<body>
<h1>마이페이지</h1>
<div th:replace="/fragments/layout.html :: fragment-body-menu"></div>
<div>
<a href="/member/info">회원 정보 수정</a>
|
<a href="/member/password">비밀번호 변경</a>
|
<a href="/member/takecourse">내 수강 목록</a>
<hr>
</div>
<div class="list">
<table>
<thead>
<tr>
<th> NO </th>
<th>
등록일
</th>
<th>
강좌명
</th>
<th>
상태
</th>
<th>
비고
</th>
</tr>
</thead>
<tbody id="dataList">
<tr th:each="x, i : ${list}">
<td th:text="${i.index + 1}">1</td>
<td>
<p th:text="${x.RegDtText}">2021.01.01</p>
</td>
<td>
<p th:text="${x.subject}">강좌명 </p>
</td>
<td>
<p th:if="${x.status eq 'REQ'}">수강신청</p>
<p th:if="${x.status eq 'COMPLETE'}">결제완료</p>
<p th:if="${x.status eq 'CANCEL'}">수강취소</p>
</td>
<td>
<div class="row-buttons" th:if="${x.status eq 'REQ'}">
<button type="button" th:value="${x.id}"> 수강취소 처리 </button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
thymeleaf index
기능을 이용해서 NO 번호 매겨주기
axios
로 수강취소 버튼을 누르면 만들어둔 api를 호출해서 수강취소 취소되게 만듦<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$('.row-buttons button').on('click', function() {
var id = $(this).val();
var msg = '수강취소 처리 하시겠습니까?';
if (!confirm(msg)) {
return false;
}
var url = '/api/member/course/cancel.api';
var parameter = {
takeCourseId: id
};
axios.post(url, parameter).then(function(response) {
console.log(response);
console.log(response.data);
response.data = response.data || {};
response.data.header = response.data.header || {};
if (!response.data.header.result) {
alert(response.data.header.message);
return false;
}
//정상적일때
alert(' 강좌가 정상적으로 취소되었습니다. ');
location.reload();
}).catch(function(err) {
console.log(err);
});
return false;
});
});
</script>
@RequiredArgsConstructor
@RestController
public class ApiMemberController {
private final TakeCourseService takeCourseService; // 현재 수강 목록을 가져오기 위해서
@PostMapping("/api/member/course/cancel.api")
public ResponseEntity<?> cancelCourse(Model model
, @RequestBody TakeCourseInput parameter
, Principal principal) {
String userId = principal.getName();
// 내가 신청한 강좌인지 확인
TakeCourseDto detail = takeCourseService.detail(parameter.getTakeCourseId());
if (detail == null) {
ResponseResult responseResult = new ResponseResult(false, "수강 신청 정보가 존재하지 않습니다.");
return ResponseEntity.ok().body(responseResult);
}
// 나의 수강신청 정보가 아닐 때
if (userId == null || !userId.equals(detail.getUserId())) {
ResponseResult responseResult = new ResponseResult(false, "본인의 수강 신청 정보만 취소할 수 있습니다.");
return ResponseEntity.ok().body(responseResult);
}
ServiceResult result = takeCourseService.cancel(parameter.getTakeCourseId());
if (!result.isResult()) {
ResponseResult responseResult = new ResponseResult(false, result.getMessage());
return ResponseEntity.ok().body(responseResult);
}
ResponseResult responseResult = new ResponseResult(true);
return ResponseEntity.ok().body(responseResult);
}
}
TakeCourseDto detail(long id);
ServiceResult cancel(long id);
@Override
public TakeCourseDto detail(long id) {
Optional<TakeCourse> optionalTakeCourse = takeCourseRepository.findById(id);
if (optionalTakeCourse.isPresent()) {
return TakeCourseDto.of(optionalTakeCourse.get());
}
return null;
}
@Override
public ServiceResult cancel(long id) {
Optional<TakeCourse> optionalTakeCourse = takeCourseRepository.findById(id);
if (!optionalTakeCourse.isPresent()) {
return new ServiceResult(false, "수강 정보가 존재하지 않습니다.");
}
TakeCourse takeCourse = optionalTakeCourse.get();
takeCourse.setStatus(TakeCourseCode.STATUS_CANCEL);
takeCourseRepository.save(takeCourse);
return new ServiceResult();
}
public static TakeCourseDto of(TakeCourse takeCourse) {
return TakeCourseDto.builder()
.id(takeCourse.getId())
.courseId(takeCourse.getCourseId())
.userId(takeCourse.getUserId())
.payPrice(takeCourse.getPayPrice())
.status(takeCourse.getStatus())
.regDt(takeCourse.getRegDt())
.build();
}
long takeCourseId;
take_course_dto 테이블이 갑자기 생성이 됨..
TakeCourseDto.java
에 @Entity
가 붙어있었다ㅠㅠ
<a href="/member/withdraw"> 회원 탈퇴 </a>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>마이페이지</title>
</head>
<body>
<h1>회원탈퇴</h1>
<div th:replace="/fragments/layout.html :: fragment-body-menu"></div>
<div>
<a href="/member/info">회원 정보 수정</a>
|
<a href="/member/password">비밀번호 변경</a>
|
<a href="/member/takecourse">내 수강 목록</a>
<hr>
</div>
<div>
<p>
회원 탈퇴를 하면
서비스를 더이상 이용하실 수 없습니다.
<br>
회원 탈퇴를 진행하시겠습니까?
</p>
<form id="submitForm" method="post">
<div>
<input type="password" name="password" placeholder="현재 비밀번호 입력" required/>
</div>
<div>
<button type="submit">회원 탈퇴 하기</button>
</div>
</form>
</div>
</body>
</html>
@GetMapping("/member/withdraw")
public String memberWithdraw(Model model) {
return "member/withdraw";
}
@PostMapping("/member/withdraw")
public String memberWithdrawSubmit(Model model
, MemberInput parameter
, Principal principal) {
String userId = principal.getName();
ServiceResult result = memberService.withdraw(userId,parameter.getPassword());
if (!result.isResult()) {
model.addAttribute("message", result.getMessage());
return "common/error";
}
return "redirect:/member/logout";
}
ServiceResult withdraw(String userId, String password);
loadUserByUsername
에
if (Member.MEMBER_STATUS_WITHDRAW.equals(member.getUserStatus())) {
throw new MemberStopUserException("탈퇴된 회원입니다.");
}
@Override
public ServiceResult withdraw(String userId, String password) {
Optional<Member> optionalMember = memberRepository.findById(userId);
if (!optionalMember.isPresent()) {
return new ServiceResult(false,"회원 정보가 존재하지 않습니다.");
}
Member member = optionalMember.get(); // 데이터 가져오기
// 패스워드 같지 않으면 에러!
if (!PasswordUtils.equals(password, member.getPassword())) {
return new ServiceResult(false, "비밀번호가 일치하지 않습니다.");
}
member.setUserName("삭제 회원");
member.setPhone("");
member.setPassword("");
member.setRegDt(null);
member.setUptDt(null);
member.setEmailAuthYn(false);
member.setEmailAuthDt(null);
member.setEmailAuthKey(null);
member.setResetPasswordKey("");
member.setResetPasswordLimitDt(null);
member.setUserStatus(MemberCode.MEMBER_STATUS_WITHDRAW);
member.setZipcode("");
member.setAddr("");
member.setAddrDetail("");
memberRepository.save(member);
return new ServiceResult();
}
String MEMBER_STATUS_WITHDRAW = "WITHDRAW";
public class PasswordUtils {
//비교
public static boolean equals(String plaintext, String hashed) {
if (plaintext == null || plaintext.length() < 1) {
return false;
}
if (hashed == null || hashed.length() < 1) {
return false;
}
return BCrypt.checkpw(plaintext, hashed);
}
// 해시 값 주기
public static String encPassword(String plaintext) {
if (plaintext == null || plaintext.length() < 1) {
return "";
}
return BCrypt.hashpw(plaintext,BCrypt.gensalt());
}
}
비밀번호가 일치하지 않으면 에러화면
회원 탈퇴가 완료되면 메인페이지로 이동
탈퇴된 아이디로 로그인을 하면 탈퇴된 회원입니다 라고 출력됨