diff --git a/src/main/java/sgis/attach/entity/Attach.java b/src/main/java/sgis/attach/entity/Attach.java new file mode 100644 index 0000000..820cea9 --- /dev/null +++ b/src/main/java/sgis/attach/entity/Attach.java @@ -0,0 +1,26 @@ +package sgis.attach.entity; + +import java.util.List; +import java.util.UUID; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Attach { + + private long attachment_id; //파일 PK + private long postId; //게시글아이디 + private String fileName; //원본 파일명 + private String storedFileName; //서버에 저장된 고유 파일명 (UUID등) + private String filePath; //파일 저장 경로 + private long fileSize; //파일 크기(바이트) + private String fileType; //파일 MIME(타입(e.g., image/jpeg) + private long downloadCount; //다운로드횟수 + private String createdAt; //생성일 +} diff --git a/src/main/java/sgis/attach/mapper/AttachMapper.java b/src/main/java/sgis/attach/mapper/AttachMapper.java new file mode 100644 index 0000000..2e34f1c --- /dev/null +++ b/src/main/java/sgis/attach/mapper/AttachMapper.java @@ -0,0 +1,14 @@ +package sgis.attach.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; + +import sgis.attach.entity.Attach; + +@Mapper //- Mybatis API +public interface AttachMapper { + public List selectAttachList(); + + public int insertAttachList(List attachList); +} diff --git a/src/main/java/sgis/attach/service/AttachService.java b/src/main/java/sgis/attach/service/AttachService.java new file mode 100644 index 0000000..86c6325 --- /dev/null +++ b/src/main/java/sgis/attach/service/AttachService.java @@ -0,0 +1,21 @@ +package sgis.attach.service; + +import java.util.List; +import sgis.attach.entity.Attach; + +public interface AttachService { + + /** + * 모든 첨부파일을 조회합니다. + * @return 모든 Attach 객체의 리스트 + */ + List getLists(); + + /** + * 새 게시글을 생성합니다. + * @param post 생성할 Post 객체 + * @return 생성된 Post 객체 (ID 포함) + */ + boolean insertAttachList(List attachList); + +} \ No newline at end of file diff --git a/src/main/java/sgis/attach/service/impl/AttachServiceImpl.java b/src/main/java/sgis/attach/service/impl/AttachServiceImpl.java new file mode 100644 index 0000000..3120cbb --- /dev/null +++ b/src/main/java/sgis/attach/service/impl/AttachServiceImpl.java @@ -0,0 +1,39 @@ +package sgis.attach.service.impl; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import sgis.attach.entity.Attach; +import sgis.attach.mapper.AttachMapper; +import sgis.attach.service.AttachService; + +import java.util.List; + +@Service +public class AttachServiceImpl implements AttachService { + + private final AttachMapper attachMapper; + + @Autowired + public AttachServiceImpl(AttachMapper attachMapper) { + this.attachMapper = attachMapper; + } + + @Override + public List getLists() { + return attachMapper.selectAttachList(); + } + + @Override + @Transactional + public boolean insertAttachList(List attachList) { + int insertedRows = attachMapper.insertAttachList(attachList); + if (insertedRows > 0) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/sgis/board/controller/BoardRestController.java b/src/main/java/sgis/board/controller/BoardRestController.java index 160f16c..84c440f 100644 --- a/src/main/java/sgis/board/controller/BoardRestController.java +++ b/src/main/java/sgis/board/controller/BoardRestController.java @@ -1,11 +1,13 @@ package sgis.board.controller; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; +import java.text.SimpleDateFormat; import java.time.OffsetDateTime; // OffsetDateTime import 추가 import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -15,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; // ModelAttribute import는 다른 메서드를 위해 유지 import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -24,20 +25,23 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.multipart.MultipartFile; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import egovframework.com.cmm.util.EgovUserDetailsHelper; +import sgis.attach.entity.Attach; +import sgis.attach.service.AttachService; import sgis.board.entity.Board; import sgis.board.entity.Member; import sgis.board.entity.Post; // Post entity import import sgis.board.mapper.BoardMapper; import sgis.board.mapper.MemberMapper; -import sgis.board.mapper.PostMapper; import sgis.board.service.PostService; +import sgis.com.util.FileUploadUtil; +import sgis.com.vo.FileVO; import sgis.com.vo.SessionVO; import sgis.com.web.BaseController; @@ -50,6 +54,9 @@ public class BoardRestController extends BaseController { @Autowired PostService postService; + @Autowired + AttachService attachService; + @Autowired MemberMapper memberMapper; @@ -192,7 +199,8 @@ public class BoardRestController extends BaseController { @RequestParam("boardContent") String content, // Using @RequestParam(value = "parentPostId", required = false) for optionality. // If it comes as an empty string, it will be null. - @RequestParam(value = "parentPostId", required = false) Long parentPostId // This is good + @RequestParam(value = "parentPostId", required = false) Long parentPostId/*, // This is good + @RequestParam("files") List files*/ ) { Post newPost = new Post(); newPost.setBoardCategoryId(boardCategoryId); @@ -233,6 +241,41 @@ public class BoardRestController extends BaseController { // PostService를 통해 게시글 생성 Post createdPost = postService.createPost(newPost); + // 게시글 등록 성공시 파일 서버 저장 및 테이블 insert 처리 +/* Date nowDate = new Date(); + String strNowYyyy = new SimpleDateFormat("yyyy").format(nowDate); + String strNowMm = new SimpleDateFormat("MM").format(nowDate); + String fileUploadPath = "uploads/files/" + strNowYyyy + "/" + strNowMm + "/"; + HashMap uploadedFileList = FileUploadUtil.multiUploadFormFile(files, fileUploadPath); + List tbFileList = new ArrayList(); // TB_ATTACHMENT에 insert할 리스트데이타 + // 서버에 파일 업로드 처리 후, 성공하거나 util이 처리한 파일 데이타를 리턴하면 아래 수행 + if (uploadedFileList != null && uploadedFileList.containsKey("file_list")) { + Object fileListObj = uploadedFileList.get("file_list"); + + if (fileListObj instanceof List) { + List fileList = (List) fileListObj; + + for (Object obj : fileList) { + if (obj instanceof FileVO) { + FileVO fileVO = (FileVO) obj; + Attach attach = new Attach(); + attach.setPostId(createdPost.getPostId()); + attach.setFileName(fileVO.getFileName()); // 원본파일명 + attach.setStoredFileName(fileVO.getStoredFileName()); // 실제저장파일명 + attach.setFilePath(fileVO.getFilePath()); // 파일저장경로 + attach.setFileSize(fileVO.getFileSize()); // 파일크기 + attach.setFileType(fileVO.getFileType()); // 파일MIME타입 + + tbFileList.add(attach); + } + } + } + + if (tbFileList.size()>0) { + attachService.insertAttachList(tbFileList); + } + }*/ + // 생성된 Post 객체 반환 (클라이언트에게 성공 여부 및 생성된 게시글 정보 전달) return createdPost; } diff --git a/src/main/java/sgis/com/util/FileUploadUtil.java b/src/main/java/sgis/com/util/FileUploadUtil.java new file mode 100644 index 0000000..0b47273 --- /dev/null +++ b/src/main/java/sgis/com/util/FileUploadUtil.java @@ -0,0 +1,97 @@ +package sgis.com.util; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import egovframework.com.cmm.service.EgovProperties; +import sgis.com.vo.FileUtil; +import sgis.com.vo.FileVO; +import sgis.com.web.BaseController; +/** + * @FileName : FileUploadUtil.java + * @Date : 2025. 7. 15. + * @Creator : + * @Discription : + */ +public class FileUploadUtil extends BaseController { + //logger 설정 + private final static Logger logger = LoggerFactory.getLogger(FileUploadUtil.class); + + /** + * 파일 업로드 처리 - 다중 + * @param request 파일 Multipart + * @param path uploads/files/yyyy/mm + * @return + */ + public static HashMap multiUploadFormFile(List files, String path) { + HashMap result = new HashMap(); // 최종리턴 파일목록(DB INSERT 할 데이타) +// HttpSession session = request.getSession(); + MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) files; + Iterator fileNameIterator = mpRequest.getFileNames(); + + String uploadPath = EgovProperties.getProperty("Globals.File.StoredPath").replaceAll("/", "\\"); + String uploadDirPath = ""; + if(path.startsWith(System.getProperty("file.separator"))) { + uploadDirPath = path.substring(1).replaceAll("/", "\\"); + } else { + uploadDirPath = path.replaceAll("/", "\\"); + } + uploadDirPath = uploadPath + uploadDirPath; + String originalFileName = ""; + String realFileName = ""; + List fileList = new ArrayList(); + while (fileNameIterator.hasNext()) { + //파일정보를 하나씩 취득한다 + MultipartFile multiFile = mpRequest.getFile((String) fileNameIterator.next()); + if (multiFile.getSize() > 0) { + originalFileName = multiFile.getOriginalFilename(); + realFileName = FileUtil.getUniqueFileName(uploadDirPath, originalFileName); + + + File file = new File(uploadDirPath + realFileName); + + // 디렉토리가 존재 하지 않을 경우 생성 + if(!file.exists()) { + file.setExecutable(false, true); + file.setReadable(true); + file.setWritable(false, true); + file.mkdirs(); + } + try{ + multiFile.transferTo(file); + FileVO uploadFile = new FileVO(); + // 파일 업로드 파일 원본파일명 + uploadFile.setFileName(originalFileName); + // 파일 업로드 파일 저장파일명 + uploadFile.setStoredFileName(realFileName); + // 파일 업로드 파일 저장경로 + uploadFile.setFilePath(uploadDirPath); + // 파일 업로드 파일 크기 + uploadFile.setFileSize(multiFile.getSize()); + // 파일 업로드 파일 MIME타입 + uploadFile.setFileType(multiFile.getContentType()); + + + fileList.add(uploadFile); + } catch (IllegalStateException e) { + logger.error("IllegalStateException"); + // 에러메세지리턴 + } catch (IOException e) { + logger.error("IOException"); + // 에러메세지리턴 + } + } + } + result.put("file_list", fileList); + return result; + } +} diff --git a/src/main/java/sgis/com/vo/FileUtil.java b/src/main/java/sgis/com/vo/FileUtil.java new file mode 100644 index 0000000..304f852 --- /dev/null +++ b/src/main/java/sgis/com/vo/FileUtil.java @@ -0,0 +1,63 @@ +package sgis.com.vo; + +import java.io.File; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 파일 처리 구현 클래스 + * @author + */ +public class FileUtil { + /** 로그처리 객체 */ + private static final Logger logger = LoggerFactory.getLogger(FileUtil.class); + + + /** + * 파일 이름이 디렉토리에 이미 존재하는지 체크한다 + * @param realPath 업로드디렉토리 + * @param fileName 업로드파일이름 + * @return String 파일이름 + */ + public static String getUniqueFileName(String realPath, String fileName) { + + int idx = 0; + int cnt = 1; + String temp = getSavedFileName(fileName); // 저장할 파일명 생성 + File tempFile = new File(realPath+temp); // 기존에 동일한 파일명의 파일 존재하는지 체크 + + //이미존재하는 파일인지 확인 + while(tempFile.exists()) { + //기존에 존재하면 확장자랑 파일명을 분리해서 [숫자]를 붙여서 파일이름을 짓는다 + if((idx=fileName.indexOf(".")) != -1){ + String left = fileName.substring(0, idx); + String right = fileName.substring(idx); + temp = left + "[" + cnt + "]" + right; + }else{ + temp = fileName + "[" + cnt + "]"; + } + + tempFile = new File(realPath+temp); + cnt++; + } + //실제 저장될 파일이름 저장 + fileName = temp; + + return fileName; + } + + public static String getSavedFileName(String originalFileName) { + // 확장자 추출 + String ext = ""; + int lastDot = originalFileName.lastIndexOf('.'); + if (lastDot != -1) { + ext = originalFileName.substring(lastDot); // .pdf, .jpg 등 + } + + // UUID 생성 + 확장자 붙이기 + String savedFileName = UUID.randomUUID().toString() + ext; + return savedFileName; + } +} + diff --git a/src/main/java/sgis/com/vo/FileVO.java b/src/main/java/sgis/com/vo/FileVO.java new file mode 100644 index 0000000..345e579 --- /dev/null +++ b/src/main/java/sgis/com/vo/FileVO.java @@ -0,0 +1,36 @@ +package sgis.com.vo; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @FileName : FileVO.java + * @Date : 2025. 7. 15. + * @Creator : + * @Discription : file vo + */ +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class FileVO implements Serializable { + //serialVersionUID + private static final long serialVersionUID = 6268293686980181848L; + private long attachment_id; //파일 PK + private String postId; //게시글아이디 + private String fileName; //원본 파일명 + private String storedFileName; //서버에 저장된 고유 파일명 (UUID등) + private String filePath; //파일 저장 경로 + private long fileSize; //파일 크기(바이트) + private String fileType; //파일 MIME(타입(e.g., image/jpeg) + private long downloadCount; //다운로드횟수 + private String createdAt; //생성일 + + +} diff --git a/src/main/resources/egovframework/mapper/sgis/attach/AttachMapper.xml b/src/main/resources/egovframework/mapper/sgis/attach/AttachMapper.xml new file mode 100644 index 0000000..f1624e9 --- /dev/null +++ b/src/main/resources/egovframework/mapper/sgis/attach/AttachMapper.xml @@ -0,0 +1,76 @@ + + + + + + + + + SELECT COALESCE(MAX(IDX) + 1, 1) FROM TB_ATTACHMENT + + insert into TB_ATTACHMENT( + attachment_id + ,post_id + ,file_name + ,stored_file_name + ,file_path + ,file_size + ,file_type + ,download_count + ,created_at + ) values( + #{attachment_id} + ,#{post_id} + ,#{file_name} + ,#{stored_file_name} + ,#{file_path} + ,#{file_size} + ,#{file_type} + ,0 + ,NOW() + ) + + + + + SELECT COALESCE(MAX(IDX) + 1, 1) FROM TB_ATTACHMENT + + insert into TB_ATTACHMENT( + attachment_id + ,post_id + ,file_name + ,stored_file_name + ,file_path + ,file_size + ,file_type + ,download_count + ,created_at + ) values + + ( + #{attachment_id} + ,#{post_id} + ,#{file_name} + ,#{stored_file_name} + ,#{file_path} + ,#{file_size} + ,#{file_type} + ,0 + ,NOW() + ) + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/sgis/com/board/boardWrite.jsp b/src/main/webapp/WEB-INF/jsp/sgis/com/board/boardWrite.jsp index 869eb7d..fd249e8 100644 --- a/src/main/webapp/WEB-INF/jsp/sgis/com/board/boardWrite.jsp +++ b/src/main/webapp/WEB-INF/jsp/sgis/com/board/boardWrite.jsp @@ -80,6 +80,17 @@ readonly="readonly"/> + + 파일첨부 + + +
+

여기에 파일을 드래그 앤 드롭 하거나 클릭해서 파일 선택

+ +
    +
    + + 내용 @@ -101,7 +112,9 @@ diff --git a/src/main/webapp/com/css/style.css b/src/main/webapp/com/css/style.css index 3ab8353..84c9e2a 100644 --- a/src/main/webapp/com/css/style.css +++ b/src/main/webapp/com/css/style.css @@ -1806,7 +1806,12 @@ footer .newsletter input { color: #6f6f6f; letter-spacing: normal; } .table .input {margin: 0px;} .table textarea {display: block;} - +.table #dropZone { height: auto !important; position: relative; } +.table #dropZone p { padding: 10px; } +.table #fileInput { position: absolute; top:0; left: 0; width: 100%; height: 100%; opacity: 0; } +.table #dropZone.dragover { background-color: #e9f7fe; border-color: #17a2b8; } +.table #dropZone #fileList { margin-bottom: 0} +.table .remove-btn { background: url(../img/common/icon/ico_btn_delete_s.png) no-repeat 10% 50%; display: inline-block; padding: 6px 0px 0px 23px; width: 60px; height: 30px; background-color: #a5d0e0; color: #fff; vertical-align: middle; border-radius: 6px;} .table-content-item {min-height: 200px;} .table-top-btn-group {position: relative; width: 100%; display: table; box-sizing: border-box; text-align: right; padding-bottom: 20px;} .table-bottom-btn-group {position: relative; width: 100%; display: table; box-sizing: border-box; text-align: right; padding-top: 20px;}