[요약]
- Member 테이블에 ROLE 컬럼을 추가하여 주인장과 일반 사용자의 기능을 구분한다.
- JSTL을 활용하여 .jsp 를 구현한다.
- ApplicationContext를 활용하여 BoardService의 구현체를 동적으로 가져온다.
1. Member 테이블에 ROLE 컬럼 추가 및 관련 클래스 수정
[MySQL Workbench 수정]
- 컬럼 : ROLE 추가 / Default값 : 'USER'
- bitcamp ROLE 수정 : 'ADMIN(주인장)'
[MemberDto 수정]
- ROLE 추가 (getter, setter 포함)
public class MemberDto {
...
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
...
[Member-mapper 수정]
- 로그인 시 ROLE 추가
<select id="login" parameterType="member" resultType="member">
SELECT ID
, USERNAME
, PASSWORD
, EMAIL
, NICKNAME
, TEL
, ROLE
FROM MEMBER
WHERE USERNAME = #{username}
AND PASSWORD = #{password}
</select>
2. 자유게시판 글 등록 추가 (로그인한 경우에만)
[free-list.jsp 수정]
- JSTL 추가
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
...
<body>
// loginMember(ID, PW 포함) 이 Null이 아닐 때
<c:if test="${loginMember ne null}">
<div class="container mt-3 mb-5 w-50 d-flex justify-content-end">
<button type="button" class="btn btn-outline-secondary" onclick="location.href='/board/post.do'">글 등록</button>
</div>
</c:if>
...
- 참고(JSTL)
el 표기법과 JSTL
[요약] - el표기법 : 자바에서 받아온 데이터 표출 - JSTL : 자바문법을 사용하기 위한 기술 (.jsp 에서 사용) [el 표기법(Expression Language)] - JSP 에서 ${} 표기되는 기법 - Java에서 받아온 데이터를 표
maverick11471.tistory.com
3. 공지사항 글 등록 추가 (주인장만)
[Notice-list.jsp 수정]
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
...
<body>
// loginMember가 null이 아니고 loginMember의 role이 ADMIN일때
<c:if test="${loginMember ne null and loginMember.role eq 'ADMIN'}">
<%-- <c:if test="${loginMember != null && loginMember.role == 'ADMIN'}"> --%>
<div class="container mt-3 mb-5 w-50 d-flex justify-content-end">
<button type="button" class="btn btn-outline-secondary" onclick="location.href='/board/post.do'">글 등록</button>
</div>
</c:if>
4. 글 등록 - 카테고리 구분
* 주인장은 카테고리로 자유게시판과 공지사항이 있어야 한다
[post.jsp 수정]
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
...
// loginMember의 role이 ADMIN(주인장)일 때
<c:if test="${loginMember.role eq 'ADMIN'}">
<div class="form-group">
<label for="type">카테고리</label>
<select class="form-select" name="type" id="type">
<option value="free" selected>자유게시판</option>
<option value="notice">공지사항</option>
</select>
</div>
</c:if>
4-1. 글 등록 - 게시판 구분
글 등록을 할 수 있는 게시판은 2개가 있다.(공지사항, 자유게시판)
그런데 글 등록 후 '등록' 버튼을 누르면 이 게시글이 공지사항으로 갈지 자유게시판으로 갈지 지정해 두지 않았다.
이를 지정해주려 한다.
[요약]
type 변수를 생성하여 type에 따른 화면 조성
[BoardDto 수정]
- type 변수 추가
* getter, setter 추가
public class BoardDto {
private String type = "free";
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
[BoardController 수정]
@Controller
@RequestMapping("/board")
public class BoardController {
// boardService 객체 생성
private BoardService boardService;
// ApplicationContext 객체 생성
private ApplicationContext applicationContext;
@Autowired
public BoardController(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@PostMapping("/post.do")
public String post(BoardDto boardDto) {
// 게시판 타입에 따른 동적 의존성 주입
if(boardDto.getType().equals("free")) {
// 즉, 스프링 컨테이너에서 "freeBoardServiceImpl" 이름의 Bean을 찾아와서,
// 그 Bean이 BoardService 인터페이스의 구현체인지 확인하고 있습니다.
// 이렇게 하면 boardService 변수에 BoardService 인터페이스의 구현체가 할당됩니다.
// 이를 통해 boardService를 BoardService 인터페이스의 메서드를 호출할 수 있게 됩니다.
boardService = applicationContext.getBean("freeBoardServiceImpl", BoardService.class);
// free-list 일 때
} else {
// notice-list 일 때
boardService = applicationContext.getBean("noticeServiceImpl", BoardService.class);
}
boardService.post(boardDto);
if(boardDto.getType().equals("free")) {
return "redirect:/board/free-list.do";
} else {
return "redirect:/board/notice-list.do";
}
}
Spring Framework / ApplicationContext 역할
ApplicationContext는 Spring 프레임워크에서 사용되는 핵심 인터페이스 중 하나입니다. 이 인터페이스는 Spring 애플리케이션의 컨테이너 역할을 하며, 다음과 같은 주요 기능을 제공합니
maverick11471.tistory.com
[post.jsp 수정]
- loginMember는 memberdao.login으로 login 시 갖고있는 정보들이 있다.
- readonly 수정이 안되고 읽는 것만 가능
...
// 자유게시판의 valuer값을 free로 지정
<select class="form-select" name="type" id="type">
<option value="free" selected>자유게시판</option>
<option value="notice">공지사항</option>
</select>
...
...
// 글 등록 시 작성자 칸에 nickname이 들어가도록 표현하며,
// 이는 readonly로 읽기만 가능하다.
<div class="form-group mt-3">
<label for="nickname">작성자</label>
<input type="text" class="form-control" id="nickname" name="nickname"
value="${loginMember.nickname}" readonly required>
</div>
...
[freeboard-mapper / notice-mapper 수정]
Freeboard나 notice 컬럼에 nickname이 없다.
그래서 member와 join 해야 한다.
<mapper namespace="FreeBoardDao">
<insert id="post" parameterType="board">
INSERT INTO FREEBOARD(
TITLE,
CONTENT,
WRITER_ID
) VALUES(
#{title},
#{content},
// 서브쿼리로 nickname 가져오기
(
SELECT M.ID
FROM MEMBER M
WHERE M.NICKNAME = #{nickname}
)
)
</insert>
<mapper namespace="NoticeDao">
<insert id="post" parameterType="board">
INSERT INTO NOTICE(
TITLE,
CONTENT,
WRITER_ID
) VALUES (
#{title},
#{content},
(
SELECT M.ID
FROM MEMBER M
WHERE M.NICKNAME = #{nickname}
)
)
</insert>
- 결과1. 자유게시판에 일반사용자가 글 등록
- 결과2. 공지사항에 주인장이 글 등록
그런데 글 등록 후 새로고침을 해도 주소창에 post로 나온다. (화면만 바뀌고, url 주소창은 그대로 post.do로 유지)
이를 해결하기위해 redirect를 사용한다.
[BoardController 수정]
@PostMapping("/post.do")
public String post(BoardDto boardDto) {
// 게시판 타입에 따른 동적 의존성 주입
if(boardDto.getType().equals("free")) {
boardService = applicationContext.getBean("freeBoardServiceImpl", BoardService.class);
} else {
boardService = applicationContext.getBean("noticeServiceImpl", BoardService.class);
}
boardService.post(boardDto);
// redirect로 수정(free면 free-list로, else면 notice-list로
if(boardDto.getType().equals("free")) {
return "redirect:/board/free-list.do";
} else {
return "redirect:/board/notice-list.do";
}
}
그런데 또 다른 문제점이 발생했다.
로그인만 안해도 url주소에 /post.do 만 입력해도 글 등록이 된다.
이를 방지하는 방법은 아래와 같다.
1. MemberController에서 set으로 loginMember를 지정한다
@PostMapping("/login.do")
public String login(MemberDto memberDto, Model model, HttpSession session) {
try {
// set으로 loginMember 지정. 어디든 갖다쓸 수 있음.
MemberDto loginMember = memberService.login(memberDto);
loginMember.setPassword("");
session.setAttribute("loginMember", loginMember);
return "redirect:/";
} catch (Exception e) {
model.addAttribute("loginFailMsg", e.getMessage());
return "member/login";
}
}
2. 이를 BoardController에서 get으로 loginMember를 가져온다.
@GetMapping("/post.do")
public String postView(HttpSession session) {
MemberDto loginMember = (MemberDto) session.getAttribute("loginMember");
// loginMember가 null이면 로그인창으로 이동
if(loginMember == null) {
return "redirect:/member/login.do";
}
// 아니면 post 게시판으로 이동한다.
return "board/post";
}
2-1. 자유게시판 등록한 글이 계속해서 나오게 수정
[boardController 수정]
- Model 객체에 BoardList를 넣는다. 이 때 키 값을 freeBoardList로 지정한다.
@GetMapping("/free-list.do")
public String freeListView(Model model) {
boardService = applicationContext.getBean("freeBoardServiceImpl", BoardService.class);
model.addAttribute("freeBoardList", boardService.getBoardList());
return "board/free-list";
}
[free-list.jsp 수정]
- Controller에서 지정한 freeBoardList를 item으로 삼아 한개씩 freeBoard로 꺼내 속성을 지정한다.
- c:forEach로 하나씩 꺼내온다.
<tbody class="table-group-divider">
<c:forEach items="${freeBoardList}" var="freeBoard">
// onclick 시 url을 지정하고 쿼리스트링 방식으로 클릭 시
// url 주소창에 freeBoard.id가 지정될 수 있도록 한다.
<tr class="board-tr" onclick="location.href='/board/free-detail.do?id=${freeBoard.id}'">
<td>${freeBoard.id}</td>
<td>${freeBoard.title}</td>
<td>${freeBoard.nickname}</td>
<td>
<javatime:format value="${freeBoard.regdate}" pattern="yyyy-MM-dd"/>
</td>
<td>${freeBoard.cnt}</td>
</tr>
</c:forEach>
</tbody>
[쿼리스트링으로 구현 시 주소창에 id가 나온다]
[이전에 등록한 자유게시판 목록 전체가 나온다]
3-1. 공지사항 등록한 글이 계속해서 나오게 수정
자유게시판과 방법은 동일하다.
[boardController 수정]
@GetMapping("/notice-list.do")
public String noticeListView(Model model) {
boardService = applicationContext.getBean("noticeServiceImpl", BoardService.class);
model.addAttribute("noticeList", boardService.getBoardList());
return "board/notice-list";
}
[notice-list.jsp 수정]
<div class="container mt-3 mb-5 w-75 card-wrapper">
<c:forEach items="${noticeList}" var="notice">
<div class="card" style="width: 18rem;">
<svg class="bd-placeholder-img card-img-top" width="100%" height="180" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Image cap" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#868e96"></rect></svg>
<div class="card-body">
<h5 class="card-title">${notice.title}</h5>
<p class="card-text">작성일:
<!--fmt:parseDate: LocalDateTime 타입을 Date 타입으로 변환-->
<fmt:parseDate value="${notice.regdate}" pattern="yyyy-MM-dd'T'HH:mm" var="parsedRegdate" type="both"/>
<!--fmt:formatDate: Date타입의 날짜를 형식 지정에 맞게 표출-->
<fmt:formatDate value="${parsedRegdate}" pattern="yyyy-MM-dd"></fmt:formatDate>
</p>
<a href="/board/notice-detail.do" class="btn btn-outline-secondary btn-sm">자세히 보기</a>
</div>
</div>
</c:forEach>
위에서 fmt 라는 낯선 단어가 보일 것이다.
이는 왜 시행된 것일까?
부록. 시간타입 변경
공지사항과 자유게시판 모두 등록시간에 대문자 T가 들어가 있어 이상하게 나온다.
이를 수정하기 위해 아래와 같이 조치한다.
[notice-list 수정]
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
...
<p class="card-text">작성일:
<!--fmt:parseDate: LocalDateTime 타입을 Date 타입으로 변환-->
<fmt:parseDate value="${notice.regdate}" pattern="yyyy-MM-dd'T'HH:mm" var="parsedRegdate" type="both"/>
<!--fmt:formatDate: Date타입의 날짜를 형식 지정에 맞게 표출-->
<fmt:formatDate value="${parsedRegdate}" pattern="yyyy-MM-dd"></fmt:formatDate>
</p>
...
- parseDate를 지정해서 타입을 Date 타입으로 변환하고,
- 이를 활용하여 formatDate로 날짜 형식에 맞게 표출한다.
그런데 이 과정이 너무 귀찮다..
그래서 자유게시판은 다르게 수정하였다.
<%@ taglib prefix="javatime" uri="http://sargue.net/jsptags/time" %>
...
<c:forEach items="${freeBoardList}" var="freeBoard">
<tr class="board-tr" onclick="location.href='/board/free-detail.do?id=${freeBoard.id}'">
<td>${freeBoard.id}</td>
<td>${freeBoard.title}</td>
<td>${freeBoard.nickname}</td>
<td>
<javatime:format value="${freeBoard.regdate}" pattern="yyyy-MM-dd"/>
</td>
<td>${freeBoard.cnt}</td>
</tr>
</c:forEach>
...
이는 javatime이라는 라이브러리를 사용한 것인데
사용방법은 아래와 같다.
- 구글 검색
[pom.xml 주입]
<dependency>
<groupId>net.sargue</groupId>
<artifactId>java-time-jsptags</artifactId>
<version>2.0.0</version>
</dependency>
시간 값 jstl 설정 시 유의사항
Moddate regdate 는 'yyyy-MM-ddTHH:mm:ss' 형태여야 하는데
Name 값을 빼버리면 이 값이 백엔드로 안넘어간다.
[BoardDto]
// 'yyyy-MM-ddTHH:mm:ss' => 이 형태의 값이 넘어와야 된다.
private LocalDateTime regdate;
private LocalDateTime moddate;
[freeboard-detail]
- name 값이 있어야 input 시 내용이 넘어간다. 그런데 넘어갈 때 yyyy-MM-ddTHH:mm:ss 형식으로 넘어간다.
하지만 front 화면단에는 위 형식으로 표현하면 이상하기 때문에 pattern="yyyy-MM-dd"를 지정해 주었었다.
이대로만 하면 오류가 발생하게 된다.
이를 방지하기 위해 script를 추가하여 형식에 맞게 값을 넘겨줘야 한다.
<div class="form-group mt-3">
<label for="regdate">등록일</label>
<input type="text" class="form-control" id="regdate" name="regdate"
value="<javatime:format value="${notice.regdate}" pattern="yyyy-MM-dd"/>" required>
</div>
<div class="form-group mt-3">
<label for="moddate">수정일</label>
<input type="text" class="form-control" id="moddate" name="moddate"
value="<javatime:format value="${notice.moddate}" pattern="yyyy-MM-dd"/>" required>
</div>
[freeboard-detail] 수정
<script>
$(() => {
$("#modify-form").on("submit", (e) => {
$("#regdate").val(`\${\$("#regdate").val()}T00:00:00`);
$("#moddate").val(`\${\$("#moddate").val()}T00:00:00`);
});
});
</script>
'백엔드 > Spring Framework' 카테고리의 다른 글
게시판 구현 / 5. Mybatis 동적쿼리로 검색하기 기능 구현 (0) | 2024.07.19 |
---|---|
게시판 구현 / 4. 게시판 디테일(글 수정, 삭제) (0) | 2024.07.17 |
게시판 구현 / 2. 로그인 (0) | 2024.07.16 |
게시판 구현 / 1. 회원가입 / JsonObject 만들기 (0) | 2024.07.16 |
Ajax(Asynchronous Javascript And Xml) (0) | 2024.07.16 |