[싱글이의 영수증] 등록하기
업데이트:
싱글이의 영수증 등록하기
등록하기는 썸머노트를 이용해 구현했다.
섬머노트를 사용하기 위해 홍페이지에서 js와 css를 다운로드해서 resources폴더에 넣어두었다.
섬머노트를 활용하기 위해 mySummernote.js에 게시글 작성 부분의 크기를 지정하고, 언어를 지정해주고, 플레이스홀더 작성 툴바 변경 등 추가적인 작업을 해준다
(플레이스홀더, 툴바는 필요한 경우 해주면 된다)
또한 섬머노트로 이미지를 올릴 경우 업로드된 이미지를 ajax를 통해서 비동기 형식으로 서버에 바로 전송되도록 insertImage 메서드를 실행해준다.
- 출력화면
등록화면 이동 script
$("#insertBoard").on("click", function() {
location.href = "insert";
});
화면 전환 Controller
@RequestMapping("insert")
public String reviewInsertView() {
return "review/reviewInsert";
}
CSS
<style>
.container {
border: 1px solid;
padding: 19px 27px 15px;
box-sizing: border-box;
border-radius: 2px;
border-color: #efefef;
}
/* 셀렉트 박스 */
/* 제목 입력 */
.titleArea {
width: 100%;
font-size: 23px;
border: none;
}
.titleArea:focus {
outline: none;
}
.note-editor {
width: 100% !important;
}
</style>
JSP
- summernote 사용시 필요한 jsp파일
<script src="${contextPath }/resources/summernote/js/summernote-lite.js"></script>
<!-- 이 코드가 있어야 섬머노트 사용 가능 -->
<script src="${contextPath }/resources/summernote/js/summernote-ko-KR.js"></script>
<!-- 한글 패치 -->
<script src="${contextPath }/resources/summernote/js/mySummernote.js"></script>
<!-- 개인이 만든 js -->
- 등록화면
<div class="container" style="margin-bottom: 5px;">
<div class="row ">
<div class="col-md-12">
<form action="reviewInsert" enctype="multipart/form-data" method="post" role="form" onsubmit="return validate();">
<div class="col-md-12">
<div class="row">
<div class="col-md-4">
<select class="form-control div small" id="category" name="categoryCode" style="width: 150px;">
<option value="21">가구</option>
<option value="22">생활용품</option>
<option value="23">전자기기</option>
<option value="24">기타</option>
</select>
</div>
<div class="col-md-12 py-2">
<input class="titleArea" name="boardTitle" placeholder="제목을 적어주세요." autocomplete="off"></input>
<hr>
</div>
</div>
<!-- 내용 -->
<div class="form-group">
<textarea class="form-control" name="boardContent" id="summernote" rows="10" style="resize: none;"></textarea>
</div>
<div class="text-center" style="margin-bottom: 10px;">
<button type="submit" class="btn btn-success maincolor-re">등록</button>
<a class="btn btn-success maincolor-re" href="${sessionScope.returnListURL}">취소</a>
</div>
</div>
</form>
</div>
</div>
</div>
- 유효성 검사
<script>
function validate(){
if($(".titleArea").val().trim().length ==0){
swal("제목을 입력해 주세요.");
$(".titleArea").focus();
return false;
}
if($("#summernote").val().trim().length ==0){
swal("내용을 입력해 주세요.");
$(".summernote").focus();
return false;
}
}
</script>
등록 Controller
게시글 정보를 등록할 때 파라미터로 얻어 온 게시글 정보를 담은 map과 파일 저장 경로를 매개변수로 사용한다.
우선 게시글이 등록되어야 될 게시글 번호를 얻어온 후, 등록된 게시글을 insert 진행
insert 성공 후 섬머노트에 작성된 내용 중 img태그의 src속성의 값을 검사하여 매칭되는 값을 matcher객체에 저장한다.
matcher객체에 다음 요소가 있을 때까지 반복문을 수행하며 파일경로,이름,레벨,게시글번호를 이용해 attachment객체를 생성 해준다.
처음 등록된 img를 썸네일로 지정하기 위해 파일 레벨은 1부터 후위 증가 연산자를 통해 한 단계씩 높여준다
@RequestMapping("reviewInsert")
public String reviewInsert(@ModelAttribute Review review, @ModelAttribute(name="loginMember", binding=false) Member loginMember,
HttpServletRequest request, RedirectAttributes ra) {
Map<String,Object> map = new HashMap<String,Object>();
map.put("memberNo", loginMember.getMemberNo());
map.put("boardTitle", review.getBoardTitle());
map.put("boardContent", review.getBoardContent());
map.put("categoryCode", review.getCategoryCode());
// 파일 저장 경로 설정
String savePath = request.getSession().getServletContext().getRealPath("resources/reviewBoardImages");
// 게시글 map, 이미지 images, 저장경로 savePath
// 게시글 삽입 Service 호출
int result = service.insertReview(map, savePath);
String url = null;
if(result>0) {
swalIcon = "success";
swalTitle = "게시글 등록 성공";
url = "redirect:"+result;
// 새로 작성한 게시글 상세 조회 시 목록으로 버튼 경로 지정하기
request.getSession().setAttribute("returnListURL", "list");
}else {
swalIcon = "error";
swalTitle = "게시글 등록 실패";
url = "redirect:insert";
}
ra.addFlashAttribute("swalIcon",swalIcon);
ra.addFlashAttribute("swalTitle",swalTitle);
return url;
}
summernote에 업로드된 이미지 저장
Controller
@ResponseBody
@RequestMapping(value= {"insertImage", "{boardNo}/insertImage"})
public String insertImage(HttpServletRequest request, @RequestParam("uploadFile") MultipartFile uploadFile) {
// 서버에 이미지를 저장할 폴더 경로 얻어오기
String savePath = request.getSession().getServletContext().getRealPath("resources/reviewBoardImages");
ReviewAttachment at = service.insertImage(uploadFile, savePath);
return new Gson().toJson(at);
}
Service
ReviewAttachment insertImage(MultipartFile uploadFile, String savePath);
ServiceImpl
@Override
public ReviewAttachment insertImage(MultipartFile uploadFile, String savePath) {
// 중복을 방지하기 위해, 파일명 변경해 줌
String fileName = rename(uploadFile.getOriginalFilename());
// 웹 상 접근 주소
String filePath = "/resources/reviewBoardImages";
// 돌려 보내줄 파일 정보를 Attachment 객체에 담아서 전달.
ReviewAttachment at = new ReviewAttachment();
at.setFilePath(filePath);
at.setFileName(fileName);
// 서버에 파일 저장(transferTo())
try {
uploadFile.transferTo(new File(savePath + "/" + fileName));
} catch (Exception e) {
e.printStackTrace();
throw new InsertAttachmentFailException("summerote 파일 업로드 실패");
}
return at;
}
파일명 변경 메소드
public String rename(String originFileName) {
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
String date = sdf.format(new java.util.Date(System.currentTimeMillis()));
int ranNum = (int) (Math.random() * 100000); // 5자리 랜덤 숫자 생성
String str = "_" + String.format("%05d", ranNum);
// String.format : 문자열을 지정된 패턴의 형식으로 변경하는 메소드
// %05d : 오른쪽 정렬된 십진 정수(d) 5자리(5)형태로 변경. 빈자리는 0으로 채움(0)
String ext = originFileName.substring(originFileName.lastIndexOf("."));
return date + str + ext;
}
- 게시글 등록
Service
int insertReview(Map<String, Object> map, String savePath);
ServiceImpl
@Transactional(rollbackFor = Exception.class)
@Override
public int insertReview(Map<String, Object> map, String savePath) {
int result = 0; // 결과 저장
// 게시글 번호 얻어오기
int boardNo = dao.selectNextNo();
System.out.println(boardNo);
System.out.println(map.get(boardNo));
// 게시글 삽입
if (boardNo > 0) {
map.put("boardNo", boardNo);
result = dao.insertBoard(map);
if (result > 0) {
List<ReviewAttachment> uploadImages = new ArrayList<ReviewAttachment>();
String filePath = "/resources/reviewBoardImages";
Pattern pattern = Pattern.compile("<img[^>]*src=[\"']?([^>\"']+)[\"']?[^>]*>");
// SummerNote에 작성된 내용 중 img태그의 src속성의 값을 검사하여 매칭되는 값을 Matcher객체에 저장함.
Matcher matcher = pattern.matcher((String) map.get("boardContent"));
String fileName = null; // 파일명 변환 후 저장할 임시 참조 변수
String src = null; // src 속성값을 저장할 임시 참조 변수
int fileLevel = 1;
while (matcher.find()) {
src = matcher.group(1);
filePath = src.substring(src.indexOf("/", 2), src.lastIndexOf("/"));
fileName = src.substring(src.lastIndexOf("/") + 1); // 업로드된 파일명만 잘라서 별도로 저장
ReviewAttachment at = new ReviewAttachment(filePath, fileName, fileLevel, boardNo);
uploadImages.add(at);
fileLevel++;
}
if (!uploadImages.isEmpty()) {
// 파일 정보 삽입 DAO 호출
result = dao.insertAttachmentList(uploadImages);
if (result == uploadImages.size()) {
result = boardNo;
} else { // 파일 정보를 DB에 삽입 실패
throw new InsertAttachmentFailException("파일 정보 DB삽입 실패");
}
} else {// 업로드된 이미지가 없을 경우
result = boardNo;
}
}
}
return result;
}
- 게시글 번호 얻어오기
DAO
public int selectNextNo() {
return sqlSession.selectOne("reviewMapper.selectNextNo");
}
Mapper
<select id="selectNextNo" resultType="_int">
SELECT SEQ_BNO.NEXTVAL FROM DUAL
</select>
- 게시글 등록
DAO
public int insertBoard(Map<String, Object> map) {
return sqlSession.insert("reviewMapper.insertBoard",map);
}
Mapper
<insert id="insertBoard" parameterType="map">
INSERT INTO BOARD
VALUES(#{boardNo}, #{boardTitle}, #{boardContent},
DEFAULT, DEFAULT, DEFAULT, 2, #{categoryCode}, #{memberNo} )
</insert>
- 파일 정보 등록
DAO
public int insertAttachmentList(List<ReviewAttachment> uploadImages) {
return sqlSession.insert("reviewMapper.insertAttachmentList", uploadImages);
}
Mapper
INSERT시 서브쿼리를 사용하면 서브쿼리의 결과 행 수 만큼 INSERT가 수행된다.
더미 테이블을 이용해서 원하는 형태의 결과를 1행 조회하는 구문을 만들 수 있다.
UNION ALL을 이용하면 컬럼 구성이 같은 RESULT SET 을 하나로 합칠 수 있다.
서브쿼리를 이용한 INSERT시 시퀀스.NEXTVAL가 서브 쿼리에 포함되어 있다면 각 행이 삽입 될 때 마다 시퀀스 값이 증가함.
<insert id="insertAttachmentList" parameterType="list">
INSERT INTO BOARD_FILE
SELECT SEQ_FNO.NEXTVAL, A.* FROM (
<foreach collection="list" item="item" separator="UNION ALL">
SELECT #{item.filePath} FILE_PATH,
#{item.fileName} FILE_NM,
#{item.parentBoardNo} PARENT_BOARD_NO,
#{item.fileLevel} FILE_LEVEL
FROM DUAL
</foreach>
)A
</insert>
공유하기
Twitter Google+ LinkedIn
댓글남기기