[ERP project] 게시판
게시판
프로젝트 구조도
├─ BulletinBoard
├─ BulletinBoard.js
├─ BulletinBoardComment.js
├─ BulletinBoardHashTag.js
├─ BulletinBoardModal.js
├─ BulletinBoardPages.js
├─ BulletinBoardPagesEdit.js
├─ FormattedDate.js
├─ HashTagCustom.js
├─ HashtagCustom.css
├─ LikeHeartBtn.js
├─ UpdatePostCount.js
└─ css
│ ├─ BulletinBoard.css
│ ├─ BulletinBoardComment.css
│ ├─ BulletinBoardHashTag.css
│ └─ LikeHeartBtn.css
Front-end
서버에서 데이터 가져오기
// 백엔드단에서 리스트 객체를 가져오는 부분
useEffect(() => {
axios
.get("/api/bulletinboard")
.then((res) => {
const sortedData = res.data.sort(
(a, b) => new Date(b.postDate) - new Date(a.postDate)
);
setBulletinBoardPost(sortedData);
message.success("데이터를 성공적으로 갱신하였습니다.");
})
.catch((error) =>
message.error("데이터를 갱신하는 도중 에러가 발생하였습니다.")
);
}, []);
useEffect
훅: 이 코드는 React 컴포넌트 내에서useEffect
훅을 사용하고 있습니다.useEffect
는 컴포넌트가 렌더링되고 난 후에 비동기 작업을 수행하기 위해 사용됩니다. 빈 배열 ([]
)을 두 번째 매개변수로 전달하여, 컴포넌트가 처음 렌더링될 때 한 번만 실행되도록 설정되어 있습니다. 즉, 컴포넌트가 처음으로 마운트될 때만 실행됩니다.- Axios를 사용한 데이터 가져오기: Axios는 HTTP 요청을 보내고 받는 데 사용되는 라이브러리입니다.
axios.get("/api/bulletinboard")
를 통해 백엔드 API로 GET 요청을 보냅니다. 백엔드는 게시판 게시물 목록을 반환할 것으로 예상됩니다. - 비동기 요청 처리:
axios
는 Promise를 반환하므로.then()
및.catch()
를 사용하여 비동기 요청의 성공 및 실패를 처리합니다.
.then((res) => { ... })
: 성공한 경우, 백엔드에서 반환한 데이터는res
매개변수에 들어옵니다. 이 데이터는 게시물 목록으로 예상되며, 날짜(postDate
)를 기준으로 내림차순으로 정렬됩니다. 정렬된 데이터는setBulletinBoardPost
함수를 사용하여 React 컴포넌트의 상태로 설정됩니다. 이로써 게시물 목록이 컴포넌트의 상태로 저장되어 화면에 표시됩니다..catch((error) => { ... })
: 실패한 경우, 오류 메시지를 화면에 표시하기 위해message.error("데이터를 갱신하는 도중 에러가 발생하였습니다.")
가 호출됩니다.
- 성공 메시지 표시: 데이터 갱신이 성공하면
message.success("데이터를 성공적으로 갱신하였습니다.")
를 사용하여 성공 메시지가 화면에 표시됩니다.
이렇게 코드는 백엔드 API에서 데이터를 가져와서 정렬한 후 React 컴포넌트의 상태로 설정하고, 성공 또는 실패에 따라 메시지를 표시하는 간단한 데이터 로딩 및 처리 로직을 구현하고 있습니다.
게시판 카테고리
// 게시판 카테고리
const categoryOptions = [
{ label: "전체", key: "전체 게시판", value: "CategoryAll" },
{ label: "공지사항", key: "공지사항", value: "CategoryNotice" },
{ label: "사내게시판", key: "사내게시판", value: "Categoryemployee" },
// ... 다른 카테고리 옵션 ...
];
// 선택한 카테고리의 label 값
const handleCategoryChange = (event) => {
const selectedCategory = event.target.value;
setPostCategory(selectedCategory);
if (selectedCategory === "CategoryAll") {
setCurrentPagePosts(bulletinBoardPost.slice(0, postsPerPage));
} else {
const filteredPosts = bulletinBoardPost.filter(
(post) =>
post.postCategory === selectedCategory ||
selectedCategory === "전체 게시판"
);
setCurrentPagePosts(filteredPosts.slice(0, postsPerPage));
}
};
이 코드는 게시판에서 게시물을 카테고리별로 필터링하고, 선택한 카테고리에 따라 현재 페이지에 보여질 게시물을 업데이트하는 부분을 구현한 것입니다. 코드를 상세하게 설명하겠습니다:
categoryOptions
: 이 배열은 게시판의 카테고리 옵션을 정의합니다. 각 항목은 객체로 표현되며,label
은 화면에 표시될 카테고리명,key
는 내부 식별자(예: 코드에서 사용) 및value
는 카테고리를 식별할 값을 나타냅니다. “전체” 카테고리를 나타내는 항목과 다른 게시판 카테고리도 나열되어 있습니다.handleCategoryChange
함수: 이 함수는 사용자가 카테고리 선택 상자를 변경할 때 호출됩니다. 이 함수는 선택한 카테고리를 받아와서 현재 선택한 카테고리를setPostCategory
함수를 사용하여 React 컴포넌트의 상태로 설정합니다.- 카테고리에 따른 게시물 필터링:
- 만약 선택한 카테고리가 “전체 게시판”(
CategoryAll
)인 경우, 모든 게시물을 보여주기 위해bulletinBoardPost
배열에서 처음부터postsPerPage
까지의 게시물을setCurrentPagePosts
함수를 사용하여 현재 페이지 게시물로 설정합니다. - 그렇지 않은 경우, 선택한 카테고리에 해당하는 게시물만 필터링하여
filteredPosts
배열에 저장합니다. 이때postCategory
는 게시물 객체의 카테고리를 나타내며,selectedCategory
는 사용자가 선택한 카테고리를 나타냅니다. “전체 게시판”을 선택한 경우에도 해당 항목을 포함한 게시물을 보여줍니다. - 마지막으로,
setCurrentPagePosts
함수를 사용하여 현재 페이지에 보여질 게시물을filteredPosts
배열에서 처음부터postsPerPage
까지의 게시물로 설정합니다.
이렇게 코드는 사용자가 카테고리를 선택할 때 선택한 카테고리에 따라 화면에 표시되는 게시물을 필터링하고 업데이트하는 데 사용됩니다.
카테고리 렌더링 방법
<CFormSelect
aria-label="Default select example"
options={categoryOptions.map((option) => ({
label: option.label,
value: option.key,
}))}
onChange={handleCategoryChange}
value={postCategory}
/>
조회수 이벤트
// `updatePostCount`는 게시물의 조회수를 업데이트하는 비동기 함수
const updatePostCount = async (postNum, postCountNum) => {
try {
// 서버에 PUT 요청을 보내서 게시물의 조회수를 업데이트
const response = await axios.put(`/api/bulletinboard/update/${postNum}`, {
// 기존 조회수에 1을 더한 값을 요청 본문에 포함
postCountNum: postCountNum + 1,
});
// 응답의 상태 코드가 200일 경우, 성공으로 판단
if (response.status === 200) {
console.log("조회수 업데이트 성공");
// 클라이언트 측에서 현재 저장된 게시물 목록을 순회하며
const updatedBulletinBoardPost = bulletinBoardPost.map((post) => {
// 업데이트 대상 게시물을 찾았을 경우
if (post.postNum === postNum) {
// 해당 게시물의 조회수만 업데이트하고 나머지 정보는 그대로 유지
return { ...post, postCountNum: postCountNum + 1 };
} else {
// 업데이트 대상이 아닌 게시물은 그대로 반환
return post;
}
});
// 업데이트된 게시물 목록을 콘솔에 출력
console.log(updatedBulletinBoardPost);
// 상태를 업데이트하여 리렌더링을 유발
setBulletinBoardPost(updatedBulletinBoardPost);
}
} catch (error) {
// 요청 중 오류가 발생하면 오류를 콘솔에 출력합니다.
console.error("조회수 업데이트 실패", error);
}
};
- 게시물 번호(
postNum
)와 현재 조회수(postCountNum
)를 인자로 받는 함수입니다. - axios를 이용하여 백엔드에 해당 게시물의 조회수를 1 증가시키라는 PUT 요청을 보냅니다.
- 만약 백엔드로부터 성공적인 응답(200 상태 코드)을 받으면, 클라이언트 측의 게시물 목록(
bulletinBoardPost
)에서 해당 게시물을 찾아 그 조회수를 1 증가시킵니다. - 해당 게시물의 조회수가 업데이트 된 새로운 목록을
setBulletinBoardPost
를 이용하여 상태를 업데이트하여 리렌더링을 유발시킵니다. - 만약 중간에 어떤 오류가 발생하면, 오류 메시지를 콘솔에 출력합니다.
게시판 검색창
// `handleSearchTermChange` 함수는 검색어 입력 필드의 값이 변경될 때 호출
const handleSearchTermChange = (event) => {
// 이벤트 객체로부터 대상의 값(검색어)을 가져와 `setSearchTerm`을 사용하여 상태를 업데이트
setSearchTerm(event.target.value);
};
// `handleSearch` 함수는 검색 버튼 클릭 시나 다른 검색 트리거에 의해 호출
const handleSearch = () => {
// `bulletinBoardPost` 배열을 순회하면서 게시물의 제목이 검색어를 포함하고 있는지 확인
const searchedPosts = bulletinBoardPost.filter((post) =>
post.postTitle.includes(searchTerm)
);
// 검색 결과에 해당하는 게시물들을 `setSearchedPosts`를 이용하여 상태를 업데이트
setSearchedPosts(searchedPosts);
};
// `useEffect`는 React의 Side Effect Hook 이다!
useEffect(() => {
// 게시물 목록(`bulletinBoardPost`)이 변경될 때마다 호출된다.
// 게시물 목록이 변경되면 검색된 게시물 목록(`searchedPosts`)을
// 전체 게시물 목록으로 리셋. 즉, 검색 필터링을 초기화한다.
setSearchedPosts(bulletinBoardPost);
// `bulletinBoardPost`의 변경을 감지하여 이 `useEffect`를 실행한다
}, [bulletinBoardPost]);
로직 요약:
handleSearchTermChange
: 사용자가 검색어 입력 필드의 값을 변경할 때마다 해당 값을 검색어 상태(searchTerm
)로 설정합니다.handleSearch
: 사용자가 검색을 실행하려고 할 때, 현재 게시물 목록(bulletinBoardPost
)에서 검색어가 포함된 게시물을 필터링하여 검색된 게시물 상태(searchedPosts
)를 업데이트합니다.useEffect
: 게시물 목록(bulletinBoardPost
)이 변경될 때마다 검색된 게시물 목록(searchedPosts
)을 전체 게시물 목록으로 초기화하여 검색 필터링을 리셋합니다.
페이지네이션
// `onChange` 함수는 페이지가 변경될 때마다 호출됩니다.
const onChange = (page) => {
// 현재 페이지 번호를 상태로 설정합니다.
setCurrent(page);
// 현재 페이지의 시작 인덱스를 계산합니다.
const startIndex = (page - 1) * postsPerPage;
// 현재 페이지의 끝 인덱스를 계산합니다.
const endIndex = startIndex + postsPerPage;
// `bulletinBoardPost` 배열을 순회하면서
// 게시물의 제목이 검색어를 포함하고 있는지 확인하여 필터링합니다.
const filteredPosts = bulletinBoardPost.filter((post) =>
post.postTitle.includes(searchTerm)
);
// 필터링된 게시물 목록에서 현재 페이지에 해당하는 부분만 잘라냅니다.
setCurrentPagePosts(filteredPosts.slice(startIndex, endIndex));
};
로직 요약:
onChange
함수는 페이지 번호가 변경될 때 호출되며, 변경된 페이지 번호를 인자로 받습니다.- 해당 페이지 번호를 기반으로 게시물 목록에서 시작 인덱스(
startIndex
)와 끝 인덱스(endIndex
)를 계산합니다. - 게시물 목록(
bulletinBoardPost
)을 순회하면서 검색어(searchTerm
)가 포함된 게시물만 필터링합니다. - 필터링된 게시물 목록에서 현재 페이지에 해당하는 부분을 잘라내어, 현재 페이지의 게시물 목록(
currentPagePosts
) 상태를 업데이트합니다.
게시물 상세페이지
// 유저 정보 받아오기
import Cookies from "js-cookie";
const BulletinBoardPages = (props) => {
// 게시물의 ID를 URL로부터 얻기
const { id } = useParams();
// 게시물과 댓글 상태 설정
const [post, setPost] = useState(null);
const [comments, setComments] = useState([]);
// 유저 정보 가져오기
const staffInfo = JSON.parse(Cookies.get("staffInfo"));
// 현재 게시물의 ID를 숫자로 변환
const currentId = parseInt(id, 10);
// 이전 및 다음 게시물의 ID 계산
const prevPost = currentId - 1;
const nextPost = currentId + 1;
// 최대 및 최소 게시물 ID 상태 설정
const [maxPostId, setMaxPostId] = useState(null);
const [minPostId] = useState(1); // 게시물 ID가 1부터 시작한다고 가정
// 유저 정보 로깅
console.log(staffInfo);
// 게시물에 대한 댓글 불러오기
useEffect(() => {
fetch(`/api/bulletinboard/BulletinBoardPages/${id}/comments`)
.then((response) => response.json())
.then((comment) => {
console.log(id);
setComments(comment); // 댓글 상태 설정
});
}, [id]);
// 선택된 ID에 해당하는 게시물 데이터 불러오기
useEffect(() => {
fetch(`/api/bulletinboard/BulletinBoardPages/${id}`)
.then((response) => response.json())
.then((data) => {
setPost(data); // 게시물 상태 설정
});
}, [id]);
// 게시물 데이터가 아직 없으면 로딩 메시지 표시
if (!post) return <div>Loading...</div>;
// 게시물 삭제 기능
const handleDelete = async () => {
try {
// 사용자 확인 요청
if (!confirm("게시물을 삭제하시겠습니까?")) {
alert("취소 되었습니다.");
return;
}
// 게시물 삭제 API 호출
await axios.delete(
`/api/bulletinboard/BulletinBoardPages/${post.postNum}`
);
alert("삭제 되었습니다.");
navigate("/BulletinBoard/BulletinBoard"); // 게시판 메인 페이지로 이동
} catch (error) {
console.error("삭제 불가", error);
}
};
// ... 나머지 코드 ...
};
- 유저 정보:
Cookies
를 통해 로컬 쿠키에서 유저 정보인staffInfo
를 가져옵니다. - 게시물 및 댓글 상태 설정: React의
useState
를 사용하여 게시물과 댓글 정보를 저장하기 위한 상태를 초기화합니다. - 게시물 ID 처리: 현재 페이지의 게시물 ID를 가져오며, 이를 기반으로 이전 및 다음 게시물의 ID를 계산합니다.
- 댓글 불러오기:
useEffect
를 사용하여 게시물의 ID가 변경될 때마다 해당 게시물의 댓글을 API 호출로 불러옵니다. - 게시물 데이터 불러오기: 마찬가지로
useEffect
를 사용하여 게시물의 ID가 변경될 때 해당 게시물의 데이터를 API 호출로 불러옵니다. - 게시물 삭제: 게시물을 삭제하기 전 사용자에게 확인을 받고, 확인된 경우 해당 게시물을 삭제하는 API를 호출합니다. 삭제 후에는 게시판의 메인 페이지로 이동합니다.
// 게시물의 수정 및 삭제 버튼 영역
<div className="d-grid gap-2 d-md-flex justify-content-md-end BulletinBoardPages-AddDelBtn">
{/* 현재 로그인한 사용자가 게시물 작성자와 동일할 경우 삭제 버튼을 표시 */}
{staffInfo.empId === post.empId && (
<CButton color="danger" role="button" onClick={handleDelete}>
삭제
</CButton>
)}
{/* 현재 로그인한 사용자가 게시물 작성자와 동일할 경우 수정 버튼을 표시 */}
{staffInfo.empId === post.empId && (
<CButton
color="success"
role="button"
onClick={() => {
// 수정 버튼 클릭 시, 게시물 수정 페이지로 이동
navigate(`/BulletinBoard/BulletinBoardPagesEdit/${post.postNum}`);
}}
>
수정
</CButton>
)}
</div>
설명:
- 버튼 영역의 스타일: 이 div는 게시물의 수정 및 삭제 버튼을 담고 있으며, Bootstrap의 유틸리티 클래스를 사용하여 버튼의 배치 및 간격을 조절하고 있습니다.
- 삭제 버튼 표시 조건:
staffInfo.empId === post.empId
조건은 현재 로그인한 사용자의 ID와 게시물의 작성자 ID를 비교하여 일치할 경우에만 삭제 버튼을 표시하도록 합니다. 이로 인해 게시물의 작성자만 해당 게시물을 삭제할 수 있습니다. - 수정 버튼 표시 조건: 삭제 버튼과 마찬가지로 현재 로그인한 사용자의 ID와 게시물의 작성자 ID가 일치할 경우에만 수정 버튼을 표시합니다.
- 수정 버튼 클릭 동작: 수정 버튼을 클릭하면
navigate
함수를 사용하여 게시물 수정 페이지로 이동합니다. 이 때, 게시물의 고유 번호 (post.postNum
)를 URL에 포함하여 해당 게시물의 수정 페이지로 이동하게 됩니다.
이러한 방식으로 게시물의 작성자만 해당 게시물을 수정하거나 삭제할 수 있는 권한을 가지게 되며, 다른 사용자는 이러한 버튼을 볼 수조차 없게 됩니다.
게시물 수정
import "./css/BulletinBoard.css";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useParams } from "react-router-dom";
import { Container, Col, Row } from "react-bootstrap";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import BulletinBoardHashTag from "./BulletinBoardHashTag";
import axios from "axios";
import {
CButton,
CFormTextarea,
CCard,
CCol,
CForm,
CBadge,
CFormSelect,
CRow,
CFormInput,
} from "@coreui/react";
import { Input, Pagination } from "antd";
import LikeHeartBtn from "./LikeHeartBtn"; // Make sure the path is correct
const BulletinBoardPagesEdit = (props) => {
const [postTitle, setPostTitle] = useState("");
const [postContent, setPostContent] = useState("");
const [hashtagName, setHashtagName] = useState("");
const [postCategory, setPostCategory] = useState("");
const [selectedHashTags, setSelectedHashTags] = useState([]);
//게시물 불러오기
const { id } = useParams();
const [post, setPost] = useState(null);
const navigate = useNavigate();
useEffect(() => {
fetch(`/api/bulletinboard/BulletinBoardPages/${id}`)
.then((response) => response.json())
.then((data) => {
setPost(data);
setPostTitle(data.postTitle);
setPostContent(data.postContent);
setHashtagName(data.hashtagName);
setPostCategory(data.postCategory);
setSelectedHashTags(data.hashtagName.split(","));
});
}, [id]);
const categoryOptions = [
{ label: "공지사항", key: "공지사항", value: "CategoryNotice" },
{ label: "사내게시판", key: "사내게시판", value: "Categoryemployee" },
];
const handleCategoryChange = (event) => {
const selectedLabel = event.target.value;
setPostCategory(selectedLabel);
};
const handleHashTagChange = (newHashTags) => {
setSelectedHashTags(newHashTags);
};
const handlePostUpdate = async () => {
try {
const updatedPost = {
postTitle: postTitle,
postContent: postContent,
hashtagName: selectedHashTags.join(","),
postCategory: postCategory,
postDateEdit: new Date(),
};
// Send the updatedPost data to the backend API
await axios.put(
`/api/bulletinboard/BulletinBoardPagesEdit/update/${id}`,
updatedPost
);
alert("게시물이 정상적으로 수정되었습니다.");
navigate(`/BulletinBoard/BulletinBoardPages/${id}`);
} catch (error) {
console.error("There was an error updating the post!", error);
}
};
return (
<>
<Container className="BulletinBoardPages-Container">
<CCard className="BulletinBoardPages-CCard">
<CRow className="BulletinBoardModalInputSize">
<h2>게시글 수정</h2>
<CCol md={3}>
<CFormSelect
aria-label="카테고리"
options={categoryOptions.map((option) => ({
label: option.label,
value: option.label,
}))}
onChange={handleCategoryChange}
value={postCategory}
/>
</CCol>
<CCol md={9}>
<CFormInput
className="title-input"
type="text"
placeholder="제목"
name="postTitle"
value={postTitle}
onChange={(e) => setPostTitle(e.target.value)}
/>
</CCol>
</CRow>
<CKEditor
editor={ClassicEditor}
data={postContent}
value={postContent}
onReady={(editor) => {
console.log("에디터가 준비되었습니다!", editor);
}}
onChange={(event, editor) => {
const data = editor.getData();
setPostContent(data);
}}
/>
<BulletinBoardHashTag
onHashTagChange={handleHashTagChange}
value={hashtagName}
/>
<div className="d-grid gap-2 d-md-flex justify-content-md-end BulletinBoardModalBtn">
<button
type="button"
className="btn btn-outline-warning me-md-2"
onClick={() => {
navigate(`/BulletinBoard/BulletinBoardPages/${id}`);
}}
>
취소하기
</button>
<button
className="btn btn-outline-primary me-md-2"
onClick={handlePostUpdate}
>
수정하기
</button>
</div>
</CCard>
</Container>
</>
);
};
export default BulletinBoardPagesEdit;
코드를 살펴보면, 주어진 BulletinBoardPagesEdit
라는 컴포넌트는 사용자가 게시글을 수정할 수 있는 기능을 제공합니다. 다음은 주요 기능 및 코드 구성에 대한 설명입니다.
- Imports 및 초기 설정: 여러 라이브러리와 컴포넌트들이 가져옵니다. CKEditor를 포함하여 필요한 컴포넌트와 스타일을 import 합니다.
-
State 초기화:
const [postTitle, setPostTitle] = useState(""); const [postContent, setPostContent] = useState(""); const [hashtagName, setHashtagName] = useState(""); const [postCategory, setPostCategory] = useState(""); const [selectedHashTags, setSelectedHashTags] = useState([]);
위 코드는 컴포넌트 내에서 사용될 상태 변수들을 초기화합니다
-
게시물 정보 불러오기:
const { id } = useParams(); useEffect(() => { ... });
페이지 URL에서 게시물의 ID를 가져와서 해당 게시물의 데이터를 fetch하여 상태를 설정합니다.
-
카테고리 옵션 및 카테고리 변경 핸들러:
javascriptCopy code const categoryOptions = [ ... ]; const handleCategoryChange = (event) => { ... };
카테고리 옵션을 정의하고, 선택된 카테고리에 따라 상태를 업데이트하는 함수를 정의합니다.
-
해시태그 변경 핸들러:
javascriptCopy code const handleHashTagChange = (newHashTags) => { ... };
사용자가 해시태그를 변경할 때 해당 해시태그 리스트를 업데이트하는 함수입니다.
-
게시물 업데이트 핸들러:
const handlePostUpdate = async () => { ... };
사용자가 수정 버튼을 클릭했을 때 게시물 정보를 업데이트하는 함수입니다. Axios를 사용하여 API 엔드포인트에 PUT 요청을 보내서 게시물 정보를 업데이트하고, 성공적으로 업데이트되면 해당 게시물 페이지로 리다이렉트됩니다.
- 렌더링:
- 컴포넌트는 Bootstrap과 CoreUI 라이브러리를 사용하여 게시글 수정 UI를 렌더링합니다.
- CKEditor를 사용하여 게시글의 내용을 편집합니다.
BulletinBoardHashTag
컴포넌트를 사용하여 해시태그를 수정할 수 있게 합니다. .- 취소 및 수정 버튼을 통해 사용자는 게시물을 수정하거나 수정을 취소할 수 있습니다
이 컴포넌트는 게시글의 정보를 불러와서 제목, 내용, 해시태그, 카테고리 등을 수정할 수 있게 합니다.
수정한 내용을 저장하면 백엔드 서버에 변경 사항이 업데이트되며, 사용자는 해당 게시물 페이지로 리다이렉트됩니다.
게시글 댓글
// 부모댓글 입력 로직
****const handleSubmitComment = async () => {
try {
const data = {
commentDate: new Date(),
postNum: postNum,
empId: staffInfo.empId, // staffInfo에서 empId 가져옴
empNum: staffInfo.empNum, // staffInfo에서 empNum 가져옴
email: staffInfo.email, // staffInfo에서 email 가져옴
commentContent: comment,
};
const response = await axios.post(
`api/bulletinboard/BulletinBoardPages/addParentComment/${postNum}`,
data
);
setComment("");
alert("정상 등록 되었습니다.");
location.reload();
} catch (error) {
console.error("에러 발생:", error);
}
};
//자식 댓글
**** const handleSubmit = async () => {
try {
const data = {
commentDate: new Date(),
postNum: postNum,
parentCommentId: parentComment, // 수정된 부분
empId: staffInfo.empId, // staffInfo에서 empId 가져옴
empNum: staffInfo.empNum, // staffInfo에서 empNum 가져옴
email: staffInfo.email, // staffInfo에서 email 가져옴
commentContent: reply,
};
const response = await axios.post(
`api/bulletinboard/BulletinBoardPages/addChildComment/${postNum}`,
data
);
setReply("");
alert("정상 등록 되었습니다.");
location.reload();
} catch (error) {
console.error("에러 발생:", error);
console.log(parentComment);
}
};
// CommentContent 컴포넌트 정의
// 댓글 내용을 표시하고 관리하는 React 함수 컴포넌트입니다.
const CommentContent = ({
author, // 댓글 작성자
time, // 댓글 작성 시간
text, // 댓글 내용
postNum, // 해당 댓글이 속한 게시글 번호
commentId, // 댓글의 고유 ID
empId, // 댓글 작성자의 직원 ID
isChild = false, // 댓글이 부모 댓글인지 자식 댓글인지 판별하는 prop. 기본값은 false, 즉 부모 댓글입니다.
}) => {
// 댓글 편집 상태를 관리하는 state. 기본값은 false.
const [editing, setEditing] = useState(false);
// 편집된 댓글 내용을 관리하는 state. 기본값은 전달된 text prop입니다.
const [editedText, setEditedText] = useState(text);
// 로그인한 사용자의 정보를 쿠키에서 가져옵니다.
const staffInfo = JSON.parse(Cookies.get("staffInfo"));
// 댓글 작성자의 empId와 현재 로그인한 사용자의 empId를 콘솔에 출력합니다.
console.log("댓글 작성자 empId:", empId);
console.log("현재 로그인한 사용자 empId:", staffInfo.empId);
// 댓글 편집 입력 필드의 값을 state에 반영하는 함수입니다.
const handleEditChange = (event) => {
setEditedText(event.target.value);
};
// 댓글을 업데이트하는 함수입니다.
const handleUpdate = async () => {
// 현재 로그인한 사용자가 댓글의 작성자가 아닐 경우
if (empId !== staffInfo.empId) {
alert("댓글의 작성자만 수정할 수 있습니다.");
return;
}
try {
// 업데이트할 댓글 데이터를 정의합니다.
const data = {
commentContent: editedText,
commentEditDate: new Date(),
};
// 부모 댓글과 자식 댓글에 대한 API 엔드포인트를 구분합니다.
// isChild 값이 true면 자식 댓글의 엔드포인트를, false면 부모 댓글의 엔드포인트를 사용합니다.
const endPoint = isChild
? `/api/bulletinboard/BulletinBoardPages/${postNum}/updateChildComment/${commentId}`
: `/api/bulletinboard/BulletinBoardPages/${postNum}/updateParentComment/${commentId}`;
// axios를 사용해 댓글 데이터를 서버에 업데이트 요청합니다.
await axios.put(endPoint, data);
// 성공적으로 댓글이 업데이트 되었을 경우, 사용자에게 알림 메시지를 표시합니다.
alert("댓글이 성공적으로 수정되었습니다.");
// (여기서부터 코드가 잘림. 이후의 작업이나 추가적인 동작은 제공된 코드에서 확인할 수 없습니다.)
} catch (error) {
// 오류가 발생한 경우, 오류 메시지나 관련 동작을 여기에서 처리할 수 있습니다.
// 현재 코드에서는 해당 부분이 제공되지 않았습니다.
}
};
// ... (컴포넌트의 나머지 부분)
};
<CRow>
{/* 부모 댓글 */}
<CommentContent
author={empId}
time={<FormattedDate date={commentDate} />}
text={commentContent}
onUpdate={handleUpdateComment}
postNum={postNum}
commentId={commentId}
empId={empId}
/>
<ReplyForm postNum={postNum} parentComment={commentId} />
</CRow>
</div>
</CForm>
{/* 자식 댓글들 */}
{childComments && childComments.length > 0 ? (
<ul>
{childComments.map((childComment, index) => (
<li key={index} className="childText">
<CRow>
<CommentContent
author={childComment.empId}
time={<FormattedDate date={childComment.commentDate} />}
text={childComment.commentContent}
onUpdate={handleUpdateComment}
postNum={postNum}
commentId={childComment.commentId}
empId={childComment.empId}
isChild={true} // 자식 댓글임을 나타내는 prop을 추가합니다.
/>
</CRow>
댓글 작성
import React, { useState, useEffect, Childre, useCallback } from "react";
import axios from "axios";
import { CRow, CCol, CButton, CFormTextarea, CForm } from "@coreui/react";
import { Input, Pagination } from "antd";
import FormattedDate from "./FormattedDate";
import Cookies from "js-cookie";
import "./css/BulletinBoardComment.css";
export const CommentInput = ({ postNum }) => {
const [comment, setComment] = useState("");
const staffInfo = JSON.parse(Cookies.get("staffInfo"));
console.log(staffInfo);
const handleCommentChange = (event) => {
setComment(event.target.value);
};
// 부모댓글 입력 로직
const handleSubmitComment = async () => {
try {
const data = {
commentDate: new Date(),
postNum: postNum,
empId: staffInfo.empId, // staffInfo에서 empId 가져옴
empNum: staffInfo.empNum, // staffInfo에서 empNum 가져옴
email: staffInfo.email, // staffInfo에서 email 가져옴
commentContent: comment,
};
const response = await axios.post(
`api/bulletinboard/BulletinBoardPages/addParentComment/${postNum}`,
data
);
setComment("");
alert("정상 등록 되었습니다.");
location.reload();
} catch (error) {
console.error("에러 발생:", error);
}
};
return (
<CRow className="BulletinBoardPages-MainComment">
<CCol md={10} sm="auto">
<input type="hidden" className="article-id" />
<input type="hidden" className="parent-comment-id" />
<CFormTextarea
className="BulletinBoardPages-MainCommentBox"
placeholder="댓글 작성시 상대방에 대한 매너를 지켜주세요. "
rows="3"
required
value={comment}
onChange={handleCommentChange}
/>
</CCol>
<CCol md={2} sm="auto">
<CButton
className="form-control mt-2"
type="button"
onClick={handleSubmitComment}
>
쓰기
</CButton>
</CCol>
</CRow>
);
};
// CommentContent 컴포넌트 내에서 수정 기능 추가
const CommentContent = ({
author,
time,
text,
postNum,
commentId,
empId,
isChild = false, // isChild prop을 추가. 기본값은 false (부모 댓글)
}) => {
const [editing, setEditing] = useState(false);
const [editedText, setEditedText] = useState(text);
const staffInfo = JSON.parse(Cookies.get("staffInfo"));
console.log("댓글 작성자 empId:", empId);
console.log("현재 로그인한 사용자 empId:", staffInfo.empId);
const handleEditChange = (event) => {
setEditedText(event.target.value);
};
const handleUpdate = async () => {
if (empId !== staffInfo.empId) {
alert("댓글의 작성자만 수정할 수 있습니다.");
return;
}
try {
const data = {
commentContent: editedText,
commentEditDate: new Date(),
};
// 부모 댓글과 자식 댓글에 대한 엔드포인트를 구분
const endPoint = isChild
? `/api/bulletinboard/BulletinBoardPages/${postNum}/updateChildComment/${commentId}`
: `/api/bulletinboard/BulletinBoardPages/${postNum}/updateParentComment/${commentId}`;
await axios.put(endPoint, data);
alert("댓글이 성공적으로 수정되었습니다.");
} catch (error) {
console.error("실패");
if (
error.response &&
error.response.data &&
error.response.data.message
) {
alert("댓글 수정 중 오류: " + error.response.data.message);
console.log("Server response:", error.response.data);
} else {
alert("댓글 수정 중 알 수 없는 오류가 발생했습니다.");
}
}
setEditing(false);
};
return (
<CCol md={10} xs="auto" className="textBoxBoard">
<strong>{author} </strong>
<small>
<time>{time}</time>
</small>
{editing ? (
<>
<CFormTextarea value={editedText} onChange={handleEditChange} />
<CButton
className="BulletinBoardPages-DelBtn"
color="success"
variant="ghost"
onClick={handleUpdate}
>
저장
</CButton>
<CButton
className="BulletinBoardPages-DelBtn"
color="secondary"
variant="ghost"
onClick={() => setEditing(false)}
>
취소
</CButton>
</>
) : (
<>
<p className="mb-1">{text}</p>
{author === staffInfo.empId && (
<CButton
id="EditBtn"
className="BulletinBoardPages-DelBtn"
color="primary"
variant="ghost"
onClick={() => setEditing(true)}
>
수정
</CButton>
)}
{!editing && author === staffInfo.empId && (
<CButton
className="BulletinBoardPages-DelBtn"
color="danger"
variant="ghost"
onClick={() => {
if (isChild) {
deleteChildComment(postNum, commentId);
} else {
deleteParentComment(postNum, commentId);
}
}}
>
삭제
</CButton>
)}
</>
)}
</CCol>
);
};
// 부모 댓글 삭제
function deleteParentComment(postNum, commentId) {
fetch(
`api/bulletinboard/BulletinBoardPages/${postNum}/deleteParentComment/${commentId}`,
{
method: "DELETE",
}
)
.then((response) => {
if (response.ok) {
alert("부모 댓글이 성공적으로 삭제되었습니다."); // 정상 삭제 알림
location.reload();
return;
} else {
throw new Error("Failed to delete the parent comment.");
}
})
.then((data) => {
if (data && !data.success) {
alert("부모 댓글 삭제 중 오류가 발생했습니다: " + data.message);
console.log("postNum", postNum, "commentId:", commentId);
}
})
.catch((error) => {
console.error("Error:", error);
console.log("postNum", postNum, "commentId:", commentId);
if (error.message !== "Failed to delete the parent comment.") {
alert("댓글 삭제 중 오류가 발생했습니다.");
}
});
}
// ===================================
// 자식 댓글 삭제
function deleteChildComment(postNum, commentId) {
console.log("Received commentId:", commentId); // 여기서 확인
fetch(
`api/bulletinboard/BulletinBoardPages/${postNum}/deleteChildComment/${commentId}`,
{
method: "DELETE",
}
)
.then((response) => {
if (response.ok) {
alert("자식 댓글이 성공적으로 삭제되었습니다."); // 정상 삭제 알림
location.reload();
return;
} else {
throw new Error("Failed to delete the comment.");
}
})
.then((data) => {
if (data && !data.success) {
alert("자식 댓글 삭제 중 오류가 발생했습니다: " + data.message);
}
})
.catch((error) => {
if (error.message !== "Failed to delete the comment.") {
alert("댓글 삭제 중 오류가 발생했습니다.");
}
});
}
const handleUpdateComment = (updatedText) => {
// 여기에서 API를 호출하여 댓글 내용을 업데이트
console.log("Updated text:", updatedText);
// 예를 들면, axios.put() 메서드를 사용하여 댓글을 업데이트
};
// 자식 댓글 등록 폼
const ReplyForm = ({ postNum, parentComment }) => {
const [reply, setReply] = React.useState("");
const staffInfo = JSON.parse(Cookies.get("staffInfo"));
const handleReplyChange = (event) => {
setReply(event.target.value);
};
const handleSubmit = async () => {
try {
const data = {
commentDate: new Date(),
postNum: postNum,
parentCommentId: parentComment, // 수정된 부분
empId: staffInfo.empId, // staffInfo에서 empId 가져옴
empNum: staffInfo.empNum, // staffInfo에서 empNum 가져옴
email: staffInfo.email, // staffInfo에서 email 가져옴
commentContent: reply,
};
const response = await axios.post(
`api/bulletinboard/BulletinBoardPages/addChildComment/${postNum}`,
data
);
setReply("");
alert("정상 등록 되었습니다.");
location.reload();
} catch (error) {
console.error("에러 발생:", error);
console.log(parentComment);
}
};
// ... 나머지 코드 ...
return (
<li className="child-comment">
<input type="hidden" className="article-id" />
<div className="row">
<CCol md={12}>
<details>
<summary className="comment-toggle">댓글 달기</summary>
<CForm className="comment-form">
<input type="hidden" className="article-id" />
<input type="hidden" className="parent-comment-id" />
<CRow>
<CCol md={9}>
<CFormTextarea
className="comment-textbox"
placeholder="댓글 쓰기.."
rows="2"
required
value={reply}
onChange={handleReplyChange}
/>
</CCol>
<CCol md={2}>
<CButton
className="form-control btn btn-primary mt-2"
color="info"
type="button"
onClick={handleSubmit}
>
쓰기
</CButton>
</CCol>
</CRow>
</CForm>
</details>
</CCol>
</div>
</li>
);
};
// 전체 댓글 폼
export const SingleComment = ({ comment, postNum }) => {
const { commentContent, commentDate, empId, commentId, childComments } =
comment;
const handleUpdateComment = useCallback((updatedContent) => {
console.log("Updated Content:", updatedContent);
// 여기서 업데이트 로직을 구현합니다. (필요한 경우)
}, []);
return (
<div className="comment">
<CForm>
<Input type="hidden" />
<div className="comment-content">
<CRow>
{/* 부모 댓글 */}
<CommentContent
author={empId}
time={<FormattedDate date={commentDate} />}
text={commentContent}
onUpdate={handleUpdateComment}
postNum={postNum}
commentId={commentId}
empId={empId}
/>
<ReplyForm postNum={postNum} parentComment={commentId} />
</CRow>
</div>
</CForm>
{/* 자식 댓글들 */}
{childComments && childComments.length > 0 ? (
<ul>
{childComments.map((childComment, index) => (
<li key={index} className="childText">
<CRow>
<CommentContent
author={childComment.empId}
time={<FormattedDate date={childComment.commentDate} />}
text={childComment.commentContent}
onUpdate={handleUpdateComment}
postNum={postNum}
commentId={childComment.commentId}
empId={childComment.empId}
isChild={true} // 자식 댓글임을 나타내는 prop을 추가합니다.
/>
</CRow>
</li>
))}
</ul>
) : null}
</div>
);
};
CommentInput
컴포넌트
- 부모 댓글을 입력하기 위한 폼이다.
Cookies.get("staffInfo")
를 사용하여 현재 로그인된 사용자의 정보를 가져온다.- 댓글을 작성하고 “쓰기” 버튼을 클릭하면
handleSubmitComment
함수를 호출하여 API로 데이터를 전송하고 댓글을 등록한다.
CommentContent
컴포넌트- 댓글의 내용을 표시하는 컴포넌트이다.
- 댓글 내용, 작성자, 작성 시간을 표시한다.
- 현재 로그인한 사용자가 댓글 작성자와 동일하면 댓글을 수정하거나 삭제할 수 있는 버튼이 표시된다.
- 댓글 수정 시에는 텍스트 영역에 수정 내용을 입력하고 저장할 수 있다.
- isChild prop은 댓글이 부모 댓글인지 자식 댓글인지를 나타낸다.
deleteParentComment
및deleteChildComment
함수- 각각 부모 댓글과 자식 댓글을 삭제하는 기능이다.
- fetch 함수를 사용하여 API에 DELETE 요청을 보내 댓글을 삭제한다.
ReplyForm
컴포넌트- 부모 댓글에 대한 자식 댓글을 입력하는 폼이다.
- 부모 댓글의 ID는
parentComment
prop을 통해 전달된다. - “댓글 달기” 버튼을 클릭하면 텍스트 영역과 버튼이 표시된다.
- 자식 댓글을 작성하고 “쓰기” 버튼을 클릭하면
handleSubmit
함수를 호출하여 API로 데이터를 전송하고 댓글을 등록한다.
SingleComment
컴포넌트- 하나의 부모 댓글과 그에 속한 자식 댓글들을 표시하는 컴포넌트이다.
- 부모 댓글 아래에는 자식 댓글 입력 폼(ReplyForm)이 있다.
- 부모 댓글에 속한 자식 댓글들은 리스트 형태로 표시된다.
위의 설명은 제시된 코드의 주요 기능과 각 컴포넌트의 역할을 요약한 것이다. 코드는 댓글 관련 기능을 구현하기 위해 axios를 사용하여 API 요청을 보내는 로직, 상태 관리, UI 구성 등 다양한 요소들을 포함하고 있다.
게시글 페이지에서 렌더링 하는 화면
<CRow>
<CCol md={12} lg={12} className="mb-4 BulletinBoardPages-Comment">
<ul>
{Array.isArray(comments) &&
comments.map((comment) => (
<SingleComment
comment={comment}
postNum={post.postNum}
key={comment.parentCommentId}
/>
))}
</ul>
</CCol>
</CRow>
게시판 글 쓰기
import React, { useState } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import {
CButton,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CCol,
CFormInput,
CRow,
CFormSelect,
} from "@coreui/react";
import axios from "axios";
import BulletinBoardHashTag from "./BulletinBoardHashTag";
import Cookies from "js-cookie";
function BulletinBoardModal() {
const [visibleLg, setVisibleLg] = useState(false);
const [empNum, setEmpNum] = useState("");
const [empId, setEmpId] = useState("");
const [postContent, setPostContent] = useState("");
const [HashtagName, setHashtagName] = useState("");
const [postCountNum, setPostCountNum] = useState("");
const [postTitle, setPostTitle] = useState("");
const [postCategory, setPostCategory] = useState("");
const [selectedHashTags, setSelectedHashTags] = useState([]);
const [email, setEmail] = useState("");
const [hashtagDate, setHashtagDate] = useState("");
const staffInfo = JSON.parse(Cookies.get("staffInfo"));
console.log(staffInfo);
const handleBulletinBoardPostUpdate = async () => {
try {
const newPost = {
empNum: staffInfo.empNum, // staffInfo에서 empNum 가져옴
empId: staffInfo.empId, // staffInfo에서 empId 가져옴
postTitle: postTitle,
postContent: postContent,
hashtagName: selectedHashTags.join(","), // Join selectedHashTags into a string
postDate: new Date(),
postCountNum: 0,
postLike: 0,
postRecommend: 0,
postCategory: postCategory,
email: staffInfo.email, // staffInfo에서 email 가져옴
hashtagDate: new Date(),
};
console.log("등록하기 버튼 클릭됨");
// Send the newPost data to the backend API
const response = await axios.post("/api/bulletinboard/add", newPost);
// Clear the form fields after successful submission
setEmpNum("");
setEmpId("");
setEmail("");
setPostTitle("");
setPostContent("");
setHashtagName("");
setPostCountNum("");
setPostCategory("");
setHashtagDate("");
alert("정상 등록 되었습니다.");
// Close the modal
setVisibleLg(false);
console.log(newPost);
window.location.reload();
// Perform any other actions you need after successful submission
} catch (error) {
console.error("에러 발생:", error);
}
};
const handleHashTagChange = (newHashTags) => {
setSelectedHashTags(newHashTags);
console.log("Selected Hash Tags:", newHashTags);
};
const categoryOptions = [
{ label: "전체", key: "전체 게시판", value: "CategoryAll" },
{ label: "공지사항", key: "공지사항", value: "CategoryNotice" },
{ label: "사내게시판", key: "사내게시판", value: "Categoryemployee" },
// ... 다른 카테고리 옵션 ...
];
const handleCategoryChange = (event) => {
const selectedLabel = event.target.value;
setPostCategory(selectedLabel);
};
return (
<>
<CCol className="BulletinBoardPostBtn">
<CButton color="light" onClick={() => setVisibleLg(!visibleLg)}>
게시글 작성하기
</CButton>
</CCol>
<CModal size="lg" visible={visibleLg} onClose={() => setVisibleLg(false)}>
<CModalHeader>
<CModalTitle>글쓰기</CModalTitle>
</CModalHeader>
<CModalBody>
<CRow className="BulletinBoardModalInputSize">
<CCol md={3}>
<CFormSelect
aria-label="카테고리"
options={categoryOptions.map((option) => ({
label: option.label,
value: option.label,
}))}
onChange={handleCategoryChange}
/>
</CCol>
<CCol md={9}>
<CFormInput
className="title-input"
type="text"
placeholder="제목"
name="postTitle"
value={postTitle}
onChange={(e) => setPostTitle(e.target.value)}
/>
</CCol>
</CRow>
<CKEditor
editor={ClassicEditor}
onReady={(editor) => {
console.log("에디터가 준비되었습니다!", editor);
}}
onChange={(event, editor) => {
const data = editor.getData();
console.log("에디터의 내용을 가져옵니다", {
event,
editor,
data,
});
setPostContent(data);
}}
/>
<BulletinBoardHashTag
onHashTagChange={handleHashTagChange}
value={HashtagName}
/>
<div className="d-grid gap-2 d-md-flex justify-content-md-end BulletinBoardModalBtn">
<button
type="button"
onClick={() => setVisibleLg(false)}
className="btn btn-outline-warning me-md-2"
>
취소하기
</button>
<button
type="button"
onClick={handleBulletinBoardPostUpdate}
className="btn btn-outline-primary me-md-2"
>
등록하기
</button>
</div>
</CModalBody>
</CModal>
</>
);
}
export default BulletinBoardModal;
해시태그
const baseTagifySettings = {
blacklist: [],
maxTags: 6,
backspace: "edit",
placeholder: "해시태그를 입력해보세요! 최대 6개 까지 가능합니다.",
editTags: 1,
dropdown: {
maxItems: 20, // 드롭다운 메뉴에서 몇개 정도 항목을 보여줄지
classname: "tags-look", // 드롭다운 메뉴 엘리먼트 클래스 이름. 이걸로 css 선택자로 쓰면 된다.
enabled: 1, // 단어 몇글자 입력했을떄 추천 드롭다운 메뉴가 나타날지
closeOnSelect: true, // 드롭다운 메뉴에서 태그 선택하면 자동으로 꺼지는지 안꺼지는지
},
callbacks: {},
};
function TagField({
label,
name,
initialValue = [],
suggestions = [],
onChange,
}) {
const handleChange = (e) => {
const newTags = e.detail.tagify.value.map((item) => item.value);
onChange(newTags);
};
const settings = {
...baseTagifySettings,
whitelist: suggestions,
callbacks: {
add: handleChange,
remove: handleChange,
blur: handleChange,
edit: handleChange,
invalid: handleChange,
click: handleChange,
focus: handleChange,
"edit:updated": handleChange,
"edit:start": handleChange,
},
};
return (
<div className="form-group">
<label htmlFor={"field-" + name}>{label}</label>
<Tagify
settings={settings}
value={initialValue.join(",")}
onAdd={handleChange}
onRemove={handleChange}
onBlur={handleChange}
// ... other events ...
/>
</div>
);
}
function BulletinBoardHashTag({ onHashTagChange, value }) {
const suggestions = [
"급여",
"연봉",
"연차",
"공지사항",
"영업팀",
"인사팀",
// ... other suggestions ...
];
const initialValue = value ? value.split(",") : [];
return (
<div className="BulletinBoardHashTag">
<TagField
initialValue={initialValue}
suggestions={suggestions}
onChange={onHashTagChange} // Pass the onChange function from parent
/>
</div>
);
}
-
Imports:
import React, { useRef, useState } from "react"; import "@yaireo/tagify/dist/tagify.css"; import Tagify from "@yaireo/tagify/dist/react.tagify"; import {} from "@coreui/react"; import "./css/BulletinBoardHashTag.css";
- React 및 필요한 React hook을 가져온다.
- Tagify 라이브러리 및 관련 스타일을 가져온다.
- CoreUI의 React 컴포넌트들을 가져온다.
- 해당 컴포넌트에 대한 CSS를 가져온다.
-
Base Tagify Settings:
javascriptCopy code const baseTagifySettings = { ... };
- Tagify의 기본 설정입니다.
-
TagField Component:
function TagField({ label, name, initialValue = [], suggestions = [], onChange, }) { ... }
- 해시태그 입력 필드의 핵심 컴포넌트입니다.
label
,name
,initialValue
,suggestions
,onChange
와 같은 props를 받습니다.- 여기에서
handleChange
함수를 사용하여 태그의 변경을 처리합니다.
-
BulletinBoardHashTag Component:
function BulletinBoardHashTag({ onHashTagChange, value }) { ... }
TagField
컴포넌트를 사용하는 래퍼 컴포넌트입니다.- 초기 해시태그 값과 해시태그가 변경될 때 호출되는 함수를 props로 받습니다.
-
Export Statement:
export default BulletinBoardHashTag;
- 다른 React 파일에서
BulletinBoardHashTag
컴포넌트를 사용할 수 있게끔 export합니다.
- 다른 React 파일에서
세부적으로 나누어 설명하면 위와 같습니다. 이 코드는 사용자에게 해시태그 입력 인터페이스를 제공하며, 특정 해시태그 제안을 보여주고, 사용자의 해시태그 입력 변화를 상위 컴포넌트에 알려주는 역할을 하게 된다.
Back-End
package com.project.backend.controller;
import com.project.backend.dto.BulletinBoard.BulletinBoardDto;
import com.project.backend.dto.BulletinBoard.ChildCommentDto;
import com.project.backend.dto.BulletinBoard.ParentCommentDto;
import com.project.backend.service.BulletinBoard.BulletinBoardServicetotal;
import com.project.backend.service.BulletinBoard.CommentService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/bulletinboard")
public class BulletinBoardController {
private final BulletinBoardServicetotal bulletinBoardServicetotal;
private final CommentService commentService;
public BulletinBoardController(BulletinBoardServicetotal bulletinBoardServicetotal, CommentService commentService) {
this.bulletinBoardServicetotal = bulletinBoardServicetotal;
this.commentService = commentService;
}
@GetMapping
public ResponseEntity<List<BulletinBoardDto>> getBulletinBoardData() {
List<BulletinBoardDto> bulletinBoardData = bulletinBoardServicetotal.getBulletinBoardPost();
return new ResponseEntity<>(bulletinBoardData, HttpStatus.OK);
}
// 게시물 등록
@PostMapping("/add")
public ResponseEntity<String> addBulletinBoardPost(@RequestBody BulletinBoardDto bulletinBoardDto) {
try {
bulletinBoardServicetotal.createBulletinBoardData(bulletinBoardDto);
return ResponseEntity.ok("게시물이 등록되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("게시물 등록 중 오류가 발생했습니다.");
}
}
//게시물 페이지 이동 렌더링
@GetMapping("/BulletinBoardPages/{postNum}")
public ResponseEntity<BulletinBoardDto> getBulletinBoardPost(@PathVariable Long postNum) {
BulletinBoardDto bulletinBoardPost = bulletinBoardServicetotal.getBulletinBoardPostByPostNum(postNum);
return new ResponseEntity<>(bulletinBoardPost, HttpStatus.OK);
}
// 조회수 카운트
@PutMapping("/update/{postNum}")
public ResponseEntity<String> updateBulletinBoardPost(@PathVariable Long postNum, @RequestBody BulletinBoardDto bulletinBoardDto) {
try {
bulletinBoardDto.setPostNum(postNum);
bulletinBoardServicetotal.updateBulletinBoardPost(bulletinBoardDto);
return ResponseEntity.ok("게시물이 업데이트되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("게시물 업데이트 중 오류가 발생했습니다.");
}
}
//게시물 삭제
@DeleteMapping("/BulletinBoardPages/{postNum}")
public ResponseEntity<String> deleteBulletinBoard(@PathVariable Long postNum) {
try {
bulletinBoardServicetotal.deleteBulletinBoard(postNum);
return ResponseEntity.ok("게시물이 성공적으로 삭제되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("게시물을 삭제하는 도중 오류가 발생했습니다.");
}
}
//게시물 수정
@PutMapping("/BulletinBoardPagesEdit/update/{postNum}")
public ResponseEntity<String> updatePost(@PathVariable Long postNum, @RequestBody BulletinBoardDto bulletinBoardDto) {
try {
bulletinBoardServicetotal.updateBulletinBoardPost(postNum, bulletinBoardDto);
return ResponseEntity.ok("게시물이 업데이트되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("게시물 업데이트 중 오류가 발생했습니다.");
}
}
//부모 댓글 저장 로직
@PostMapping("/BulletinBoardPages/addParentComment/{postNum}")
public ResponseEntity<String> addParentComment(@PathVariable Long postNum, @RequestBody ParentCommentDto parentCommentDto) {
try {
commentService.addParentComment(postNum, parentCommentDto);
return ResponseEntity.ok("부모 댓글이 등록되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("부모 댓글 등록 중 오류가 발생했습니다.");
}
}
//자식 댓글 저장 로직
@PostMapping("/BulletinBoardPages/addChildComment/{postNum}")
public ResponseEntity<String> addChildComment(
@PathVariable Long postNum,
@RequestBody ChildCommentDto childCommentDto
) {
try {
// 클라이언트에서 받아온 parentCommentId를 사용하여 자식 댓글에 연결
Long parentCommentId = childCommentDto.getParentCommentId();
if (parentCommentId == null) {
return ResponseEntity.badRequest().body("부모 댓글 ID가 제공되지 않았습니다.");
}
commentService.addChildComment(postNum, parentCommentId, childCommentDto);
return ResponseEntity.ok("자식 댓글이 등록되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("자식 댓글 등록 중 오류가 발생했습니다.");
}
}
//부모 댓글과 자식 댓글 보여주기
@GetMapping("/BulletinBoardPages/{postNum}/comments")
public ResponseEntity<List<ParentCommentDto>> getCommentsByBulletinBoardId(@PathVariable Long postNum) {
List<ParentCommentDto> comments = commentService.getCommentsByBulletinBoardId(postNum);
return ResponseEntity.ok(comments);
}
// 부모 댓글 삭제
@DeleteMapping("/BulletinBoardPages/{postNum}/deleteParentComment/{commentId}")
public ResponseEntity<String> deleteParentComment(@PathVariable Long postNum, @PathVariable Long commentId) {
try {
commentService.deleteParentComment(postNum, commentId);
return ResponseEntity.ok("부모 댓글 및 관련된 자식 댓글들이 삭제되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("부모 댓글 삭제 중 오류가 발생했습니다.");
}
}
// 자식 댓글 삭제
@DeleteMapping("/BulletinBoardPages/{postNum}/deleteChildComment/{commentId}")
public ResponseEntity<String> deleteChildComment(@PathVariable Long postNum, @PathVariable Long commentId) {
try {
commentService.deleteChildComment(postNum, commentId);
return ResponseEntity.ok("자식 댓글이 삭제되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("자식 댓글 삭제 중 오류가 발생했습니다.");
}
}
@PutMapping("/BulletinBoardPages/{postNum}/updateParentComment/{commentId}")
public ResponseEntity<?> updateParentComment(@PathVariable Long postNum, @PathVariable Long commentId, @RequestBody ParentCommentDto request) {
try {
commentService.editParentComment(postNum, commentId, request);
return ResponseEntity.ok().body("댓글이 성공적으로 수정되었습니다.");
} catch (Exception e) {
return ResponseEntity.badRequest().body("댓글 수정 중 오류가 발생했습니다.");
}
}
@PutMapping("/BulletinBoardPages/{postNum}/updateChildComment/{commentId}")
public ResponseEntity<?> updateChildComment(@PathVariable Long postNum, @PathVariable Long commentId, @RequestBody ChildCommentDto request) {
try {
commentService.editChildComment(postNum, commentId, request);
return ResponseEntity.ok().body("자식 댓글이 성공적으로 수정되었습니다.");
} catch (Exception e) {
return ResponseEntity.badRequest().body("자식 댓글 수정 중 오류가 발생했습니다.");
}
}
}
Dto
package com.project.backend.dto.BulletinBoard;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Getter
@Setter
public class BulletinBoardDto {
private Long postNum;
private String empId;
private String empNum;
private String postTitle;
private String postContent;
private Date postDate;
private int postCountNum;
private int postLike;
private int postRecommend;
private String postCategory;
private Date postDateEdit;
private String hashtagName;
private Date hashtagDate;
private Date hashtagDateEdit;
private String email;
@Getter
@Setter
public class LikeToggleRequestDto {
private Long postNum; // 게시물 번호
private String empId; // 좋아요를 클릭한 직원의 ID
}
@Getter
@Setter
public class RecommendToggleRequestDto {
private Long postNum; // 게시물 번호
private String empId; // 추천을 클릭한 직원의 ID
}
@Getter
@Setter
public class LikeToggleResponseDto {
private boolean success;
private boolean likeStatus; // 현재 좋아요 상태 (예: 좋아요가 활성화되었는지 여부)
private int likeCount; // 해당 게시물의 전체 좋아요 수
}
@Getter
@Setter
public class RecommendToggleResponseDto {
private boolean success;
private boolean recommendStatus; // 현재 추천 상태 (예: 추천이 활성화되었는지 여부)
private int recommendCount; // 해당 게시물의 전체 추천 수
}
}
지식 댓글
package com.project.backend.dto.BulletinBoard;
import java.util.Date;
public class ChildCommentDto {
private String empId;
private String empNum;
private String email;
private String commentContent;
private Date commentDate;
private Date commentEdit;
private Date commentEditDate;
private Long commentId;
private Long parentCommentId;
public Long getParentCommentId() {
return parentCommentId;
}
public void setParentCommentId(Long parentCommentId) {
this.parentCommentId = parentCommentId;
}
// getters and setters
public Long getCommentId() {
return commentId;
}
public void setCommentId(Long commentId) {
this.commentId = commentId;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public String getEmpNum() {
return empNum;
}
public void setEmpNum(String empNum) {
this.empNum = empNum;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCommentContent() {
return commentContent;
}
public void setCommentContent(String commentContent) {
this.commentContent = commentContent;
}
public Date getCommentDate() {
return commentDate;
}
public void setCommentDate(Date commentDate) {
this.commentDate = commentDate;
}
public Date getCommentEdit() {
return commentEdit;
}
public void setCommentEdit(Date commentEdit) {
this.commentEdit = commentEdit;
}
public Date getCommentEditDate() {
return commentEditDate;
}
public void setCommentEditDate(Date commentEditDate) {
this.commentEditDate = commentEditDate;
}
}
부모 댓글
package com.project.backend.dto.BulletinBoard;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
public class ParentCommentDto {
private Long commentId;
private Long postNum;
private String empId;
private String empNum;
private String email;
private String commentContent;
private Date commentDate;
private Date commentEditDate; // 수정 댓글 저장 날짜a
// 자식 댓글 리스트
private List<ChildCommentDto> childComments;
public Long getCommentId() {
return commentId;
}
public List<ChildCommentDto> getChildComments() {
return childComments;
}
public void setChildComments(List<ChildCommentDto> childComments) {
this.childComments = childComments;
}
public void setCommentId(Long commentId) {
this.commentId = commentId;
}
public Long getPostNum() {
return postNum;
}
public void setPostNum(Long postNum) {
this.postNum = postNum;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public String getEmpNum() {
return empNum;
}
public void setEmpNum(String empNum) {
this.empNum = empNum;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCommentContent() {
return commentContent;
}
public void setCommentContent(String commentContent) {
this.commentContent = commentContent;
}
public Date getCommentDate() {
return commentDate;
}
public void setCommentDate(Date commentDate) {
this.commentDate = commentDate;
}
public Date getCommentEditDate() {
return commentEditDate;
}
public void setCommentEditDate(Date commentEditDate) {
this.commentEditDate = commentEditDate;
}
}
게시판 엔티티
package com.project.backend.entity.bulletinboard;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.*;
@Entity
@Table(name = "bulletinboard")
@Getter
@Setter
@ToString
public class BulletinBoard {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY )
@Column(name = "post_Num")
private Long postNum;
@Column(name = "emp_Id") //로그인 정보와 조인
private String empId;
@Column(name = "emp_Num") // 조그인 정보에서 받아와야함
private String empNum;
@Column(name = "post_Title", nullable = false)
private String postTitle;
@Column(name = "post_Content", nullable = false, length = 50000)
private String postContent;
@Column(name = "post_Date", nullable = false)
private Date postDate;
@Column(name = "post_Count_Num")
private int postCountNum;
@Column(name = "post_Like")
private int postLike;
@Column(name = "post_Recommend")
private int postRecommend;
@Column(name = "post_Category")
private String postCategory;
@Column(name = "post_Date_Edit")
private Date postDateEdit;
@Column(name = "hashtag_Name")
private String hashtagName;
// nullable = false
@Column(name = "hashtag_date")
private Date hashtagDate;
@Column(name = "hashtag_date_edit")
private Date hashtagDateEdit;
@Column(name = "email")
private String email;
@OneToMany(mappedBy = "bulletinBoard", cascade = CascadeType.ALL)
private List<ParentComment> comments;
// Constructors, other methods, if any
}
자식 엔티티
package com.project.backend.entity.bulletinboard;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Entity
@Table(name = "child_comment")
@Getter
@Setter
public class ChildComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_Id", nullable = false)
private Long commentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_comment_Id", nullable = false)
private ParentComment parentComment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "bulletin_board_Id")
private BulletinBoard bulletinBoard;
@Column(name = "emp_Id")
private String empId;
@Column(name = "emp_Num")
private String empNum;
@Column(name = "email")
private String email;
@Column(name = "comment_Content")
private String commentContent;
@Column(name = "comment_Date")
private Date commentDate;
@Column(name = "comment_Edit")
private Date commentEdit;
@Column(name = "comment_Edit_Date")
private Date commentEditDate;
// Constructors, other methods, if any
}
부모 엔티티
package com.project.backend.entity.bulletinboard;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Entity
@Table(name = "parent_comment")
@Getter
@Setter
public class ParentComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_Id", nullable = false)
private Long commentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_Num", nullable = false)
private BulletinBoard bulletinBoard;
@OneToMany(mappedBy = "parentComment", cascade = CascadeType.ALL)
private List<ChildComment> childComments = new ArrayList<>();
@Column(name = "emp_Id")
private String empId;
@Column(name = "emp_Num")
private String empNum;
@Column(name = "email")
private String email;
@Column(name = "comment_Content")
private String commentContent;
@Column(name = "comment_Date")
private Date commentDate;
@Column(name = "comment_Edit_Date")
private Date commentEditDate; // 수정 일자
// Constructors, other methods, if any
}
댓글 서비스
package com.project.backend.service.BulletinBoard;
import com.project.backend.dto.BulletinBoard.ChildCommentDto;
import com.project.backend.dto.BulletinBoard.ParentCommentDto;
import com.project.backend.entity.bulletinboard.BulletinBoard;
import com.project.backend.entity.bulletinboard.ChildComment;
import com.project.backend.entity.bulletinboard.ParentComment;
import com.project.backend.repository.bulletinboard.BulletinBoardRepository;
import com.project.backend.repository.bulletinboard.ChildCommentRepository;
import com.project.backend.repository.bulletinboard.ParentCommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
@Service
public class CommentService {
@Autowired
private BulletinBoardRepository bulletinBoardRepository;
@Autowired
private ParentCommentRepository parentCommentRepository;
@Autowired
private ChildCommentRepository childCommentRepository;
public void addParentComment(Long postNum, ParentCommentDto parentCommentDto) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + postNum));
ParentComment parentComment = new ParentComment();
parentComment.setBulletinBoard(bulletinBoard);
parentComment.setCommentContent(parentCommentDto.getCommentContent());
parentComment.setEmpId(parentCommentDto.getEmpId());
parentComment.setEmpNum(parentCommentDto.getEmpNum());
parentComment.setEmail(parentCommentDto.getEmail());
parentComment.setCommentDate(parentCommentDto.getCommentDate());
parentComment.setCommentEditDate(parentCommentDto.getCommentEditDate());
parentCommentRepository.save(parentComment);
}
public void addChildComment(Long postNum, Long parentCommentId, ChildCommentDto childCommentDto) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + postNum));
ParentComment parentComment = parentCommentRepository.findById(parentCommentId)
.orElseThrow(() -> new IllegalArgumentException("해당 부모 댓글이 없습니다. id=" + parentCommentId));
ChildComment childComment = new ChildComment();
childComment.setBulletinBoard(bulletinBoard);
childComment.setParentComment(parentComment);
// Set the rest of the fields from childCommentDto
childComment.setEmpId(childCommentDto.getEmpId());
childComment.setEmpNum(childCommentDto.getEmpNum());
childComment.setEmail(childCommentDto.getEmail());
childComment.setCommentContent(childCommentDto.getCommentContent());
childComment.setCommentDate(childCommentDto.getCommentDate());
childComment.setCommentEdit(childCommentDto.getCommentEdit());
childComment.setCommentEditDate(childCommentDto.getCommentEditDate());
childCommentRepository.save(childComment);
}
public List<ParentCommentDto> getCommentsByBulletinBoardId(Long postNum) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + postNum));
List<ParentComment> parentComments = parentCommentRepository.findByBulletinBoard(bulletinBoard);
return parentComments.stream()
.map(parentComment -> {
ParentCommentDto parentCommentDto = new ParentCommentDto();
parentCommentDto.setCommentContent(parentComment.getCommentContent());
parentCommentDto.setCommentId(parentComment.getCommentId());
parentCommentDto.setEmpId(parentComment.getEmpId());
parentCommentDto.setEmpNum(parentComment.getEmpNum());
parentCommentDto.setEmail(parentComment.getEmail());
parentCommentDto.setCommentDate(parentComment.getCommentDate());
parentCommentDto.setCommentEditDate(parentComment.getCommentEditDate());
// 자식 댓글 리스트를 가져와서 ParentCommentDto에 추가
List<ChildComment> childCommentsForThisParent = childCommentRepository.findByParentComment(parentComment);
List<ChildCommentDto> childCommentDtos = childCommentsForThisParent.stream().map(childComment -> {
ChildCommentDto childCommentDto = new ChildCommentDto();
childCommentDto.setCommentContent(childComment.getCommentContent());
childCommentDto.setCommentId(childComment.getCommentId());
childCommentDto.setEmpId(childComment.getEmpId());
childCommentDto.setEmpNum(childComment.getEmpNum());
childCommentDto.setEmail(childComment.getEmail());
childCommentDto.setCommentDate(childComment.getCommentDate());
childCommentDto.setCommentEdit(childComment.getCommentEdit());
childCommentDto.setCommentEditDate(childComment.getCommentEditDate());
return childCommentDto;
}).collect(Collectors.toList());
parentCommentDto.setChildComments(childCommentDtos);
return parentCommentDto;
})
.collect(Collectors.toList());
}
// 부모 댓글 삭제
public void deleteParentComment(Long postNum, Long commentId) {
// 게시글의 존재 확인
BulletinBoard bulletinBoard = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + postNum));
// 댓글의 존재 확인
ParentComment parentComment = parentCommentRepository.findById(commentId)
.orElseThrow(() -> new IllegalArgumentException("해당 부모 댓글이 없습니다. id=" + commentId));
// 댓글 삭제
// CascadeType.ALL로 설정했기 때문에 부모 댓글 삭제 시 연관된 자식 댓글들도 모두 삭제됩니다.
parentCommentRepository.delete(parentComment);
}
// 자식 댓글 삭제
public void deleteChildComment(Long postNum, Long childCommentId) {
// 게시글의 존재 확인
BulletinBoard bulletinBoard = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + postNum));
// 자식 댓글의 존재 확인
ChildComment childComment = childCommentRepository.findById(childCommentId)
.orElseThrow(() -> new IllegalArgumentException("해당 자식 댓글이 없습니다. id=" + childCommentId));
// 자식 댓글 삭제
childCommentRepository.delete(childComment);
}
// 부모 댓글 수정
public void editParentComment(Long postNum, Long commentId, ParentCommentDto parentCommentDto) {
// 게시글과 댓글을 찾습니다.
ParentComment parentComment = parentCommentRepository.findById(commentId)
.orElseThrow(() -> new IllegalArgumentException("해당 부모 댓글이 없습니다. id=" + commentId));
// 댓글 내용과 수정일자만 업데이트 합니다.
parentComment.setCommentContent(parentCommentDto.getCommentContent());
parentComment.setCommentEditDate(new Date()); // 댓글 수정 시간을 현재 시간으로 업데이트
parentCommentRepository.save(parentComment);
}
// 자식 댓글 수정
public void editChildComment(Long postNum, Long childCommentId, ChildCommentDto childCommentDto) {
ChildComment childComment = childCommentRepository.findById(childCommentId)
.orElseThrow(() -> new IllegalArgumentException("해당 자식 댓글이 없습니다. id=" + childCommentId));
// 댓글 내용과 수정일자만 업데이트 합니다.
childComment.setCommentContent(childCommentDto.getCommentContent());
childComment.setCommentEditDate(new Date()); // 댓글 수정 시간을 현재 시간으로 업데이트
childCommentRepository.save(childComment);
}
}
게시판 서비스 impl
package com.project.backend.service.BulletinBoard;
import com.project.backend.dto.BulletinBoard.BulletinBoardDto;
import com.project.backend.entity.bulletinboard.BulletinBoard;
import com.project.backend.repository.bulletinboard.BulletinBoardRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Transactional
@Service
public class BulletinBoardServiceImpl implements BulletinBoardService {
private final BulletinBoardRepository bulletinBoardRepository;
@Autowired
public BulletinBoardServiceImpl(BulletinBoardRepository bulletinBoardRepository) {
this.bulletinBoardRepository = bulletinBoardRepository;
}
@Override
public List<BulletinBoardDto> getBulletinBoardPost() {
List<BulletinBoard> bulletinBoards = bulletinBoardRepository.findAll();
List<BulletinBoardDto> bulletinBoardDtos = new ArrayList<>();
for (BulletinBoard bulletinBoard : bulletinBoards) {
BulletinBoardDto bulletinBoardDto = new BulletinBoardDto();
BeanUtils.copyProperties(bulletinBoard, bulletinBoardDto);
bulletinBoardDtos.add(bulletinBoardDto);
}
return bulletinBoardDtos;
}
@Override
public void createBulletinBoardData(BulletinBoardDto bulletinBoardDto) {
BulletinBoard bulletinBoard = new BulletinBoard();
BeanUtils.copyProperties(bulletinBoardDto, bulletinBoard);
bulletinBoardRepository.save(bulletinBoard);
}
@Override
public BulletinBoardDto getBulletinBoardPostByPostNum(Long postNum) {
if(postNum == null) {
throw new IllegalArgumentException("postNum은 null일 수 없습니다.");
}
BulletinBoard bulletinBoard = bulletinBoardRepository.findByPostNum(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 postNum의 게시물이 존재하지 않습니다."));
BulletinBoardDto bulletinBoardDto = new BulletinBoardDto();
BeanUtils.copyProperties(bulletinBoard, bulletinBoardDto);
return bulletinBoardDto;
}
}
게시판 서비스
package com.project.backend.service.BulletinBoard;
import com.project.backend.dto.BulletinBoard.BulletinBoardDto;
import com.project.backend.entity.bulletinboard.BulletinBoard;
import com.project.backend.exception.bulletinboard.ResourceNotFoundException;
import com.project.backend.repository.bulletinboard.BulletinBoardRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class BulletinBoardServicetotal {
private final BulletinBoardRepository bulletinBoardRepository;
@Autowired
public BulletinBoardServicetotal(BulletinBoardRepository bulletinBoardRepository) {
this.bulletinBoardRepository = bulletinBoardRepository;
}
public List<BulletinBoardDto> getBulletinBoardPost() {
List<BulletinBoard> bulletinBoards = bulletinBoardRepository.findAll();
List<BulletinBoardDto> bulletinBoardDtos = new ArrayList<>();
for (BulletinBoard bulletinBoard : bulletinBoards) {
BulletinBoardDto bulletinBoardDto = new BulletinBoardDto();
BeanUtils.copyProperties(bulletinBoard, bulletinBoardDto);
bulletinBoardDtos.add(bulletinBoardDto);
}
return bulletinBoardDtos;
}
public void createBulletinBoardData(BulletinBoardDto bulletinBoardDto) {
BulletinBoard bulletinBoard = new BulletinBoard();
BeanUtils.copyProperties(bulletinBoardDto, bulletinBoard);
bulletinBoardRepository.save(bulletinBoard);
}
public BulletinBoardDto getBulletinBoardPostByPostNum(Long postNum) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findByPostNum(postNum)
.orElseThrow(() -> new ResourceNotFoundException("Post not found with postNum " + postNum));
BulletinBoardDto bulletinBoardDto = new BulletinBoardDto();
BeanUtils.copyProperties(bulletinBoard, bulletinBoardDto);
return bulletinBoardDto;
}
public void updateBulletinBoardPost(BulletinBoardDto bulletinBoardPost) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findByPostNum(bulletinBoardPost.getPostNum())
.orElseThrow(() -> new ResourceNotFoundException("Post not found with postNum " + bulletinBoardPost.getPostNum()));
bulletinBoard.setPostCountNum(bulletinBoardPost.getPostCountNum());
bulletinBoardRepository.save(bulletinBoard);
}
//게시물 삭제
public void deleteBulletinBoard(Long postNum) {
bulletinBoardRepository.deleteById(postNum);
}
//게시물 수정
public void updateBulletinBoardPost(Long postNum, BulletinBoardDto bulletinBoardDto) {
BulletinBoard post = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시물이 없습니다. id=" + postNum));
post.setPostTitle(bulletinBoardDto.getPostTitle());
post.setPostContent(bulletinBoardDto.getPostContent());
post.setHashtagName(bulletinBoardDto.getHashtagName());
post.setPostCategory(bulletinBoardDto.getPostCategory());
post.setPostDateEdit(bulletinBoardDto.getPostDateEdit());
bulletinBoardRepository.save(post);
}
//===========================
}
@Entity
@Table(name = "bulletinboard")
@Getter
@Setter
@ToString
public class BulletinBoard {
// ... (다른 필드들)
// 게시판과 댓글 사이의 일대다 관계를 나타내는 필드입니다.
// 게시판 한 개에 여러 개의 부모 댓글이 연결될 수 있기 때문에 List<ParentComment>로 정의하였습니다.
// mappedBy 속성은 ParentComment 엔티티의 "bulletinBoard" 필드와 연결을 나타냅니다.
// cascade = CascadeType.ALL은 게시판이 삭제되면 해당 게시판에 연결된 댓글도 모두 삭제됨을 의미합니다.
@OneToMany(mappedBy = "bulletinBoard", cascade = CascadeType.ALL)
private List<ParentComment> comments;
// ... (나머지 생성자, 메서드 등)
}
ParentComment 엔티티
@Entity
@Table(name = "parent_comment")
@Getter
@Setter
public class ParentComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_Id", nullable = false)
private Long commentId;
// ...
// ParentComment와 ChildComment 사이의 일대다 관계를 표현합니다.
// ParentComment 하나에 여러 ChildComment가 연결될 수 있기 때문입니다.
// mappedBy 속성은 ChildComment 엔티티의 "parentComment" 필드와 연결을 나타냅니다.
// cascade = CascadeType.ALL는 부모 댓글이 삭제되면 해당 부모 댓글에 연결된 자식 댓글들도 모두 삭제됨을 의미합니다.
@OneToMany(mappedBy = "parentComment", cascade = CascadeType.ALL)
private List<ChildComment> childComments = new ArrayList<>();
// ...
}
ParentComment와 ChildComment 사이의 일대다 관계를 표현한다. ParentComment 하나에 여러 ChildComment가 연결될 수 있기 때문이다. mappedBy 속성은 ChildComment 엔티티의 “parentComment” 필드와 연결을 나타낸다. cascade = CascadeType.ALL는 부모 댓글이 삭제되면 해당 부모 댓글에 연결된 자식 댓글들도 모두 삭제됨을 의미한다.
ChildComment 엔티티:
@Entity
@Table(name = "child_comment")
@Getter
@Setter
public class ChildComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_Id", nullable = false)
private Long commentId;
// ...
// ChildComment와 ParentComment 사이의 다대일 관계를 표현합니다.
// 여러 ChildComment가 한 ParentComment에 연결될 수 있기 때문입니다.
// @JoinColumn은 해당 ChildComment 테이블의 "parent_comment_Id" 컬럼이 외래 키로 사용됨을 나타냅니다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_comment_Id", nullable = false)
private ParentComment parentComment;
// ...
}
연관관계 설명:
ParentComment
엔티티는 여러 개의ChildComment
엔티티를 가질 수 있습니다. 이 관계는ParentComment
의@OneToMany
애노테이션을 통해 정의된다. 이 때,mappedBy
속성은 이 관계가ChildComment
의parentComment
필드에 의해 매핑되었음을 나타낸다.- 반대로,
ChildComment
엔티티는 한 개의ParentComment
엔티티에 속할 수 있다. 이 관계는ChildComment
의@ManyToOne
애노테이션을 통해 정의되며,@JoinColumn
애노테이션은 이 관계가 데이터베이스 테이블의 어떤 컬럼에 의해 표현되는지를 나타낸다. - 추가로,
cascade = CascadeType.ALL
옵션이 설정되어 있어, 부모 댓글(ParentComment
)이 삭제될 경우 그에 연결된 모든 자식 댓글(ChildComment
)도 함께 삭제된다.
이렇게 ParentComment
와 ChildComment
사이에는 일대다, 다대일 관계가 설정되어 있다.
게시판 service
1. 게시글 전체 조회 (getBulletinBoardPost
)
public List<BulletinBoardDto> getBulletinBoardPost() {
List<BulletinBoard> bulletinBoards = bulletinBoardRepository.findAll();
List<BulletinBoardDto> bulletinBoardDtos = new ArrayList<>();
for (BulletinBoard bulletinBoard : bulletinBoards) {
BulletinBoardDto bulletinBoardDto = new BulletinBoardDto();
BeanUtils.copyProperties(bulletinBoard, bulletinBoardDto);
bulletinBoardDtos.add(bulletinBoardDto);
}
return bulletinBoardDtos;
}
bulletinBoardRepository.findAll()
를 통해 모든 게시글을 조회합니다.- 조회된
BulletinBoard
엔터티 목록을BulletinBoardDto
목록으로 변환합니다. - 변환된 DTO 목록을 반환합니다.
2. 게시글 생성 (createBulletinBoardData
)
public void createBulletinBoardData(BulletinBoardDto bulletinBoardDto) {
BulletinBoard bulletinBoard = new BulletinBoard();
BeanUtils.copyProperties(bulletinBoardDto, bulletinBoard);
bulletinBoardRepository.save(bulletinBoard);
}
- 전달받은
BulletinBoardDto
를BulletinBoard
엔터티로 변환합니다. - 변환된 엔터티를 저장하여 DB에 새 게시글을 생성합니다.
3. 게시글 번호로 조회 (getBulletinBoardPostByPostNum
)
public BulletinBoardDto getBulletinBoardPostByPostNum(Long postNum) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findByPostNum(postNum)
.orElseThrow(() -> new ResourceNotFoundException("Post not found with postNum " + postNum));
BulletinBoardDto bulletinBoardDto = new BulletinBoardDto();
BeanUtils.copyProperties(bulletinBoard, bulletinBoardDto);
return bulletinBoardDto;
}
bulletinBoardRepository.findByPostNum(postNum)
를 사용하여 게시글 번호로 게시글을 조회합니다.- 조회된
BulletinBoard
엔터티를BulletinBoardDto
로 변환하여 반환합니다.
4. 게시글 수정 (조회수 업데이트: updateBulletinBoardPost
)
public void updateBulletinBoardPost(BulletinBoardDto bulletinBoardPost) {
BulletinBoard bulletinBoard = bulletinBoardRepository.findByPostNum(bulletinBoardPost.getPostNum())
.orElseThrow(() -> new ResourceNotFoundException("Post not found with postNum " + bulletinBoardPost.getPostNum()));
bulletinBoard.setPostCountNum(bulletinBoardPost.getPostCountNum());
bulletinBoardRepository.save(bulletinBoard);
}
- 게시글 번호를 기반으로 게시글을 조회합니다.
- 조회된 게시글의 조회수를 업데이트하고, DB에 저장합니다.
5. 게시글 삭제 (deleteBulletinBoard
)
public void deleteBulletinBoard(Long postNum) {
bulletinBoardRepository.deleteById(postNum);
}
- 게시글 번호를 기반으로 게시글을 삭제합니다.
6. 게시글 수정 (updateBulletinBoardPost
- 오버로딩 버전)
public void updateBulletinBoardPost(Long postNum, BulletinBoardDto bulletinBoardDto) {
BulletinBoard post = bulletinBoardRepository.findById(postNum)
.orElseThrow(() -> new IllegalArgumentException("해당 게시물이 없습니다. id=" + postNum));
post.setPostTitle(bulletinBoardDto.getPostTitle());
post.setPostContent(bulletinBoardDto.getPostContent());
post.setHashtagName(bulletinBoardDto.getHashtagName());
post.setPostCategory(bulletinBoardDto.getPostCategory());
post.setPostDateEdit(bulletinBoardDto.getPostDateEdit());
bulletinBoardRepository.save(post);
}
- 게시글 번호와 수정할 내용이 담긴 DTO를 전달받습니다.
- 게시글 번호로 게시글을 조회하고, 해당 게시글의 내용을 전달받은 DTO의 내용으로 업데이트합니다.
- 수정된 게시글을 DB에 저장합니다.
이 클래스는 게시판에 대한 핵심 CRUD 작업을 수행합니다: 생성, 읽기, 업데이트, 삭제. 코드 내에서 예외 처리도 이루어지며, 게시글을 찾을 수 없는 경우 등에 대해 적절한 예외 메시지를 반환합니다.
import { jsPDF } from "jspdf"; //라이브러리 import
const doc = new jsPDF(); // 새로운 PDF 파일을 생성
doc.text("Hello world!", 10, 10); // 내용을 입력
doc.save("sample.pdf"); //PDF의 이름 설정
deleteById vs delete
deleteById를 사용하면, 서비스 로직에서 메서드 하나만 사용해도 조회와 삭제가 모두 가능하다. 또한, 내부적으로 id에 대한 null체크도 해주기 때문에 의도치않은 NullPointerException도 예방할 수 있다.
하지만, deleteById는 내부적인 findById 조회시에 데이터가 없을 경우, EmptyResultDataAccessException이 고정으로 발생한다.
반면에, findById + delete를 사용한다면, 직접 예외처리를 커스텀할 수 있다는 장점이 있다.
개인 공부 기록용 블로그입니다.
본문에 오류가 포함되어 있을 수도 있습니다.
댓글 또는 메일 주시면 수정하도록 하겠습니다.🍀
Leave a comment