Spring/게시판 만들기 프로젝트

[Spring Boot] #3_2 게시판 만들기

sujin7837 2020. 12. 21. 13:32
반응형

학습 목표

-로그인 기능 구현을 통한 쿠키와 세션에 대한 대략적인 이해

-로그인 사용자에 대한 접근 제한

 

학습 목차

3-1. 로그인 기능 구현

3-2. 로그인 상태에 따른 메뉴 처리 및 로그아웃

3-3. 로그인 사용자에 한해 자신의 정보를 수정하도록 수정

3-4. 중복 제거 및 읽기 좋은 코드를 위한 리팩토링

3-5. 질문하기, 질문목록 기능 구현

3-6. 원격 서버에 소스 코드 배포

 

 

//User.java

@Entity	
public class User {
	@Id	
    @GeneratedValue	
    private Long id;
    
    @Column(nullable=false, length=20)	
	private String userId;
    
    private String password;
    private String name;
    private String email;
    
    public Long getId() {	//1.
    	return id;
    }
    
    public void setUserId(String userId) {
    	this.userId=userId;
    }
    
    public String getPassword() {	//1.
    	return password;
    }
    
    public void setPassword(String password) {
    	this.password=password;
    }
    
    public void setName(String name) {
    	this.name=name;
    }
    
    public void setEmail(String email) {
    	this.email=email;
    }
    
    public void update(User newUser) {	
    	this.password=newUser.password;
        this.name=newUser.name;
        this.email=newUser.email;
    }
    
    @Override
    public String toString() {	
    	return "User [userId=" + userId + ", password=" + password + ", name=" + name, "email=" + email +"]";
    }
}
   

1. id와 password에 대한 getter를 만들어줍니다.

 

//HttpSessionUtils.java
//session에 관한 중복을 제거하기 위한 클래스

public class HttpSessionUtils {
	public static final String USER_SESSION_KEY="sessionedUser";	//1.

	public static boolean isLoginUser(HttpSession session) {
    	Object sessionedUser=session.getAttribute(USER_SESSION_KEY);
        if(sessionedUser==null) {	//2.
        	return false;
        }
        return true;
    }
    
    public static User getUserFromSession(HttpSession session) {	//3.
    	if(!isLoginUser(session) {
        	return null;
        }
        
        return (User)session.getAttribute(USER_SESSION_KEY);
    }
}

1. USER_SESSION_KEY: 값을 전달하기 위한 상수(대문자와 언더바(_)로 이루어진 변수는 관례적으로 상수)

 

2. 로그인을 했는지 확인하는 코드입니다.

 

3. 세션으로부터 사용자 정보를 가져오는 메소드입니다.

 

//UserController.java

@Controller
@RequestMapping("/users")	
public class UserController {
	@Autowired	
	private UserRepository userRepository;

	@GetMapping("/loginForm")	
    public String loginForm() {
    	return "/sessionedUser/login";	
    }
    
    @PostMapping("/login")	
    public String login(String userId, String password, HttpSession session) {	
    	User user=userRepository.findByUserId(userId);	
        if(user==null) {	
        	System.out.println("Login Failure!");
        	return "redirect:/users/loginForm";	
        }
        if(!password.equals(user.getPassword()) {	
        	System.out.println("Login Failure!");	
        	return "redirect:/users/loginForm";
        }
        System.out.println("Login Success!");
        session.setAttribute(HttpSessionUtils.USER_SESSION_KEY, user);	//1.
        
        return "redirect:/";
    }
    
    @GetMapping("/logout")	
    public String logout(HttpSession session) {
    	session.removeAttribute(HttpSessionUtils.USER_SESSION_KEY);	//1.
        return "redirect:/";
    }

	@GetMapping("/form")	
    public String form() {
    	return "/user/form";
    }

	@PostMapping("")	
	public String create(User user) {
    	System.out.println("user : " + user);
        userRepository.save(user);	
    	return "redirect:/users";	
    }
    
    @GetMapping("")
    public String list(Model model) {
    	model.addAttribute("users", userRepository.findAll());	
    	return "/user/list";
    }    
    
    @GetMapping("/{id}/form")	
    public String updateForm(@PathVariable Long id, Model model, HttpSession session) {
    	Object tempUser=session.getAtrribute("sessionedUser");	
        if(HttpSessionUtils.isLoginUser(session)) {	//2.	
        	return "redirect:/users/loginForm";
        }
        
        User sessionedUser=HttpSessionUtils.getUserFromSession(session);	//3.
        if(!id.equals(sessionedUser.getId()){	
        	throw new IllegalStateException("You can't update the another user");
        }
        
        User user=userRepository.findById(id).get();
    	model.addAttribute("user", userRepository.findById(id).get())	
    	return "/user/updateForm";
    }
    
    @PutMapping("/{id}")
    public String update(@PathVariable Long id, User updatedUser, HttpSession session) {
    	Object tempUser=session.getAtrribute("sessionedUser");	
        if(tempUser==null) {	
        	return "redirect:/users/loginForm";
        }
        
        User sessionedUser=(User)tempUser;
        if(!id.equals(sessionedUser.getId()){	
        	throw new IllegalStateException("You can't update the another user");
        }
        
    	User user=userRepository.findById(id).get();	
        user.update(updatedUser);	
        userRepository.save(user);	
    	return "redirect:/users";	
    }
}

1. sessionedUser를 HttpSessionUtils.USER_SESSION_KEY로 바꿔줍니다.

 

2. tempUser를 HttpSessionUtils클래스의 isLoginUser 메소드를 이용해서 바꿔줍니다.

 

3. tempUser를 HttpSessionUtils클래스의 getUserFromSession 메소드를 이용해서 바꿔줍니다.

 

//User.java

@Entity	
public class User {
	@Id	
    @GeneratedValue	
    private Long id;
    
    @Column(nullable=false, length=20)	
	private String userId;
    
    private String password;
    private String name;
    private String email;
    
    public boolean matchId(Long newId) {	//2.
    	if(newId==null) {
        	return false;
        }
        return newId.equals(id);
    }
    
    public void setUserId(String userId) {
    	this.userId=userId;
    }
    
    public boolean matchPassword(String newPassword) {	//1.
    	if(newPassword==null) {
        	return false;
        }
        return newPassword.equals(password);
    }
    
    public void setPassword(String password) {
    	this.password=password;
    }
    
    public void setName(String name) {
    	this.name=name;
    }
    
    public void setEmail(String email) {
    	this.email=email;
    }
    
    public void update(User newUser) {	
    	this.password=newUser.password;
        this.name=newUser.name;
        this.email=newUser.email;
    }
    
    @Override
    public String toString() {	
    	return "User [userId=" + userId + ", password=" + password + ", name=" + name, "email=" + email +"]";
    }
}
   

1. password가 일치하는지 확인하는 메소드를 만들어줘서 password에 대한 get 메소드를 없앱니다.

 

2. id가 일치하는지 확인하는 메소드를 만들어서 id에 대한 get 메소드를 없앱니다.

 

//UserController.java

@Controller
@RequestMapping("/users")	
public class UserController {
	@Autowired	
	private UserRepository userRepository;

	@GetMapping("/loginForm")	
    public String loginForm() {
    	return "/sessionedUser/login";	
    }
    
    @PostMapping("/login")	
    public String login(String userId, String password, HttpSession session) {	
    	User user=userRepository.findByUserId(userId);	
        if(user==null) {	
        	System.out.println("Login Failure!");
        	return "redirect:/users/loginForm";	
        }
        if(!user.matchPassword(password)) {	//1.
        	System.out.println("Login Failure!");	
        	return "redirect:/users/loginForm";
        }
        System.out.println("Login Success!");
        session.setAttribute(HttpSessionUtils.USER_SESSION_KEY, user);	
        
        return "redirect:/";
    }
    
    @GetMapping("/logout")	
    public String logout(HttpSession session) {
    	session.removeAttribute(HttpSessionUtils.USER_SESSION_KEY);	
        return "redirect:/";
    }

	@GetMapping("/form")	
    public String form() {
    	return "/user/form";
    }

	@PostMapping("")	
	public String create(User user) {
    	System.out.println("user : " + user);
        userRepository.save(user);	
    	return "redirect:/users";	
    }
    
    @GetMapping("")
    public String list(Model model) {
    	model.addAttribute("users", userRepository.findAll());	
    	return "/user/list";
    }    
    
    @GetMapping("/{id}/form")	
    public String updateForm(@PathVariable Long id, Model model, HttpSession session) {
    	Object tempUser=session.getAtrribute("sessionedUser");	
        if(HttpSessionUtils.isLoginUser(session)) {	
        	return "redirect:/users/loginForm";
        }
        
        User sessionedUser=HttpSessionUtils.getUserFromSession(session);	
        if(!sessionedUser.matchId(id)){	//2.
        	throw new IllegalStateException("You can't update the another user");
        }
        
        User user=userRepository.findById(id).get();
    	model.addAttribute("user", userRepository.findById(id).get())	
    	return "/user/updateForm";
    }
    
    @PutMapping("/{id}")
    public String update(@PathVariable Long id, User updatedUser, HttpSession session) {
    	Object tempUser=session.getAtrribute("sessionedUser");	
        if(tempUser==null) {	
        	return "redirect:/users/loginForm";
        }
        
        User sessionedUser=(User)tempUser;
        if(!sessionedUser.matchId(id)){	//2.
        	throw new IllegalStateException("You can't update the another user");
        }
        
    	User user=userRepository.findById(id).get();	
        user.update(updatedUser);	
        userRepository.save(user);	
    	return "redirect:/users";	
    }
}

1. matchPassword 메소드를 이용하게 되면, User 클래스에서 password에 대한 get 메소드가 필요 없어집니다.

 

2. matchId 메소드를 이용하게 되면, User 클래스에서 id에 대한 get 메소드가 필요 없어집니다.

 

1.,2.번 방식을 통해 데이터를 가지고 있는 클래스로부터 데이터를 자꾸 꺼내오지 않아도 되도록 바뀝니다. 데이터를 외부로 노출시키지 않는 것이 좋기 때문에, 객체를 만들어서 데이터가 아닌 객체를 이용하는 방식을 이용하는 것이 객체지향 개발에서 좋습니다.

 

//application.properties

spring.mustache.suffix: .html
spring.mustache.prefix=classpath:/templates/
spring.mustache.expose-session-attributes=true

spring.datasource.url=jdbc:h2:~/my-slipp	
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true	//1.
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

1. 쿼리를 좀 더 깔끔하게 정리해서 볼 수 있습니다.


<!--/qna/form-->
<!--form.html-->

<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="utf-8">
{{> /include/header}}
</head>
<body>
{{> /include/navigation}}

<div class="container" id="main">
   <div class="col-md-12 col-sm-12 col-lg-10 col-lg-offset-1">
      <div class="panel panel-default content-main">
          <form name="question" method="post" action="/questions">	<!--1.-->
              <div class="form-group">
                  <label for="title">제목</label>
                  <input type="text" class="form-control" id="title" name="title" placeholder="제목"/>
              </div>
              <div class="form-group">
                  <label for="contents">내용</label>
                  <textarea name="contents" id="contents" rows="5" class="form-control"></textarea>
              </div>
              <button type="submit" class="btn btn-success clearfix pull-right">질문하기</button>
              <div class="clearfix" />
          </form>
        </div>
    </div>
</div>

{{> /include/footer}}
	</body>
</html>

1. submit 버튼을 눌러서 질문을 등록했을 때, action에 저장된 "/questions"라는 url로 제목과 내용이 전달됩니다.

 

//QuestionController.java

@Controller	//1.
@RequestMapping("/questions")	//2.
public class QuestionController {
	@GetMapping("/form")
    public String form(HttpSession session) {	//3. 
    	if(HttpSessionUtils.isLoginUser(session)) {
        	return "/users/loginForm";
        }
    	return "/qna/form";
    }
    
    @PostMapping("")
    public String create(String title, String contents, HttpSession session) {	//4.
    	if(!HttpSessionUtils.isLoginUser(session)) {
        	return "/users/loginForm";
        }
        User sessionUser=HttpSessoinUtils.getUserFromSession(session);
        
    	return "redirect:/";
    }
}

1. 질문하기 form.html의 컨트롤러를 추가해줍니다.

 

2. 컨트롤러와 form.html 파일을 연결해줍니다.

 

3. 질문하는 사용자의 정보를 받아옵니다.

 

4. 질문을 생성합니다.

 

<!--index.html-->

<div class="col-md-3 qna-write">
	<a href="/questions/form" class="btn btn-primary pull-right" role="button">질문하기</a>	<!--1.-->
</div>

1. 질문하기 경로를 RequestMapping의 id인 "/questions"의 form으로 바꿔줍니다.

 

//Question.java

@Entity
public class Question {	//1.
	@Id
    @GeneratedValue
    private Long id;
    private String writer;
    private String title;
    private String contents;
    
    public Question() {}	//2.
    
    public Question(String writer, String title, String contents) {
    	super();
        this.writer=writer;
        this.title=title;
        this.contents=contents;
    }
}

1. 질문에 대한 정보를 저장하는 클래스를 만듭니다.

 

2. Question에 대한 default 값, 즉 기본 생성자가 필요합니다.

 

//QuestionRepository.java

public interface QuestionRepository extends JpaRepository<Question, Long> {	//1.

}

1. Question들을 DB에 연결하는 역할을 하는 인터페이스 입니다.

 

//User.java

@Entity	
public class User {
	@Id	
    @GeneratedValue	
    private Long id;
    
    @Column(nullable=false, length=20)	
	private String userId;
    
    private String password;
    private String name;
    private String email;
    
    public boolean matchId(Long newId) {	
    	if(newId==null) {
        	return false;
        }
        return newId.equals(id);
    }
    
    public void setUserId(String userId) {
    	this.userId=userId;
    }
    
    public String getUserId() {	//1.
    	return userId;
    }
    
    public boolean matchPassword(String newPassword) {	
    	if(newPassword==null) {
        	return false;
        }
        return newPassword.equals(password);
    }
    
    public void setPassword(String password) {
    	this.password=password;
    }
    
    public void setName(String name) {
    	this.name=name;
    }
    
    public void setEmail(String email) {
    	this.email=email;
    }
    
    public void update(User newUser) {	
    	this.password=newUser.password;
        this.name=newUser.name;
        this.email=newUser.email;
    }
    
    @Override
    public String toString() {	
    	return "User [userId=" + userId + ", password=" + password + ", name=" + name, "email=" + email +"]";
    }
}
   

1. userId에 대한 get 메소드가 필요합니다.

 

//QuestionController.java

@Controller	
@RequestMapping("/questions")	
public class QuestionController {
	@Autowired	//2.
    private QuestionRepository questionRepository;

	@GetMapping("/form")
    public String form(HttpSession session) {	
    	if(HttpSessionUtils.isLoginUser(session)) {
        	return "/users/loginForm";
        }
    	return "/qna/form";
    }
    
    @PostMapping("")
    public String create(String title, String contents, HttpSession session) {	
    	if(!HttpSessionUtils.isLoginUser(session)) {
        	return "/users/loginForm";
        }
        User sessionUser=HttpSessoinUtils.getUserFromSession(session);
        Question newQuestion=new Question(sessionUser.getUserId(), title, contents);	//1.
        questionRepository.save(newQuestion);	//3.
    	return "redirect:/";
    }
}

1. Question을 생성합니다.

 

2. @Autowired: 해당 Repository를 스프링 프레임워크가 관리하고 있는데, 이것을 사용하고자 하니 인자를 전달해달라고 요청하는 어노테이션

 

3. questionRepository에 질문을 저장합니다.

 

//HomeController.java

@Controller
public class HomeController {
	@Autowired
	private Questionrepository questionRepository;	//1.

	@GetMapping("")
    public String home() {	
    	model.addAttribute("questions", questionRepository.findAll());	//2.
    	return "index";
    }
}

 

1. 질문 목록을 가져옵니다.

 

2. "questions"라는 이름으로 model에 조회할 데이터 목록을 담아서 전달합니다.

 

<!--index.html-->

<div class="container" id="main">
		<div class="col-md-12 col-sm-12 col-lg-10 col-lg-offset-1">
			<div class="panel panel-default qna-list">
				<ul class="list">
					{{#questions}}	<!--1.-->
					<li>
						<div class="wrap">
							<div class="main">
								<strong class="subject"> <a href="/questions/{{id}}">{{title}}</a>	<!--2.-->
								</strong>
								<div class="auth-info">
									<i class="icon-add-comment"></i> <span class="time">2020-12-21 18:47</span> 
                                    <a href="./user/profile.html" class="author">{{writer.userId}}</a>	<!--2.-->
								</div>
								<div class="reply" title="댓글">
									<i class="icon-reply"></i> <span class="point">8</span>
								</div>
							</div>
						</div>
					</li>
					{{/questions}}
				</ul>
				<div class="row">
					<div class="col-md-3"></div>
					<div class="col-md-6 text-center">
						<ul class="pagination center-block" style="display: inline-block;">
							<li><a href="#">«</a></li>
							<li><a href="#">1</a></li>
							<li><a href="#">2</a></li>
							<li><a href="#">3</a></li>
							<li><a href="#">4</a></li>
							<li><a href="#">5</a></li>
							<li><a href="#">»</a></li>
						</ul>
					</div>
					<div class="col-md-3 qna-write">
						<a href="/questions/form" class="btn btn-primary pull-right"
							role="button">질문하기</a>
					</div>
				</div>
			</div>
		</div>
	</div>

1. 질문 목록에 해당되는 내용이므로 mustache를 이용해서 html에 연결해줍니다.

 

2. mustache 문법으로 질문의 제목과 글쓴이를 전달해줍니다.

반응형