게시물 작성 기능 작업완료

게시물 조회 기능 작업중.
master
강석 최 2021-12-15 16:07:35 +09:00
parent 326932d08f
commit 0d2f7b92ab
14 changed files with 365 additions and 61 deletions

View File

@ -3,17 +3,13 @@ package com.dbnt.kcgfilemanager.controller;
import com.dbnt.kcgfilemanager.model.Board;
import com.dbnt.kcgfilemanager.model.UserInfo;
import com.dbnt.kcgfilemanager.service.BoardService;
import com.dbnt.kcgfilemanager.service.UserInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequiredArgsConstructor
@ -36,4 +32,16 @@ public class BoardController {
content.setFileList(request.getMultiFileMap().get("uploadFiles"));
return boardService.saveContent(content);
}
@GetMapping("/contentList")
public ModelAndView contentList(Board board){
ModelAndView mav = new ModelAndView("board/contentList");
mav.addObject("pageTitle", boardService.getPageTitle(board.getCategorySeq()));
board.setQueryInfo();
mav.addObject("contentList", boardService.selectContentList(board));
board.setContentCnt(boardService.selectContentListCnt(board));
board.setPaginationInfo();
mav.addObject("searchParams", board);
return mav;
}
}

View File

@ -60,8 +60,8 @@ public class adminController {
}
@GetMapping("/userMgt")
public ModelAndView userMgt(UserInfo userInfo) {
userInfo.setQueryInfo();
ModelAndView mav = new ModelAndView("admin/userMgt");
userInfo.setQueryInfo();
mav.addObject("userInfoList", userInfoService.selectUserInfoList(userInfo));
userInfo.setContentCnt(userInfoService.selectUserInfoListCnt(userInfo));
userInfo.setPaginationInfo();

View File

@ -0,0 +1,12 @@
package com.dbnt.kcgfilemanager.mapper;
import com.dbnt.kcgfilemanager.model.Board;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface BoardMapper {
List<Board> selectContentList(Board board);
Integer selectContentListCnt(Board board);
}

View File

@ -38,9 +38,9 @@ public class Board extends BaseModel{
private LocalDateTime createDate;
@Transient
private List<FileInfo> childFiles;
private String createName;
@Transient
private List<HashTagLink> hashTags;
private Integer fileCnt;
@Transient
private String hashTagStr;

View File

@ -3,6 +3,8 @@ package com.dbnt.kcgfilemanager.repository;
import com.dbnt.kcgfilemanager.model.FileInfo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FileInfoRepository extends JpaRepository<FileInfo, FileInfo.FileInfoId> {
import java.util.Optional;
public interface FileInfoRepository extends JpaRepository<FileInfo, FileInfo.FileInfoId> {
Optional<FileInfo> findTopByContentSeqOrderByFileSeqDesc(Integer contentSeq);
}

View File

@ -29,7 +29,6 @@ public class BoardCategoryService {
}
return categoryList;
}
@Transactional
public void insertBoardCategory(List<BoardCategory> categoryList){
boardCategoryRepository.saveAll(categoryList);

View File

@ -1,39 +1,41 @@
package com.dbnt.kcgfilemanager.service;
import com.dbnt.kcgfilemanager.model.Board;
import com.dbnt.kcgfilemanager.model.BoardLog;
import com.dbnt.kcgfilemanager.model.HashTag;
import com.dbnt.kcgfilemanager.model.HashTagLink;
import com.dbnt.kcgfilemanager.repository.BoardLogRepository;
import com.dbnt.kcgfilemanager.repository.BoardRepository;
import com.dbnt.kcgfilemanager.repository.HashTagLinkRepository;
import com.dbnt.kcgfilemanager.repository.HashTagRepository;
import com.dbnt.kcgfilemanager.mapper.BoardMapper;
import com.dbnt.kcgfilemanager.model.*;
import com.dbnt.kcgfilemanager.repository.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class BoardService {
private BoardRepository boardRepository;
private BoardLogRepository boardLogRepository;
private HashTagRepository hashTagRepository;
private HashTagLinkRepository hashTagLinkRepository;
private final BoardRepository boardRepository;
private final BoardLogRepository boardLogRepository;
private final HashTagRepository hashTagRepository;
private final HashTagLinkRepository hashTagLinkRepository;
private final FileInfoRepository fileInfoRepository;
private final BoardCategoryRepository boardCategoryRepository;
private final BoardMapper boardMapper;
@Transactional
public Integer saveContent(Board content){
boardRepository.save(content);
saveBoardLog(content.getContentSeq(), "I", null, content.getCreateId());
saveHashTagLink(content.getContentSeq(), content.getHashTagStr());
saveUploadFiles(content.getContentSeq(), content.getFileList());
return content.getContentSeq();
Integer contentSeq = boardRepository.save(content).getContentSeq();
saveBoardLog(contentSeq, "I", null, content.getCreateId());
saveHashTagLink(contentSeq, content.getHashTagStr());
saveUploadFiles(contentSeq, content.getCategorySeq(), content.getFileList());
return contentSeq;
}
public void saveBoardLog(Integer contentSeq, String status, String description, String createId){
private void saveBoardLog(Integer contentSeq, String status, String description, String createId){
BoardLog lastLog = boardLogRepository.findTopByContentSeqOrderByLogSeqDesc(contentSeq).orElse(null);
BoardLog log = new BoardLog();
log.setContentSeq(contentSeq);
@ -43,14 +45,17 @@ public class BoardService {
log.setCreateId(createId);
boardLogRepository.save(log);
}
public HashTag saveHashTag(String tagName){
private HashTag saveHashTag(String tagName){
HashTag tag = new HashTag();
tag.setTagName(tagName);
return hashTagRepository.save(tag);
}
public void saveHashTagLink(Integer contentSeq, String hashTagStr){
private void saveHashTagLink(Integer contentSeq, String hashTagStr){
String[] hashTagAry = hashTagStr.trim().replaceAll(",", "").split("#");
for(String tagName : hashTagAry){
if(!tagName.equals("")){
HashTag tag = hashTagRepository.findByTagName(tagName).orElse(null);
if(tag==null){
tag = saveHashTag(tagName);
@ -61,8 +66,65 @@ public class BoardService {
hashTagLinkRepository.save(link);
}
}
public void saveUploadFiles(Integer contentSeq, List<MultipartFile> files){
}
private void saveUploadFiles(Integer contentSeq, Integer categorySeq, List<MultipartFile> files){
FileInfo lastFileInfo = fileInfoRepository.findTopByContentSeqOrderByFileSeqDesc(contentSeq).orElse(null);
int fileSeq = lastFileInfo==null?1:(lastFileInfo.getFileSeq()+1);
for(MultipartFile file : files){
System.out.println(file.getName());
String saveName = UUID.randomUUID().toString();
String path = makeFilePath(categorySeq);
File saveFile = new File(path+"\\"+saveName);
if(file.getSize()!=0){ // 저장될 파일 확인
if(!saveFile.exists()){ // 저장될 경로 확인
if(saveFile.getParentFile().mkdirs()){
try{
saveFile.createNewFile();
}catch (IOException e){
e.printStackTrace();
}
}
}
try {
file.transferTo(saveFile);
}catch (IllegalStateException | IOException e){
e.printStackTrace();
}
}
String[] originalFilename = Objects.requireNonNull(file.getOriginalFilename()).split("\\.");
FileInfo fileInfo = new FileInfo();
fileInfo.setContentSeq(contentSeq);
fileInfo.setFileSeq(fileSeq++);
fileInfo.setOriginalName(originalFilename[0]);
fileInfo.setExtention(originalFilename[1]);
fileInfo.setConversionName(saveName);
fileInfo.setSavePath(path);
fileInfoRepository.save(fileInfo);
}
}
public String getPageTitle(Integer categorySeq) {
BoardCategory category = boardCategoryRepository.findById(categorySeq).orElse(null);
if(category.getParentSeq()==null){
return category.getCategoryName();
}
return getPageTitle(category.getParentSeq())+" > "+category.getCategoryName();
}
public String makeFilePath(Integer categorySeq){
BoardCategory category = boardCategoryRepository.findById(categorySeq).orElse(null);
if(category.getParentSeq()==null){
return "D:\\kcgFileManager\\"+category.getCategoryName();
}
return makeFilePath(category.getParentSeq())+"\\"+category.getCategoryName();
}
public List<Board> selectContentList(Board board) {
return boardMapper.selectContentList(board);
}
public Integer selectContentListCnt(Board board) {
return boardMapper.selectContentListCnt(board);
}
}

View File

@ -1,5 +1,9 @@
spring.devtools.livereload.enabled=true
#?? ??? ?? ??.
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=100MB
#thymeleaf
spring.thymeleaf.prefix=classpath:templates/
spring.thymeleaf.check-template-location=true

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dbnt.kcgfilemanager.mapper.BoardMapper">
<select id="selectContentList" resultType="Board" parameterType="Board">
SELECT A.TITLE AS title,
C.FILE_CNT AS fileCnt,
B.NAME AS createName,
A.CREATE_DATE AS createDate
FROM BOARD A
INNER JOIN USER_INFO B
ON A.CREATE_ID = B.USER_ID
INNER JOIN (SELECT CONTENT_SEQ, MAX(FILE_SEQ) AS FILE_CNT
FROM FILE_INFO
GROUP BY CONTENT_SEQ ) C
ON A.CONTENT_SEQ = C.CONTENT_SEQ
WHERE A.CATEGORY_SEQ = #{categorySeq}
ORDER BY A.CREATE_DATE DESC
LIMIT #{viewCnt} OFFSET #{firstIndex}
</select>
<!--<if test="endDate != null and endDate != ''">
AND A.CREATE_DATE &lt;= #{endDate}
</if>-->
<select id="selectContentListCnt" resultType="int" parameterType="int">
SELECT COUNT(*)
FROM BOARD A
INNER JOIN USER_INFO B
ON A.CREATE_ID = B.USER_ID
INNER JOIN (SELECT CONTENT_SEQ, MAX(FILE_SEQ) AS FILE_CNT
FROM FILE_INFO
GROUP BY CONTENT_SEQ ) C
ON A.CONTENT_SEQ = C.CONTENT_SEQ
WHERE CATEGORY_SEQ = ${categorySeq}
</select>
</mapper>

View File

@ -49,6 +49,8 @@ $(document).on('click', '.fileDelete', function (){
}
})
$(document).on('click', '#saveBtn', function (){
if(contentCheck()){
if(confirm("저장하시겠습니까?")){
const formData = new FormData($("#contentForm")[0]);
for(const file of files) {
if(!file.isDelete)
@ -60,13 +62,18 @@ $(document).on('click', '#saveBtn', function (){
url : "/board/saveContent",
processData: false,
contentType: false,
success : function(data) {
$("#contentSeq").val(data);
success : function(result) {
if(result>0){
alert("저장되었습니다.");
location.href = "/board/contentList?categorySeq="+$("#categorySeq").val();
}
},
error : function(xhr, status) {
}
})
}
}
})
$(document).on('change', '.categorySelector', function (){
@ -117,13 +124,41 @@ function setFileDiv(file, idx){
uploadDiv.append(fileInfo);
}
function contentCheck(){
let flag = true;
if(!$("#categorySeq").val()){
alert("분류 선택이 되지 않았습니다.")
flag = false;
}
if(!$("#title").val()){
alert("제목을 입력해주세요.")
flag = false;
}
if($(".fileDelete ").length===0){
alert("업로드 할 파일이 없습니다.")
flag = false;
}
let totalSize = 0;
for(const file of files) {
if(!file.isDelete){
totalSize+=file.size;
if(file.size>20971520){
alert("파일당 사이즈는 20MB을 넘길 수 없습니다.")
}
}
}
if(totalSize>104857600){
alert("첨부파일의 용량 합은 100MB를 넘길 수 없습니다.")
}
return flag;
}
function byteCalculation(size) {
const bytes = parseInt(size);
const s = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
const e = Math.floor(Math.log(bytes)/Math.log(1024));
if(e === "-Infinity") return "0 "+s[0];
else
return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
else return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
}

View File

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<th:block layout:fragment="script">
<script type="text/javascript" th:src="@{/js/board/contentList.js}"></script>
</th:block>
<div layout:fragment="content">
<main class="pt-3">
<h4 th:text="${pageTitle}"></h4>
<div class="row mx-0">
<div class="col-12 card text-center">
<div class="card-body">
<div class="row justify-content-start">
<div class="col-7">
<!--검색 form-->
<!--<form method="get" th:action="@{/admin/userMgt}">
<input type="hidden" name="pageIndex" id="pageIndex" th:value="${searchParams.pageIndex}">
<div class="row justify-content-between">
<div class="col-auto row">
<div class="col-auto">
<label for="viewCnt" class="col-form-label">데이터 수</label>
</div>
<div class="col-auto">
<select class="form-select" name="viewCnt" id="viewCnt">
<th:block th:each="num : ${#numbers.sequence(1,5)}">
<option th:value="${num*10}" th:text="${num*10}" th:selected="${searchParams.viewCnt==num*10}"></option>
</th:block>
</select>
</div>
</div>
<div class="col-auto">
<div class="row justify-content-end">
<div class="col-auto">
<select class="form-select" id="searchConditionSelector">
<option value="userId" th:selected="${searchParams.userId!=null and searchParams.userId!=''}">아이디</option>
<option value="name" th:selected="${searchParams.name!=null and searchParams.name!=''}">이름</option>
<option value="positionName" th:selected="${searchParams.positionName!=null and searchParams.positionName!=''}">부서</option>
<option value="departmentName" th:selected="${searchParams.departmentName!=null and searchParams.departmentName!=''}">직급</option>
<option value="createDate" th:selected="${searchParams.startDate!=null and searchParams.startDate!=''} or ${searchParams.endDate!=null and searchParams.endDate!=''}">생성일</option>
</select>
</div>
<div class="col-auto" id="searchTextDiv">
<input type="text" class="form-control" id="textSearch" th:value="${
(searchParams.userId!=null and searchParams.userId!='')?searchParams.userId:(
(searchParams.name!=null and searchParams.name!='')?searchParams.name:(
(searchParams.positionName!=null and searchParams.positionName!='')?searchParams.positionName:(
(searchParams.departmentName!=null and searchParams.departmentName!='')?searchParams.departmentName:''
)
)
)
}">
</div>
<div class="col-auto input-group w-auto input-daterange" id="dateSelectorDiv" style="display: none">
<input type="text" class="form-control" id="startDate" name="startDate" placeholder="시작일" autocomplete="off" disabled th:value="${searchParams.startDate}">
<input type="text" class="form-control" id="endDate" name="endDate" placeholder="종료일" autocomplete="off" disabled th:value="${searchParams.endDate}">
</div>
<div class="col-auto">
<input type="submit" class="btn btn-primary" id="searchBtn" value="검색">
</div>
</div>
</div>
</div>
</form>-->
<div class="row-cols-6">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>제목</th>
<th>파일 수</th>
<th>작성자</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<tr class="contentTr" th:each="content:${contentList}">
<td>
<input type="checkbox" class="contentCheckBox" th:value="${content.contentSeq}">
</td>
<td th:text="${content.title}"></td>
<td th:text="${content.fileCnt}"></td>
<td th:text="${content.createName}"></td>
<th:block th:if="${#dates.format(#dates.createNow(), 'yyyy-MM-dd')} == ${#temporals.format(content.createDate, 'yyyy-MM-dd')}">
<td th:text="${#temporals.format(content.createDate, 'HH:mm:ss')}"></td>
</th:block>
<th:block th:if="${#dates.format(#dates.createNow(), 'yyyy-MM-dd')} != ${#temporals.format(content.createDate, 'yyyy-MM-dd')}">
<td th:text="${#temporals.format(content.createDate, 'yyyy-MM-dd')}"></td>
</th:block>
</tr>
</tbody>
</table>
</div>
<div class="row justify-content-center">
<div class="col-auto">
<nav aria-label="Page navigation">
<ul class="pagination">
<th:block th:if="${searchParams.pageIndex>3}">
<li class="page-item" th:data-pageindex="${(searchParams.pageIndex)-3}">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
</th:block>
<th:block th:each="num : ${#numbers.sequence(searchParams.startNum, searchParams.endNum)}">
<li class="page-item" th:data-pageindex="${num}" th:classappend="${searchParams.pageIndex==num?'active':''}">
<a class="page-link" href="#" th:text="${num}"></a>
</li>
</th:block>
<th:block th:if="${searchParams.maxNum>searchParams.endNum+2}">
<li class="page-item" th:data-pageindex="${(searchParams.pageIndex)+3}">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</th:block>
</ul>
</nav>
</div>
</div>
</div>
<div class="col-5">
<ul class="nav nav-tabs" id="userTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="infoTab" data-bs-toggle="tab" type="button" role="tab">내용</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="categoryTab" data-bs-toggle="tab" type="button" role="tab">이력</button>
</li>
</ul>
<div class="tab-content border border-top-0" id="userContent">
<div class="py-5">
<h3>왼쪽 목록에서 선택해주세요.</h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</html>

View File

@ -30,7 +30,7 @@
<label for="title" class="col-sm-2 col-form-label">분류 선택</label>
<div class="col-sm">
<select class="form-select categorySelector" data-depth="1">
<option selected>분류를 선택해주세요</option>
<option value="" selected>분류를 선택해주세요</option>
<th:block th:each="depth1:${session.categoryList}">
<option th:value="${depth1.categorySeq}" th:text="${depth1.categoryName}"></option>
</th:block>
@ -38,7 +38,7 @@
</div>
<div class="col-sm">
<select class="form-select categorySelector" data-depth="2" disabled>
<option selected>분류를 선택해주세요</option>
<option value="" selected>분류를 선택해주세요</option>
<th:block th:each="depth1:${session.categoryList}">
<th:block th:each="depth2:${depth1.childCategoryList}">
<option th:value="${depth2.categorySeq}" th:data-parentseq="${depth2.parentSeq}" th:text="${depth2.categoryName}"></option>
@ -48,7 +48,7 @@
</div>
<div class="col-sm">
<select class="form-select categorySelector" data-depth="3" disabled>
<option selected>분류를 선택해주세요</option>
<option value="" selected>분류를 선택해주세요</option>
<th:block th:each="depth1:${session.categoryList}">
<th:block th:each="depth2:${depth1.childCategoryList}">
<th:block th:each="depth3:${depth2.childCategoryList}">
@ -59,8 +59,8 @@
</select>
</div>
<div class="col-sm">
<select class="form-select categorySelector" data-depth="4" name="categorySeq" disabled>
<option selected>분류를 선택해주세요</option>
<select class="form-select categorySelector" data-depth="4" name="categorySeq" id="categorySeq" disabled>
<option value="" selected>분류를 선택해주세요</option>
<th:block th:each="depth1:${session.categoryList}">
<th:block th:each="depth2:${depth1.childCategoryList}">
<th:block th:each="depth3:${depth2.childCategoryList}">

View File

@ -50,7 +50,7 @@
<div class="collapse ps-3" th:id="|collapse${depth3.categorySeq}|">
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1">
<th:block th:each="depth4:${depth3.childCategoryList}">
<li><a href="#" class="bi bi-dash link-dark rounded ps-3 text-decoration-none" th:text="${depth4.categoryName}"></a></li>
<li><a th:href="|@{/board/contentList}?categorySeq=${depth4.categorySeq}|" class="bi bi-dash link-dark rounded ps-3 text-decoration-none" th:text="${depth4.categoryName}"></a></li>
</th:block>
<th:block th:if="${depth3.childCategoryList.size==0}">
<li>등록된 소분류가 없습니다.</li>