Notice
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 캘린더 라이브러리
- MRC
- stock option
- 밸류즈 홈페이지
- 공통메서드
- 로그인
- register
- RCPS
- 밸류즈
- 달력 라이브러리
- ui탬플릿
- 관리자페이지
- Ajax
- 마이페이지
- 회원가입로직
- jsonwebtoken
- Token
- 로그인 로직
- Styled Components
- mypage
- Update
- 이미지 업로드
- 배포
- 빌드 및 배포
- Typesciprt
- 스프링시큐리티
- userManagement
- react
- 인증처리
- 파생상품평가
Archives
- Today
- Total
I T H
[포트폴리오 프로젝트 7] Q & A 게시판 구현 본문
[게시판 테이블 ]
- 게시글에 대한 테이블
-- MYPORTFOLIO.TBL_QNA_INFO definition
CREATE TABLE `TBL_QNA_INFO` (
`CODE_ID` varchar(36) NOT NULL COMMENT '코드아이디',
`UP_CODE_ID` varchar(36) DEFAULT NULL COMMENT '상위코드아이디',
`TOP_CODE_ID` varchar(36) DEFAULT NULL COMMENT '원글코드아이디',
`LVL` int(11) DEFAULT NULL COMMENT '글 레벨',
`USER_ID` varchar(50) DEFAULT NULL COMMENT '사용자 아이디',
`USER_EMAIL` varchar(50) DEFAULT NULL COMMENT '사용자 이메일',
`USER_NAME` varchar(20) DEFAULT NULL COMMENT '사용자 이름',
`QNA_TITLE` varchar(100) DEFAULT NULL COMMENT '제목',
`QNA_DESC` text DEFAULT NULL COMMENT '내용',
`TARGET_USER_ID` varchar(50) DEFAULT NULL COMMENT '답글대상자아이디',
`PRIVATE_YN` char(1) DEFAULT NULL COMMENT '비밀글여부',
`PASSWORD` char(4) DEFAULT NULL COMMENT '비밀번호',
`CNT` int(11) DEFAULT NULL COMMENT '조회수',
`DELETE_YN` char(1) DEFAULT NULL COMMENT '삭제여부',
`INPUT_DATETIME` datetime DEFAULT NULL COMMENT '등록일자',
PRIMARY KEY (`CODE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 파일에 대한 테이블
-- MYPORTFOLIO.TBL_QNA_FILE definition
CREATE TABLE `TBL_QNA_FILE` (
`CODE_ID` varchar(36) NOT NULL COMMENT '문의글 코드아이디',
`FILE_ID` varchar(36) NOT NULL COMMENT '파일아이디',
`USER_ID` varchar(50) DEFAULT NULL COMMENT '사용자 아이디',
`FILE_ORG_NAME` varchar(200) DEFAULT NULL COMMENT '파일 원본 이름',
`FILE_NAME` varchar(200) DEFAULT NULL COMMENT '파일 이름',
`INPUT_DATETIME` datetime DEFAULT NULL COMMENT '등록일자',
PRIMARY KEY (`CODE_ID`,`FILE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
[qna.jsp] - WEB-INF/views/qna.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE html>
<html lang="kr">
<%@include file="/resources/inc/header.jsp"%>
<style>
label {
display: inline-block;
margin-bottom: 5px;
font-weight: inherit;
}
b{
font-weight: bold;
font-size: 17px;
}
.row{
justify-content: center;
display: flex; /* display: flex; 균등분할하는기능*/
padding: 5px;
}
.row2 {
justify-content: center;
padding: 5px;
}
.row3 {
padding: 5px;
display: block; /* display 기능을 block처리 */
}
.form-label{
font-size: 14px;
font-weight: bolder;
}
.bgCol{
padding: 50px 40px 40px 50px;
background: #f5f5f5;
border-bottom: 1px solid #e6e6e6;
color: #666;
line-height: 2;
}
.form-control{
height: 35px;
}
</style>
<body class="home">
<%@include file="/resources/inc/top.jsp"%>
<input id="hidUserId" type="hidden" value="${userId}">
<input id="hidUserName" type="hidden" value="${userName}">
<input id="hidUserEmail" type="hidden" value="${userEmail}">
<main id="main">
<div class="container" id="boardList">
<div class="section featured topspace">
<h2 class="section-title"><span>Q & A 게시판</span></h2>
<div class="row">
<div class="col-md-3 mb-3">
<label class="form-label" >제목</label>
<input type="text" class="form-control txtCondition" id="txtBoardTitle" value="" placeholder="" required>
</div>
<div class="col-md-3 mb-3">
<label class="form-label" >작성자</label>
<input type="text" class="form-control txtCondition" id="txtBoardWriter" value="" placeholder="" required>
</div>
<!-- 버튼 -->
<div class="col-md-8" style="text-align: right;">
<a id="btnSearch" class="btn btn-warning txtCondition">조회</a>
<a id="btnSave" class="btn btn-success txtCondition">글쓰기</a>
</div>
</div>
<br/>
<!-- 그리드 사용 -->
<div id="grid"></div>
</div> <!-- / section -->
</div> <!-- /container -->
</main>
<!-- 팝업 영역 - 신규문의글등록 -->
<div id="ex1" class="modal" style="height:590px;">
<h4 id="modalLabel" align="center">문의글 등록</h4>
<div class="row2">
<div class="col-md-12">
<small>제목</small>
<input type="text" class="form-control popTxt" id="txtTitle" value="" placeholder="제목 입력" required>
</div>
<div class="col-md-12">
<small>이메일</small>
<input type="text" class="form-control" id="txtUserEmail" value="" placeholder="이메일 입력" required>
</div>
<div class="col-md-12">
<small>이름</small>
<input type="text" class="form-control" id="txtUserName" value="" placeholder="이름 입력" required>
</div>
<div class="col-md-12">
<small>내용</small>
<textarea class="form-control popTxt" id="txtDesc" placeholder="내용 입력" style="width:100%; resize:none;"></textarea>
</div>
<div class="col-md-12">
<small>첨부파일</small>
<input type="file" class="form-control popTxt" name="file" id="file" min="0" placeholder="첨부파일" value="">
</div>
<div class="col-md-12">
<small>비밀번호</small>
<input type="password" class="form-control popTxt" id="txtPassword" placeholder="비밀번호 입력" value="" readonly="readonly">
</div>
<div class="col-md-12">
<small>비밀글 여부</small>
<select class="form-control" id="cmbPrivate">
<option id="N">공개</option>
<option id="Y">비공개</option>
</select>
</div>
</div>
<br/>
<div align="right">
<a id="btnWrite" class="btn btn-warning">등록</a>
<a href="#" class="btn btn-success" rel="modal:close">닫기</a>
<a id="btnReturn" class="btn btn-danger" rel="modal:close">목록으로</a>
</div>
</div>
<!-- 비밀글여부 팝업창 -->
<div id="exPwd" class="modal" style="height:320px;">
<h5 align="center">문의내용 확인</h5>
<small>비공개 문의글은 등록 시 입력한 비밀번호를 입력해야 확인 가능합니다.</small>
<hr>
<div class="row" style="display:block;">
<div class="col-md-10 mb-3">
<small>비밀번호</small>
<input type="password" class="form-control popTxt" id="txtPopPwd" value="" placeholder="비밀번호 입력" required>
</div>
</div>
<hr>
<div align="right">
<button class="btn btn-success" id="btnPasswordCheck">확인</button>
<a href="#" class="btn btn-warning" rel="modal:close">닫기</a>
</div>
</div>
<!-- 게시글 상세보기 -->
<div id="boardDetail" style="display: none;">
<div class="container" >
<div class="section featured topspace" >
<h2 class="section-title"><span>게시글 상세보기</span></h2>
<div class="bgCol">
<div class="row">
<div class="col-sm-4 col-md-4">
<label class="form-label" >No</label>
<input type="text" id="descNo" class="form-control">
</div>
<div class="col-sm-4 col-md-4">
<label class="form-label" >작성자</label>
<input type="text" id="descWriter" class="form-control">
</div>
<div class="col-sm-4 col-md-4">
<label class="form-label" >작성일자</label>
<input type="text" id="descDate" class="form-control">
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<label class="form-label" >제목</label>
<input type="text" id="descTitle" class="form-control">
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<label class="form-label">내용</label>
<textarea class="form-control" id="descText" style="width:100%;" resize:none;"></textarea>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<label class="form-label">첨부파일</label>
<div id="FileList"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<sec:authorize access="isAuthenticated()">
<hr/>
<label class="form-label">답글쓰기</label>
<textarea class="form-control" id="textReply" style="width:100%; resize:none;"></textarea>
<br/>
<a id="btnReply" class="btn btn-warning">답글등록</a>
</sec:authorize>
<a id="btnReturn2" class="btn btn-danger">목록으로</a>
<sec:authorize access="isAuthenticated()">
<a id="btnQnaDelete" class="btn btn-success" style="display:none;" >문의글삭제</a>
</sec:authorize>
</div>
</div>
<br/>
<br/>
<hr>
</div> <!-- / section -->
</div> <!-- /container -->
<div class="container" >
<div class="section featured topspace">
<div class="checkout_detail_area mt-50 clearfix">
<div class="cart-title row2">
<h4>댓글</h4>
<div class="divider"></div>
</div>
</div>
<div id="panelReply" style="min-height: 400px; overflow-x: hidden; overflow-y:auto; font-size: 0.9rem; padding: 1rem;">
<p>답변이 없습니다.</p>
</div>
</div>
</div>
</div>
<div style="clear:both;"></div>
<%@include file="/resources/inc/footer.jsp"%>
<%@include file="/resources/inc/incJs.jsp"%>
<!-- page script -->
<script src="/resources/views/qna.js"></script>
</body>
</html>
[qna.js] - resources/views/qna.js
/**
* qna.js
* @author thevalue
* @since 2023
* @DESC Q & A 게시판
*/
(function(){
function Qna(){
//private variables
var userId = $("#hidUserId").val();
var userName = $("#hidUserName").val();
var userEmail = $("#hidUserEmail").val();
// alert(userId + ", " + userName + ", " + userEmail);
//그리드 객체 담기위한 변수
var grid = null;
var SELECTED_CODE_ID = ""; //문의글 원글에 대한 코드 정보 // TOP_CODE_ID
var SELECTED_REPLY_ID = ""; //1레벨 댓글에 대한 코드 정보 //UP_CODE_ID
var OPENED_REPLY_ID = ""; //열려있는 답글 리스트 재조회 시 다시 열도록 사용 // ▼
//초기화 메서드
function _init(){
//이벤트 처리 함수 호출
bindEvent();
//등록된 문의글 리스트 조회
findBoardList();
}
function bindEvent(){
//조회버튼 클릭 이벤트
$(".txtCondition").keydown(function(key) {
//엔터키 쳤을때 이벤트
if (key.keyCode == 13) {
findBoardList();
}
});
$("#btnSearch").on("click", function(){
findBoardList();
});
//글쓰기 버튼 클릭시 팝업창띄우기 이벤트
$("#btnSave").on("click", function(){
$("#modalLabel").html("문의글 등록");
//세션에서 받아온 이메일과 이름 데이터를 넣어준다.
$("#txtUserEmail").val(userEmail);
$("#txtUserName").val(userName);
$("#ex1").modal({
clickClose: false
});//모달(팝업) 창 오픈
// 이미 입력된 정보가 있을 수 있으므로
// 각 항목을 초기화
$(".popTxt").val("");
});
//신규 문의글등록 버튼 클릭 이벤트
$("#btnWrite").on("click", function(){
saveBoardInfo();
})
//비밀글 여부
$("#cmbPrivate").on("change", function(){
var id = $("#cmbPrivate option:selected")[0].id;
console.log("id", id);
if(id == "N"){ //n : 공개 //Y : 비공개
$("#txtPassword").val("")
$("#txtPassword").attr("readonly", true);
} else {
$("#txtPassword").attr("readonly", false);
}
})
//목록으로 가기 버튼클릭 이벤트 boardList
$("#btnReturn, #btnReturn2").on("click", function(){
//modal 팝업창 닫기
$.modal.close();
$("#boardDetail").hide();
$("#boardList").show();
//재조회
findBoardList();
})
//문의글 삭제 (문의글 상세보기 할때 보임)
$("#btnQnaDelete").on("click", function(){
saveBoardInfoRemove();
})
}//bindEvent
//등록된 게시글 리스트 조회
function findBoardList(){
//alert("리스트조회");
var obj = {
txtBoardTitle: $("#txtBoardTitle").val(),
txtBoardWriter: $("#txtBoardWriter").val(),
};
cfFind("/qna/findBoardList.do", obj, function(data){
//글 번호 세팅
//조회한 리스트에 NUM_IDX 컬럼이 추가되는 것임
$.each(data, function(idx, node){
node["NUM_IDX"] = (idx + 1); //NUM_IDX(글번호 컬럼 추가 => grid에 뿌릴때 사용할것)
if(node.PRIVATE_YN == "Y"){ //비밀글이라면
node["QNA_TITLE"] = "🔐 " + node.QNA_TITLE; //비밀글이면 제목에 열쇠모양 표시를 앞에 넣어줌.
}
});
console.log(data);
//그리드 만들기(표)
setGrid(data);
}, true, "POST");
}
//신규 문의글 등록
function saveBoardInfo(){
var obj = setParam();
if(!obj){
return;
}
var subObj = {};
var codeInfo = "";
cfFind("/qna/findCodeInfo.do", subObj, function(subData){
//TBL_QNA_INFO테이블의 코드아이디(codeInfo - PK)에 넣을 데이터 조회
console.log("subData", subData);
obj.codeInfo = subData.code;
codeInfo = subData.code;
}, true, "POST");
var file = $("#file").val();
console.log(file);
//파일이 포함되지 않은 경우 글등록
if(file == "" || file == null){
console.log(obj);
cfSave("/qna/saveBoardInfo.do", obj, function(data){
if(data.success){
alert("문의글이 등록되었습니다.");
$(".popTxt").val(""); //등록폼 초기화
$("#btnReturn").click(); //목록으로 가기
} else {
alert("글 등록 실패, 관리자에게 문의하시기 바랍니다.");
return;
}
}, true, "POST");
} else { //파일이 포함된 경우 글등록
var ext = $("#file").val().split('.').pop().toLowerCase();
//ex) abc.txt 에서 txt 확장자만 추출
if($.inArray(ext,['gif', 'png', 'jpg', 'jpeg', 'zip']) == -1){
alert("gif,png,jpg,jpeg,zip 파일만 업로드 할수 있습니다.");
return false;
}
var dataObj = {
codeInfo: codeInfo,
userId: userId
};
cfUpload("/qna/fileUpload.do", dataObj, function(rData){
console.log("rData", rData);
if(rData.success){
cfSave("/qna/saveBoardInfo.do", obj, function(data){
if(data.success){
alert("문의글이 등록되었습니다.");
// setInitQnaForm();//문의글 초기화 메서드 호출
$(".popTxt").val(""); //등록폼 초기화
$("#btnReturn").click(); //목록으로 가기
}
},true, "POST");
} else {
alert("글 등록 실패, 관리자에게 문의하시기 바랍니다.");
return;
}
});
}
}//문의글 등록 함수 끝
//grid setting
function setGrid(gridData){
console.log(gridData);
//이미 그리드가 그려져 있으면
if(grid != null){
grid.destroy();//초기화하기 위해 사용
}
//그리드 컬럼 정보 세팅
var columns = [{
header: '번호',
name: 'NUM_IDX',
align: "center",
width: 170
}, {
header: '제목',
name: 'QNA_TITLE',
align: "center",
width: 170
}, {
header: '관리자답변',
name: 'REPLY_YN',
align: "center",
width: 170
}, {
header: '작성자',
name: 'USER_NAME',
align: "center",
width: 170
}, {
header: '조회수',
name: 'CNT',
align: "center",
width: 170
}, {
header: '등록일자',
name: 'INPUT_DATETIME',
align: "center",
width: 170
}];
//그리드 옵션 설정 및 그리드 생성
grid = new tui.Grid({
el: document.getElementById('grid'),
columns: columns,
scrollX: true,
scrollY: "auto",
bodyHeight: 420,
width: "100%",
contextMenu: null,
columnOptions: {
resizable: true //컬럼 사이즈 조정
},
pageOptions: {
perPage: 5, //한번에 보여줄 데이터 수
useClient: true
}
//rowHeaders: ['checkbox'] //체크박스
});
grid.resetData(gridData);//그리드 데이터 세팅
tui.Grid.applyTheme('striped'); //줄무늬 스타일 적용
//그리드 더블클릭 이벤트
//1개의 행을 더블클릭할 경우
//grid.on("click"),function(selected){}
grid.on("dblclick", function(selected){
var rowIdx = selected.rowKey; //선택된 행의 인덱스
var item = grid.getRow(rowIdx);
console.log(item);
//비밀글인 경우와 비밀글이 아닌경우 글 상세보기 처리
if(item.PRIVATE_YN == "Y" && userId != "admin"){//비밀글이면서 관리자가 아닐 경우에만 비밀번호 입력 모달창 띄우기
//다른 방법은 User_Auth !=2(1:일반 사용자 , 2는 관리자)
$("#exPwd").modal({
clickClose:false
});
//txtPopPwd 비공개 문의글 확인(비밀글인 경우 비밀번호 입력후 보기)
$("#btnPasswordCheck").off("click");
$("#btnPasswordCheck").on("click", function(){
var pwd = $("#txtPopPwd").val();
if(pwd == "" || pwd == null){
alert("비공개글 비밀번호를 입력하시기 바랍니다.");
return;
}
if(pwd == item.PASSWORD){ //입력한 비밀번호와 비밀글의 비밀번호와 일치
//modal 팝업창 닫기
$.modal.close();
$("#txtPopPwd").val(""); //초기화
//글 상세조회
setBoardDetail(item);
} else {
alert("비밀번호가 일치하지 않습니다.");
return;
}
});
} else { //비밀글이 아닐 경우
//글 상세조회
setBoardDetail(item);
}
});
} //grid 끝
//게시글 상세보기
function setBoardDetail(item){
//클릭한 사용자 정보 팝업창에 세팅
console.log("item", item); //한 행에 대한 데이터
SELECTED_CODE_ID = item.CODE_ID;
//문의글 상세내용 세팅
$("#descNo").val("No." + item.NUM_IDX); //글번호
// console.log(item.QNA_TITLE);
var title = item.QNA_TITLE;
// console.log(title.substr(8));
console.log(title.includes("🔐"));
if(title.includes("🔐")){
title = title.substr(8);
$("#descTitle").val(title); //제목
} else {
$("#descTitle").val(title);
}
$("#btnQnaDelete").hide();//삭제버튼 초기화
if(item.USER_ID == "" || item.USER_ID == null){
$("#descWriter").val(item.USER_NAME);
} else {
if(userId == item.USER_ID){ //로그인한 유저아이디와 문의글을 썻던 유저아이디와 같다면
$("#btnQnaDelete").show();
}
$("#descWriter").val(item.USER_NAME + "(" + item.USER_ID + ")");
}
$("#descDate").val(item.INPUT_DATETIME);
$("#descText").val(item.QNA_DESC);
//파일리스트 불러오기
var obj = {
codeInfo: SELECTED_CODE_ID
};
console.log(obj);
cfFind("/qna/findBoardFileList.do", obj, function(data){ //글 상세보기에서 파일 리스트 뿌리기
console.log("data", data);
var html = "";
if(data.length <= 0){
html +="<input class='form-control' placeholder='등록된 첨부파일이 없습니다' readonly/>"
}
$.each(data, function(idx, node){
html += "<input id = '" + idx + "' class='lineFile form-control' style='cursor: pointer;' value='"
html += node.FILE_ORG_NAME;
html += "'/>"
});
$("#FileList").html(html);
$(".lineFile").off("click");
//파일 다운로드
$(".lineFile").on("click", function(){
var id = $(this)[0].id;
console.log(id);
var obj = {
fileName: data[id].FILE_NAME,
fileOrgName: data[id].FILE_ORG_NAME,
fileDir: "qna"
};
cfCustomExcelDownloadDyn("/downloadFiles", obj); //파일다운로드 -> 메인컨트롤러에 있음.
});
}, true, "POST");
$("#boardList").hide();
$("#boardDetail").show();
//글 조회수 업데이트
var saveObj = {
codeInfo: SELECTED_CODE_ID
}
cfSave("/qna/saveBoardCnt.do", saveObj, function(data){
console.log(data.success); //true
}, true, "POST")
//답글쓰기 버튼 이벤트
$("#btnReply").off("click");
$("#btnReply").on("click", function(){
saveReplyInfo(item);
});
//댓글 리스트 조회
findReplyList();
}//게시글 상세보기
//문의글 삭제 (TBL_QNA_INFO, TBL_QNA_FILE 데이터 삭제)
function saveBoardInfoRemove(){
var obj = {
codeInfo : SELECTED_CODE_ID
}
var result = confirm("문의글을 삭제하시겠습니까?");
if(result){
cfSave("/qna/saveBoardInfoRemove.do", obj, function(data){
if(data.success){
alert("문의글 삭제가 완료되었습니다.");
$("#btnReturn2").click(); //목록으로가기
}
},true, "POST");
}
}
//답글쓰기
function saveReplyInfo(item){
//jsp내에서 시큐리티 인증 처리 없을때는 아래와 같이 사용한다. 시큐리티를 처리한다면 아래부분 사용할 필요는 없다.
/* if(userId == null || userId == ""){//로그인한 사용자만 댓글 달수있게 추가
alert("로그인 후 댓글 사용하실 수 있습니다.");
$("#txtReply").val("");
return;
}*/
var reply = $("#textReply").val();
if(reply == "" || reply == null){
alert("답글 내용을 입력하시기 바랍니다.");
return;
}
var obj = {
qnaTitle: "답글",
userEmail: userEmail,
userName: userName,
qnaDesc: reply,
password:"",
userId:userId,
privateYn:"N",
codeInfo:"",//서버사이드에서 UUID로 처리
upCodeId: SELECTED_CODE_ID,
topCodeId: SELECTED_CODE_ID,
targetUserId:""
}
cfSave("/qna/saveReplyInfo.do", obj, function(data){
console.log(obj);
if(data.success){
alert("답글 입력이 완료되었습니다.");
$("#textReply").val("");
//답글 리스트 재조회
findReplyList();
}
}, true, "POST");
} //답글쓰기
//댓글 리스트 조회
function findReplyList(){
var obj = {
upCodeId: SELECTED_CODE_ID
};
//답글 영역 초기화
$("#panelReply").html("<p>답변이 없습니다.</p>");
var html = "";
cfFind("/qna/findReplyList.do", obj, function(data){
console.log("data", data);
$.each(data, function(idx, node){
html += "<div class='row row3'>";
html += " <div class='col-md-6'>";
html += " <p style='margin: 0 0 15px 0;'><b>" + node.USER_NAME + "(" + node.USER_ID + ") </b>";
html += " | <span>" + node.INPUT_DATETIME + "</span>";
if(userId !="" && node.USER_ID != userId) {//본인 댓글에는 답글 쓰지 못하도록 함.
html += " | <span id='" + node.CODE_ID + "' class='w-btn w-btn-green button-hover btnSubReply mainReply' style='cursor:pointer'>답글쓰기</span></p>";
} else {
html += "</p>";
}
html += " </div>"
html += " <div id= 'subBtnArea_" + node.CODE_ID + "' class='col-md-6' style='text-align: right;'>";
if(userId !="" && node.USER_ID == userId) {//본인 댓글은 수정, 삭제 버튼 활성화
html += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubModify'>수정</button>"
html += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubDelete'>삭제</button>"
} else {
html += " ";
}
html += " </div>";
html += "</div>";
html += "<div id = '" + node.USER_ID + "' class='row row3'>";
html += " <div id='modNot_" + node.CODE_ID + "' class='col-md-12'>"
html += " <b>" + node.TARGET_USER_ID +"</b> <label id='modify'>" + node.QNA_DESC + "</label>"
html += " </div>"
html += " <div id='mod_" + node.CODE_ID + "' class='col-md-12' style='display: none;'>";
html += " <textarea class='form-control' id='txtMod_" + node.CODE_ID + "' rows='1' style='width: 100%; resize: none;'>" + node.QNA_DESC + "</textarea><br/>"
html += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyModOk'>수정</button>";
html += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyModCancel'>취소</button>";
html += " </div>"
html += " <div id='re_" + node.CODE_ID + "' class='col-md-12 rePanel mb-3' style='display: none;'>";
html += " <textarea class='form-control' id='txt_" + node.CODE_ID + "' rows='1' style='width: 100%; resize: none;'></textarea><br/>"
html += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyOk'>등록</button>";
html += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyCancel'>취소</button>";
html += " </div>";
html += "</div>";
if(node.REPLY_CNT > 0){
html += " <div class='col-md-12'>";
html +=" <p id='" + node.CODE_ID + "' class='btnReplyCnt off' style='cursor:pointer'><span id='span_" + node.CODE_ID + "'>▲</span> 답글 " + node.REPLY_CNT + "건</p>";
html +=" <div style='margin-left: 25px; padding: 5px 10px;' id='reply_" + node.CODE_ID + "'></div>";
html += " </div>";
}
html += "<hr>";
});
if(data.length > 0){
//답글 리스트 세팅
$("#panelReply").html(html);
//버튼 클릭 이벤트 처리
setBtnEventAfterHtml();
//대댓글 카운트 클릭 이벤트
//댓글에 대한 댓글 리스트를 조회하여 화면에 출력함
$(".btnReplyCnt").off("click"); //off -> 버튼클릭할때마다 이벤트 처리가 누적되기 때문에 한번 초기화 시켜주는것임.
$(".btnReplyCnt").on("click", function(){
console.log($(this)[0].id);
var subId = $(this)[0].id; //id에 <p id = NODE_ID> 이렇게 36자리가 매핑되있음. 그값을 subId에 넣음. id값은 매핑되있는 거에 따라 달라질수있음.
var onOff = $(this).hasClass("off"); //off라는 클래스이름이 있는지 체크
//열린 댓글 화면들 모두 닫기 위해서 처리 START
$(this).removeClass("off"); //off라는 클래스 이름을 지움
$(this).removeClass("on"); //on이라는 클래스 이름을 지움
$("span[id^='span_']").html("▲"); //span태그에서 id가 span_ 로 시작하는 모든 아이디를 찾아서 적용해라.
$("div[id^='reply_']").html(""); //div태그에서 id가 reply_로 시작하는 모든 아이디를 찾아서 적용해라.
$("div[id^='reply_']").css("border", "none");
$("div[id^='reply_']").css("background", "none");
$("div[id^='re_']").hide();
$("div[id^='mod_']").hide();
$("div[id^='modNot_']").show();
//열린 댓글 화면들 모두 닫기 위해서 처리 END
//토글기능처리 ▲ ▼
if(onOff){ //off라는 클래스 이름이 있으면
$(this).addClass("on"); //토글기능 : off -> on
$("#span_" + subId).html("▼");
OPENED_REPLY_ID = subId; //subId; //<p id = node.CODE_ID > 36자리
} else {
$(this).addClass("off"); //토글기능 : on -> off
$("#span_" + subId).html("▲");
OPENED_REPLY_ID = "";
}
if(onOff){
findSubReplyList();
} else {
$("#reply_" + subId).html("");
}
});
}
},true, "POST");
}//댓글 리스트 조회
//화살표▼ 답글 클릭 시 대댓글 리스트 조회
function findSubReplyList(){
var subObj = {
upCodeId: OPENED_REPLY_ID //(댓글에 대한 CODE아이디) -> upCodeId(부모코드)
};
var subHtml = "";
cfFind("/qna/findReplyList.do", subObj, function(subData){
console.log("subData", subData);
$.each(subData, function(idx, node){
subHtml += "<div id = '" + node.USER_ID + "' class='row row3'>";
subHtml += " <div class='col-md-6'>";
subHtml += " <p style='margin: 0 0 15px 0;'><b>" + node.USER_NAME + "(" + node.USER_ID + ") </b>";
subHtml += " | <span>" + node.INPUT_DATETIME + "</span>";
if(userId !="" && node.USER_ID != userId) {//본인 댓글에는 답글 쓰지 못하도록 함.
subHtml += " | <span id='" + node.CODE_ID + "' class='w-btn w-btn-green button-hover btnSubReply' style='cursor:pointer'>답글쓰기</span></p>";
} else {
subHtml += "</p>";
}
subHtml += " </div>";
subHtml += " <div id= 'subBtnArea_" + node.CODE_ID + "' class='col-md-6' style='text-align: right;'>";
if(userId !="" && node.USER_ID == userId) {//본인 댓글은 수정, 삭제 버튼 활성화
subHtml += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubModify'>수정</button>"
subHtml += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubDelete'>삭제</button>"
} else {
subHtml += " ";
}
subHtml += " </div>";
subHtml += "</div>";
subHtml += "<div id = '" + node.USER_ID + "' class='row row3'>";
subHtml += " <div id='modNot_" + node.CODE_ID + "' class='col-md-12'><p>"
subHtml += " <b>" + node.TARGET_USER_ID +"</b> <label id='modify'>" + node.QNA_DESC + "</label>"
subHtml += " </div>"
subHtml += " <div id='mod_" + node.CODE_ID + "' class='col-md-12' style='display: none;'>";
subHtml += " <textarea class='form-control' id='txtMod_" + node.CODE_ID + "' rows='1' style= 'resize: none;'>" + node.QNA_DESC + "</textarea><br/>";
subHtml += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyModOk'>수정</button>";
subHtml += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyModCancel'>취소</button>";
subHtml += " </div>"
subHtml += " <div id='re_" + node.CODE_ID + "' class='col-md-12 rePanel mb-3' style='display: none;'>";
subHtml += " <textarea class='form-control' id='txt_" + node.CODE_ID + "' rows='1' style='width: 100%; resize: none;'></textarea><br/>";
subHtml += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyOk'>등록</button>";
subHtml += " <button id='" + node.CODE_ID + "' class='btn btn-sm btn-secondary fg-white btnSubReplyCancel'>취소</button>";
subHtml += " </div>"
subHtml += "</div>";
});
if(subData.length > 0){
$("#reply_" + OPENED_REPLY_ID).html(subHtml);
$("#reply_" + OPENED_REPLY_ID).css("border", "2px solid whitesmoke");
$("#reply_" + OPENED_REPLY_ID).css("background", "#f5f5f5");
// 버튼 클릭 이벤트 처리
// 대댓글인 경우 아이디를 보내주어 답글 등록시 사용하도록함
setBtnEventAfterHtml(OPENED_REPLY_ID);
}
}, false, "POST");
}
//html 출력후
//버튼클릭 이벤트 처리
function setBtnEventAfterHtml(){
//답글쓰기버튼 이벤트 처리 (답글에 대한 대댓글을 쓸때 사용하는 버튼)
$(".btnSubReply").off("click");
$(".btnSubReply").on("click", function(){
$(".rePanel").hide();//대댓글 입력창 모두 숨김 처리 ->뒤에서 $("#re_" + id).fadeIn(); 으로 댓글쓰는부분열것임.
// 사용자가 해당 글에서 대댓글 쓸때 다른곳의 대댓글쓰는 창을 모두 숨김처리하기 위한 기능임.
//1레벨 댓글의 답글쓰기 버튼을 클릭한 경우에는 오픈된 대댓글 리스트를 모두 닫고 코드값을 초기화
var mainReply = $(this).hasClass("mainReply"); //mainReply라는 클래스 선택자가 있다면
if(mainReply){
OPENED_REPLY_ID = ""; //열려있는 답글 리스트 재조회 시 다시 열도록 하는 변수 ▼
//열린 댓글 화면들 모두 닫기 위해서 처리 START
$(".btnReplyCnt").removeClass("on"); //.btnReplyCnt로 시작하는 클래스 중에서 열려있는속성(class = on)을 다 제거하고 on = ▼ 열림
$(".btnReplyCnt").addClass("off");//.btnReplyCnt로 시작하는 클래스 중에서 닫힌속성(class = off)를 다시 열어준다. off = ▲ 닫힘
$("span[id^='span_']").html("▲"); //span태그중에서 id선택자가 span_ 로 시작하는것을 찾아서 ▲ 를 넣어줌.
$("div[id^='reply_']").html("");
$("div[id^='reply_']").css("border", "none");
$("div[id^='reply_']").css("background", "none");
$("div[id^='re_']").hide();
$("div[id^='mod_']").hide();
$("div[id^='modNot_']").show();
//열린 댓글 화면들 모두 닫기 위해서 처리 END
}
var id = $(this)[0].id; // NODE.CODEID로 이미 매핑된 36자리 코드를 변수 id에 넣음.
//<span id='" + node.CODE_ID + "' class='btnSubReply mainReply' style='cursor:pointer;'>[답글쓰기]</span>
if(OPENED_REPLY_ID != ""){ // ▼
SELECTED_REPLY_ID = OPENED_REPLY_ID
//var SELECTED_REPLY_ID = ""; //1레벨 댓글에 대한 코드 정보 //UP_CODE_ID(부모코드아이디)
} else {
SELECTED_REPLY_ID = id;
}
//alert(id);
$("#re_" + id).fadeIn(); //fadeIn() : 서서히 나타나게 하는 함수
});
//대댓글 취소 버튼 이벤트
$(".btnSubReplyCancel").off("click");
$(".btnSubReplyCancel").on("click", function(){
$(".rePanel").hide();//대댓글 입력창 모두 숨김 처리
});
//대댓글 등록 버튼 이벤트
$(".btnSubReplyOk").off("click");
$(".btnSubReplyOk").on("click", function(){
var id = $(this)[0].id; //id => node.CODE_ID로 이미 매핑된 36자리
var value = $("#txt_" + id).val();
if(value == "" || value == null){
alert("답글 내용을 입력하시기 바랍니다.");
return;
}
var writeUserId = ""; //@아이디를 쓰기 위한 변수
if(OPENED_REPLY_ID != ""){ // ▼ 대댓글리스트가 보여짐.
writeUserId = "@" + $(this).parent().parent()[0].id; //댓글 3단계 이후부터 대대댓글(3단계) 달때 누구한테 댓글 달껀지 상대방의 @아이디
}
var subObj = {
qnaTitle: "답글",
userEmail: userEmail,
userName: userName,
qnaDesc: value,
password:"",
userId:userId,
privateYn:"N",
codeInfo:"",//서버사이드에서 UUID로 처리, pk이기 떄문에 값이 중복되면 안됨!
upCodeId: SELECTED_REPLY_ID, //부모(1레벨 댓글)
topCodeId: SELECTED_CODE_ID, //조상(원글=문의글)
targetUserId: writeUserId || ""
}
console.log(subObj);
cfSave("/qna/saveReplyInfo.do", subObj, function(subData){ //대댓글 입력도 답글쓰기와 같은 메서드를 사용함.
if(subData.success){
alert("답글 입력이 완료되었습니다.");
$("#txt_" + id).val("");
//답글 리스트 재조회
findReplyList();
//열려있는 대댓글이 있었다면.. ▼
//대댓글 리스트 재조회
if(OPENED_REPLY_ID != ""){
findSubReplyList();
$("#reply_" + OPENED_REPLY_ID).fadeIn(); //대댓글 리스트 영역 열기
$("p[id='" + OPENED_REPLY_ID + "']").removeClass("off");
$("p[id='" + OPENED_REPLY_ID + "']").addClass("on");
$("#span_" + OPENED_REPLY_ID).html("▼");
};
$("#mod_" + id).hide();
$("#modNOt_" + id).show();
}
}, true, "POST");
});
//댓글,대댓글 수정버튼 클릭 이벤트
$(".btnSubModify").off("click");
$(".btnSubModify").on("click", function(){
var id = $(this)[0].id;
console.log("id", id);
$("#modNot_" + id).hide();
$("#subBtnArea_" + id).hide();
$("#mod_" + id).show();
});
//수정 완료 버튼 클릭 이벤트
$(".btnSubReplyModOk").off("click");
$(".btnSubReplyModOk").on("click", function(){
var id = $(this)[0].id;
var value = $("#txtMod_" + id).val();
if(value == "" || value == null){
alert("답글 내용을 입력하시기 바랍니다.");
return;
}
var subObj = {
codeInfo: id,
desc: value
};
var result = confirm("수정하시겠습니까?");
if(result){
cfSave("/qna/saveReplyInfoUpdate.do", subObj, function(subData){
console.log("subObj", subObj);
if(subData.success){
alert("답글 수정이 완료되었습니다.");
//답글 리스트 재조회
findReplyList();
//열려있는 대댓글이 있었다면..
//대댓글 리스트 재조회
if(OPENED_REPLY_ID != ""){
findSubReplyList();
$("#reply_" + OPENED_REPLY_ID).fadeIn(); //대댓글 리스트 영역 열기
$("p[id='" + OPENED_REPLY_ID + "']").removeClass("off");
$("p[id='" + OPENED_REPLY_ID + "']").addClass("on");
$("#span_" + OPENED_REPLY_ID).html("▼");
}
$("#mod_" + id).hide();
$("#modNot_" + id).show();
}
}, true, "POST");
}
});
//수정 취소 버튼 클릭 이벤트
$(".btnSubReplyModCancel").off("click");
$(".btnSubReplyModCancel").on("click", function(){
var id = $(this)[0].id;
$("#mod_" + id).hide();
$("#modNot_" + id).show();
$("#subBtnArea_" + id).show();
});
//댓글 삭제 버튼 클릭 이벤트
$(".btnSubDelete").off("click");
$(".btnSubDelete").on("click", function(){
var id = $(this)[0].id;
var subObj = {
codeInfo: id,
};
var result = confirm("삭제하시겠습니까?");
if(result){
cfSave("/qna/saveReplyInfoRemove.do", subObj, function(subData){
console.log("subObj", subObj);
if(subData.success){
alert("답글 삭제가 완료되었습니다.");
//답글 리스트 재조회
findReplyList();
//열려있는 대댓글이 있었다면..
//대댓글 리스트 재조회
if(OPENED_REPLY_ID != ""){
findSubReplyList();
$("#reply_" + OPENED_REPLY_ID).fadeIn(); //대댓글 리스트 영역 열기
$("p[id='" + OPENED_REPLY_ID + "']").removeClass("off");
$("p[id='" + OPENED_REPLY_ID + "']").addClass("on");
$("#span_" + OPENED_REPLY_ID).html("▼");
}
}
}, true, "POST");
}
});
} //end
//문의글등록시 사용될 파라미터 정보를 세팅
function setParam(){
var txtTitle = $("#txtTitle").val();
//var txtUserEmail = $("#txtUserEmail").val();
// var txtUserName = $("#txtUserName").val();
var code = $("#txtDesc").val();
var password = $("#txtPassword").val();
var cmbPrivate = $("#cmbPrivate option:selected")[0].id; //N or Y
//alert(cmbPrivate);
if(txtTitle == ""){
alert("제목을 입력하시기 바랍니다.");
return;
}
/* if(txtUserEmail == ""){
alert("이메일을 입력하시기 바랍니다.");
return;
}
if(txtUserName == ""){
alert("성명을 입력하시기 바랍니다.");
return;
}*/
if(code == "" || code == null){
alert("문의내용을 입력하시기 바랍니다.");
return;
}
if(cmbPrivate == "Y" && password ==""){
alert("비밀번호를 입력하시기 바랍니다.");
return;
}
var obj = {
qnaTitle: txtTitle,
userEmail: userEmail,
userName: userName,
qnaDesc: code,
password: password,
userId: userId,
privateYn: cmbPrivate,
codeInfo: "",
upCodeInfo: "",
topCodeInfo: "",
lvl: 1
};
return obj;
}
function _finalize() {
}
return {
init: _init,
finalize: _finalize
}
}
var qna = new Qna();
qna.init();
})();
[MainController.java] - src/main/java/kr.co.values/main/web/MainController.java
- 파일다운로드 부분 추가
package kr.co.values.main.web;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import kr.co.values.main.persistence.MainMapper;
@Controller
public class MainController {
@Autowired
private MainMapper mainMapper;
@RequestMapping("/")
public String Main() {
return "index";
}
//프로필 조회
@RequestMapping("/main/searchProfile.do")
@ResponseBody
public List<Map<String, Object>> searchProfile(@RequestBody Map<String, Object> params){
List<Map<String, Object>> list = mainMapper.searchProfile(params);
return list;
}
/**
* 파일 다운로드
* @throws IOException
*/
@RequestMapping("/downloadFiles")
@ResponseBody
public void downloadFiles(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletContext context = request.getSession().getServletContext();
String path = context.getRealPath("/resources/upload"); //파일업로드 경로
String fileName = request.getParameter("fileName");
System.out.println("fileName : " + fileName);
String fileOrgName = request.getParameter("fileOrgName");
System.out.println("fileOrgName : " + fileOrgName);
String fileDir = request.getParameter("fileDir");
System.out.println("fileDir : " + fileDir);
ServletOutputStream servletOutputStream = null;
OutputStream outs = null;
try{
String filename = fileName;
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "inline; filename=\"" + java.net.URLEncoder.encode(fileOrgName, "UTF-8").replaceAll("\\+", "\\ ") + "\";");
servletOutputStream = response.getOutputStream();
outs = response.getOutputStream();
downloadFile(outs, path, filename);
}
catch(Exception e){
response.setContentType("text/html");
response.getOutputStream().write(0);
}
finally {
try {
servletOutputStream.flush();
servletOutputStream.close();
}catch(Exception e) {
}
}
}
/**
* 파일 다운로드 메서드 호출
*/
private void downloadFile(OutputStream servletOutputStream, String path, String filename) throws Exception {
FileInputStream fileInputStream = null;
try {
System.out.println("path + \"/\" + filename : " + path + "/" + filename);
File file = new File(path + "/" + filename);
fileInputStream = new FileInputStream(file);
byte[] b = new byte[2048];
int data = 0;
while ((data=(fileInputStream.read(b, 0, b.length))) != -1) {
servletOutputStream.write(b, 0, data);
}
}
catch(Exception e) {
throw e;
}
finally {
if(fileInputStream != null){
fileInputStream.close();
}
}
}
}
[QnaController.java] - src/main/java/kr.co.values/qna/web/QnaController.java
package kr.co.values.qna.web;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileUploadException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
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.multipart.MultipartFile;
import kr.co.values.qna.persistence.QnaMapper;
import kr.co.values.qna.service.QnaService;
@Controller
public class QnaController {
@Autowired
private QnaService qnaService;
@Autowired
private QnaMapper qnaMapper;
@RequestMapping("/qna.do")
public String qna() {
return "qna";
}
//신규 코드정보 조회 // TBL_QNA_INFO테이블의 코드아이디(PK)에 넣을 데이터 조회
@RequestMapping("/qna/findCodeInfo.do")
@ResponseBody
public Map<String, Object> findCodeInfo(@RequestBody Map<String, Object> param ){
String code = qnaMapper.findCodeInfo(param);
System.out.println("code" + code);
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", code);
return result;
}
//신규 문의글 등록
@RequestMapping("/qna/saveBoardInfo.do")
@ResponseBody
public Map<String, Object> saveBoardInfo(@RequestBody Map<String, Object> param){
Map<String, Object> result = new HashMap<String, Object>();
qnaService.saveBoardInfo(param);
result.put("success", true);
return result;
}
//파일 업로드
@RequestMapping("/qna/fileUpload.do")
@ResponseBody
public Map<String, Object> fileUploadSubmit(@RequestParam("file") MultipartFile part, HttpServletRequest request)
throws FileUploadException {
System.out.println("컨트롤러 유무 테스트");
ServletContext context = request.getSession().getServletContext();
String path = context.getRealPath("/resources/upload"); //파일업로드 경로
System.out.println("path: " + path);
String fileName = part.getOriginalFilename();
System.out.println("fileName : " + fileName);
String codeInfo = request.getParameter("codeInfo");
String userId = request.getParameter("userId");
System.out.println("codeInfo : " + codeInfo);
System.out.println("userId : " + userId);
SimpleDateFormat fm = new SimpleDateFormat("yyyyMMddHHmmssSS");
//SS를 쓴경우 한번에 파일여러개 올릴경우 중복되지 않는다.
Date time = new Date();
String forTime = fm.format(time);
int pos = fileName.lastIndexOf(".");
String ext = fileName.substring(pos + 1); //png, jpg등 확장자
try {
File f = new File(path + "/" + forTime + "." + ext);
part.transferTo(f);
} catch (Exception e) {
throw new FileUploadException(); //파일 업로드 오류
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("codeInfo", codeInfo);
map.put("userId", userId);
map.put("fileOrgName", fileName);
map.put("fileName", forTime + "." + ext);
qnaMapper.saveBoardFile(map);
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
return result;
}
//등록된 문의글 리스트 조회
@RequestMapping("/qna/findBoardList.do")
@ResponseBody
public List<Map<String, Object>> findBoardList(@RequestBody Map<String, Object> param){
List<Map<String, Object>> list = qnaMapper.findBoardList(param);
return list;
}
//등록된 첨부파일 조회
@RequestMapping("/qna/findBoardFileList.do")
@ResponseBody
public List<Map<String, Object>> findBoardFileList(@RequestBody Map<String, Object> param){
System.out.println(param);
List<Map<String, Object>> list = qnaMapper.findBoardFileList(param);
System.out.println("등록된첨부파일 조회 LIST : " + list);
return list;
}
//문의글 조회 카운트 증가
@RequestMapping("/qna/saveBoardCnt.do")
@ResponseBody
public Map<String, Object> saveBoardCnt(@RequestBody Map<String, Object> param){
qnaService.saveBoardCnt(param);
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
return result;
}
//등록된 문의글 삭제
@RequestMapping("/qna/saveBoardInfoRemove.do")
@ResponseBody
public Map<String, Object> saveBoardInfoRemove(@RequestBody Map<String,Object> param, HttpServletRequest request){
Map<String, Object> result = new HashMap<String, Object>();
qnaService.saveBoardInfoRemove(param, request);
result.put("success", true);
return result;
}
//답글쓰기
@RequestMapping("/qna/saveReplyInfo.do")
@ResponseBody
public Map<String, Object> saveReplyInfo(@RequestBody Map<String,Object> param){
Map<String, Object> result = new HashMap<String, Object>();
qnaService.saveReplyInfo(param);
result.put("success", true);
return result;
}
//등록된 답글 리스트 조회
@RequestMapping("/qna/findReplyList.do")
@ResponseBody
public List<Map<String, Object>> findReplyList(@RequestBody Map<String, Object> param){
List<Map<String, Object>> list = qnaMapper.findReplyList(param);
return list;
}
//등록된 답글 수정
@RequestMapping("/qna/saveReplyInfoUpdate.do")
@ResponseBody
public Map<String, Object> saveReplyInfoUpdate(@RequestBody Map<String, Object> param){
System.out.println("param" + param);
Map<String, Object> result = new HashMap<String, Object>();
qnaService.saveReplyInfoUpdate(param);
result.put("success", true);
return result;
}
//등록된 답글 삭제
@RequestMapping("/qna/saveReplyInfoRemove.do")
@ResponseBody
public Map<String, Object> saveReplyInfoRemove(@RequestBody Map<String, Object> param){
System.out.println("param" + param);
Map<String, Object> result = new HashMap<String, Object>();
qnaService.saveReplyInfoRemove(param);
result.put("success", true);
return result;
}
}
[QnaService.java] - src/main/java/kr.co.values/qna/service/QnaService.java
package kr.co.values.qna.service;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
public interface QnaService {
//신규 문의글 등록
void saveBoardInfo(Map<String, Object> map);
//문의글 조회 카운트 증가
void saveBoardCnt(Map<String, Object> map);
//등록된 문의글 정보 삭제 ( TBL_QNA_INFO , TBL_QNA_FILE)
void saveBoardInfoRemove(Map<String, Object> map, HttpServletRequest request);
//답글 및 대댓글 쓰기
void saveReplyInfo(Map<String, Object> map);
//답글 및 대댓글 수정
void saveReplyInfoUpdate(Map<String, Object> map);
//답글 및 대댓글 삭제
void saveReplyInfoRemove(Map<String, Object> map);
}
[QnaServiceImpl.java] - src/main/java/kr.co.values/qna/service/QnaServiceImpl.java
package kr.co.values.qna.service;
import java.io.File;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.co.values.qna.persistence.QnaMapper;
@Service
public class QnaServiceImpl implements QnaService {
@Autowired
private QnaMapper qnaMapper;
@Override
public void saveBoardInfo(Map<String, Object> map) {
qnaMapper.saveBoardInfo(map);
}
@Override
public void saveBoardCnt(Map<String, Object> map) {
qnaMapper.saveBoardCnt(map);
}
@Override
public void saveBoardInfoRemove(Map<String, Object> map, HttpServletRequest request) {
qnaMapper.saveBoardInfoRemove(map);
//첨부한 파일이 있는지 조회 후 있으면 파일도 삭제한다.
List<Map<String, Object>> files = qnaMapper.findBoardFileList(map);
for(Map<String, Object> fileMap : files) {
//실제 파일 삭제
ServletContext context = request.getSession().getServletContext();
String path = context.getRealPath("/resources/upload"); //업로드 경로
System.out.println("path: " + path);
String fileName = (String)fileMap.get("FILE_NAME");
System.out.println("fileNAme: " + fileName);
File file = new File(path + "/" + fileName);
if(file.exists()) {
if(file.delete()) {
qnaMapper.saveBoardFileRemove(fileMap); //파일 테이블도 삭제
System.out.println("파일삭제 성공");
} else {
System.out.println("파일삭제 실패");
}
}
}
}
@Override
public void saveReplyInfo(Map<String, Object> map) {
qnaMapper.saveReplyInfo(map);
}
@Override
public void saveReplyInfoUpdate(Map<String, Object> map) {
qnaMapper.saveReplyInfoUpdate(map);
}
@Override
public void saveReplyInfoRemove(Map<String, Object> map) {
qnaMapper.saveReplyInfoRemove(map);
}
}
[QnaMapper.java] - src/main/java/kr.co.values/qna/persistence/QnaMapper.java
package kr.co.values.qna.persistence;
import java.util.List;
import java.util.Map;
public interface QnaMapper {
//신규 코드 정보 조회 // TBL_QNA_INFO테이블의 코드아이디(PK)에 넣을 데이터 조회
String findCodeInfo(Map<String, Object> param);
//문의글 정보 등록
void saveBoardInfo(Map<String, Object> params);
//문의글 첨부파일 등록
void saveBoardFile(Map<String, Object> params);
//등록된 문의글 리스트 조회
List<Map<String, Object>> findBoardList(Map<String, Object> params);
//문의글 조회 카운트 증가
void saveBoardCnt(Map<String, Object> params);
//등록된 첨부파일 리스트 조회
List<Map<String, Object>> findBoardFileList(Map<String, Object> params);
//문의글 정보 삭제(TBL_QNA_INFO, TBL_QNA_FILE)
void saveBoardInfoRemove(Map<String, Object> params);
void saveBoardFileRemove(Map<String, Object> params);
//답글쓰기
void saveReplyInfo(Map<String, Object> params);
//답글 리스트 조회
List<Map<String, Object>> findReplyList(Map<String, Object> params);
//답글 수정
void saveReplyInfoUpdate(Map<String, Object> params);
//답글 삭제
void saveReplyInfoRemove(Map<String, Object> params);
}
[QnaMapper.xml] - src/main/java/kr.co.values/qna/persistence/QnaMapper.xml
<?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="kr.co.values.qna.persistence.QnaMapper">
<select id="findCodeInfo" resultType="string" parameterType="hashmap">
SELECT UUID() AS CODE_INFO
</select>
<insert id="saveBoardInfo" parameterType="hashmap">
INSERT INTO MYPORTFOLIO.TBL_QNA_INFO (
CODE_ID
, UP_CODE_ID
, TOP_CODE_ID
, LVL
, USER_ID
, USER_EMAIL
, USER_NAME
, QNA_TITLE
, QNA_DESC
, TARGET_USER_ID
, PRIVATE_YN
, PASSWORD
, CNT
, DELETE_YN
, INPUT_DATETIME
) VALUES (
#{codeInfo}
,#{upCodeId}
,#{topCodeId}
,#{lvl}
,#{userId}
,#{userEmail}
,#{userName}
,#{qnaTitle}
,#{qnaDesc}
,''
,#{privateYn}
,#{password}
,0
,'N'
, NOW()
)
</insert>
<insert id="saveBoardFile" parameterType="hashmap">
INSERT INTO MYPORTFOLIO.TBL_QNA_FILE (
CODE_ID
,FILE_ID
,USER_ID
,FILE_ORG_NAME
,FILE_NAME
,INPUT_DATETIME
) VALUES(
#{codeInfo}
, UUID()
, #{userId}
, #{fileOrgName}
, #{fileName}
, NOW()
)
</insert>
<select id="findBoardList" parameterType="hashmap" resultType="hashmap">
SELECT DISTINCT A.CODE_ID
,A.UP_CODE_ID
,A.TOP_CODE_ID
,A.LVL
,A.USER_ID
,A.USER_EMAIL
,A.USER_NAME
,A.QNA_TITLE
,A.QNA_DESC
,A.TARGET_USER_ID
,A.PRIVATE_YN
,A.PASSWORD
,A.CNT
,A.DELETE_YN
, DATE_FORMAT(A.INPUT_DATETIME, '%Y-%m-%d %T') AS INPUT_DATETIME
, CASE WHEN B.CODE_ID IS NULL THEN '답변대기중' ELSE '답변완료' END REPLY_YN
FROM MYPORTFOLIO.TBL_QNA_INFO A
LEFT OUTER JOIN MYPORTFOLIO.TBL_QNA_INFO B
ON A.CODE_ID = B.UP_CODE_ID
AND B.LVL = 2
AND B.USER_ID IN (SELECT USER_ID FROM TBL_USER_INFO WHERE USER_AUTH = '2')
WHERE 1=1
AND A.LVL = 1
<if test="!''.equals(txtBoardTitle)">
AND A.QNA_TITLE LIKE CONCAT('%', #{txtBoardTitle}, '%')
</if>
<if test="!''.equals(txtBoardWriter)">
AND A.USER_NAME LIKE CONCAT('%', #{txtBoardWriter}, '%')
</if>
ORDER BY A.INPUT_DATETIME DESC
</select>
<select id="findBoardFileList" parameterType="hashmap" resultType="hashmap">
SELECT A.CODE_ID
,A.FILE_ID
,A.USER_ID
,A.FILE_ORG_NAME
,A.FILE_NAME
, DATE_FORMAT(A.INPUT_DATETIME, '%Y-%m-%d %T') AS INPUT_DATETIME
FROM
MYPORTFOLIO.TBL_QNA_FILE A
WHERE 1=1
AND A.CODE_ID = #{codeInfo}
ORDER BY A.INPUT_DATETIME DESC
</select>
<update id="saveBoardCnt" parameterType="hashmap">
UPDATE MYPORTFOLIO.TBL_QNA_INFO
SET CNT = CNT + 1
WHERE 1=1
AND CODE_ID = #{codeInfo}
</update>
<delete id="saveBoardInfoRemove" parameterType="hashmap">
DELETE FROM MYPORTFOLIO.TBL_QNA_INFO
WHERE 1=1
AND CODE_ID = #{codeInfo} OR TOP_CODE_ID = #{codeInfo}
</delete>
<delete id="saveBoardFileRemove" parameterType="hashmap">
DELETE FROM MYPORTFOLIO.TBL_QNA_FILE
WHERE 1=1
AND CODE_ID = #{CODE_ID} AND FILE_ID = #{FILE_ID}
</delete>
<insert id="saveReplyInfo" parameterType="hashmap">
INSERT INTO MYPORTFOLIO.TBL_QNA_INFO (
CODE_ID
, UP_CODE_ID
, TOP_CODE_ID
, LVL
, USER_ID
, USER_EMAIL
, USER_NAME
, QNA_TITLE
, QNA_DESC
, TARGET_USER_ID
, PRIVATE_YN
, PASSWORD
, CNT
, DELETE_YN
, INPUT_DATETIME
) VALUES (
UUID()
, #{upCodeId}
, #{topCodeId}
, (SELECT * FROM (SELECT LVL + 1 FROM MYPORTFOLIO.TBL_QNA_INFO WHERE CODE_ID = #{upCodeId} LIMIT 1)T)
, #{userId}
, #{userEmail}
, #{userName}
, #{qnaTitle}
, #{qnaDesc}
, #{targetUserId}
, #{privateYn}
, #{password}
, 0
, 'N'
, NOW()
)
</insert>
<select id="findReplyList" parameterType="hashmap" resultType="hashmap">
SELECT A.CODE_ID
,A.UP_CODE_ID
,A.TOP_CODE_ID
,A.LVL
,A.USER_ID
,A.USER_EMAIL
,A.USER_NAME
,A.QNA_TITLE
,A.QNA_DESC
,A.TARGET_USER_ID
,A.PRIVATE_YN
,A.PASSWORD
,A.CNT
,A.DELETE_YN
, DATE_FORMAT(A.INPUT_DATETIME, '%Y-%m-%d %T') AS INPUT_DATETIME
, (SELECT COUNT(*) FROM MYPORTFOLIO.TBL_QNA_INFO B WHERE B.UP_CODE_ID = A.CODE_ID) AS REPLY_CNT
FROM MYPORTFOLIO.TBL_QNA_INFO A
WHERE 1=1
AND A.UP_CODE_ID = #{upCodeId}
ORDER BY A.INPUT_DATETIME DESC
</select>
<update id="saveReplyInfoUpdate" parameterType="hashmap">
UPDATE MYPORTFOLIO.TBL_QNA_INFO
SET QNA_DESC = #{desc}
WHERE 1=1
AND CODE_ID = #{codeInfo}
</update>
<delete id="saveReplyInfoRemove" parameterType="hashmap">
DELETE FROM MYPORTFOLIO.TBL_QNA_INFO
WHERE CODE_ID = #{codeInfo} OR UP_CODE_ID = #{codeInfo}
</delete>
</mapper>
'Spring MyPortfolio Project' 카테고리의 다른 글
[포트폴리오 프로젝트 9] 푸터 페이지 & About 페이지 (1) | 2024.01.24 |
---|---|
[포트폴리오 프로젝트 8] 포트폴리오 프로젝트 구현 – 관리자 페이지 (0) | 2024.01.24 |
[포트폴리오 프로젝트 6] 관리자메뉴 - 사용자관리 페이지 구현 (0) | 2024.01.24 |
[포트폴리오 프로젝트 5] 로그인 페이지 구현(spring security포함) (0) | 2024.01.24 |
[포트폴리오 프로젝트 4] 회원가입 페이지 구현 (0) | 2024.01.24 |