728x90
๋ฐ์ํ
ํ๋ก๊ทธ๋๋ฐ ์ธ๊ณ๋ฅผ ํ๊ตฌํฉ์๋ค.
Spring Boot๋ฅผ ํ์ฉํ์ฌ ํ์ด์ง์ ๊ฒ์ํ์ ๊ตฌํํ ์ ์์ต๋๋ค.
๊ตฌํ ํ๋ฉด
๊ฒ์ํ์ ๊ณต์ง์ฌํญ, Q&A ํญ๋ชฉ์ด ์์ผ๋ฉฐ ๊ธ์ฐ๊ธฐ, ์ด๋ฏธ์ง ์ฒจ๋ถ, ๊ธ์ ์์ ์ญ์ ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. Q&A์๋ ์ผ๋ฐ ๊ธ์ฐ๊ธฐ, ์ถฉ์ ์ ์ถ๊ฐ์์ฒญ, Car์ ๋ณด ์ถ๊ฐ์์ฒญ์ด ์์ด ํด๋น ํญ๋ชฉ์ ๊ด๋ฆฌ์ ๊ณ์ ์ผ๋ก ๋ฐ๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
pagination ํ์ฉํ์ฌ ํ๋จ์ ํ์ด์ง ๋ฒํธ๋ฅผ ๋ถ์ฌํ๊ณ , ๊ฒ์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์์ต๋๋ค.
์์ฑ ์ฝ๋
BoardController.java
package com.example.board.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpSession;
import com.example.board.model.Board;
import com.example.board.model.Comment;
import com.example.board.model.FileAtch;
import com.example.board.model.User;
import com.example.board.repository.BoardRepository;
import com.example.board.repository.CommentRepository;
import com.example.board.repository.FileAtchRepository;
@Controller
public class BoardController {
@Autowired
BoardRepository boardRepository;
@Autowired
CommentRepository commentRepository;
@Autowired
FileAtchRepository fileAtchRepository;
@Autowired
HttpSession session;
@GetMapping("/board/image")
public String image() {
return "/board/image";
}
@GetMapping("/board/delete/{id}")
public String boardDelete(@PathVariable("id") long id) {
User loggedUser = (User) session.getAttribute("user_info");
String loggedName = loggedUser.getEmail();
Optional<Board> dbBoard = boardRepository.findById(id);
// String savedName = dbBoard.get().getUserId();
String savedName = dbBoard.get().getUser().getEmail();
if (savedName.equals(loggedName)) {
Board board = new Board();
board.setId(id);
boardRepository.deleteById(id);
return "redirect:/board/list";
} else {
return "redirect:/board/view?id=" + id;
}
}
@GetMapping("/board/update/{id}")
public String boardUpdate(Model model, @PathVariable("id") long id) {
Optional<Board> data = boardRepository.findById(id);
Board board = data.get();
model.addAttribute("board", board);
return "board/update";
}
@PostMapping("/board/update/{id}")
public String boardUpdate(
@ModelAttribute Board board,
@RequestParam("file") MultipartFile mFile,
@PathVariable("id") long id) {
User user = (User) session.getAttribute("user_info");
if (user == null) {
return "redirect:/login";
}
String userId = user.getEmail();
Optional<Board> data = boardRepository.findById(id);
if (data.isPresent()) {
Board existingBoard = data.get();
// !userId.equals(existingBoard.getUserId())
if (!userId.equals(existingBoard.getUser().getEmail())) {
// ์ฌ์ฉ์๊ฐ ๊ฒ์๋ฌผ์ ์์ฑ์๊ฐ ์๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌํ ๋ด์ฉ
return "redirect:/board/view?id=" + id; // ์: ๊ฒ์๋ฌผ ๋ณด๊ธฐ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
}
// ๊ฒ์๋ฌผ ์์ ๊ถํ์ด ์๋ ๊ฒฝ์ฐ, ์
๋ฐ์ดํธ ์งํ
existingBoard.setTitle(board.getTitle());
existingBoard.setContent(board.getContent());
// ํ์ผ ์ ์ฅ ๋ก์ง ์์
String originalFilename = mFile.getOriginalFilename();
FileAtch fileAtch = new FileAtch();
fileAtch.setOriginalName(originalFilename);
fileAtch.setSaveName(originalFilename);
fileAtch.setBoard(existingBoard);
fileAtchRepository.save(fileAtch);
String filename = mFile.getOriginalFilename();
File file = new File("C:/files/" + filename);
try {
mFile.transferTo(file);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
// ํ์ผ ์ ์ฅ ๋ก์ง ๋
boardRepository.save(existingBoard);
}
return "redirect:/board/" + id;
}
@GetMapping("/board/{id}")
public String boardView(Model model, @PathVariable("id") long id) {
Optional<Board> data = boardRepository.findById(id);
Board board = data.get();
model.addAttribute("board", board);
return "board/view";
}
@GetMapping("/board/list")
public String boardList(Model model,
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") int P) {
Sort sort = Sort.by(Order.desc("id"));
Pageable pageable = PageRequest.of(P - 1, 10, sort);
// Page<Board> list = boardRepository.findAll(pageable);
Page<Board> page;
if (keyword != null && !keyword.isEmpty()) {
page = boardRepository.findByTitleContaining(keyword, pageable);
} else {
page = boardRepository.findAll(pageable);
}
List<Board> list = page.getContent();
model.addAttribute("list", list);
int startPageGroup = ((P - 1) / 10) * 10;
int totalPages = page.getTotalPages();
// calculate end page
int startPage = Math.max(1, startPageGroup + 1);
int endPage = Math.min(totalPages, startPageGroup + 10);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
model.addAttribute("prevGroupStart", Math.max(1, startPage - 10));
model.addAttribute("nextGroupStart", Math.min(totalPages, startPage + 10));
model.addAttribute("totalPages", totalPages);
User user = (User) session.getAttribute("user_info");
if (user != null) {
int userCoins = user.getCoin();
model.addAttribute("userCoin", userCoins);
}
return "board/list";
}
@GetMapping("/board/write")
public String boardWrite() {
return "board/write";
}
@PostMapping("/board/write")
@Transactional(rollbackFor = { ArithmeticException.class })
public String boardWrite(
@ModelAttribute Board board,
@RequestParam("file") MultipartFile[] mFiles) {
User user = (User) session.getAttribute("user_info");
if (user == null) {
return "redirect:/signin";
}
board.setUser(user);
user.getBoards().add(board);
Board saveBoard = boardRepository.save(board);
for (MultipartFile mFile : mFiles) {
String originalFilename = mFile.getOriginalFilename();
FileAtch fileAtch = new FileAtch();
fileAtch.setOriginalName(originalFilename);
fileAtch.setSaveName(originalFilename);
fileAtch.setBoard(saveBoard);
fileAtchRepository.save(fileAtch);
String filename = mFile.getOriginalFilename();
File file = new File("C:/files/" + filename);
try {
mFile.transferTo(file);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
}
boardRepository.save(board);
return "redirect:/board/list";
}
@PostMapping("/board/comment")
public String comment(@ModelAttribute Comment comment, @RequestParam int boardId) {
User user = (User) session.getAttribute("user_info");
String name;
if (user != null) {
name = user.getName(); // ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์ด๋ฆ์ ๊ฐ์ ธ์ด
} else {
name = "Anonymous"; // ๋ก๊ทธ์ธํ์ง ์์ ๊ฒฝ์ฐ์๋ "Anonymous"
}
comment.setWriter(name);
comment.setWriter(name);
comment.setCreDate(new Date());
Board board = new Board();
board.setId(boardId);
comment.setBoard(board);
commentRepository.save(comment);
return "redirect:/board/view?id=" + boardId;
}
@GetMapping("/board/comment/remove")
public String commentRemove(@ModelAttribute Comment comment, @RequestParam int boardId) {
// 1๋ฒ new Comment(), setId()
// 2๋ฒ @ModelAttribute Comment comment
commentRepository.delete(comment);
return "redirect:/board/view?id=" + boardId;
}
@GetMapping("/board/fileAtch/remove")
public String fileAtchRemove(@ModelAttribute FileAtch fileAtch, @RequestParam int boardId) {
// 1๋ฒ new Comment(), setId()
// 2๋ฒ @ModelAttribute Comment comment
fileAtchRepository.delete(fileAtch);
return "redirect:/board/view?id=" + boardId;
}
@GetMapping("/board/view")
public String view(Model model, @RequestParam long id) {
Optional<Board> opt = boardRepository.findById(id);
model.addAttribute("board", opt.get());
return "board/view";
}
}
BoardRepository.java
package com.example.board.repository;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.board.model.Board;
public interface BoardRepository extends JpaRepository<Board, Long> {
List<Board> findTop5ByOrderByIdDesc();
Page<Board> findByTitleContaining(String keyword, Pageable pageable);
}
Board.java
package com.example.board.model;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import lombok.Data;
@Entity
@Data
public class Board{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
private String content;
// private String userId;
@ManyToOne
@JoinColumn(name = "member_id")
User user; //user_id
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
List<Comment> comments = new ArrayList<>();
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
List<FileAtch> fileAtchs = new ArrayList<>();
}
list.html
<!DOCTYPE html>
<html lang="en">
<head th:replace="common/head">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script>
</head>
<style>
@font-face {
font-family: 'TheJamsil5Bold';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2302_01@1.0/TheJamsil5Bold.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
body {
font-family: 'TheJamsil5Bold';
height: 100vh;
/* ํ๋ฉด ๋์ด์ 100%๋ก ์ค์ */
background-color: #000;
background-size: cover;
/* ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ๋ง๊ฒ ์กฐ์ */
background-repeat: no-repeat;
/* ์ด๋ฏธ์ง ๋ฐ๋ณต์ ์ค์ง */
}
.table {
color: white;
}
#write-btn {
background-color: #f2f5c0;
width: 150px;
height: 40px;
position: fixed;
float: right;
right: 120px;
bottom: 20px;
}
#write-btn:hover {
background-color: #e2ce38;
border-color: #e2ce38;
}
#search-btn {
background-color: #f2f5c0;
border-color: #f2f5c0;
}
.pagination-form {
margin-top: 20px;
}
.search-form {
margin-top: 20px;
margin-bottom: 40px;
}
.pagination .page-link {
color: #7e7c53;
}
.pagination .page-link:hover {
color: #FFFFFF;
background-color: #e2ce38;
}
.title h1{
text-align: center;
margin-bottom: 3rem;
color: white;
}
</style>
<body>
<div th:replace="common/header">
</div>
<nav th:replace="common/nav">
</nav>
<div class="title">
<h1>๊ณต์ง์ฌํญ</h1>
</div>
<div class="container mt-5">
<div class="row">
<table class="table table-hover">
<thead>
<tr>
<th>๋ฒํธ</th>
<th>์ ๋ชฉ</th>
<th>์์ฑ์</th>
</tr>
</thead>
<tbody>
<tr th:each="board : ${list}" th:onclick="'window.location = \'/board/view?id=' + ${board.id} + '\''">
<td th:text="${board.id}"></td>
<td th:text="${board.title}"></td>
<td th:text="${board.user.name}"></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-block" id="write-btn">๊ธ์ฐ๊ธฐ</button>
<div class="d-flex justify-content-center pagination-form">
<ul class="pagination">
<li class="page-item">
<a class="page-link" th:href="@{/board/list(P=1)}">ใ</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{/board/list(P=${prevGroupStart})}">ใ</a>
</li>
<th:block th:each="pageNumber : ${#numbers.sequence(startPage, endPage)}">
<li class="page-item" th:if="${pageNumber > 0}">
<a class="page-link" th:href="@{/board/list(P=${pageNumber})}">[[ ${pageNumber} ]]</a>
</li>
</th:block>
<li class="page-item">
<a class="page-link" th:href="@{/board/list(P=${nextGroupStart})}">ใ</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{/board/list(P=${totalPages})}">ใ</a>
</li>
</ul>
</div>
<div class="d-flex justify-content-center search-form">
<form th:action="@{/board/list}" method="get">
<input type="text" name="keyword" placeholder="๊ฒ์์ด ์
๋ ฅ">
<button type="submit" id="search-btn">๊ฒ์</button>
</form>
</div>
</div>
</div>
<script>
document.querySelector('#write-btn').addEventListener('click', () => {
location = '/board/write';
});
</script>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en">
<head th:replace="common/head">
</head>
<style>
@font-face {
font-family: 'TheJamsil5Bold';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2302_01@1.0/TheJamsil5Bold.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
body {
font-family: 'TheJamsil5Bold';
height: 100vh;
/* ํ๋ฉด ๋์ด์ 100%๋ก ์ค์ */
background-color: #000;
background-size: cover;
/* ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ๋ง๊ฒ ์กฐ์ */
background-repeat: no-repeat;
/* ์ด๋ฏธ์ง ๋ฐ๋ณต์ ์ค์ง */
color: white;
}
.btn {
background-color: #eee788;
border-color: #eee788;
text-decoration: none;
color: black;
}
.btn:hover {
background-color: #9c9b83;
border-color: #9c9b83;
}
</style>
<body>
<div th:replace="common/header">
</div>
<nav th:replace="common/nav">
</nav>
<div class="container mt-5">
<div class="row">
<form method="post" th:action="@{/board/update/} + ${board.id}" enctype="multipart/form-data">
<div class="mb-3">
<label for="title">Title:</label>
<input type="text" class="form-control" id="title" name="title" th:value="${board.title}">
</div>
<div class="mb-3">
<label for="content">Content:</label>
<textarea class="form-control" rows="5" name="content" id="content">[[ ${board.content} ]]</textarea>
</div>
<input type="file" name="file" id="" multiple>
<br>
<br>
<div class="d-grid gap-2">
<button class="btn btn-primary" type="submit" id="complete">์์ ์๋ฃ</button>
</div>
</form>
</div>
</div>
<div th:replace="common/footer">
</div>
</body>
</html>
view.html
<!DOCTYPE html>
<html lang="en">
<head th:replace="common/head"></head>
<style>
@font-face {
font-family: 'TheJamsil5Bold';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2302_01@1.0/TheJamsil5Bold.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
body {
font-family: 'TheJamsil5Bold';
height: 100vh;
/* ํ๋ฉด ๋์ด์ 100%๋ก ์ค์ */
background-color: #000;
background-size: cover;
/* ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ๋ง๊ฒ ์กฐ์ */
background-repeat: no-repeat;
/* ์ด๋ฏธ์ง ๋ฐ๋ณต์ ์ค์ง */
}
</style>
<body>
<div th:replace="common/header"></div>
<nav th:replace="common/nav"></nav>
<div class="container mt-5">
<div class="card">
<div class="card-body">
<span class="badge bg-primary rounded-pill" th:text="${board.id} + '๋ฒ'"></span>
<h5 class="card-title" th:text="${board.title}"></h5>
<h6 class="card-subtitle mb-2 text-muted" th:text="${board.user.email}"></h6>
<p class="card-text" th:text="${board.content}"></p>
<div id="imageContainer">
<img th:each="fileAtch : ${board.fileAtchs}" th:src="@{/download(id=${fileAtch.id})}" alt="์ฒจ๋ถ๋ ์ด๋ฏธ์ง">
</div>
<form action="/board/comment" method="post">
<input type="text" name="content">
<input type="hidden" name="boardId" th:value="${board.id}">
<button type="submit">๋๊ธ๐๋ฌ๊ธฐ</button>
</form>
<hr>
๋๊ธโ๏ธ
<ul th:each="comment : ${board.comments}">
<li>
[[ ${comment.content} ]] / [[ ${comment.writer} ]] / [[${#dates.format(comment.creDate, 'yyyy-MM-dd
HH:mm:ss')}]]
<!-- ๋๊ธ ์ญ์ ๋ฒํผ -->
<button
th:if="${session.user_info != null && session.user_info.email == board.user.email || session.user_info.name ==comment.writer }"
th:onclick="'commentRm(' + ${comment.id} +')'">๋์ญ๐ฅฒ</button>
</li>
</ul>
<hr>
์ฒจ๋ถ๋ ํ์ผ๐ฅ
<!-- ์ฒจ๋ถ๋ ํ์ผ ๋ชฉ๋ก -->
<ul th:each="fileAtch : ${board.fileAtchs}">
<li style="list-style-type: none;">
<a th:href="@{/download(id=${fileAtch.id})}">
[[${fileAtch.originalName}]]
</a>
<!-- ํ์ผ ์ญ์ ๋ฒํผ -->
<button th:if="${session.user_info != null && session.user_info.email == board.user.email}"
th:style="${fileAtch.originalName == '' ? 'display:none;' : ''}"
th:onclick="'fileRm(' + ${fileAtch.id} +')'">ํ์ผ์ญ์ โ๏ธ
</button>
</li>
</ul>
</div>
</div>
</div>
<div class="container mt-5">
<ul class="nav justify-content-end">
<li class="nav-item">
<a class="nav-link" th:href="@{/board/list}" id="list">๋ชฉ๋ก</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{'/board/update/' + ${board.id}}" id="update">์์ </a>
</li>
<li class="nav-item">
<a class="nav-link" th:data-num="${board.id}" id="delete">์ญ์ </a>
</li>
</ul>
</div>
<script>
// ํ์ผ ์
๋ก๋ input ์์์ change ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ์ฌ ์ด๋ฏธ์ง๋ฅผ ํ์
document.getElementById("fileInput").addEventListener("change", function (event) {
var files = event.target.files; // ์ ํ๋ ํ์ผ๋ค
// ์ด๋ฏธ์ง๋ฅผ ํ์ํ ์์ญ์ DOM ์์
var imageContainer = document.getElementById("imageContainer");
// ์ ํ๋ ํ์ผ๋ค์ ์ํํ๋ฉด์ ์ด๋ฏธ์ง๋ฅผ ํ์
for (var i = 0; i < files.length; i++) {
var file = files[i];
var imageURL = URL.createObjectURL(file); // ํ์ผ URL ์์ฑ
// ์ด๋ฏธ์ง๋ฅผ ํ์ํ๋ <img> ์์ ์์ฑ ๋ฐ ์ค์
var imageElement = document.createElement("img");
imageElement.src = imageURL;
// ์ด๋ฏธ์ง๋ฅผ ํ์ํ ์์ญ์ ์ถ๊ฐ
imageContainer.appendChild(imageElement);
}
});
</script>
<script>
var authorUserId = "[[${board.user.email}]]";
document.querySelector('#update').addEventListener('click', (e) => {
e.preventDefault();
// ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
var currentUser = "[[${session.user_info != null ? session.user_info.email : ''}]]"; // null ์ฒดํฌ ์ถ๊ฐ
if (currentUser !== authorUserId) {
alert('๊ฒ์๊ธ ์์ฑ์๋ง ์์ ํ ์ ์์ต๋๋ค.'); // ์์ ๊ถํ์ด ์์ ๋ ๊ฒฝ๊ณ ์ฐฝ ํ์
} else {
// ์์ ํ์ด์ง๋ก ์ด๋
window.location.href = e.target.getAttribute("href");
}
});
document.querySelector('#delete').addEventListener('click', (e) => {
e.preventDefault();
if (confirm('์ญ์ ํ์๊ฒ ์ต๋๊น?')) {
const num = e.target.dataset.num;
console.log(num);
if (num !== "null")
location = `/board/delete/${num}`;
}
});
function commentRm(id) {
const isOk = confirm('์ญ์ ํ์๊ฒ ์ต๋๊น?');
if (isOk) {
location = `/board/comment/remove?id=${id}&boardId=[[${board.id}]]`;
}
}
function fileRm(id) {
const isOk = confirm('์ญ์ ํ์๊ฒ ์ต๋๊น?');
if (isOk) {
location = `/board/fileAtch/remove?id=${id}&boardId=[[${board.id}]]`;
}
}
</script>
</body>
</html>
write.html
<!DOCTYPE html>
<html lang="en">
<head th:replace="common/head">
</head>
<style>
@font-face {
font-family: 'TheJamsil5Bold';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2302_01@1.0/TheJamsil5Bold.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
body {
font-family: 'TheJamsil5Bold';
height: 100vh;
/* ํ๋ฉด ๋์ด์ 100%๋ก ์ค์ */
background-color: #000;
background-size: cover;
/* ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ๋ง๊ฒ ์กฐ์ */
background-repeat: no-repeat;
/* ์ด๋ฏธ์ง ๋ฐ๋ณต์ ์ค์ง */
color: white;
}
.submit-color {
background-color: #f2f5c0;
border-color: #f2f5c0;
text-decoration: none;
color: black;
}
.submit-color:hover {
background-color: #e2ce38;
border-color: #e2ce38;
color: black;
}
</style>
<body>
<div th:replace="common/header">
</div>
<nav th:replace="common/nav">
</nav>
<div class="container mt-5">
<div class="row">
<form method="post" action="/board/write" enctype="multipart/form-data">
<div class="mb-3">
<label for="title">Title:</label>
<input type="text" class="form-control" id="title" name="title">
</div>
<th:block th:unless="${session.name} == null">
<input type="text" name="writer" th:value=${session.name}>
</th:block>
<div class="mb-3">
<label for="content">Content:</label>
<textarea class="form-control" rows="5" name="content" id="content"></textarea>
</div>
<input type="file" name="file" id="" multiple>
<br>
<br>
<div class="d-grid gap-2">
<button class="btn btn-primary submit-color" id="complete">์์ฑ์๋ฃ</button>
</div>
</form>
</div>
</div>
<div th:replace="common/footer">
</div>
</body>
</html>
์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ํ์ด์ง์ ๊ฒ์ํ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
728x90
๋ฐ์ํ