* 이 포스트는 전 배달의민족, 현재 인프런에 계시고 유튜브 개발바닥의 크리에이터이신 개발자 이동욱님의 '스프링부트와 AWS로 혼자 구현하는 웹 서비스'를 기반으로 작성된 코드를 기반으로 진행중인 프로젝트에 대한 글임을 알립니다. 포스트 맨 아래에 관련 링크가 있습니다.
책 부분이 끝난 후부터는 코드를 커스텀하는 과정을 포스팅합니다.
책 부분이 궁금하시면 ready부터 보시길 추천합니다.
참고 : https://github.com/kth1017/S1
이전 포스트에 적은 내용대로 이번 포스트부터는 코드를 커스텀하는 과정을 진행해보려고 합니다. 이번 포스트에서 진행되는 과정은 템플릿 엔진을 mustache에서 thymeleaf로 변경하는 것입니다. 코드를 뜯어 고치는 과정은 반드시 단계적으로 진행되어야 하기에 현재 작성된 index, post-save, posts-update 세 파일 구조를 유지한 채로 변경하겠습니다.(사실 작은 프로젝트기에 처음부터 구조를 다 뜯어고쳐도 무방하지만 습관을 들이기위해...)
변경하는 이유는 다음과 같습니다.
- 페이지를 수정하기엔 mustache의 공식 문서가 너무 부실함
- 책에도 언급했듯 커뮤니티 버전에서 사용하는게 유리한 점 빼고는 mustache보다 thymeleaf가 모든 면에서 앞섬
- react를 이용해 분리하는 것도 고려했으나 시간이 너무 소모될 것 같아 보류
그러면 시작하겠습니다.
1. build.gradle에 의존성 추가
implementation('org.springframework.boot:spring-boot-starter-thymeleaf')
2. 권한 변경
#SecurityConfig
- 수정할 때 계속 유저 권한을 주는 것은 매우 불편하니 모든 권한을 풀겠습니다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable() // h2 console 사용을 위한 비활성화
.and()
.authorizeRequests() //url별 권한 관리 시작
.antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile").permitAll()
//.antMatchers("/api/v1/**").hasRole(Role.USER.name())
//.anyRequest().authenticated() // 이외 url은 인증사용자 설정
3. 페이지 변경
#시작 전
- 타임리프로 고칠때 네비게이션 바를 만들고 거기에 로그인 버튼을 둘 예정이라 header, footer는 이후 추가할 것이기에 일단은 템플릿 분할을 하지 않고 진행하겠습니다. 타임리프의 기초 문법 설명은 생략하겠습니다.
#index.html
전체 코드
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>스프링부트 웹서비스</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<h1>스프링부트로 시작하는 웹 서비스 Ver.2</h1>
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
<span th:if="${userName} != null">Logged in as: <span id="user">[[${userName}]]</span>
<a href="/logout" class="btn btn-info active" role="button">Logout</a>
</span>
<a th:if="${userName} == null" href="/oauth2/authorization/google" class="btn btn-success active" role="button">Google Login</a>
<a th:if="${userName} == null" href="/oauth2/authorization/naver" class="btn btn-secondary active" role="button">Naver Login</a>
</div>
</div>
<br>
<!-- 목록 출력 영역 -->
<table class="table table-horizontal table-bordered">
<thead class="thead-strong">
<tr>
<th>게시글번호</th>
<th>제목</th>
<th>작성자</th>
<th>최종수정일</th>
</tr>
</thead>
<tbody id="tbody">
<tr th:each="post : ${posts}">
<td>[[${post.id}]]</td>
<td><a th:href="@{/posts/update/{postId}(postId=${post.id})}">[[${post.title}]]</a></td>
<td>[[${post.author}]]</td>
<td>[[${post.modifiedDate}]]</td>
</tr>
</tbody>
</table>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<!--index.js 추가-->
<script src="/js/app/index.js"></script>
</body>
</html>
- 다른 부분은 거의 비슷한데 신경이 좀 쓰이는 부분만 언급하겠습니다. 일단 mustache에는 없는 if문이 없어 {{#userName}}를 썼던 부분을 다음과 같이 바꿔줍니다. 단 수정 과정 중이기에 이렇게 썼지만 평소에는 당연히 !=null은 쓰면 안되고 .isEmpty() 등을 써야합니다.
<span th:if="${userName} != null">Logged in as:
두번째로 볼 것은 th:each로 iterator를 넣는 코드입니다. 이 자체는 어려울 것 없이 다음과 같습니다.
<tr th:each="post : ${posts}">
<td>[[${post.id}]]</td>
...
하지만 다음 코드부터 iterator를 url에 집어넣을때 아래와 같이 작성하면 일단 인텔리제이가 post.id를 인식하지 않고, { 가 %7B로 인코딩되기에 작동하지 않을 때가 있습니다.
// 인식 안될 때 있음
<a th:href="@{/posts/update/{post.id}}">
이럴 경우 url 종단에 ()로 들어갈 변수를 명시해줘서 코드를 작성합니다. (근데 타임리프는 인텔리제이가 제대로 인식하지 않아도 렌더링되면 거의 대부분 정상으로 동작하기에 위의 코드가 되시는 분들은 그냥 진행하셔도 됩니다.)
// 아래 코드는 제대로 인식함
<a th:href="@{/posts/update/{postId}(postId=${post.id})}">
#posts-save.html
전체코드
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>스프링부트 웹서비스</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="col-md-12">
<div class="col-md-4">
<form>
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" placeholder="제목을 입력하세요">
</div>
<div class="form-group">
<label for="author"> 작성자 </label>
<input type="text" class="form-control" id="author" placeholder="작성자를 입력하세요">
</div>
<div class="form-group">
<label for="content"> 내용 </label>
<textarea class="form-control" id="content" placeholder="내용을 입력하세요"></textarea>
</div>
</form>
<a href="/" role="button" class="btn btn-secondary">취소</a>
<button type="button" class="btn btn-primary" id="btn-save">등록</button>
</div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<!--index.js 추가-->
<script src="/js/app/index.js"></script>
</html>
#posts-update.html
전체코드
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>스프링부트 웹서비스</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<h1>게시글 수정</h1>
<div class="col-md-12">
<div class="col-md-4">
<form>
<div class="form-group">
<label for="title">글 번호</label>
<input type="text" class="form-control" id="id" th:field="*{post.id}" readonly>
</div>
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" th:field="*{post.title}">
</div>
<div class="form-group">
<label for="author"> 작성자 </label>
<input type="text" class="form-control" id="author" th:field="*{post.author}" readonly>
</div>
<div class="form-group">
<label for="content"> 내용 </label>
<textarea class="form-control" id="content">[[${post.content}]]</textarea>
</div>
</form>
<a href="/" role="button" class="btn btn-secondary">취소</a>
<button type="button" class="btn btn-primary" id="btn-update">수정 완료</button>
<button type="button" class="btn btn-danger" id="btn-delete">삭제</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<!--index.js 추가-->
<script src="/js/app/index.js"></script>
</body>
</html>
- posts-save와 크게 다를 것은 없는데 한 가지 짚고갈 건 기존 mustache에서 value로 불러왔던 정보를
<input type="text" class="form-control" id="id" value="{{post.id}}" readonly>
다음과 같이 th:filed를 사용하여 수정해줍니다.
<input type="text" class="form-control" id="id" th:field="*{post.id}" readonly>
마지막으로 당부해드릴 말씀은 다 잘하고 <html>만 써서 타임리프 작동이 안되서 헤매는 분들을 많이 봤습니다. 의존성 넣어줘도 반드시 아래 코드 추가하는거 잊지 마시길 바랍니다.(사실 저도 빼먹어서 시간 날린적 있어요...)
<html xmlns:th="http://www.thymeleaf.org">
.기존 mustache 코드들은 as_is 폴더를 만들어 넣어두었습니다. 다음 포스트는 페이지를 본격적으로 수정할 것 같습니다.
refer
이동욱님 블로그의 관련 포스트 : https://jojoldu.tistory.com/539?category=717427
개발바닥 유튜브 :https://www.youtube.com/channel/UCSEOUzkGNCT_29EU_vnBYjg
개발바닥
본격 세계최초 DEV 엔터테인먼트 토크쇼 두 스타트업 개발자의 요절복통 이야기 구독 안하면 장애남!!
www.youtube.com
이동욱님 github의 해당 repository : https://github.com/jojoldu/freelec-springboot2-webservice
'Project' 카테고리의 다른 글
P1_게시판 프로젝트_3_thymeleaf layout (0) | 2022.04.25 |
---|---|
P1_게시판 프로젝트_2_thymeleaf index (0) | 2022.04.19 |
P1_클론 프로젝트(feat. 스프링부트와 AWS로 혼자 구현하는 웹 서비스)_책 부분 End (0) | 2022.04.08 |
P1_클론 프로젝트(feat. 스프링부트와 AWS로 혼자 구현하는 웹 서비스)_8 (0) | 2022.04.08 |
P1_클론 프로젝트(feat. 스프링부트와 AWS로 혼자 구현하는 웹 서비스)_7 (0) | 2022.04.08 |