목차 | |
1. | file |
1. file
◈ build.gradle (boot_JPA)
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '3.2.11'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.ezen'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
implementation 'org.springframework.boot:spring-boot-starter-web'
// https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4.1
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
// https://mvnrepository.com/artifact/org.apache.tika/tika-core
implementation 'org.apache.tika:tika-core:2.4.1'
// https://mvnrepository.com/artifact/org.apache.tika/tika-parsers
implementation 'org.apache.tika:tika-parsers:2.4.1'
// https://mvnrepository.com/artifact/net.coobird/thumbnailator
implementation 'net.coobird:thumbnailator:0.4.17'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
/*providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'*/
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
◈ application.properties
spring.application.name=boot_JPA
server.port=8089
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/bootdb2
spring.datasource.username=springUser
spring.datasource.password=mysql
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.thymeleaf.cache=false
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=D:/_myProject/_java/_fileUpload/
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=40MB
uploadPath=file:///D:/_myProject/_java/_fileUpload/
◈ WebMvcConfig.java
package com.ezen.boot_JPA.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// upload 경로 매핑
String uploadPath = "file:///D:\\_myProject\\_java\\_fileUpload\\";
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations(uploadPath);
}
}
◈ File.java
package com.ezen.boot_JPA.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class File extends TimeBase{
@Id
private String uuid;
@Column(name = "save_dir", nullable = false)
private String saveDir;
@Column(name = "file_name", nullable = false)
private String fileName;
@Column(name = "file_type", nullable = false, columnDefinition = "integer default 0")
private int fileType;
private long bno;
@Column(name = "file_size")
private long fileSize;
}
◈ FileDTO.java
package com.ezen.boot_JPA.dto;
import lombok.*;
import java.time.LocalDateTime;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FileDTO {
private String uuid;
private String saveDir;
private String fileName;
private int fileType;
private long bno;
private long fileSize;
private LocalDateTime regAt;
private LocalDateTime modAt;
}
◈ BoardFileDTO.java
package com.ezen.boot_JPA.dto;
import lombok.*;
import java.util.List;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class BoardFileDTO {
private BoardDTO boardDTO;
private List<FileDTO> fileDTOList;
}
◈ BoardController.java
package com.ezen.boot_JPA.controller;
import com.ezen.boot_JPA.dto.BoardDTO;
import com.ezen.boot_JPA.dto.BoardFileDTO;
import com.ezen.boot_JPA.dto.FileDTO;
import com.ezen.boot_JPA.dto.PagingVO;
import com.ezen.boot_JPA.handler.FileHandler;
import com.ezen.boot_JPA.handler.FileRemoveHandler;
import com.ezen.boot_JPA.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
@Slf4j
@RequestMapping("/board/*")
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
private final FileHandler fileHandler;
@GetMapping("/register")
public void register(){}
/* @PostMapping("/register")
public String register(BoardDTO boardDTO){
log.info(">>> boardDTO >>> {}", boardDTO);
// insert, update, delete => return 1 row
// jpa insert, update, delete => return id
Long bno = boardService.insert(boardDTO);
log.info(">>> insert >>> {}", bno > 0 ? "OK" : "FAIL");
return "/index";
}*/
@PostMapping("/register")
public String Register(BoardDTO boardDTO, @RequestParam(name = "files", required = false)
MultipartFile[] files){
List<FileDTO> flist = null;
if(files != null && files[0].getSize() > 0){
// 파일 핸들러 작업
flist = fileHandler.uploadFiles(files);
}
long bno = boardService.insert(new BoardFileDTO(boardDTO, flist));
return "/index";
}
/*@GetMapping("/list")
public void list(Model model){
// paging이 없는 케이스
List<BoardDTO> list = boardService.getList();
model.addAttribute("list", list);
}*/
@GetMapping("/list")
public void list(Model model, @RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo){
// 화면에서 들어오는 pageNo = 1 / 0으로 처리가 되어야 함
// 화면에서 들어오는 pageNo = 2 / 1로 처리가 되어야 함
log.info(">>> pageNo >>> {}", pageNo);
pageNo = (pageNo == 0 ? 0 : pageNo - 1);
log.info(">>> pageNo >>> {}", pageNo);
Page<BoardDTO> list = boardService.getList(pageNo);
log.info(">>> list >>> {}", list.toString());
log.info(">>> totalCount >>> {}", list.getTotalElements()); // 전체 글 수
log.info(">>> totalPage >>> {}", list.getTotalPages()); // 전체 페이지 수 => realEndPage
log.info(">>> pageNumber >>> {}", list.getNumber()); // 전체 페이지 번호 => pageNo
log.info(">>> pageSize >>> {}", list.getSize()); // 한 페이지에 표시되는 길이 => qty
log.info(">>> next >>> {}", list.hasNext()); // next 여부
log.info(">>> prev >>> {}", list.hasPrevious()); // prev 여부
PagingVO pgvo = new PagingVO(list, pageNo);
log.info(">>> pgvo >>> {}", pgvo.toString());
model.addAttribute("list", list);
model.addAttribute("pgvo", pgvo);
}
@GetMapping("/detail")
public void modify(Model model, @RequestParam("bno") Long bno){
// BoardDTO boardDTO = boardService.getDetail(bno);
BoardFileDTO boardFileDTO = boardService.getDetail(bno);
model.addAttribute("boardFileDTO", boardFileDTO);
}
/* @PostMapping("/modify")
public String modify(BoardDTO boardDTO, RedirectAttributes redirectAttributes){
Long bno = boardService.modify(boardDTO);
redirectAttributes.addAttribute("bno", boardDTO.getBno());
return "redirect:/board/detail";
} */
@PostMapping("/modify")
public String modify(BoardDTO boardDTO, @RequestParam(name = "files", required = false) MultipartFile[] files, RedirectAttributes redirectAttributes){
List<FileDTO> flist = null;
if(files != null && files[0].getSize() > 0){
flist = fileHandler.uploadFiles(files);
}
Long bno = boardService.modify(new BoardFileDTO(boardDTO, flist));
redirectAttributes.addAttribute("bno", boardDTO.getBno());
return "redirect:/board/detail";
}
@GetMapping("/delete")
public String delete(@RequestParam("bno") Long bno){
boardService.delete(bno);
return "redirect:/board/list";
}
@ResponseBody
@DeleteMapping("/file/{uuid}")
public String fileRemove(@PathVariable("uuid") String uuid){
FileDTO fvo = boardService.getFile(uuid);
long bno = boardService.fileRemove(uuid);
FileRemoveHandler fr = new FileRemoveHandler();
boolean isDel = fr.deleteFile(fvo);
return (bno > 0 && isDel) ? "1" : "0";
}
}
◈ BoardService.java
package com.ezen.boot_JPA.service;
import com.ezen.boot_JPA.dto.BoardDTO;
import com.ezen.boot_JPA.dto.BoardFileDTO;
import com.ezen.boot_JPA.dto.FileDTO;
import com.ezen.boot_JPA.entity.Board;
import com.ezen.boot_JPA.entity.File;
import org.springframework.data.domain.Page;
import java.util.List;
public interface BoardService {
// 추상 메서드만 가능한 인터페이스
// 메서드가 default(접근제한자) 구현 가능
Long insert(BoardDTO boardDTO);
long insert(BoardFileDTO boardFileDTO);
// BoardDTO(class): bno title writer content regAt modAt
// Board(table) : bno title writer content
// BoardDTO => board 변환
// 화면에서 가져온 BoardDTO 객체를 저장을 위한 Board 객체로 변환
default Board convertDtoToEntity(BoardDTO boardDTO){
return Board.builder()
.bno(boardDTO.getBno())
.title(boardDTO.getTitle())
.writer(boardDTO.getWriter())
.content(boardDTO.getContent())
.build();
}
// board => BoardDTO 변환
// DB에서 가져온 Board 객체를 화면에 뿌리기 위한 BoardDTO 객체로 변환
default BoardDTO convertEntityToDto(Board board){
return BoardDTO.builder()
.bno(board.getBno())
.title(board.getTitle())
.writer(board.getWriter())
.content(board.getContent())
.regAt(board.getRegAt())
.modAt(board.getModAt())
.build();
}
// File 객체 convert
// FileDTO => File Entity
default File convertDtoToEntity(FileDTO fileDTO){
return File.builder()
.uuid(fileDTO.getUuid())
.saveDir(fileDTO.getSaveDir())
.fileName(fileDTO.getFileName())
.fileType(fileDTO.getFileType())
.bno(fileDTO.getBno())
.fileSize(fileDTO.getFileSize())
.build();
}
// File Entity => FileDTO
default FileDTO convertEntityToDto(File file){
return FileDTO.builder()
.uuid(file.getUuid())
.saveDir(file.getSaveDir())
.fileName(file.getFileName())
.fileType(file.getFileType())
.bno(file.getBno())
.fileSize(file.getFileSize())
.regAt(file.getRegAt())
.modAt(file.getModAt())
.build();
}
// List<BoardDTO> getList();
Page<BoardDTO> getList(int pageNo);
// BoardDTO getDetail(Long bno);
BoardFileDTO getDetail(Long bno);
// Long modify(BoardDTO boardDTO);
Long modify(BoardFileDTO boardFileDTO);
void delete(Long bno);
long fileRemove(String uuid);
FileDTO getFile(String uuid);
}
◈ BoardServiceImpl.java
package com.ezen.boot_JPA.service;
import com.ezen.boot_JPA.dto.BoardDTO;
import com.ezen.boot_JPA.dto.BoardFileDTO;
import com.ezen.boot_JPA.dto.FileDTO;
import com.ezen.boot_JPA.entity.Board;
import com.ezen.boot_JPA.entity.File;
import com.ezen.boot_JPA.repository.BoardRepository;
import com.ezen.boot_JPA.repository.FileRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
@Service
public class BoardServiceImpl implements BoardService{
private final BoardRepository boardRepository;
private final FileRepository fileRepository;
@Override
public Long insert(BoardDTO boardDTO) {
// 저장 객체는 Board
// save() : insert 후 저장 객체의 id를 리턴
// save() Entity 객체를 파라미터로 전송
return boardRepository.save(convertDtoToEntity(boardDTO)).getBno();
}
@Transactional
@Override
public long insert(BoardFileDTO boardFileDTO) {
// long bno = boardRepository.save(convertDtoToEntity(boardFileDTO.boardDTO())).getBno();
long bno = insert(boardFileDTO.getBoardDTO());
if (bno > 0 && boardFileDTO.getFileDTOList() != null){
for(FileDTO fileDTO : boardFileDTO.getFileDTOList()){
fileDTO.setBno(bno);
bno = fileRepository.save(convertDtoToEntity(fileDTO)).getBno();
}
}
return bno;
}
// @Override
// public List<BoardDTO> getList() {
// // 컨트롤러로 보내야 하는 리턴은 List<BoardDTO>
// // DB에서 가져오는 리턴은 List<Board> > BoardDTO 객체로 변환
// // findAll()
// // 정렬 : Sort.by(Sort.Direction.DESC, "정렬기준 칼럼명")
// List<Board> boardList = boardRepository.findAll(Sort.by(Sort.Direction.DESC, "bno"));
// /* List<BoardDTO> boardDTOList = new ArrayList<>();
// for(Board board : boardList){
// boardDTOList.add(convertEntityToDto(board));
// }*/
// List<BoardDTO> boardDTOList = boardList.stream()
// .map(b -> convertEntityToDto(b)).toList();
// return boardDTOList;
// }
@Override
public Page<BoardDTO> getList(int pageNo) {
// pageNo = 0부터 시작
// 0 => limit 0, 10 / 1 => limit 10, 10
Pageable pageable = PageRequest.of(pageNo, 10,
Sort.by("bno").descending());
Page<Board> list = boardRepository.findAll(pageable);
Page<BoardDTO> boardDTOList = list.map(b -> convertEntityToDto(b));
return boardDTOList;
}
@Override
public BoardFileDTO getDetail(Long bno) {
Optional<Board> optional = boardRepository.findById(bno);
if(optional.isPresent()){
BoardDTO boardDTO = convertEntityToDto(optional.get());
// file bno에 일치하는 모든 파일 리스트를 가져오기
// select * from file where bno = #{bno}
List<File> flist = fileRepository.findByBno(bno);
List<FileDTO> fileDTOList = flist.stream()
.map(f -> convertEntityToDto(f)).toList();
BoardFileDTO boardFileDTO = new BoardFileDTO(boardDTO, fileDTOList);
log.info(">>> boardFileDTO >>> {}", boardFileDTO);
return boardFileDTO;
}
return null;
}
@Override
public Long modify(BoardFileDTO boardFileDTO) {
long bno = insert(boardFileDTO);
return bno;
/*Optional<Board> optional = boardRepository.findById(boardFileDTO.getBoardDTO().getBno());
if(optional.isPresent()){
Board board = optional.get();
board.setTitle(boardFileDTO.getBoardDTO().getTitle());
board.setContent(boardFileDTO.getBoardDTO().getContent());
long bno = boardRepository.save(board).getBno();
if (bno > 0 && boardFileDTO.getFileDTOList() != null) {
for (FileDTO fileDTO : boardFileDTO.getFileDTOList()) {
fileDTO.setBno(bno);
bno = fileRepository.save(convertDtoToEntity(fileDTO)).getBno();
}
return bno;
}
}
return 0L;*/
}
/* @Override
public BoardDTO getDetail(Long bno) {
*//* findById : 아이디(PK)를 주고 해당 객체를 리턴
findById의 리턴타입 Optional<Board> 타입으로 리턴
Optional<T> : nullPointException이 발생하지 않도록 도와줌.
Optional.isEmpty() : null일 경우 확인 가능 true / false
Optional.isPresent() : 값이 있는지 확인 true / false
Optional.get() : 객체 가져오기
* *//*
Optional<Board> optional = boardRepository.findById(bno);
if(optional.isPresent()){
BoardDTO boardDTO = convertEntityToDto(optional.get());
return boardDTO;
}
return null;
}*/
// @Override
// public Long modify(BoardDTO boardDTO) {
// // update : jpa는 업데이트가 없음.
// // 기존 객체를 가져와서 set 수정 후 다시 저장
// Optional<Board> optional = boardRepository.findById(boardDTO.getBno());
// if(optional.isPresent()){
// Board entity = optional.get();
// entity.setTitle(boardDTO.getTitle());
// entity.setContent(boardDTO.getContent());
// // 다시 저장 (기존 객체에 덮어쓰기)
// return boardRepository.save(entity).getBno();
// }
// return null;
// }
// 삭제 : deleteById(id)
@Override
public void delete(Long bno) {
boardRepository.deleteById(bno);
}
@Override
public long fileRemove(String uuid) {
Optional<File> optional = fileRepository.findById(uuid);
if(optional.isPresent()){
fileRepository.deleteById(uuid);
return optional.get().getBno();
}
return 0;
}
@Override
public FileDTO getFile(String uuid) {
Optional<File> optional = fileRepository.findById(uuid);
log.info(">>> op >>> {}", optional);
if(optional.isPresent()){
FileDTO fileDTO = convertEntityToDto(optional.get());
return fileDTO;
}
return null;
}
}
◈ FileRepository.java
package com.ezen.boot_JPA.repository;
import com.ezen.boot_JPA.entity.File;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface FileRepository extends JpaRepository<File, String> {
List<File> findByBno(long bno);
}
◈ FileHandler.java
package com.ezen.boot_JPA.handler;
import com.ezen.boot_JPA.dto.FileDTO;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.tika.Tika;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Slf4j
@Component
public class FileHandler {
private final String UP_DIR = "D:\\_myProject\\_java\\_fileUpload\\";
public List<FileDTO> uploadFiles(MultipartFile[] files){
List<FileDTO> flist = new ArrayList<>();
LocalDate date = LocalDate.now();
// 2024-11-15 =? 2024\\11\\15
String today = date.toString().replace("-", File.separator);
// D:\_myProject\_java\_fileUpload\2024\11\15
File folders = new File(UP_DIR, today);
// mkdir = 1개의 폴더만 // mkdirs = 여러 개
if(!folders.exists()){
folders.mkdirs(); // 여러 개 생성
}
/* 폴더 생성 완료 */
for(MultipartFile file : files){
FileDTO fileDTO = new FileDTO();
fileDTO.setSaveDir(today);
fileDTO.setFileSize(file.getSize());
String originalFileName = file.getOriginalFilename();
String onlyFileName = originalFileName.substring(originalFileName.lastIndexOf(File.separator)+1);
fileDTO.setFileName(onlyFileName);
UUID uuid = UUID.randomUUID();
fileDTO.setUuid(uuid.toString());
// ------------ fvo 설정 마무리
// 디스크에 저장할 파일 설정
String fullFileName = uuid.toString()+"_"+onlyFileName;
String thumbFileName = uuid.toString()+"_th_"+onlyFileName;
File storeFile = new File(folders, fullFileName); // 실제 저장 객체
// 저장
try{
file.transferTo(storeFile); // 실제 파일의 값을 저장 File 객체에 기록
if(isImageFile(storeFile)){
fileDTO.setFileType(1);
File thumbnail = new File(folders, thumbFileName);
// 썸네일 작업
Thumbnails.of(storeFile).size(100, 100).toFile(thumbnail);
}
} catch (Exception e){
e.printStackTrace();
}
// for문 안
flist.add(fileDTO);
}
return flist;
}
private boolean isImageFile(File file) throws IOException {
String mimeType = new Tika().detect(file);
return mimeType.startsWith("image");
}
}
◈ boardRegister.js
console.log("boardRegister.js in");
document.getElementById('trigger').addEventListener('click',()=>{
document.getElementById('file').click();
});
// 실행파일 막기 / 20MB 이상
const regExp = new RegExp("\.(exe|sh|bat|jar|dll|msi)$"); // 실행 파일 막기
const maxSize = 1024*1024*20;
function fileValidation(fileName, fileSize){
if(regExp.test(fileName)){
return 0;
}else if(fileSize > maxSize){
return 0;
}else{
return 1;
}
}
document.addEventListener('change',(e)=>{
if(e.target.id == 'file'){
const fileObject = document.getElementById('file').files;
console.log(fileObject);
document.getElementById('regBtn').disabled = false;
const fileZone = document.getElementById('fileZone');
// 이전에 추가한 파일 삭제
fileZone.innerHTML = "";
let ul = `<ul class="list-group list-group-flush">`;
let isOk = 1; // 여러 파일에 대한 값을 확인하기 위해 1 *
for(let files of fileObject){
let vaild = fileValidation(file.name, file.size);
isOk *= vaild;
ul += `<li class="list-group-item">`;
ul += `<div class="ms-2 me-auto">`;
ul += `${vaild ? '<div class="fw-bold">업로드 가능</div>' : '<div class="fw-bold text-danger">업로드 불가능</div>'}`;
ul += `${file.name}</div>`;
ul += `<span class="badge text-bg-${vaild ? 'success' : 'danger'} rounded-pill">${file.size}Bytes</span></li>`;
}
ul += `</ul>`;
fileZone.innerHTML = ul;
if(isOk == 0){
document.getElementById('regBtn').disabled = true;
}
}
});
◈ boardDetail.js
console.log("board detail.js in!!");
document.getElementById('listBtn').addEventListener('click', ()=>{
location.href = "/board/list";
});
document.getElementById('modBtn').addEventListener('click',()=>{
document.getElementById('title').readOnly=false;
document.getElementById('content').readOnly=false;
// 버튼 생성
let modBtn = document.createElement("button");
modBtn.setAttribute("type","submit");
modBtn.setAttribute("id","regBtn");
modBtn.classList.add("btn","btn-outline-warning");
modBtn.innerText="Submit";
// 추가
document.getElementById("modForm").appendChild(modBtn);
document.getElementById("modBtn").remove();
document.getElementById("delBtn").remove();
// file -> fileUpload 버튼 disabled = false
document.getElementById('trigger').disabled = false;
let fileDelBtn = document.querySelectorAll(".file-x");
console.log(fileDelBtn);
for(let delBtn of fileDelBtn){
delBtn.disabled = false;
}
});
document.addEventListener('click',(e)=>{
if(e.target.classList.contains('file-x')){
console.log(e.target);
let uuid = e.target.dataset.uuid;
fileRemoveToServer(uuid).then(result => {
if(result > 0){
alert("파일 삭제 성공");
e.target.closest('li').remove();
}
})
}
});
// 비동기 데이터 보내기
async function fileRemoveToServer(uuid) {
try{
const url = '/board/file/'+uuid;
const config = {
method:"delete"
}
const resp = await fetch(url, config);
const result = await resp.text();
return result;
}catch(error){
console.log(error);
}
}
◈ register.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content" class="container-md">
<h1>Boot Register Page</h1>
<hr>
<div>
<form action="/board/register" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="t" class="form-label">Title</label>
<input type="text" class="form-control" name="title" id="t" placeholder="title...">
</div>
<div class="mb-3">
<label for="w" class="form-label">Writer</label>
<input type="text" class="form-control" name="writer" id="w" placeholder="writer...">
</div>
<div class="mb-3">
<label for="c" class="form-label">Content</label>
<textarea class="form-control" name="content" id="c" cols="10" rows="3">content...</textarea>
</div>
<!-- 파일 등록 라인 -->
<div class="mb-3">
<label for="file">File :</label>
<input type="file" class="form-control" name="files" id="file" multiple style="display:none;">
</div>
<button type="button" id="trigger" class="btn btn-primary">File Upload</button>
<!-- file 출력 라인 -->
<div class="input-group mb-3" id="fileZone"></div>
<button type="submit" class="btn btn-primary" id="regBtn">register</button>
</form>
</div>
<script th:src="@{/js/boardRegister.js}"></script>
</div>
◈ detail.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content">
<div class="container-md">
<h1>Board Detail Page [[${boardFileDTO.boardDTO.bno}]]</h1>
<form action="/board/modify" method="post" enctype="multipart/form-data" id="modForm" th:with="boardDTO=${boardFileDTO.boardDTO}">
<div class="mb-3">
<label for="r" class="form-label">Created At</label>
<input type="text" class="form-control" name="regDate" id="r" th:value="${boardDTO.regAt}" readonly>
</div>
<div class="mb-3">
<input type="hidden" class="form-control" name="bno" id="n" th:value="${boardDTO.bno}">
</div>
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" name="title" id="title" th:value="${boardDTO.title}" readonly>
</div>
<div class="mb-3">
<label for="writer" class="form-label">Writer</label>
<input type="text" class="form-control" name="writer" id="writer" th:value="${boardDTO.writer}" readonly>
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control" name="content" id="content" rows="3" readonly>[[${boardFileDTO.boardDTO.content}]]</textarea>
</div>
<!-- 파일 출력라인 -->
<div class="mb-3">
<ul class="list-group">
<li th:each="fvo:${boardFileDTO.fileDTOList}" class="list-group-item">
<div th:if="${fvo.fileType > 0}" class="ms-2 me-auto">
<img th:src="@{|/upload/${fvo.saveDir}/${fvo.uuid}_${fvo.fileName}|}" alt="img" />
</div>
<div th:unless="${fvo.fileType > 0}" class="ms-2 me-auto">
<!-- 일반 파일은 다운로드 가능 -->
<a th:href="@{|/upload/${fvo.saveDir}/${fvo.uuid}_${fvo.fileName}|}" th:download="${fvo.fileName}">
<!-- 파일 모양 아이콘 -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-paperclip" viewBox="0 0 16 16">
<path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z"/>
</svg>
</a>
</div>
<div class="ms-2 me-auto">
<div class="fw-bold">[[${fvo.fileName}]]</div>
[[${fvo.regAt}]]
</div>
<span class="badge text-bg-success rounded-pill">[[${fvo.fileSize}]]Bytes</span>
<button type="button" th:data-uuid="${fvo.uuid}" class="btn btn-outline-danger bnt-nm file-x" disabled>x</button>
</li>
</ul>
</div>
<!-- file 추가 라인 -->
<div class="mb-3">
<input type="file" class="form-control" name="files" id="file" multiple style="display:none;">
</div>
<button type="button" id="trigger" class="btn btn-primary" disabled>File Upload</button> <br>
<!-- 파일 수정 등록 라인 -->
<div class="input-group mb-3" id="fileZone"></div>
<button type="button" id="listBtn" class="btn btn-primary">List</button>
<button type="button" id="modBtn" class="btn btn-warning">Modify</button>
<a th:href="@{/board/delete(bno=${boardDTO.bno})}">
<button type="button" id="delBtn" class="btn btn-danger">Delete</button>
</a>
</form>
<!-- comment line -->
<!-- post -->
<div class="input-group mb-3" >
<span class="input-group-text" id="cmtWriter"></span>
<input type="text" id="cmtText" class="form-control" placeholder="Add Comment..." aria-label="Username" aria-describedby="basic-addon1">
<button type="button" id="cmtAddBtn" class="btn btn-secondary">post</button>
</div>
<!-- spread -->
<ul class="list-group list-group-flush" id="cmtListArea">
<li class="list-group-item">
<div class="ms-2 me-auto">
<div class="fw-bold">writer</div>
Content
</div>
<span class="badge text-bg-primary rounded-pill">regDate</span>
</li>
</ul>
<!-- moreBtn-->
<div>
<button type="button" id="moreBtn" data-page="1" class="btn btn-dark" style="visibility:hidden">MORE + </button>
</div>
<!-- modal -->
<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="cmtWriterMod">${authNick }</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="text" class="form-control" id="cmtTextMod">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" id="cmtModBtn" class="btn btn-primary">changes</button>
</div>
</div>
</div>
</div>
<script th:inline="javascript">
let bnoVal = [[${boardFileDTO.boardDTO.bno}]]
console.log(bnoVal);
</script>
</div>
<script th:src="@{/js/boardDetail.js}"></script>
<script th:src="@{/js/boardComment.js}"></script>
<script th:src="@{/js/boardRegister.js}"></script>
<script>
spreadCommentList(bnoVal);
</script>
</div>
▷ 출석
'Java > JPA' 카테고리의 다른 글
JPA 기초(search) - AWS 풀스택 과정 86일차 (0) | 2024.11.27 |
---|---|
JPA 기초(security) - AWS 풀스택 과정 85일차 (0) | 2024.11.26 |
JPA 기초(comment) - AWS 풀스택 과정 83일차 (0) | 2024.11.22 |
JPA 설정 및 기초 - AWS 풀스택 과정 82일차 (0) | 2024.11.21 |