일기장 토이 프로젝트
📚 프로젝트 초기 설정, 시작하기 앞서
프로젝트 초기 설정 해줘야 한다. (블로그 초기 설정 참고하기)
css 추가(css 추가는 자료 찾아보면 금방 나온다.)
프로젝트 초기 설정 해줘야 한다. (블로그 초기 설정 참고하기)
css 추가(css 추가는 자료 찾아보면 금방 나온다.)
📖 A. domain
domain/member/Member
package project.mydailytp.domain.member;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
// 로그인할 때 사용하는 클래스
@Data
public class Member {
// id 지정 (DB 번호)
private Long id;
@NotEmpty
private String nickname; // 사용자 이름
@NotEmpty
private String loginId; // 로그인 ID
@NotEmpty
private String password; // 비밀번호
}
domain/member/MemberRepository
package project.mydailytp.domain.member;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.*;
@Slf4j
@Repository
public class MemberRepository {
// Member : id, email, password, nickname
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L; // member가 추가될 때마다 +1 된다.
// 저장하기 위해서 호출한 메서드
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
// id를 찾습니다.
public Member findById(Long id) {
return store.get(id);
}
// 로그인을 시도한 id가 회원가입한 id인지 확인하기 위해 호출한 메서드
public Optional<Member> findByLoginId(String loginId) {
return findAll().stream()
.filter(m -> m.getLoginId().equals(loginId))
.findFirst();
}
// 저장소에서 값들을 모두 꺼내서 ArrayList에 담는다.
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
// 저장소를 비운다.
public void clearStore() {
store.clear();
}
}
domain/login/LoginService
package project.mydailytp.domain.login;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import project.mydailytp.domain.member.Member;
import project.mydailytp.domain.member.MemberRepository;
@Service
@RequiredArgsConstructor // final이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.
public class LoginService {
private final MemberRepository memberRepository;
public Member login(String loginId, String password) {
return memberRepository.findByLoginId(loginId)
.filter(m -> m.getPassword().equals(password))
.orElse(null);
}
}
domain/mypage/MyPage
package project.mydailytp.domain.mypage;
import lombok.Data;
@Data
public class MyPage {
private Long id; // 번호
private String name;
private int birthday; // 생년월일
private String school; // 학교
private String position; // 기술스택
private String book; // 책
private String game; // 게임
private String study; // 공부
private String blog; // 블로그
public MyPage() {
}
public MyPage(String name, int birthday, String school, String position, String book, String game, String study, String blog) {
this.name = name;
this.birthday = birthday;
this.school = school;
this.position = position;
this.book = book;
this.game = game;
this.study = study;
this.blog = blog;
}
}
domain/mypage/MyPageRepository
package project.mydailytp.domain.mypage;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Repository
public class MyPageRepository {
// id와 Item요소들을 저장하기 위한 변수
private static final Map<Long, MyPage> store = new HashMap<>();
private static long sequence = 0L; // Long의 초기값
// 값을 저장하기 위한 메소드
public MyPage save(MyPage item) {
item.setId(++sequence);
store.put(item.getId(), item);
return item;
}
// 값을 반환하기 위해 생성한 메소드
public MyPage findById(Long id){
return store.get(id);
}
// 저장되어 있는 값들을 ArrayList로 반환해준다.
public List<MyPage> findAll() {
return new ArrayList<>(store.values());
}
private String name;
private Long birthday; // 생년월일
private String school; // 학교
private String position; // 기술스택
// 업데이트
public void update(Long itemId, MyPage updateParam) {
MyPage findItem = findById(itemId);
findItem.setName(updateParam.getName());
findItem.setBirthday(updateParam.getBirthday());
findItem.setSchool(updateParam.getSchool());
findItem.setPosition(updateParam.getPosition());
findItem.setBook(updateParam.getBook());
findItem.setBlog(updateParam.getBlog());
findItem.setGame(updateParam.getGame());
findItem.setStudy(updateParam.getStudy());
}
// 전체적으로 정리
public void clearStore(){
store.clear();
}
}
📖 B. WebConfig, TestData
WebConfig
package project.mydailytp;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/" ,"/members/add",
"/login", "/logout", "/css/**", "/*.ico", "/error");
}
}
TestDataInit
package project.mydailytp;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import project.mydailytp.domain.member.Member;
import project.mydailytp.domain.member.MemberRepository;
import project.mydailytp.domain.mypage.MyPageRepository;
import javax.annotation.PostConstruct;
@Component
@RequiredArgsConstructor
public class TestDataInit {
private final MyPageRepository myPageRepository;
private final MemberRepository memberRepository;
@PostConstruct
public void init() {
Member member = new Member();
member.setNickname("테스트");
member.setLoginId("test");
member.setPassword("test!");
memberRepository.save(member);
}
}
이제부터는 web
이다.
📚 1. 홈 화면
✔️ 실행된 부분
MydailytpApplication
Application
이 실행된다.
HomeController
localhost:8080
을 실행하였을 때mainHome.html
화면을 띄워준다.
mainHome.html
- 나의 페이지, 사진 페이지, 달력 페이지가 띄워진다.
- 페이지 버튼을 클릭 시 해당 페이지로 이동한다. (현재는 나의 페이지만 실행된다.)
📚 2. 로그인
나의 페이지 사진 페이지 달력 페이지를 눌렸을 때 로그인이 되어있지 않다면, 로그인 화면이 띄워진다.
로그인 화면 과정 : 로그인 화면 → 회원가입 → 홈 화면 → 로그인 화면 → 나의 페이지
📖 A. 로그인 화면
나의 페이지 사진 페이지 달력 페이지를 눌렸을 때 로그인이 되어있지 않다면, 로그인 화면이 띄워진다.
로그인 화면 과정 : 로그인 화면 → 회원가입 → 홈 화면 → 로그인 화면 → 나의 페이지
html
에서 @{/mypage}
를 클릭했을 경우 localhost:8080/mypage
로 이동한다.
mypage
구현한 쪽은?
다만, 아직 로그인이 인증이 되지 않아 인터셉터는 적절하지 않은 요청이라 판단, 컨트롤러 호출하지 않는다. (컨트롤러 예외 발생)
excludePathPatterns
을 보면 인터셉터를 적용하지 않을 부분에/mypage
가 없다.- 그럼?
- 현재 세션이
null
이다. - 미인증 사용자 요청이라 판단하여,
/login?redirectURL=
로 이동한다.
✔️ 로그인 화면
위에서 /login?redirectURL=
로 이동한다고 했으니
loginForm
메소드를 통해template/login/loginForm
이 실행된다.
template/login/loginForm
- 회원가입 페이지를 누를 시,
localhost:8080/members/add
로 이동하고 - 로그인을 클릭시
@PostMapping("/login")
이 실행된다. - 취소 버튼시 메인으로 돌아간다.
📚 3. 회원 가입 화면
✔️ 회원가입 페이지 눌렀을 때
MemberController
localhost:8080/members/add
가 실행된다.
members/addMemberForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>회원 가입</h2>
</div>
<form action="" th:action th:object="${member}" method="post">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>
<div>
<label for="nickname">이름</label>
<input type="text" id="nickname" th:field="*{nickname}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{nickname}" />
</div>
<div>
<label for="loginId">아이디</label>
<input type="text" id="loginId" th:field="*{loginId}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{loginId}" />
</div>
<div>
<label for="password">비밀번호</label>
<input type="password" id="password" th:field="*{password}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{password}" />
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">회원 가입</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg" onclick="location.href='items.html'" th:onclick="|location.href='@{/login}'|"
type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
- 이제 화면에 나온 것을 입력한다.
- 취소 버튼을 눌리시
localhost:8080/login
으로 돌아간다. - 회원 가입 버튼을 눌렀을 시 밑의
save
메소드가 실행된다.
MemberController/save
- 검증에서 오류가 발생할시 다시 회원가입 화면에 띄워진다.
- 아니라면
memberRepository
에 회원 정보를 저장하고,root: localhost:8080
으로 이동한다.
✔️ 홈 화면으로 이동함
✔️ 회원가입한 아이디, 패스워드 입력
로그인 폼이 실행되며
login/loginForm
이 실행된다.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>로그인</h2>
</div>
<form action="item.html" th:action th:object="${loginForm}" method="post">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>
<div>
<label for="loginId">로그인 ID</label>
<input type="text" id="loginId" th:field="*{loginId}" class="form-control" th:errorclass="field-error">
<div class="field-error" th:errors="*{loginId}" />
</div>
<div>
<label for="password">비밀번호</label>
<input type="password" id="password" th:field="*{password}" class="form-control" th:errorclass="field-error">
<div class="field-error" th:errors="*{password}" />
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-secondary btn-lg" type="button" th:onclick="|location.href='@{/members/add}'|">회원가입</button>
</div>
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">로그인</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg" onclick="location.href='items.html'" th:onclick="|location.href='@{/}'|" type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>`
id
와 password
를 입력한다.
loginForm
에서 로그인을 클릭했을 때 submit
이 실행되어 입력된 loginForm
이 post
형식으로 전달된다.
- 검증오류 발생시 다시
login/loginForm
으로 이동한다. - 입력한
loginId
와Password
를Member
에 발급받아,loginService
에서 체크한다.
loginid
를 찾고, 입력한 password
가 맞다면 해당 값을 return
해준다.
아니라면 null
을 반환해준다. (아닐시 다시 login/loginForm
으로 이동한다.)
// 로그인 성공했다면
// 세션이 있으면 있는 세션을 반환, 없으면 신규 세션을 반환
HttpSession session = request.getSession();
// 세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginmember);
return "redirect:"+redirectURL;
이제 마지막으로 로그인에 성공했다면
- 세션이 있으면 해당 세션을 반환받는다.
- 없으면 신규 세션을 반환받는다.
세션에 로그인 회원 정보를 보관한다. (loginService
로부터 id
와 password
발급받았다.)
이제 재요청한 주소로 이동한다.
- 메인 화면에서 →
mypage
로 이동하려 했으나, 로그인이 되지 않아 로그인 페이지로 온 상태 - 이럴 때
"redirect:"+redirectURL"
을 입력시 로그인한 후, 이전에 요청한 주소로 이동한다.
📚 4. 나의 페이지
mypage
는 WebConfig
에서 예외된 주소가 아니기 때문에 매번 실행할 때마다 로그인 인증 체크를(LoginCheckInterceptor
) 하게 된다.
mypage
는 WebConfig
에서 예외된 주소가 아니기 때문에 매번 실행할 때마다 로그인 인증 체크를(LoginCheckInterceptor
) 하게 된다.
로그인 된 상태에서 나의 페이지를 클릭했을 시에는 myPageLogin
메서드가 실행된다.
- 이제 반환 값으로
mypage/myPageList
가 실행된다.
로그인 후 바로 나의 페이지가 실행되면 myPageLogin
메서드 실행되지 않고 mypage/myPageList
가 바로 실행된다.
mypage/myPageList
가 실행된다.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>나의 페이지</h2>
</div>
<form action="item.html" th:action method="post">
<div align="right">
<input align="left" type="image" id="picture" name="picture">
<input align="right" type="text" id="name" name="name" placeholder="이름">
</div>
<div align="right">
<input type="text" id="birthday" name="birthday" placeholder="생년월일">
</div>
<div align="right">
<input type="text" id="school" name="school" placeholder="학교">
</div>
<div align="right">
<input type="text" id="position" name="position" placeholder="포지션">
</div>
<div class="py-5 text-left">
<h5>취미</h5>
</div>
<div align="left">
<input type="text" id="book" name="book" placeholder="책">
</div>
<div align="left">
<input type="text" id="game" name="game" placeholder="게임">
</div>
<div align="left">
<input type="text" id="study" name="study" placeholder="공부하고 있는 것">
</div>
<div align="left">
<input type="text" id="blog" name="blog" placeholder="블로그">
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">수정</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/}'|"
type="button">취소</button>
</div>
</div>
</form>
<div class="col">
<form th:action="@{/mypage/logout}" method="post">
<button class="w-100 btn btn-dark btn-lg" onclick="location.href='location.href='items.html'" type="submit">
로그아웃
</button>
</form>
</div>
</div> <!-- /container -->
</body>
</html>
mypage
화면 이다.
✔️ 이외 로그아웃 관련하여
form
위치를 잘 확인하며html
에 로그아웃 소스를 삽입한다.
- 로그아웃 버튼을 클릭시
/mypage/logout
소스가post
형식으로 실행된다.
- 세션이 있을 경우 제거한다.
redirect
로 되돌아가기를 한다.- 실행 시, 홈 화면으로 돌아가며 로그인을 다시해야 한다.
✔️ 나의 페이지 및 수정 edit
- 저장된 데이터들이 화면에 띄워져야한다.
초기값을 설정해야한다.
- 먼저 사용자 회원가입 정보를 저장할 때 초기값을 넣어주며,
member.id
를 저장한다.- 왜냐면, 나의 페이지 초기값들을 띄워져야하고, 어떤
user
의 데이터인지 알아야한다.
- 왜냐면, 나의 페이지 초기값들을 띄워져야하고, 어떤
로그인 Postmapping
- 로그인에 성공할 시,
session.setAttribute(SessionConst.LOGIN_MEMBER, loginmember);
세션에Member
를 저장한다.
나의 페이지 소스
- 여기서 보면, 매개변수로
Member
가 있는데 이거는 로그인할 때 저장한Member
이다. (출력해보면 아이디 패스워드에 해당하는Member
가 호출된다. 세션의 기능) mypage/myPageList
뷰를 띄워준다.
mypage/myPageList
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>나의 페이지</h2>
</div>
<form action="myPageEditForm.html" th:action th:object="${mypage}" method="post">
<div align="right">
<input align="left" type="image" id="picture" name="picture">
<input align="right" type="text" id="name" th:field="*{name}" placeholder="이름" readonly>
</div>
<div align="right">
<input type="text" id="birthday" th:field="*{birthday}" placeholder="생년월일" readonly>
</div>
<div align="right">
<input type="text" id="school" th:field="*{school}" placeholder="학교" readonly>
</div>
<div align="right">
<input type="text" id="position" th:field="*{position}" placeholder="포지션" readonly>
</div>
<div class="py-5 text-left">
<h5>취미</h5>
</div>
<div align="left">
<input type="text" id="book" th:field="*{book}" placeholder="책" readonly>
</div>
<div align="left">
<input type="text" id="game" th:field="*{game}" placeholder="게임" readonly>
</div>
<div align="left">
<input type="text" id="study" th:field="*{study}" placeholder="공부하고 있는 것" readonly>
</div>
<div align="left">
<input type="text" id="blog" th:field="*{blog}" placeholder="블로그" readonly>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg"
th:onClick="|location.href='@{/mypage/{myPageId}/edit(myPageId=${mypage.id})}'|"
type="button">수정</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/}'|"
type="button">취소</button>
</div>
</div>
</form>
<div class="col">
<form th:action="@{/mypage/logout}" method="post">
<button class="w-100 btn btn-dark btn-lg" onclick="location.href='location.href='items.html'" type="submit">
로그아웃
</button>
</form>
</div>
</div> <!-- /container -->
</body>
</html>
여기서 th:action th:object="${mypage}"
가 있는데 object
를 mypage
로 하고, 자식? 다른 메소드들을 호출한다.
<input type="text" id="birthday" th:field="*{birthday}" placeholder="생년월일" readonly>
에서 *{birthday}
와 같이
<button class="w-100 btn btn-primary btn-lg"
th:onClick="|location.href='@{/mypage/{myPageId}/edit(myPageId=${mypage.id})}'|"
type="button">수정</button>
'@{/mypage/{myPageId}/edit(myPageId=${mypage.id})}'
이게 어떤 뜻인가?
locathost:8080/mypage/{여기는 mypage의 id를}/edit
- 뒤에
(myPageId=${mypage.id})
는myPageId
에mypage
의id
를 넣어주겠다는 의미!
ex) mypage.id
가 2라면
➡️ /mypage/2/edit
이 된다!
✔️ 나의 페이지 수정 화면
@GetMapping("/{myPageId}/edit"
, @PathVariable("myPageId") Long id
을 어떻게 선언한 것이며 받은 것인지 알아보면, 바로 윗줄에 html
설명을 보면, {myPageId}/edit(myPageId=${mypage.id})
와 같이 id
를 전달하였다.
➡️ 전달한 것을 받은 것이다!
이후, myPageRepository
에서 id
를 통해 데이터를 찾고 mypage
에 넣어 mypage/myPageEditForm
에서 사용한다.
📚 5. 수정 화면
나의 페이지 → 나의 페이지 수정
나의 페이지 View
호출 (@GetMapping
)
myPage
에서id
로 레포지토리에서 찾는다.- 다음,
myPageEditForm
html
화면을 띄운다.
✔️ 나의 페이지 수정 화면
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>나의 페이지 수정</h2>
</div>
<form th:action th:object="${mypage}" method="post">
<h4>개인정보 수정</h4>
<div>
<label for="name" th:text="#{mypage.name}"></label>
<input type="text" id="name" th:field="*{name}"
class="form-control" placeholder="이름 입력">
<div class="field-error" th:errors="*{name}">
이름 입력 오류
</div>
</div>
<div>
<label for="birthday" th:text="#{mypage.birthday}"></label>
<input type="text" id="birthday" th:field="*{birthday}"
th:errorclass="field-error" class="form-control"
placeholder="생일 입력">
<div class="field-error" th:errors="*{birthday}">
생년월일 입력 오류
</div>
</div>
<div>
<label for="school" th:text="#{mypage.school}"></label>
<input type="text" id="school" th:field="*{school}"
th:errorclass="field-error" class="form-control"
placeholder="학교 입력">
<div class="field-error" th:errors="*{school}">
학교 이름 오류
</div>
</div>
<div>
<label for="position" th:text="#{mypage.position}"></label>
<input type="text" id="position" th:field="*{position}"
th:errorclass="field-error" class="form-control"
placeholder="포지션 입력">
<div class="field-error" th:errors="*{position}">
기술스택 입력 오류
</div>
</div>
<h4>취미 수정</h4>
<div>
<label for="book" th:text="#{mypage.book}"></label>
<input type="text" id="book" th:field="*{book}"
th:errorclass="field-error" class="form-control"
placeholder="책 입력">
<div class="field-error" th:errors="*{book}">
책 입력 오류
</div>
</div>
<div>
<label for="game" th:text="#{mypage.game}"></label>
<input type="text" id="game" th:field="*{game}"
th:errorclass="field-error" class="form-control"
placeholder="게임 입력">
<div class="field-error" th:errors="*{game}">
게임 입력 오류
</div>
</div>
<div>
<label for="study" th:text="#{mypage.study}"></label>
<input type="text" id="study" th:field="*{study}"
th:errorclass="field-error" class="form-control"
placeholder="스터디 입력">
<div class="field-error" th:errors="*{study}">
스터디 입력 오류
</div>
</div>
<div>
<label for="blog" th:text="#{mypage.blog}"></label>
<input type="text" id="blog" th:field="*{blog}"
th:errorclass="field-error" class="form-control"
placeholder="포지션 입력">
<div class="field-error" th:errors="*{blog}">
개인 블로그 입력 오류
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">저장</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='myPageList.html'"
th:onclick="|location.href='@{/mypage}'|"
type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
여기서
<label for="값"> : label 태그는 input 태그를 제어하여 상태를 변경하게 도와주는 태그
<form th:action th:object="${mypage}" method="post">
<input type="text" id="name" th:field="*{name}" : ${mypage} → th:field="*{name}" => mypage.name
이다.
- 이제 데이터 수정을 한다.
저장 버튼을 클릭시, 아래 소스가 실행된다.
- 검증 결과 오류가 발생시 다시
나의 페이지 수정
화면으로 돌아간다. - 이제 나의 페이지 데이터를 업데이트한다.
- 그리고,
mypage
로 재요청한다.
- 나의 페이지 수정이 완료된 화면이다.
Author And Source
이 문제에 관하여(일기장 토이 프로젝트), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@chang626/일기장-토이-프로젝트저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)