큰 틀로 나눠보면 이 두가지 정도라고 할 수 있겠다. 프로젝트를 진행할 때 각자 담당할 기능을 나누면서 하고 싶은 기능이 있는 사람을 우선 순위로 두고 기능을 나눴는데… 난 사실 딱히 뭐 하나를 찝어서 하고 싶다! 라는 게 없었다.
뭘 해도 수업 때 배운 걸 잘 활용해봐야하는 건 비슷하지 않을까? 라고 생각해서 그랬다. 오히려 세미 프로젝트가 끝난 지금은 하고 싶은 게 생기기도 하고 배운 걸 잘 활용해볼 수 있는 게시판 기능을 담당해서 정말 운이 좋았다고 생각했다.
프로젝트 깃허브 확인하기
필요 소스 코드들은 github에 올려놨다! 필요한 쿼리문이나 이런 건 gitignore로 git관리에서 제외시켜서 볼 수 없지만…
중고 거래 사이트 : HiFive!
⬆️ 이건 서버 배포한 프로젝트 사이트 주소
캡쳐 이미지는 자주하는 질문에 더미 데이터를 많이 넣었으므로… 이걸로 대체한다. (더미 데이터를 좀 더 성의껏 넣을 걸 하는 후회를 지금한다...ㅠ)
우선 공지사항에는 이벤트 등 다양한 내용을 전달하므로 간단한 질문이나 이벤트 참여등에 대한 소통을 위해 사용자가 댓글을 달 수 있도록 본문에 댓글 기능을 구현했다.
물론 댓글 수정 삭제도 가능하다! 수정할 땐 전체를 다 바꾸기 보단 일부분만 수정하는 경우를 생각해서 이미 작성했던 댓글 내용을 textarea 태그에 출력해준 상태로 데이터를 입력 받을 수 있게했다.
자주하는 질문은 고정된 질문에 대한 답을 게시글로 전달하기 때문에 불필요한 댓글 기능은 지저분해보일 것 같아서 제외시켰다. 게시글로 알리는 내용 외에 대한 질문은 1:1문의하기 게시판이 따로 존재하는 것도 이유였다.
대신 자주하는 질문에는 카테고리별로 게시글을 조회할 수 있는 버튼을 추가했다.
글 작성 버튼은 관리자 권한이 있는 회원이 로그인 했을 때만 보일 수 있도록 했다. 글작성 버튼을 누르면 바뀌는 화면은 아래와 같다.
공지사항이나 자주하는 질문을 작성할 때는 전체적인 틀이 크게 다른 점이 없어서 하나의 페이지에 <select>
태그로 자주하는 질문인지 공지사항에 올리는 글인지 선택하는 걸로 구분했다.
<textarea>
태그로 게시글 내용을 입력 받아서 넘기다보니 개행에 대한 처리가 제대로 이루어지지 않았다. 나중에 jsp에 문자열을 출력해줄 때 개행이 처리될 수 있게 DB에 저장할 때 엔터 부분을 <br>
태그로 바꿔주면 출력할 때 따로 신경쓰지 않아도 개행된 상태로 출력되겠다 라고 생각했다.
근데 이렇게 처리하니까 자료형 크기를 어느 정도로 줘야할지 감이 잘 안 잡혔다... 부족해서 에러나는 것보다는 넉넉하게 주기 위해서 Long 타입을 주고 Long 타입을 쓰면서 발생한 에러를 알게돼서 좋은 점도 있었지만 좀 더 고려해볼 부분이라고 생각했다.
//게시판 저장
Board b=Board.builder()
.boardWriter(mr.getParameter("login"))
.noticeYn(mr.getParameter("titleCategory").charAt(0))
.boardCategory(mr.getParameter("QACategory"))
.boardTitle(mr.getParameter("boardTitle"))
.boardContent(mr.getParameter("boardContent").replaceAll("\n", "<br>"))
.build();
첨부파일을 받을 때 최대 크기를 지정하게 되는데 이걸 백까지 넘긴 다음에 확인되게 하지 말고 파일을 올릴 때부터 용량을 확인해서 걸러주면 좋지 않을까? 라는 생각을 해서 구현하게 된 기능이다.
덤으로 파일을 하나씩 검사하면서 업로드 될 파일을 사용자가 보는 화면에 이름을 출력해줬다.
//파일크기 체크
$(document).on("change","#formFileMultiple",function(){
let maxSize = 1024 * 1024 * 200; // 파일 최대 크기 200MB
const files = this.files; // 첨부된 파일 목록
$('#fileList').empty();
$.each(files, function(i, file){ // 첨부된 파일 순회
if(file.size > maxSize){ // 크기 체크
alert('10MB 이하의 파일만 첨부할 수 있습니다.');
$(this).val(''); // 첨부된 파일을 모두 없애줌
return;
}
// 첨부 목록 표시
$('#fileList').append('<div>' + file.name + '</div>');
});
});
<input type="file">
태그를 multiple
로 설정하면서 깨달은 점은 태그 하나에 다중 파일을 받았을 때 파일명들을 한 번에 받아올 수 없다는 거였다. 수업 때는 파일 하나만 업로드 해보고 파일 추가 버튼을 따로 만들어서 <input>
태그가 여러 개 생성되도록 만들어서 처리를 했었는데 깔끔하게 보내고 싶어서 파일 버튼을 하나로 하려고 하니 어떻게 처리해야될지 고민하다가 백에서 여러 개를 끌어올 방법이 없다면 ajax
를 이용해서 프론트에서 여러 개의 파일명을 보내주도록 해봐야겠다고 생각했다.
function uploadFile(){
const formData=new FormData();
const fileInput=$("#formFileMultiple");
if (fileInput[0].files.length > 0) { //file이 있는 경우
for (let i = 0; i < fileInput[0].files.length; i++){
formData.append('file'+i, fileInput[0].files[i]);
}
}
formData.append("login",'<%=loginMember.getUserId()%>');
formData.append("titleCategory",$(".titleSelect :selected").val());
formData.append("QACategory",$(".QASelect :selected").val());
formData.append("boardTitle",$("#baordTitle").val());
formData.append("boardContent",$("#boardContent").val());
$.ajax({
url : "<%=request.getContextPath()%>/service/boardInsertEnd.do",
type : "post",
data : formData,
contentType : false,
processData : false,
success : function(result) {
if (result) {
alert("업로드 성공했습니다.");
location.replace("<%=request.getContextPath()%>/service/boardList.do?notice=Y");
}
},
error : function() {
alert("업로드에 실패했습니다. 작성 내용을 확인하세요.");
}
});
}
file명을 반복문을 돌려서 formData 객체에 추가해서 데이터를 넘겨줬다. file명만 따로 요청을 처리하는 게 아니고 하나의 게시글로 처리를 완료해야 되기 때문에 다른 데이터도 함께 담아서 전송시켰다.
파일을 저장하는 첨부 파일 테이블과 게시글 작성 내용을 저장하는 게시글 테이블이 따로 존재하기 때문에 DB에 insert문을 두 번 실행했어야 했는데 처음엔 막연하게 하나의 서블릿에서 저장하는 insert문이 실행되는 메소드가 한 번씩 실행되면 되겠다 생각했다. 그래서 Connection 객체를 관리하는 service에 각각 접근해서 게시글 저장하기 위한 메소드 하나, 본문을 저장하기 위한 메소드 하나 이렇게 코드를 작성했다.
근데 파일 업로드가 도중에 실패해도 게시글 내용은 제대로 insert를 실행했기 때문에 commit을 해버려서 사용자가 제대로 업로드한 게 아닌 데이터가 저장이 되고, 게시글 리스트를 출력할 때 게시글 테이블에 존재하는 데이터이기 때문에 첨부 파일이 없는데도 데이터를 뽑아내서 출력해주게 됐다.
public int insertBoard(Board b, List<String> filesNames) {
Connection conn=getConnection();
int result=dao.insertBoard(conn, b);
int fileresult=0;
for(String file:filesNames) {
fileresult+=dao.insertBoardFile(conn, file);
}
if(result>0&&fileresult==filesNames.size()) commit(conn);
else rollback(conn);
close(conn);
return result;
}
그래서 insert문은 두 번 실행되는 게 맞으니 DB의 트랜잭션 관리하는 부분을 고쳐야겠다고 생각했다. 아주 단순한 방법으로 수정했지만… 이렇게 수정하니 파일 저장이 실패하거나 게시글 저장이 실패되면 전부 rollback
처리하게 되므로 불필요한 데이터가 저장되는 일을 방지할 수 있었다.
1:1 문의하기는 전체적으로 글이 보이긴 하지만 개인 정보 등 보호가 필요한 내용이 담길 수 있으므로 사용자가 글 작성 시 문의글의 공개 여부를 체크할 수 있도록 했다.
비공개 글에 체크를 한 글은 관리자나 작성자 외에는 볼 수 없게 만들고 filter를 이용해 url 주소만 수정해서 접근하는 경우에도 권한이 없으면 메인 화면으로 돌릴 수 있도록 처리했다.
문의글에 대한 메인 댓글은 관리자 권한에게만 보인다. 작성자가 추가로 문의할 수 있게 대댓글 기능을 열어뒀다.
우리 팀은 비회원에 대한 테이블을 따로 만들지 않기로 했으므로 1:1문의하기는 로그인한 사용자만 들어올 수 있도록 처리하기도 했다. 버전 업그레이드를 할 때는 비회원 테이블을 따로 생성해서 문의글을 남기고 싶은 비회원에 대한 처리를 해볼 예정이다.
신고글은 자신이 작성한 글만 모아서 볼 수 있도록 만들었다. 신고한 글을 누르면 내가 신고한 글에 대한 정보와 함께 작성한 글을 확인할 수 있게 했다.
신고는 판매글에 대한 신고와 거래 내역에 대한 신고로 나뉘는데 판매글
은 게시된 판매글에서 신고하기 버튼을 누르면 해당 게시글에 대한 정보를 불러오면서 신고할 수 있게 하고 거래 내역 신고
는 거래 완료 처리된 내역 중 2주 안의 거래만 불러와서 사용자가 거래할 내역을 선택할 수 있게 했다.
거래 내역은 한 번에 하나의 거래만 신고할 수 있도록 해야하는데 체크 버튼 이미지를 인터넷에서 가져와서 사용했다보니… radio 버튼으로 바꾸기보다는 하나만 체크할 수 있도록 함수를 만들어줬다.
function checkOnlyOne(element) {
const checkboxes = document.getElementsByName("buyListCk");
checkboxes.forEach((cb) => {
cb.checked = false;
});
element.checked = true; //클릭한 체크박스만 checked로 설정
}
Mybatis를 배우기 전에 프로젝트를 진행했어서 반복적인 코드도 많고 예외 상황에 대해서 쿼리문이 바뀔 때마다 번거롭게 처리했던 부분들도 많아서... 지금 다시 보면 업그레이드 할 점이 꽤나 많이 보인다.
아직 프로젝트 끝난지 며칠 되지도 않았는데... Spring까지 배우고 나면 코드가 더 단순하고 짧아질 수 있지 않을까라는 기대를 해보며... 수료 후에도 배운 걸 활용해서 업그레이드 해봐야겠다.
아직은 곧 시작할 파이널 준비로 바빠지겠지만... 내 코드에 고칠 점이 눈에 더 들어온다는 건 좋은 거 같다! 그만큼 며칠 사이에 많은 걸 배우고 이해한 거니까... ㅎㅎ
안녕하세요^^ git관리에서 제외시킨 내용을 알 수 있을까요? ^^;