프로젝트 초기 설정 해줘야 한다. (블로그 초기 설정 참고하기)
css 추가(css 추가는 자료 찾아보면 금방 나온다.)
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();
}
}
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
이다.
✔️ 실행된 부분
MydailytpApplication
Application
이 실행된다.
HomeController
localhost:8080
을 실행하였을 때mainHome.html
화면을 띄워준다.
mainHome.html
나의 페이지 사진 페이지 달력 페이지를 눌렸을 때 로그인이 되어있지 않다면, 로그인 화면이 띄워진다.
로그인 화면 과정 : 로그인 화면 → 회원가입 → 홈 화면 → 로그인 화면 → 나의 페이지
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")
이 실행된다.
✔️ 회원가입 페이지 눌렀을 때
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"
을 입력시 로그인한 후, 이전에 요청한 주소로 이동한다.
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
에서 사용한다.
나의 페이지 → 나의 페이지 수정
나의 페이지 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
로 재요청한다.
I’ve always enjoyed strategic games, so when I tried my hand at online blackjack, I approached it like a chess match. I studied various strategies and tips, determined to apply them to my gameplay. One evening, I decided to test my skills with a larger bankroll. I carefully observed the dealer’s patterns and used basic strategy to guide https://magicalspincasino-france.com my decisions. My initial bets were conservative, but as I gained confidence and saw positive results, I gradually increased my stakes. By the end of the session, my strategic approach had paid off. I managed to turn a starting balance of $200 into over $1,000. It was incredibly satisfying to see that planning and strategy could lead to success, proving that blackjack is not just a game of luck but also one of skill.