9:23 경 학원 도착
<9:30 1교시>
일단 수업 따라가려고 선생님이 작성한거 가져옴..

자바스크립트에서
let result="kim"; let result=name+"gura"; 이 두줄을 백틱을 사용해서 let result=` ${name}gura`; 로 합쳐서 쓸 수 있는 것처럼 타임 리프에서는 | |사이에 있는 내용이 하나의 문자열로 인식되어 타임리프언어와 자바스크립트언어를 같이 사용할 수 있다.
즉, | 번호 : ${ num } |, [[ | 번호 : ${num} | ]] 이런식으로 작성하면 오류 없이 작성할 수 있다.
링크도 마찬가지이다. @{|study?num=${num}&name=${name}|} 이런식으로 작성하면 타임리프가 참조할 수 있는 형태로 작성할 수 있다.
이거보다 불편한 형태지만 @{'study?num='+${num}+'&name='+${name}} 형태로 싱글따옴표를 사용해서 작성할수도 있긴 하다.
그리고 get 방식 파라미터에 한해서는 @{/study(num=${num}, name=${name})}이란 형태로 작성할 수도 있다.

당연히 ne 대신 != 도 된다.

페이지네이션 클래스 어떻게 만들었나 궁금해서 찾아봄
(출처 https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.css)
.pagination {
--bs-pagination-padding-x: 0.75rem;
--bs-pagination-padding-y: 0.375rem;
--bs-pagination-font-size: 1rem;
--bs-pagination-color: var(--bs-link-color);
--bs-pagination-bg: var(--bs-body-bg);
--bs-pagination-border-width: var(--bs-border-width);
--bs-pagination-border-color: var(--bs-border-color);
--bs-pagination-border-radius: var(--bs-border-radius);
--bs-pagination-hover-color: var(--bs-link-hover-color);
--bs-pagination-hover-bg: var(--bs-tertiary-bg);
--bs-pagination-hover-border-color: var(--bs-border-color);
--bs-pagination-focus-color: var(--bs-link-hover-color);
--bs-pagination-focus-bg: var(--bs-secondary-bg);
--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--bs-pagination-active-color: #fff;
--bs-pagination-active-bg: #0d6efd;
--bs-pagination-active-border-color: #0d6efd;
--bs-pagination-disabled-color: var(--bs-secondary-color);
--bs-pagination-disabled-bg: var(--bs-secondary-bg);
--bs-pagination-disabled-border-color: var(--bs-border-color);
display: flex;
padding-left: 0;
list-style: none;
}
.page-link {
position: relative;
display: block;
padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);
font-size: var(--bs-pagination-font-size);
color: var(--bs-pagination-color);
text-decoration: none;
background-color: var(--bs-pagination-bg);
border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.page-link {
transition: none;
}
}
.page-link:hover {
z-index: 2;
color: var(--bs-pagination-hover-color);
background-color: var(--bs-pagination-hover-bg);
border-color: var(--bs-pagination-hover-border-color);
}
.page-link:focus {
z-index: 3;
color: var(--bs-pagination-focus-color);
background-color: var(--bs-pagination-focus-bg);
outline: 0;
box-shadow: var(--bs-pagination-focus-box-shadow);
}
.page-link.active, .active > .page-link {
z-index: 3;
color: var(--bs-pagination-active-color);
background-color: var(--bs-pagination-active-bg);
border-color: var(--bs-pagination-active-border-color);
}
.page-link.disabled, .disabled > .page-link {
color: var(--bs-pagination-disabled-color);
pointer-events: none;
background-color: var(--bs-pagination-disabled-bg);
border-color: var(--bs-pagination-disabled-border-color);
}
.page-item:not(:first-child) .page-link {
margin-left: calc(var(--bs-border-width) * -1);
}
.page-item:first-child .page-link {
border-top-left-radius: var(--bs-pagination-border-radius);
border-bottom-left-radius: var(--bs-pagination-border-radius);
}
.page-item:last-child .page-link {
border-top-right-radius: var(--bs-pagination-border-radius);
border-bottom-right-radius: var(--bs-pagination-border-radius);
}
.pagination-lg {
--bs-pagination-padding-x: 1.5rem;
--bs-pagination-padding-y: 0.75rem;
--bs-pagination-font-size: 1.25rem;
--bs-pagination-border-radius: var(--bs-border-radius-lg);
}
.pagination-sm {
--bs-pagination-padding-x: 0.5rem;
--bs-pagination-padding-y: 0.25rem;
--bs-pagination-font-size: 0.875rem;
--bs-pagination-border-radius: var(--bs-border-radius-sm);
}
아래 코드에 대한 챗지피티의 설명

이 코드에서 #numbers와 append는 Thymeleaf 템플릿 엔진의 기능을 사용하여 HTML을 동적으로 생성하는 부분입니다. 각 부분을 설명하겠습니다.
1. #numbers.sequence(dto.startPageNum, dto.endPageNum)
#numbers는 Thymeleaf의 내장 유틸리티 객체입니다. 이 객체는 숫자와 관련된 여러 유용한 기능을 제공합니다.sequence는 #numbers의 메서드 중 하나로, 특정 범위의 숫자 리스트를 생성합니다. 이 메서드는 두 개의 인자를 받습니다:dto.startPageNum: 페이지네이션 시작 번호dto.endPageNum: 페이지네이션 끝 번호#numbers.sequence(dto.startPageNum, dto.endPageNum)은 dto.startPageNum부터 dto.endPageNum까지의 숫자 범위를 생성하여 반환합니다. 예를 들어, dto.startPageNum = 1이고 dto.endPageNum = 5라면, #numbers.sequence는 [1, 2, 3, 4, 5]를 반환합니다.
2. th:classappend="${i} == ${dto.pageNum} ? 'active'"
th:classappend는 주어진 조건이 참일 때, 해당 HTML 요소에 추가 클래스를 동적으로 붙여주는 Thymeleaf 속성입니다.이 속성은 i == dto.pageNum ? 'active'라는 조건을 사용하고 있습니다.이 조건은 현재 페이지 번호(dto.pageNum)와 i가 같으면 active 클래스를 추가하는 방식입니다.예를 들어, i가 dto.pageNum과 같으면, <li> 태그에 class="page-item active"가 추가됩니다.active 클래스를 추가함으로써, 현재 페이지를 강조 표시하는 효과를 낼 수 있습니다.
결합된 흐름
#numbers.sequence(dto.startPageNum, dto.endPageNum)는 페이지 번호 범위를 생성합니다.각 페이지 번호에 대해 th:classappend는 해당 번호가 현재 페이지(dto.pageNum)와 일치하는지 확인하고, 일치하면 그 페이지 번호에 active 클래스를 추가합니다.
결과적으로 이 코드는 페이지네이션을 구현하는데 사용됩니다. 사용자가 현재 보고 있는 페이지를 하이라이트하기 위해 active 클래스를 동적으로 적용하며, 페이지 번호 링크를 생성합니다.
타임리프에 셀렉티드 속성 : true면 selected가 추가되고, false면 추가되지 않아서 선택되지 않음

문자열이 비었는지 확인하여 그 조건을 만족할 때 p 요소를 출력하겠다는 의미의 코드

챗지피티에게도 물어봄
Thymeleaf에서 #strings는 문자열과 관련된 작업을 수행할 수 있는 내장 유틸리티 객체입니다. 이 객체는 문자열을 다루는 여러 가지 유용한 메서드를 제공합니다.
#strings.isEmpty(dto.keyword)
이 부분은 #strings.isEmpty 메서드를 사용하여 문자열이 비어 있는지를 확인하는 코드입니다.
#strings.isEmpty(dto.keyword)는 dto.keyword가 빈 문자열인지, 즉 값이 없거나 공백인지를 확인합니다.만약 dto.keyword가 비어 있지 않으면 (not을 사용하여 반전시킴), 그 결과는 true가 되어, <p> 태그가 화면에 렌더링됩니다.반대로, dto.keyword가 비어 있거나 null이라면 #strings.isEmpty(dto.keyword)는 true가 되어, th:if 조건이 만족되지 않아 <p> 태그는 렌더링되지 않습니다.
예시
dto.keyword = "hello"라면, #strings.isEmpty("hello")는 false가 되어, <p> 태그가 출력됩니다.dto.keyword = ""라면, #strings.isEmpty("")는 true가 되어, <p> 태그는 출력되지 않습니다.
결론
#strings.isEmpty(dto.keyword)는 주어진 dto.keyword가 비어 있지 않은지 검사하고, 비어 있지 않으면 해당 검색 결과에 대한 메시지를 출력하는 데 사용됩니다.
이제는 새 글 작성 페이지를 만들어야 하는데, 우리가 전에 사용했던 스마트 에디터는 jsp에 맞춰진 구조이다.
일단 SmartEditor 폴더 가져와서 static 폴더에 붙여넣기

스마트에디터 사용 설정은 2교시에 이어서.
<10:30 2교시>

스마트에디터에서 파일, 특히 이미지를 업로드했을 때 파일을 업로드해주는 기능을 하던 jsp 페이지가 webapp에 있지도 않고, static 폴더에 있으면서 jsp 관련 동작을 설정하지도 않았어서 지금으로선 동작을 안하고 있다.
그래서 이미지 업로드 요청을 받아줄 컨트롤러가 필요하다.

새로운 클래스 : SmartEditorController 만들기

스마트 에디터 컨트롤러 만들기
package com.example.spring10.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import jakarta.servlet.http.HttpServletRequest;
/*
* 이 컨트롤러가 정상동작 하기 위해서는
*
* 1. SmartEditor 폴더를 static 폴더에 붙여 넣기 하고
* 2. /upload/{imageName} 요청에 대해서 이미지를 출력해주는 ImageController 가 있어야하고
* 3. SmartEditor/photo_uploader/popup/attach_photo.js 에 있는 코드를
*
* 아래와 같이 수정해야 한다.
*
* function html5Upload() {
var tempFile,
sUploadURL;
//sUploadURL= 'file_uploader_html5.jsp'; //upload URL
//jsp 페이지에 요청하던 요청경로를 SmartEditorController 에 요청을 하도록 수정한다.
sUploadURL="/spring10/editor_upload";
여기서 /spring10 는 context path 이기 때문에 상황에 맞게 변경해야 한다.
*
*/
@Controller
public class SmartEditorController {
//업로드된 이미지를 저장할 서버의 경로 읽어오기
@Value("${file.location}")
private String fileLocation;
//ajax 업로드 요청에 대해 응답을 하는 컨트롤러 메소드
@PostMapping("/editor_upload")
@ResponseBody
public String upload(HttpServletRequest request) throws IOException {
//파일정보
String sFileInfo = "";
//파일명을 받는다 - 일반 원본파일명
String filename = request.getHeader("file-name");
//파일 확장자
String filename_ext = filename.substring(filename.lastIndexOf(".") + 1);
//확장자를소문자로 변경
filename_ext = filename_ext.toLowerCase();
//이미지 검증 배열변수
String[] allow_file = { "jpg", "png", "bmp", "gif" };
//돌리면서 확장자가 이미지인지
int cnt = 0;
for (int i = 0; i < allow_file.length; i++) {
if (filename_ext.equals(allow_file[i])) {
cnt++;
}
}
//이미지가 아님
if (cnt == 0) {
/*
* 이미지가 아니면 클라이언트에게
*
* NOTALLOW_파일명
*
* 을 응답한다.
*/
//out.println("NOTALLOW_" + filename);
return "NOTALLOW_" + filename;
} else {
//이미지이므로 신규 파일로 디렉토리 설정 및 업로드
//파일 기본경로 _ 상세경로
String filePath = fileLocation + File.separator;
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
String realFileNm = "";
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
String today = formatter.format(new java.util.Date());
realFileNm = today + UUID.randomUUID().toString() + filename.substring(filename.lastIndexOf("."));
String rlFileNm = filePath + realFileNm;
///////////////// 서버에 파일쓰기 /////////////////
InputStream is = request.getInputStream();
OutputStream os = new FileOutputStream(rlFileNm);
int numRead;
byte b[] = new byte[Integer.parseInt(request.getHeader("file-size"))];
while ((numRead = is.read(b, 0, b.length)) != -1) {
os.write(b, 0, numRead);
}
if (is != null) {
is.close();
}
os.flush();
os.close();
///////////////// 서버에 파일쓰기 /////////////////
String contextPath=request.getContextPath();
// 업로드된 이미지의 정보를 클라이언트에게 출력
sFileInfo += "&bNewLine=true";
sFileInfo += "&sFileName=" + filename;
sFileInfo += "&sFileURL="+contextPath+"/upload/"+realFileNm;
//out.println(sFileInfo);
return sFileInfo;
}
}
}

시큐리티가 iframe의 동작도 막고 있어서 sercuirtyconfig에 코드 추가할거임

iframe 을 사용할수 있도록 설정하는 코드(default 값은 사용 불가, SmartEditor에서 필요로함)
얘를 설정하지 않으면 iframe 에서 페이지를 띄울 수 없다.

스마트 에디터는 iframe이라는 페이지 안의 페이지를 띄워서 이미지를 보여주는데, 스프링 시큐리티는 iframe이 새로운 창을 띄우려고 src를 가져오는것 조차 막아버려서 이 기능을 해제하는 코드라고 이해하면 됨.
챗지피티한테도 물어봄
스프링 부트에서 iframe을 사용할 수 있도록 설정하는 이유는 보안과 관련이 있습니다. 기본적으로, 스프링 부트는 iframe을 비활성화하도록 설정되어 있습니다. 그 이유는 주로 클릭재킹(clickjacking) 공격을 방어하기 위해서입니다. 클릭재킹 공격은 사용자가 자신이 의도하지 않은 클릭을 하도록 유도하는 공격 방식으로, iframe을 악용하여 사용자를 속일 수 있습니다.
1. X-Frame-Options 헤더
스프링 부트는 기본적으로 X-Frame-Options HTTP 헤더를 설정하여 페이지를 iframe에서 렌더링하지 못하도록 막습니다. 이 헤더의 기본 값은 DENY 또는 SAMEORIGIN으로 설정되어 있습니다:
- DENY: 페이지가 어떤 iframe에도 표시되지 않도록 차단합니다.
- SAMEORIGIN: 페이지가 동일 출처에서만 iframe에 표시될 수 있도록 합니다.
즉, 기본적으로 다른 도메인에서 제공되는 페이지를 iframe에 넣을 수 없으며, 동일 출처에서만 iframe을 사용할 수 있습니다.
2. CSRF(Cross-Site Request Forgery) 보호
또한, 스프링 시큐리티는 기본적으로 CSRF(Cross-Site Request Forgery) 보호를 활성화합니다. CSRF 공격을 막기 위해 기본적으로 폼 제출 시 CSRF 토큰을 요구합니다. 이를 비활성화하는 설정도 가능합니다. SmartEditor나 다른 특정 웹 애플리케이션에서 CSRF 보호를 끄고 사용해야 할 때가 있을 수 있습니다.
3. SmartEditor에서 iframe 필요
SmartEditor는 네이버의 스마트에디터를 포함하는 에디터로, 사용자가 편집한 내용을 미리보기 위해 iframe을 필요로 할 수 있습니다. 이 경우, 스프링 부트의 기본 보안 설정이 방해가 되므로 이를 해제해야 합니다.
4. 해결 방법
스프링 시큐리티에서 iframe을 허용하려면 X-Frame-Options를 SAMEORIGIN 또는 ALLOW-FROM 등으로 설정해야 하고, CSRF 보호를 비활성화해야 할 수도 있습니다.
위 코드에서 iframe 사용을 허용하고 CSRF를 비활성화하는 방법은 다음과 같습니다:
java복사httpSecurity .headers(headers -> headers .frameOptions(frameOptions -> frameOptions.sameOrigin()) // SAMEORIGIN으로 설정하여 동일 출처에서만 iframe을 사용할 수 있도록 설정 ) .csrf(csrf -> csrf.disable()); // CSRF 보호 비활성화 (SmartEditor에서는 이를 필요로 할 수 있음)
- frameOptions.sameOrigin(): 이 설정은 iframe을 동일 출처에서만 사용할 수 있게 합니다.
- csrf.disable(): CSRF 보호를 비활성화하여, SmartEditor와 같은 특정 애플리케이션에서 요구하는 동작을 가능하게 합니다.
5. 주의 사항
- 보안상 고려사항: iframe을 사용할 때는 보안에 특히 유의해야 합니다. 특히, 외부 출처에서 iframe을 포함시키거나 CSRF 보호를 비활성화하는 경우, 악성 공격에 취약해질 수 있습니다.
- 정확한 요구사항 파악: SmartEditor나 특정 에디터가 iframe을 필요로 하는 이유를 이해하고, 해당 요구사항에 맞는 보안 설정을 하는 것이 중요합니다.
저장도 안 했는데 어떻게 글 번호를 가져오지!
저장하고 글 번호를 리턴하는 메소드가 필요함.
글을 저장하는 시점에 글번호를 넣는게 아니라 글 번호를 미리 얻어내서 저장해야 한다는 이야기.

글 번호를 미리 읽어와서 넣어줄거니까 insert에서도 #{num} 으로 바꿔줌



에러 나서 수정

아직 새글을 자세히 보는 것을 다루는 view 페이지를 만든 것은 아니어서 작성한 내용이 보이는 것은 아님.

PostService 인터페이스에서 글 하나의 정보를 가져오는 메소드 추가


Dao가 없어서 Dao로 가서 메소드 추가

Dao에 추가된 메소드와 관련해서 Mapper 작성

<11:30 3교시>
글을 저장했을 때 리다이렉트되면서 글 저장 성공 메시지를 전달할 RedirectAttributes도 함께 전달해서 글 작성이 성공적으로 이루어졌음을 보여주기로 함.

단순히 해당 글 정보만 가져올 게 아니라 이전글 정보, 다음글 정보도 어느정도는 가져와야 하고, 검색어 조건이 있을 때의 경우도 고려해야 한다.


키워드가 있을 때 이전글 글번호와 다음글 글번호까지 담아주기

서비스로 이동해서 완성해주기

Post컨트롤러로 가서

view페이지로 가서 작성



글 내용이 두번 나오는데 그 이유는 글 내용을 작성하는 코드를 서로 다르게 두번 작성했으니까임.


글작성자와 로그인한 사람이 일치한다면 버튼을 띄워준 부분을 동적으로 코딩하기.

그리고 이건 아직 수업시간에 안했는데 확인해보라는 주석이 있어서 확인해봄.

<12:30 4교시>
오후엔 댓글 기능 나간다고 함. 종이에 서로 연관 관계랑 작성할 때 놓치면 안되는 것 정리해보려고 함.

'자바풀스택 과정 > 자바 풀 스택 : 수업내용정리' 카테고리의 다른 글
자바 풀 스택 2/26 오전 기록 063-1 (0) | 2025.02.26 |
---|---|
자바 풀 스택 2/25 오후 기록 062-2 (0) | 2025.02.25 |
자바 풀 스택 2/24 오후 기록 061-2 (0) | 2025.02.24 |
자바 풀 스택 2/24 오전 기록 061-1 (0) | 2025.02.24 |
자바 풀 스택 2/21 오후 기록 060-2 (0) | 2025.02.21 |