D+58::(스프링부트_게시판_최종 실습) 로그인/로그아웃/회원가입/글목록조회/상세조회/글등록/수정/삭제

Am.Vinch·2022년 9월 19일
0
post-thumbnail

202209119_MON

  • 프록젝트 내 검색 단축기
    ctrl + shift + R : 파일명 검색
    ctrl + H : 키워드 검색

input태그 데이터 값 유지하기

실습내용
Q) 로그인 실패하면 다시 로그인창으로 이동하는데, 이 때 입력한 아이디값을 어떻게 보여지게하는가?

  • 로그인 실패시, 로그인페이지로 다시 이동할시 때 커맨드객체로 memberVO가 있기때문에 자동으로 넘어온다. 그러면 컨트롤러에서 매개변수로 사용한 MemberVO 커맨드객체를 html에서 th:object 속성값으로 커맨드객체를 가져오면, th:field속성 id,name,value값들이 자동으로 생성되는데 여기서 value값이 있기때문에 화면에 보여질 수 있다.
  • 참고사항) input 태그에 th:field값을 생성시 input태그 안에서 value값이 표현되지만, th:text값을 사용시에는 input태그 밖에서 값이 표현되는 것을 화면에서 알 수 있다. 이 둘의 차이점을 알 필요가 있다.

스프링부트_게시판페이지 완성하기

프로젝트명: FinalBoard(springBoot)

실습내용

    1. 게시글 목록 html 파일의 하단의 글쓰기 버튼 생성
    1. 버튼 클릭 시 게시글 작성 페이지로 이동
    1. 글쓰기 버튼은 로그인한 유저한테만 보여야 한다.
      (로그인안하면 글쓰기 작성 불가)
    1. 글등록을 위해 제목,내용만 입력받는다.
    1. 작성자는 로그인한 사람으로 자동등록되도록 한다.
    1. 해당 글쓰기는 Validation 처리로 한다.
    1. 글등록 후, alert() 띄우고, 게시글 목록 페이지로 이동한다.

  • 기능구현

로그인 , 로그아웃 (세션)

회원가입

글목록조회/상세보기

글등록/수정/삭제


  1. 첫화면 board_list.html 을 띄운다.
  • board_list.html
package kh.study.board.member.controller;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import kh.study.board.member.service.MemberService;
import kh.study.board.member.vo.MemberVO;

@Controller
@RequestMapping("/member")
public class MemberController {
	@Resource(name = "memberService")//서비스임플에서 어노테이션으로 만든 객체가져온다.
	private MemberService memberService;


	// ---------------------- 커맨드객체 정의 -------------------------------------------//
	// 커맨드객체는 html로 데이터를 전달하는 코드를 작성하지 않아도 자동으로 넘어간다.
	// 이때 데이터가 넘어가는 이름은 클래스명에서 앞글자만 소문자로 변경된 이름으로 넘어간다.
	//-----------------------------------------------------------------------------------//
	
	// 회원가입_a태그로 top파일에서 넘어올 때 @GetMapping매핑
	@GetMapping("/join") // a태그는 무조건 get 방식!!!(암기)
	public String join(MemberVO memberVO) {
		// 자동으로 커맨드 객체는 넘어간다.(memberVO) //커맨드객체에 아이디값 임의로 넣어주면, html로 넘어갈때 데이터 넘어감
		System.out.println(memberVO);

		return "content/member/join";
	}
	
	
	//-------------validation처리 :유효성 검사-----------------------------------------//
	// @Valid : post로 전달된 데이터가 검증 규칙을 따르는지 판단하는 어노테이션
	// 해당 어노테이션 다음에는 반드시 검증할 객체가 valid 다음 바로 와야한다
	// BindingResult :검증 대상 객체와 검증 결과의 대한 정보를 담고 있는 객체.
	// 검증객체 바로 다음에 선언되어야 한다.
	// 실제로 input태그에 입력한 데이터가 memberVO에 들어오는 걸 바인딩이라고하는데, 그 결과를 말함.
	// model도 매개변수로 넣어준이유?
	// : model 같이 매개변수 사용하면 데이터를 굳이 담지않아도(눈에보이지않아도) 바인딩결과의 오류여부정보와 검증한 객체memberVO 값을
	// 자동으로 리턴값 join.html에 보내준다.
	//----------------------------------------------------------------------------------//
	
	// 회원가입_form태그로 top파일에서 넘어올 때 @PostMapping매핑
	@PostMapping("/join")
	public String joinProcess(@Valid MemberVO memberVO, BindingResult bindingResult, Model model) {

		//System.out.println("!!!!!!" + memberVO.getMemberTell()); // "010,1111,2222"
		//System.out.println("!!!!!!" + memberVO.getMemberPw()); // "010,1111,2222"
		//String[] tells = memberVO.getMemberTells(); for(String e : tells) {//리스트는
		//반복문으로 데이터꺼내기 System.out.println(e); }

		// 1) validation 체크 (데이터 유효성 검증)
		if (bindingResult.hasErrors()) {// 바인딩하는데 오류가 생겼니?
			System.out.println("error발생!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			return "content/member/join";// 에러발생하면 join페이지로 다시 이동
		}

		// 2)회원가입 쿼리 실행
		memberService.join(memberVO);

		// 3)페이지이동
		return "redirect:/board/list";
	}

	
	// 로그인_a태그로 top파일에서 넘어올 때
	//주의사항!!! login.html에서 커맨드객체를 object로 사용하려면, 반드시 매개변수로 memberVO가 있어야한다!
	@GetMapping("/login") // a태그는 무조건 get 방식!!!(암기)
	public String login(MemberVO memberVO) {
		System.out.println("로그인 a태그로 클릭해서 넘어왔다!!!!!!!!!!!!!!!!!!");
		
		return "content/member/login";
	}
	
	// 로그인_form태그로 top파일에서 넘어올 때
	//!!로그인은 유효성검사 필요없다
	@PostMapping("/login")//form태그와 ajax일때 -> 무조건 post!! 나머지 get!
	public String loginProcess(HttpSession session, MemberVO memberVO) {
		//로그인 쿼리 실행
		MemberVO loginInfo = memberService.login(memberVO);
		System.out.println("로그인하러 넘어왔다");
		
		if (loginInfo != null) {
			//로그인 세션사용하려면 위의 쿼리문으로 만든 로그인인포만든것을 세션에 값을 넣어주는대신,
			//위에 매개변수로 만들어주면 된다.!!
			session.setAttribute("loginInfo", loginInfo);
			//로그인성공하면 바로 게시판목록페이지이동
		}
		else {
			System.out.println("로그인실패!!!!!!!!!!!!!!!!!!!!!!!!!!");
			//로그인 실패시에도 다시 로그인페이지로 이동
			// 회원가입 실패시, 입력 데이터를 유지???
			// 화면에 입력한 데이터를 그대로 가져가려면! 무조건 페이지로 이동해야한다.
			// 다시말해서, 아래 redirect로는 데이터를 가져갈 수 있는 방법을 아직 안배워서 안된다.
			//  return "redirect:/member/login"; 데이터가져갈수없다!(아직..방법안배움)
			return "content/member/login";//데이터가져가기때문에 input태그에 입력한 값들이 남아있는다.
			//대신 html에 데이터넘기려면 model객체 매개변수필요할 수 있지만!
			//커맨드 객체인 memeberVO가 이미 있기때문에 데이터를 보낼 때 굳이 model 객체가 매개변수로 필요하지않다.

		}
		return "redirect:/board/list";
	}
	// 로그아웃
	@GetMapping("/logout")
	public String logout(HttpSession session) {
		//세션 데이터 제거하기(로그아웃)
		session.removeAttribute("loginInfo");
		
		return "redirect:/board/list";
	}
}
  • 컨트롤러
    • memberController
- boardController
package kh.study.board.board.controller;
@Controller
@RequestMapping("/board")
public class BoardController {
	@Resource(name = "boardService")
	private BoardService boardService;
	
	//게시글 목록페이지
	@RequestMapping("/list")
	public String select(Model model,BoardVO boardVO) {
		model.addAttribute("boardList",boardService.selectBoard());
		return "content/board/board_list";
	}
	
	// 글쓰러가기
	@GetMapping("/reg")
	public String reg(BoardVO boardVO) {
		return"content/board/reg_board";
	}
	// 글 등록
	@PostMapping("/reg")
	public String reg(@Valid BoardVO boardVO, BindingResult bindingResult, Model model ,HttpSession session) {
		//System.out.println(boardVO);
		
		System.out.println("유효성 체크!!!!");
		
		// 주의! 순서중요하다. 유효성체크 먼저 한 후, 로그인정보값 boardVO에 넣어주기 !!
		if (bindingResult.hasErrors()) {
			System.out.println("에러발생!!!!");
			return"content/board/reg_board";//html페이지로 가야 데이터가 남아있는다
		}

		//작성자(memberId)는 PK이기때문에 글등록양식페이지에서 화면에 보이도록 띄워줘야함
		MemberVO loginInfo = (MemberVO)session.getAttribute("loginInfo");
		boardVO.setMemberId(loginInfo.getMemberId());
		boardService.insertBoard(boardVO);
		
		return "content/board/reg_result";
	}

	//게시글 상세조회
	@GetMapping("/detail")
	public String selectDetail(@RequestParam(required = false) int boardNum,Model model) {
		model.addAttribute("board", boardService.selectDetailBoard(boardNum));
		return "content/board/board_detail";
		
	}
	
	
	// 글수정하러가기
	@GetMapping("/update")
	public String update(BoardVO boardVO , int boardNum) {//매개변수는 커맨드객체 or model 값 둘 중 하나만 사용가능하다
		//(주의)수정 전에는 어떤 글을 수정할 것인지 "상세조회" 반드시 먼저해준다!!!
		
		//-----(방법1) 커맨드 객체 사용할 때---------------------------------//
		// 커맨드객체를 사용하기때문에 update_form html파일에서 th:object와 th:field 사용가능하다
		// 그리고 id,name,value 속성값이 자동생성 가능하다는 장점이 있다.
		BoardVO result =  boardService.selectDetailBoard(boardNum);
		boardVO.setBoardNum(result.getBoardNum());
		boardVO.setContent(result.getContent());
		boardVO.setTitle(result.getTitle());
		boardVO.setCreateDate(result.getCreateDate());
		boardVO.setMemberId(result.getMemberId());
		
		
		//-----(방법2) model사용할 때-------------------------------------------------//
		// 하지만, model을 사용할때는 커맨드객체사용이 불가하기때문에 달러+{객체명.컬럼명}으로 데이터불러와 사용가능하다.
		// 또 id,name,value 속성값이 자동생성되지않는다
		//model.addAttribute("board",boardService.selectDetailBoard(boardNum) );
		return "content/board/update_board_form";
	}
	// 글수정 등록
	@PostMapping("/update")
	public String update(BoardVO boardVO) {
		 boardService.update(boardVO);
		 return "content/board/update_result";//수정확인 후,alert창 띄워보기
//		 return "redirect:/board/detail?boardNum=" + boardVO.getBoardNum();//수정확인 후, 다시 상세보기로
	}
	// 글삭제
	@GetMapping("/delete")
	public String delete(int boardNum) {
		boardService.delete(boardNum);
		return "redirect:/board/list";
	}
}
  • VO
    1.memberVO
package kh.study.board.member.vo;


import java.util.Iterator;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import groovy.transform.ToString;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@ToString
public class MemberVO {
	//참고//
	//@NotNull //javax로시작하는 어노테이션 사용 : null허용x / "", " "빈값,공백 허용가능
	
	@NotBlank(message = "id는 필수입력입니다.") //null,""," " 모두 허용x
	private String memberId;
	
	//정규식 코드 사용해야한다.(복사붙여넣기)
	// 8-16자리, 영어대소문자,특수문자포함(#은 포함시키지않음! 주의!)	
//    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,16}$" 
//    		, message = "비밀번호 형식에 맞지 않습니다")
	//(편의상 일단, validation없이 사용
	@NotBlank(message = "PW는 필수입력입니다.") //null,""," " 모두 허용x
	private String memberPw;
	
	@NotBlank(message = "이름은 필수입력입니다.")
	@Size(max = 5,message = "제한된 길이를 초과했습니다.")// max : 영어숫자 상관없이 최대문자길이 뜻함
	private String memberName;
	
	@Size(min = 9, max = 12)
	private String memberTell;//01012345678
	
	private String isAdmin;
	private String memberStatus;
	
	
	//memberTells라는 name값으로 연락처 세 개의 값을 받아서 배열을 먼저 만든다.
	//배열로 만든 memberTells
	private String[] memberTells;//010,1234,5678
	
	//별도로 만들지 않아도 된다 왜냐며, 아래처럼lmbok으로 자동 생성된다.
//	public String getMemberTell() {
//	return memebrTell; }>>> 아무것도 변형을 안하면 null값이 들어온다. 
//  그럼 null값이 안들오도록 어떻게 만드냐?
	//변형하여 getter만든다.
	public String getMemberTell() {
		//아래처럼 리턴값을 던지려고 한다. 그러면 어떻게 해야할까?
		/* return memberTells[0] + memberTells[1] + memberTells[2]; */
		
		//if eles 문을 사용해서 데이터 던져준다.
		if (memberTells == null) {
			return null;
		}		
		else {// 연락처가 null이 아니라면, 배열에서 하나씩 값을 빼와서 문자열을 누적시켜서 result값으로 데이터 던진다
			String result = "";
			for(String tell : memberTells) {//010,1234,5678
				result += tell;//01012345678
			}
			return result; //01012345678 
		}
	}
}

2.boardVO

package kh.study.board.board.vo;

import groovy.transform.ToString;

import javax.validation.constraints.NotBlank;


import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@ToString
public class BoardVO {
	
	private int boardNum;
	
	@NotBlank(message = "제목은 필수 입력사항입니다.")
	private String title;
	@NotBlank(message = "내용은 필수 입력사항입니다")
	private String content;
	
	private String memberId;
	
	private String createDate;
}
  • service/serviceImpl
    • board
package kh.study.board.board.vo;

import groovy.transform.ToString;

import javax.validation.constraints.NotBlank;


import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@ToString
public class BoardVO {
	
	private int boardNum;
	
	@NotBlank(message = "제목은 필수 입력사항입니다.")
	private String title;
	@NotBlank(message = "내용은 필수 입력사항입니다")
	private String content;
	
	private String memberId;
	
	private String createDate;
}
package kh.study.board.board.service;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kh.study.board.board.vo.BoardVO;
@Service("boardService")
public class BoardServiceImpl implements BoardService{
	@Autowired//어노테이션으로 객체생성
	private SqlSessionTemplate sqlSession;
	//글등록
	@Override
	public void insertBoard(BoardVO boardVO) {
		sqlSession.insert("boardMapper.insertBoard",boardVO);
	}
	//게시글목록
	@Override
	public List<BoardVO> selectBoard() {
		return sqlSession.selectList("boardMapper.selectBoard");
	}
	//상세보기
	@Override
	public BoardVO selectDetailBoard(int boardNum) {
		return sqlSession.selectOne("boardMapper.selectDetailBoard", boardNum);
	}
	//글수정
	@Override
	public void update(BoardVO boardVO) {
		 sqlSession.update("boardMapper.update", boardVO);

	}
	//글삭제
	@Override
	public void delete(int boardNum) {
		 sqlSession.delete("boardMapper.delete",boardNum);
	}

	
}
  • member
package kh.study.board.member.service;
public interface MemberService {
	 void join(MemberVO memberVO);
	 MemberVO login(MemberVO memberVO);
}
package kh.study.board.member.service;
@Service("memberService")//컨트롤러에 보내 사용할 객체를 어노테이션을 이용해 보내준다.
public class MemberServiceImpl implements MemberService{
	@Autowired//어노테이션으로 객체생성
	private SqlSessionTemplate sqlSession;

	//회원가입
	@Override
	public void join(MemberVO memberVO) {
		sqlSession.insert("memberMapper.join", memberVO);
	}
	//로그인
	@Override
	public MemberVO login(MemberVO memberVO) {
		return sqlSession.selectOne("memberMapper.login", memberVO);
	}
}

  • mapper
    1.board-mapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--해당 파일에 모든 쿼리문을 작성 -->
<mapper namespace="boardMapper">
<resultMap type="kh.study.board.board.vo.BoardVO" id="board">
	<id column="BOARD_NUM" property="boardNum"/>
	<result column="TITLE" property="title"/>
	<result column="CONTENT" property="content"/>
	<result column="MEMBER_ID" property="memberId"/>
	<result column="CREATE_DATE" property="createDate"/>
</resultMap>
<!-- 글등록 -->	
<insert id="insertBoard">
INSERT INTO BOARD (BOARD_NUM , TITLE , CONTENT , MEMBER_ID)
VALUES(
	( SELECT  NVL ( MAX (BOARD_NUM) , 0 ) + 1  FROM BOARD) 
	, #{title} 
	, #{content}
	, #{memberId}
	)
</insert>

<!-- 게시글목록조회 -->
<select id="selectBoard" resultMap="board">
SELECT BOARD_NUM,TITLE,CONTENT,MEMBER_ID,TO_CHAR(CREATE_DATE,'YYYY-MM-DD') AS CREATE_DATE
FROM BOARD
ORDER BY BOARD_NUM DESC
</select>

<!-- 게시글 상세조회 -->
<select id="selectDetailBoard" resultMap="board">
SELECT BOARD_NUM,TITLE,CONTENT,MEMBER_ID,TO_CHAR(CREATE_DATE,'YYYY-MM-DD') AS CREATE_DATE
FROM BOARD
WHERE BOARD_NUM = #{boardNum}
</select>

<!-- 글수정 -->
<update id="update" >
UPDATE BOARD
SET TITLE = #{title}
	,CONTENT = #{content}
WHERE BOARD_NUM = #{boardNum}
</update>

<!-- 글삭제 -->
<delete id="delete">
DELETE BOARD
WHERE BOARD_NUM = #{boardNum}
</delete>

</mapper> 

2.member-mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--해당 파일에 모든 쿼리문을 작성 -->
<mapper namespace="memberMapper">
	<!-- 패키지명 클래스명 -->
	<!-- type 오류방지위해 memberVO 위에 패키지명 복붙하면 된다! -->
 	 <resultMap type="kh.study.board.member.vo.MemberVO" id="selectMember">
		<id column="MEMBER_ID" 	property="memberId"/>
		<result column="MEMBER_PW" property="memberPw"/>
		<result column="MEMBER_NAME" property="memberName"/>
		<result column="MEMBER_TELL" property="memberTell"/>
		<result column="IS_ADMIN" property="isAdmin"/>
		<result column="MEMBER_STATUS" property="memberStatus"/>
	</resultMap>  

<!-- 회원가입 -->
<!-- memberTells 배열값으로 받아오는 연락처는 어떻게 받아오는가?? -->
<!-- #{memberTell} -> memberVO.getMemberTell(); 
     : 컨트롤러에서 넘어오는 memberVO라는 객체의 getter를 호출하게된다.  
     :memberTells에서 memberTell로 다시 만들어줘야한다.-->
<insert id="join">
INSERT INTO BOARD_MEMBER (
		MEMBER_ID, MEMBER_PW, MEMBER_NAME, MEMBER_TELL,MEMBER_STATUS
) VALUES(
		#{memberId},#{memberPw},#{memberName},#{memberTell},'ACTIVE'
)
</insert>   

<!-- 로그인 -->
<!-- 주의사항!! : 조회시 관리자여부같이조회해야하면서, 회원상태가 활성화되어있어야하는 조건이 필요하다!!! -->
<select id="login" resultMap="selectMember">
SELECT MEMBER_ID , MEMBER_NAME ,IS_ADMIN
FROM BOARD_MEMBER
WHERE MEMBER_ID = #{memberId} 
AND MEMBER_PW = #{memberPw} 
AND MEMBER_STATUS = 'ACTIVE'
</select>
</mapper> 

  • static

    • css

      @charset "UTF-8";
      @font-face {
          font-family: 'HallymGothic-Regular';
          src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2204@1.0/HallymGothic-Regular.woff2') format('woff2');
          font-weight: 400;
          font-style: normal;
      }
      body{
          font-family: 'HallymGothic-Regular';
          font-size: 25px;
      }
      a{
          text-decoration: none;
          color: #224B0C;
      
      }
      a:hover {
          color: #A1C298;
      }
      .hasError {
          color: red;
          font-style: italic;
      }
      button{
          text-decoration: none;
          color: #224B0C;
      
      }

  • 가장 먼저, base-layout.html
<!DOCTYPE html>
<!-- 타임리프,레이아웃 기능 사용하겠다 -->
<html xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- 부트스트랩 사용 1 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
<link href="/css/common.css" rel="stylesheet">
</head>
<body>

<!-- 화면에 보여지는 모든 내용은 여기 div태그 안에서 표시된다 -->
<div class="container">
	<div class="row">
		<div class="col">
			<div th:replace="fragment/top::topFragment" ></div>
		</div>
	</div> 
	<div class="row">
		<div class="col">
			<div layout:fragment="content"></div>
		</div>
	</div> 
</div>
<!-- jquery 로딩 -->
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<!-- 부트스트랩 로딩 _사용 2 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>

</body>
</html>
  • top.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<div th:fragment="topFragment">
<!-- 로그인 클릭시 로그인html파일 이동방법 -->
	<!-- 1번 <a href="/board/login">login</a> -->
	<!-- 2번 <a th:href="@{/board/login}"> login</a><!-- 타임리프 경로이동시!!! 골뱅이+중괄호 -->
	<div class="row">
		<div class="col text-end" style="color:#224B0C;">
			<!-- 주의할 점 : sessionScope 는 jsp // session 은 타임리프 th-->
			<th:block th:if="${session.loginInfo == null}">
				<a th:href="@{/member/login}">login</a><!-- a태그 -> get방식!!! -->
				<a th:href="@{/member/join}">join</a><!-- a태그 -> get방식!!! -->
				<!-- <span th:onclick="location.href='/board/join';"></span> -->
			</th:block>
			<th:block th:unless="${session.loginInfo == null}">
					[[${session.loginInfo.memberName}]]님 반갑습니다😊 
				<a th:href="@{/member/logout}">LOGOUT</a><!-- a태그 -> get방식!!! -->
			</th:block>
		</div>
	</div>
	<br>
	<br>
	<div class="row">
		<div class="col text-center" style="background-color: #EEF2E6;">
			<span style="font-weight: bold; font-size: 50px; color: #3D8361;">B O A R D</span>
		</div>
	</div>
	
</div>

</html>

  • board

  • board_list

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}"><!-- 콘솔 warn 안뜨기위해 -->
	
<div layout:fragment="content">
<br>
<br>

	<div>
		<table class="table">
		  <thead class="table-light">
			<tr>
				<td>글번호</td>
				<td>글제목</td>
				<td>작성일</td>
				<td>작성자</td>
			</tr>
		  </thead>
		  
		  <tbody>
		  <th:block th:if="${#lists.size('boardList') == 0}">
			<tr>
				<td colspan="4"> 게시글이 없습니다.</td>
			</tr>
			</th:block>
			<!--  boardListr가져올때 홀따옴표 감싸서 가져오기 주의!!!! -->
		  <th:block th:unless="${#lists.size('boardList') == 0}" >
			<tr th:each="board : ${boardList}">
				<td th:text="${board.boardNum}"></td>
				<td>
					<a th:href="@{/board/detail(boardNum=${board.boardNum})}" th:text="${board.title}"></a>
				</td>
				<td th:text="${board.createDate}"></td>
				<td th:text="${board.memberId}"></td>
			</tr>
		  </th:block>
		  </tbody>
		</table>	
	</div>
	
	<div align="center">
		<th:block th:if="${session.loginInfo != null}">
			<button type="button" class=" btn btn-outline-success" th:onclick="@{location.href='/board/reg';}">글쓰기</button>
		</th:block>
	</div>
</div>

</html>
  • reg_board
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
	layout:decorate="~{layout/base_layout}">
	
<div layout:fragment="content">
	<div class="row justify-content-center">
		<form th:action="@{/board/reg}" method="post" th:object="${boardVO}">
			<div class="mb-3">
				<label for="exampleFormControlInput1" class="form-label">작성자 :</label> 
				[[${session.loginInfo.memberId}]]
			</div>
			<div class="mb-3">
				<label for="exampleFormControlInput1" class="form-label" >글 제목 :</label> 
				<input name="title" type="text" class="form-control" th:field="*{title}" placeholder="제목을 입력하세요">
				<div class="hasError" th:if="${#fields.hasErrors('title')}" th:errors="*{title}" ></div>
			</div>
			<div class="mb-3">
				<label for="exampleFormControlTextarea1" class="form-label">글 내용 :</label>
				<textarea name="content" class="form-control" th:field="*{content}" rows="3" placeholder="내용을 입력하세요"></textarea>
				<div class="hasError" th:if="${#fields.hasErrors('content')}" th:errors="*{content}" ></div>
			
			</div>
			<div align="center">
				<button class="btn btn-light" type="submit" th:onclick="@{location.href='/board/reg';}">글등록</button>
			</div>
		</form>
	</div>
</div>

</html>
  • reg_result
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}">
	
<div layout:fragment="content">
<script type="text/javascript">
	alert('글쓰기등록성공!');
	location.href='/board/list';
</script>
</div>

</html>
  • board_detail
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}">
	
<div layout:fragment="content">
		<div class="mb-3" >
		  <label for="exampleFormControlspan1" class="form-label">글 번호 :</label>
		<th:block th:text="${board.boardNum}"></th:block> 
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlspan1" class="form-label"> 작성자 :</label>
		  	<th:block th:text="${board.memberId}"></th:block> 
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlspan1" class="form-label"> 작성일 :</label>
		  <th:block th:text="${board.createDate}"></th:block> 
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlspan1" class="form-label">글제목 :</label>
	  		  <th:block th:text="${board.title}"></th:block> 
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlTextarea1" class="form-label">글내용 :</label>
		  	<div th:text="${board.content}"></div>
		</div>
		<div align="center">
			<th:block th:if="${session.loginInfo != null}">
				<button type="button" class="btn btn-light" th:onclick="|location.href='@{/board/update(boardNum=${board.boardNum})}'|">수정</button>
				<button type="button" class="btn btn-danger" th:onclick="|location.href='@{/board/delete(boardNum=${board.boardNum})}'|">삭제</button>
			</th:block>
			<button type="button" class="btn btn-dark" th:onclick="@{location.href='/board/list';}">뒤로가기</button>
		</div>
</div>

</html>
  • update_board_form
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}">
	
<div layout:fragment="content">
	<form  th:action="@{/board/update}" method="post" th:object="${boardVO}" >
		<!-- 문제점 해결 -->
		<!-- (유의) input태그에서 th:text를 사용하면 input태그밖에 값이 화면에 보여진다.
		form태그는 값이 입력되는 값을 넘겨주기 때문에 th:block이 아닌 input으로 넘겨야한다!!!  아니면 데이터를 들고가지못함
		input태그는 창이 입력받기때문에 글번호가 수정될수없도록 readonly값으로 속성을 둬야한다 -->
		
		<!-- 글번호는 히든값으로 넘겨준다 pk이기때문에 상세보기가려면 필요하기때문이다! -->

		<input type="hidden" th:field="*{boardNum}" readonly>
		<div class="mb-3">
		  <label for="exampleFormControlInput1" class="form-label"> 작성자 :</label>
		<th:block th:text="*{memberId}" <th:field="*{memberId}"></th:block>
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlInput1" class="form-label"> 작성일 :</label>
		<th:block th:text="*{createDate}" th:field="*{createDate}"></th:block>
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlInput1" class="form-label">글제목 :</label>
		  <input th:field="*{title}"  type="text" class="form-control" id="exampleFormControlInput1" >
		</div>
		
		<div class="mb-3">
		  <label for="exampleFormControlTextarea1" class="form-label">글내용 :</label>
		  <textarea th:field="*{content}" th:text="${content}" class="form-control" id="exampleFormControlTextarea1" rows="3" ></textarea>
		</div>
		<div align="center">
			<button type="submit" class="btn btn-light">수정완료</button>
		</div>
	</form>
</div>

</html>
  • update_result
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}">
	
<div layout:fragment="content">
<script type="text/javascript">
	alert('글 수정 완료했습니다.');
	location.href='/board/list';
</script>
</div>

</html>

  • member
    - join
    <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
	layout:decorate="~{layout/base_layout}">

<!-- th 타임리프사용하려면 경로이동은!!! 골뱅이+{}사용하기!!! -->
<!-- th:object="${memberVO}" 는 데이터받기위해 사용 : 컨트롤러에서 보낸 객체 받아오기!!! 
커맨드객체 memberVO는 자동으로 데이터 가져와서 사용가능하다  -->
<!-- 단, th:field는 th:object와 반드시 함께 사용하는 애트리뷰트 -->
<!-- formx태그에 th:object로 넘어오는 커맨드객체를 지정할 경우
	해당 폼 태그 내에서 th:field 속성으로 자동으로 id,name,value 값을 세팅할 수 있다. -->
<!-- 개발자모드 콘솔을 보면 th:feild속성값만으로 name,id,value값 모두 확인가능하다!!! -->

<!-- th:field 받지않고 원래 사용하던 방법  -->
<!-- id :<input type="text" name="memberId" id="memberId"> -->

<!-- th:field  받아와서 사용하는 방법 -->
<!-- *{memberId}라는 값으로 들어오는 데이터 받아와서 사용하겠다. -->

<div layout:fragment="content">
	<!-- row justify-content-center : 가운데 정렬 -->
	<div class="row justify-content-center">
		<div class="col-6">
			<form th:action="@{/member/join}" class="row g-3" method="post" th:object="${memberVO}">
				<div class="col-12">
					<label for="memberId" class="form-label"> ID </label> 
					<input type="text" class="form-control" th:field="*{memberId}">
					<!-- 이 html파일에 위의 object의 해당하는 memberVO라는 객체를 던져줬기때문에 에러발생있는지 알수있는 것이다. -->
					<!--  field 는 변수를 의미한다.-->
					<!-- * 와 {} 표기 :  object의 객체(memberVO에 담긴 데이터)로 인식한다.  -->
					<div class="hasError" th:if="${#fields.hasErrors('memberId')}" th:errors="*{memberId}"></div>
				</div>
				<div class="col-12">
					<label for="memberPw" class="form-label">PASSWORD</label> 
					<input type="password" class="f orm-control" th:field="*{memberPw}">
					<div class="hasError" th:if="${#fields.hasErrors('memberPw')}" th:errors="*{memberPw}"></div>
				</div>
				<div class="col-12">
					<label for="memberName" class="form-label">NAME</label> 
					<input type="text" class="form-control" th:field="*{memberName}">
					<div class="hasError" th:if="${#fields.hasErrors('memberName')}" th:errors="*{memberName}"></div>
				</div>
				
				<div class="col-4">
					<!-- 연락처는 타임리프 사용하면 에러난다!!! -->					
					<label for="memberTells" class="form-label">TELL</label> 
					<select name="memberTells" class="form-select" >
						<option value="010" selected>010</option>
						<option value="">011</option>
					</select>
				</div>
				<div class="col-4">
					<label for="" class="form-label">&nbsp;</label> 
					<input type="text" class="form-control" name="memberTells">
				</div>
				<div class="col-4">
					<label for="" class="form-label">&nbsp;</label> 
					<input type="text" class="form-control" name="memberTells">
				</div>
				<!-- div태그 밖으로 빼놓고 데이터를 memberTell 로 넣어준다 (memberTells 아님!! 주의) -->
					<div class="hasError" th:if="${#fields.hasErrors('memberTell')}" th:errors="*{memberTell}"></div>

				<div class="col-12 d-grid gap-2">
					<button type="submit" class="btn btn-outline-success">JOIN</button>
				</div>
			</form>
		</div>
	</div>
</div>

</html>
- login
    <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/base_layout}">
	
<div layout:fragment="content">
<br>
<br>
	<div class="row justify-content-center">
		<div class="col-6">
			<form th:action="@{/member/login}" class="row g-3" method="post" th:object="${memberVO}">
			<!--  컨트롤러에 반드시 매개변수로 memeberVO가 있어야 넘어오는 객체인 object에서 받아서 사용가능하다
			그래서 반드시 object에서 만들어 사용하려는 커맨드객체는 컨트롤러에 매개변수로 반드시 받아와야 사용가능하다. -->
				<div class="col-12">
					<label for="memberId" class="form-label" >ID</label>
					<!--th:object에서 받아온 커맨드객체 memebrVO가 있으면  th:field= 사용시 id,name,value(화면에 보이는)값  자동생성 -->
					<input type="text" class="form-control"  th:field="*{memberId}"  >
					                                                      <!-- value값에  ${memberVO.memberId} 가능 -->
				</div>
				<div class="col-12">
					<label for="memberPw" class="form-label" >PW</label>
					<input type="password" class="form-control" th:field="*{memberPw}" >
				</div>
				<div class="col-12 d-grid gap-2">
					<button type="submit" class="btn btn-outline-success">로그인</button>
					<button type="button" class="btn btn-outline-warning" onclick="location.href='/member/join';" >회원가입</button>
				</div>
			</form>
		</div>
	</div>	
</div>

</html>

  • 결과

    • 기본경로: localhost:8081/board/list
  • 첫화면: 게시글 목록 페이지

  • 회원가입

  • db 확인

  • 로그인
    :가입한 데이터 입력후, top 변경+ '글쓰기'버튼 생성

  • 로그아웃
    :버튼 클릭시 로그인 후에서 첫 화면으로 원상복귀

  • 글쓰기(글등록)

  • 글등록 성공 alert

  • 글등록 후 alert 확인 버튼 클릭시, 게시글 목록페이지 이동
    : 게시글 등록 확인

  • 글제목 클릭시, 상세보기

  • 게시글 수정(전 / 후)


    -수정완료 버튼 클릭시 alert 창

  • alert 확인 시 게시글 목록페이지 이동
    : 수정된 게시글 확인 가능

  • 게시글 삭제
    : 수정된 게시글 제목 클릭시, 상세보기 이동 후, 삭제버튼 클릭

  • 삭제확인 alert 창 확인

  • 게시글 목록페이지로 이동 후, 게시글 삭제 확인 가능

profile
Dev.Vinch

0개의 댓글