본문 바로가기

프로젝트

[SpringBoot] 경매사이트 만들기 - 경매 게시글 작성 및 상세보기

먼저 포스팅하기전 수정사항 부터 말하려고한다.

 

1. 이전에 시큐리티 설정에서 비회원은 auth/** 인 url만 허용하도록 했었는데,

생각해보니 어차피 나는 @Secured를 사용하기 때문에 굳이 url을 나눌필요를 느끼지 못했다.

그래서 auth는 없앴고, 회원만 사용가능한 부분에선 컨트롤러 쪽에 @Secured를 이용해 통제하도록 했다!

 

2. 이미지 첨부를 위해 Photo테이블을 만들고, 기존에 ActionBoard테이블에 있떤 사진 칼럼은 삭제했다!

Photo.java

package org.my.toyproject.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@SequenceGenerator(
		name = "PHOTO_SEQ_GENRATOR"
		,sequenceName = "PHOTO_SEQ"
		,initialValue = 1
		,allocationSize = 1)
public class Photo {
	
	@Id
	@GeneratedValue(
			strategy = GenerationType.SEQUENCE,
			generator = "PHOTO_SEQ_GENRATOR")
	private Long photoNo;
	
	@Column(nullable = false, length = 200)
	private String oriFileName;
	
	@Column(nullable = false, length = 200)
	private String saveFileName;
	
	@Column(nullable = false, length = 500)
	private String filePath;
	
	@Column(nullable = false)
	private Long fileSize;
	
	@ManyToOne
	@JoinColumn(name = "actionBoardNo")
	private ActionBoard actionBoard;
}

이번에 포스팅할 부분은 다음과 같다!

  • 경매 게시판 리스트 페이지
  • 경매 게시글 작성 페이지
  • 경매 게시글 작성 기능
  • 경매 게시글 상세보기 기능 및 페이지

이렇게 3가지 부분을 구현했고, 페이지 부분의 디자인은 조잡하나.. 일단 구현이 우선이라 생각했다.

디자인이야..뭐,, 언제든 바꿀수 있으니까!

 

먼저 페이지 구현 결과부터 보여주면

경매 게시글 리스트 페이지

 

경매 게시글 작성 페이지
게시글 상세보기 페이지

이렇게 3개의 페이지를 만들었고, 게시글 리스트를 보여주는 부분에서 페이징 처리는 아직하지 않았다.

페이징처리는 이전엔 페이징에 대한 로직이 있는 클래스를 만들어서 처리해으나, JPA에선 페이징을 지원해주는 api가 있기 때문에 그걸 공부하고 써볼예정임! 아무튼 이제 구현과정을 살펴보자 ㅎㅎ

(아래부터는 구현순서대로 글을 포스팅한다.)

 


먼저 들어가기전 게시글 작성에서 이미지 첨부 부분이 있는데 이를 구현하려면 몇가지 의존성 추가와 설정이 필요하다.

 

Gradle 의존성 추가

	implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'
	implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.3'

application.properties

spring.servlet.multipart.file-size-threshold=1MB //파일이 메모리에 기록되는 임계값.(속도정도라고 생각하면 될까..)
spring.servlet.multipart.max-file-size=50MB //파일 최대 사이즈
spring.servlet.multipart.max-request-size=50MB //요청 최대 사이즈

 

1.1 게시글 리스트 및 작성 페이지 컨트롤러

ActionBoardController.java

	//1. 헤더부분에 경매게시판 부분을 누르면 요청이 들어와지는 곳.
    @GetMapping("actionBoardListForm")
	public String actionBoardListForm() {
		return "actionboard/actionBoardList.tiles";
	}
	
    //2. 경매 리스트에서 글쓰기를 눌렀을때 요청이 들어와지는 곳.
	@Secured("ROLE_MEMBER")
	@GetMapping("actionBoardWriteForm")
	public String actionBoardWrite(Model model) {
		String boardName = "경매게시판";
		model.addAttribute("boardKind", boardName);
		return "actionboard/actionBoardWriteForm.tiles";
	}

먼저 첫번째로 경매 리스트를 처리하는 부분은 아직 페이징 처리를 하지 않았기때문에 단순히 구현돼 있는 jsp 파일로 이동하도록 했다.

두번째로 글쓰기는 회원만 클릭 가능하도록 @Secured 를 사용했고, 해당 게시판의 유형을 클라이언트 쪽으로 넘겨줬다.(글작성시 어디게시판에서 글쓰고 있는건지 보여주려고.)

 

 

1.2 경매 게시글 작성 jsp

actionBoardWriteForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<section id="kdg-action-write">
	<div class="container" data-aos="fade-up">

		<div class="section-title">
			<h2>Action</h2>
			<p>Writing Screen</p>
		</div>

		<form class="write-form">
			<div class="row">
				<div class="col-lg-8 mt-5 mt-lg-0">
					<div class="form-group mt-3">
						<p style="font-size: 5px;">${boardKind} 글쓰기</p>
						<input type="text" class="form-control" name="actionBoardTitle" id="actionBoardTitle" placeholder="Title" required>
					</div>
					<hr>
					<div class="row">
						<div class="col-md-6 form-group mt-3 mt-md-0">
							<input class="form-control" name="actionBoardET" id="actionBoardET" placeholder="마감시간 선택" readonly="readonly">
						</div>
						<div class="col-md-6 form-group mt-3 mt-md-0">
							<input type="number" min="0" class="form-control" name="immediatly" id="immediatly" placeholder="즉시구매가" required>
						</div>
					</div>
					<div class="row">
						<div class="col-md-6 form-group mt-3 mt-md-0">
							<input type="number" min="0" class="form-control" name="actionBoardSP" id="actionBoardSP" placeholder="시작가" required>
						</div>
						<div class="col-md-6 form-group mt-3 mt-md-0">
							<input type="number" min="0" class="form-control" name="actionBoardBid" id="actionBoardBid" placeholder="최소입찰단위" required>
						</div>
					</div>

					<div class="form-group mt-3">
						<textarea class="form-control" name="actionBoardcontent" id="actionBoardcontent" rows="10" placeholder="Message" required></textarea>
					</div>

				</div>
				<div class="col-lg-4 mt-5 mt-lg-0">
					<input type="file" id="uploadFile" name="uploadFile" accept="image/*" multiple>
					<img style="width:500px; height:480px;" id="imgPreview" src="#" alt="한개의 이미지를 미리 보여줍니다"/>
				</div>
			</div>
		</form>
		<div class="my-3"></div>
		<div class="text-center">
			<button id="writeActionButton" class="kdg-write2-button-css">Send Message</button>
		</div>

	</div>
</section>
<!-- End Contact Section -->

해당 jsp는 경매 게시글을 작성하는 폼이다. 이때 제목, 날짜, 경매시작가, 즉시구매가, 입찰단위, 내용, 이미지 첨부를 하게 된다. 이미를 첨부하기 위해 <input> 타입을 file로 하고 여러개를 올릴수 있도록 multiple을 지정해줬다. 그리고 이미지만 선택하도록 제한 했다. 또한, <form> 태그로 파일을 첨부할땐 enctype을  "multipart/form-data"로 해줘야 하는데, 나는 Ajax를 이용해 이미지를 첨부하기 때문에 js파일에 따로 정의해놨다.

 

 

1.3 경매 게시글 js

actionBoard.js

/*******************************
시간 선택하는 부분 설정
********************************/
$.datepicker.setDefaults({
	dateFormat: 'yy-mm-dd',
	prevText: '이전 달',
	nextText: '다음 달',
	monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
	monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
	dayNames: ['일', '월', '화', '수', '목', '금', '토'],
	dayNamesShort: ['일', '월', '화', '수', '목', '금', '토'],
	dayNamesMin: ['일', '월', '화', '수', '목', '금', '토'],
	showMonthAfterYear: true,
	yearSuffix: '년'
});

$(function() {
	$('#actionBoardET').datepicker();
});


/*******************************
경매게시판 글쓰기 폼에서 내용 입력후 작성버튼 눌렀을때
********************************/
let writeActionBoard = {
	init: function() {
		//function(){} 이 아닌 ()=>{} 를 사용하는 이유는 this를 바인딩하기 위함!
		$("#writeActionButton").on("click", () => {
			this.actionBoardSave();
		});
	},

	actionBoardSave: function() {
		//아래 내용이 일반 key, value로 전송되는 값이다.
		const formData = new FormData();
		formData.append("actionBoardTitle", $("#actionBoardTitle").val());
		formData.append("actionBoardET", $("#actionBoardET").val());
		formData.append("immediatly", $("#immediatly").val());
		formData.append("actionBoardSP", $("#actionBoardSP").val());
		formData.append("actionBoardBid", $("#actionBoardBid").val());
		formData.append("actionBoardcontent", $("#actionBoardcontent").val());
		formData.append("boardName", "경매게시판");

		//아래 부분은 input type file에서 파일 데이터를 가져와서 key, value로 전송하는 부분이다.
		//만약 uploadFile에 다른값을 넣으면, 당연하게도 uploadFile로 덮어씌워져 버린다.
		var files = $("#uploadFile")[0].files;
		console.log(files);
		if(files.length==0){
			alert("최소 한개이상의 이미지를 첨부 해주세요.");
			document.getElementById("uploadFile").focus();
			return;
		}else {
			for(var i=0; i<files.length; i++){
				formData.append("uploadFile", files[i]);
			}
		}
		//위의 for문은 이런뜻임.
		//formData.append("uploadFile", $("#uploadFile")[0].files[0]);
		//formData.append("uploadFile", $("#uploadFile")[0].files[1]);

		$.ajax({
			type: "POST",
			url: "saveActionBoard",
			processData: false,  // 데이터 객체를 문자열로 바꿀지에 대한 값이다. true면 일반문자...
			contentType: false,  // 해당 타입을 true로 하면 일반 text로 구분되어 진다.
			enctype: "multipart/form-data",
			data: formData,  //위에서 선언한 formdata
			success: function(result) {
				alert(result);
				location.href="/actionDetailPost?actionBoardNo="+result;
			},
			error: function(jqXHR, textStatus, errorThrown) {
				console.log(jqXHR);
				console.log(textStatus);
				console.log(errorThrown);
				if(jqXHR.status == 400){
					alert("내용을 모두 채워 주세요.")
				}
			
			}
		});
	}//writeActionBoard함수
}//writeActionBoard변수
writeActionBoard.init();//함수실행.


/*********************************
경매게시판 글쓸때 사진첨부 하면 미리 보여주기.
**********************************/
$(function() {
    $("#uploadFile").on('change', function(){
    readURL(this);
    });
});
function readURL(input) {
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
        	$("#imgPreview").attr("src", e.target.result);
        }
        reader.readAsDataURL(input.files[0]);
    }
}

js에서 첫번째 부분은 datePicker에 대한 설정(한글처리)이다. 이부분은 마감날짜를 담당하게 된다.

js에서 두번쨰 부분은 유저가 form에 입력한 data를 받고 ajax로 서버와 통신하는 부분이다. 이때 js에서 선언된 변수 formData는 file타입을 받을수 있는 객체이고, formData엔 모든 입력값과 file값을 저정해서 서버로 넘기게 된다.(한번에 데이터를 넘기는 이부분이 가장 어려웠음..ㅠ)

그리고 만약 formData에 file값이 없다면 이미 한개는 추가시키도록 만들었고, file값 외에 입력값이 다채워지지 않았다면 응답결과를 error로 나오도록 설정했다.

만약 서버와 정상적으로 통신이 성공했다면 방금 insert한 게시글의 게시글 번호를 받아와서 쿼리스트링으로 페이지를 이동시켰다.

js에서 세번째 부분은 유저가 게시글을 작성하면서 사진을 첨부했을때 첨부된 사진으 vaule를 추가해서 이미지를 미리보여주도록하는 부분이다. 

 

참고로 두번째 부분의

processData: false,  // 데이터 객체를 문자열로 바꿀지에 대한 값이다. true면 일반문자...
contentType: false,  // 해당 타입을 true로 하면 일반 text로 구분되어 진다.
enctype: "multipart/form-data"

이부분은 꼭 넣어줘야한다..!

 

 

1.4 경매 게시글 RestController

AcitonBoardApiController.java

@Secured("ROLE_MEMBER")
	@PostMapping("saveActionBoard")
	public ResponseEntity<String> savaActionPost(@RequestParam String actionBoardTitle,
			@RequestParam String actionBoardET,
			@RequestParam int immediatly,
			@RequestParam int actionBoardSP,
			@RequestParam int actionBoardBid,
			@RequestParam String actionBoardcontent,
			@RequestParam String boardName,
			@RequestParam("uploadFile") List<MultipartFile> multi,
			@AuthenticationPrincipal PrincipalDetail principal) {
		
		//유저번호로 유저객체 찾아서 갖고오기
		Member member = memberService.findMemberById(principal.getMemberNo());
		
		//경매게시판에 드가야할 정보들 셋업
		BoardKind boardKind = BoardKind.builder().boardKindName(boardName).build();
		ActionBoard actionBoard = ActionBoard.builder()
		.boardKind(boardKind)//현재 게시판 유형
		.member(member)//현재 로그인한 멤버객체
		.actionBoardTitle(actionBoardTitle)//제목(상품명)
		.actionBoardET(actionBoardET)//마감시간
		.immediatly(immediatly)//즉시구매가
		.actionBoardSP(actionBoardSP)//시작가격
		.actionBoardBid(actionBoardBid)//최소입찰단위
		.actionBoardcontent(actionBoardcontent)//내용
		.build();
		
		//서비스단에서 게시판내용과 이미지파일 한번에 DB에 삽입 후 지금 썼던 글번호 반환
		long no = actionBoardService.saveActionPost(actionBoard, multi);
		
		return new ResponseEntity<String>(String.valueOf(no), HttpStatus.OK);
	}

@RequestPram을 이용해 클라이언트쪽에서 넘겨받은 데이터들을 매개변수로 받아준다.

그리고 이미지의 경우 여러개일수도 있기때문에 리스트를 이용해 받도록했다. (참고로 이전에 파일첨부를 할땐 cos.jar를 이용해서 multipart객체를 만들었는데 스프링 부트에선 multipart를 자체적으로 지원해준다고 한다.)

그리고 입력받은 값들을 서비스쪽으로 넘기고 최종적으로 반환되는 값은 방금 insert한 게시물 번호가 되도록 했다.

 

 

1.5 경매 게시글 Service

ActionBoardService.java

package org.my.toyproject.service;

import ...

@Service
public class ActionBoardService {
	
	@Autowired
	private FileHandler fileHandler;
	
	@Autowired
	private ActionBoardRepository actionBoardRepository;
	
	@Autowired
	private PhotoRepository photoRepository;
	
	@Transactional
	public Long saveActionPost(ActionBoard actionBoard, List<MultipartFile> multi) {
		try {
			actionBoard = actionBoardRepository.save(actionBoard);//일단 파일제외하고 내용들 DB에 insert후 반환.
			List<Photo> photoList = fileHandler.transFile(multi, actionBoard);//파일들변환
			
			//모든 파일들 DB에 삽입
			for(Photo photo:photoList) 
				photoRepository.save(photo);
		} catch (Exception e) {
			System.out.println("ActionBoardService의 saveActionPost 메서드에서 에러가 났습니다.");
			System.out.println("에러명: "+e.getMessage());
		}
		
		return actionBoard.getActionBoardNo();
	}
	
	/..../
}

컨트롤러에서 넘겨받은 ActionBoard와 mulitpart 객체를 가지고 insert하게 된다. 그리고 먼저 이미지를 제외한 값들을 actionBoard테이블에 넣어준다(photo테이블은 게시글 테이블을 참조하고있기 때문). 글고 db에 저장되는 이미지들은 multi객체에 있는 값들로 저장되는게 아니라 Photo객체 있는 값들로 저장되기 때문에 multi -> Photo로 변환이 필요하다(PhotoRepository의 제네릭은 Photo테이블과 기본키타입 임을 인지해야함). 이를 수행해주도록 하기 위해 fileHandler 클래스를 만들어서 transFile이라는 메서드를 생성했다.

이후 파일들을 변환하고, 모든 이미지 파일을 Photo테이블로 저장시킨다.

 

 

1.5 multi객체 변환시키기.

FileHandler.java

package org.my.toyproject.handler;

import ...
//컨테이너에 Bean으로 등록.
@Component
public class FileHandler {
	public List<Photo> transFile(List<MultipartFile> multi, ActionBoard actionBoard) throws IllegalStateException, IOException{
		List<Photo> photoList = new ArrayList<>();
		
		//!multi.isEmpty()랑 같은 효과인데 CollectionUtils를 쓰는게 더 좋다고함.
		if(!CollectionUtils.isEmpty(multi)) {
			//이미지가 저장되는 경로
			String absolutePath = System.getProperty("user.dir")+
					"\\src\\main\\resources\\static\\bootstrap\\img\\boardImages";
			File file = new File(absolutePath);
			
			//경로에 폴더가 존재 안해?
			if(!file.exists()) {
				//그럼 그경로로 모든 폴더 만들어
				boolean makeDirStatus = file.mkdirs();
				if(!makeDirStatus) System.out.println("디렉토리 생성실패");
			}
			
			//멀티파트 객체 하나씩 변환시키자
			for(MultipartFile multiFile : multi) {
				UUID uuid = UUID.randomUUID();
				String saveName = uuid + "_" + multiFile.getOriginalFilename();//저장되는 이름(유일, 원본이름이랑 다르게)
				String savePath = absolutePath + File.separator + saveName;//저장된 이미지의 풀경로.
				
				//포토객체 셋업
				Photo photoVO = Photo.builder()
				.oriFileName(multiFile.getOriginalFilename())
				.filePath(savePath)
				.fileSize(multiFile.getSize())
				.saveFileName(saveName)
				.actionBoard(actionBoard)
				.build();
				
				photoList.add(photoVO);//리스트에 포토객체 추가
				multiFile.transferTo(new File(savePath));//위의 저장경로로 이미지 저장.(여기서 물리적으로 저장됨)
			}
		}
		
		return photoList;
	}
}

 

변환은 생각보다 간단하다.

첫번째로 저장되는 폴더의 경로를 구하고, 없다면 폴더를 만들어주도록 한다.

두번째로 저장될 이미지의 원본과 다른 유일한 이름을 만든다.(이후 저장되는 이미지가 혹시라도 이름이 겹치면 에러가 난다고함.)

세번째로 저장된 이미지의 풀경로를 구해준다.

네번째로 지금까지 구한 data로 포토객체를 생성시키고 새로운 저장공간인 리스트에 추가시켜준다.

다서번째로 업로드하려는 이미지를 실제 경로에 물리적으로 저장시켜준다.

이후 모든 포토객체가 들어간 리스트를 반환하게 되고 서비스 쪽에서 리스트안에 있는 모든 객체를 Photo테이블에 insert하게 된다!

 

 

2.1 경매게시글 상세보기

ActionBoardController.java

package org.my.toyproject.controller;

import ...

@Controller
public class ActionBoardController {
	
	@Autowired
	private ActionBoardService actionBoardService;
	
		/............./
	
	@GetMapping("actionDetailPost")
	public String actionDetailPost(@RequestParam String actionBoardNo, Model model) {
		ActionBoard actionBoard = actionBoardService.findPostByActionBoardNo(Long.parseLong(actionBoardNo));
	
		//게시글이 존재하지 않을때. 
		if(actionBoard==null) return "actionboard/actionDetailPostNotExist.tiles"; 
		model.addAttribute("actionPost",actionBoard);
		model.addAttribute("photoList", actionBoard.getPhotoList());
		System.out.println(actionBoard.getMember().getMemberNo());
		return "actionboard/actionDetailPostForm.tiles";
	}
}

Ajax통신이 정상적으로 이뤄졌을때 들어오게 되는 Controller다. 이때 ActionBoardApiController에선 응답데이터 값으로 경매 게시글 번호를 반환하도록해서 Ajax에선 해당 값을 쿼리스트링을 이용해 여기로 값을 넘겨줬다.

이후 해당 경매게시글에 대한 정보를 찾아서 jsp로 값들을 뿌려주게된다.

 

 

2.2 경매게시글 상세보기 뷰

actionDetailPostForm.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" %>

<!-- 로그인 하면 principal객체 생성 -->
<sec:authorize access="isAuthenticated()">
	<sec:authentication property="principal" var="principal"/>
</sec:authorize>

<c:set var="post" value="${requestScope.actionPost }"></c:set>

<section id="kdg-action-write">
	<div class="container" data-aos="fade-up">
		<div class="section-title">
			<h2>Action</h2>
			<p>Writing Screen</p>
		</div>
		<div class="table-responsive">
			<table class="table mytable table-bordered">
				<colgroup>
					<col width="13%" />
					<col width="17%" />
					<col width="13%" />
					<col width="17%" />
					<col width="13%" />
					<col width="17%" />
				</colgroup>
				<tr>
					<th class="table-active">글제목</th>
					<td>${post.actionBoardTitle}</td>
					<th class="table-active">조회수</th>
					<td>${post.actionBoardView}</td>
					<th class="table-active">작성자</th>
					<td>${post.member.nick }</td>
				</tr>
				<tr>
					<th class="table-active">분류</th>
					<td>${post.boardKind.boardKindName}</td>
					<th class="table-active">경매시작일</th>
					<td>${post.actionBoardST}</td>
					<th class="table-active">경매마감일</th>
					<td>${post.actionBoardET}</td>
				</tr>
				<tr>
					<td colspan="6" class="cotentWrap">
					<pre>${post.actionBoardcontent} ${post.actionBoardSP} ${post.immediatly} ${post.actionBoardBid} // ${post.member.memberNo } ${principal.memberNo }</pre>
					</td>
				</tr>
				<tr>
					<td colspan="6" class="contentWrap">
						<c:forEach var="photo" items="${requestScope.photoList}">
							${photo.photoNo} ${photo.oriFileName} ${photo.saveFileName} ${photo.filePath} ${photo.fileSize}
						</c:forEach>
					</td>
				</tr>
			</table>
			
			<!-- 좋아요, 목록, 수정, 삭제 버튼 표시 START-->
			<div class="row">
				<div class="col-9">
				<c:choose>
					<c:when test="${post!=null}">
						<input type="hidden" id="postNo" value="${37 }">
						<span id="likeView">
						<c:choose>
							<c:when test="${post.actionBoardNo==33}">
								<input type="image" id="no" src="bootstrap/img/kdg/no-heart.png" onclick="like(false)">
								<!-- <input type="button" value="좋아요!" onclick="return like()"> -->
							</c:when>
							<c:otherwise>
								<input type="image" id="yes" src="bootstrap/img/kdg/yes-heart.png" onclick="like(true)">
							</c:otherwise>
						</c:choose>
						</span>
						<span id="like_result">좋아요 총개수</span>
					</c:when>
					<c:otherwise>
						<img alt="좋아요" src="images/no-heart.png">
						<span>좋아요 총개수</span>
					</c:otherwise> 			
				</c:choose>
				</div>
				<div class="col-3" >
				<!-- 모든 사용자는 목록 btn을 볼 수 있다. -->
				<button id="joinButton" class="kdg-detailPost-btn">목록</button>
				
				<!-- 내가 쓴 글만 수정 삭제 가능 -->
				<c:if test="${not empty principal }">
					<c:if test="${post.member.memberNo==principal.memberNo}">
						<button id="joinButton" class="kdg-detailPost-btn">수정</button>
						<button id="joinButton" class="kdg-detailPost-btn">삭제</button>
					</c:if>
				</c:if>
				</div>
			</div>
			<!-- 좋아요, 목록, 수정, 삭제 버튼 표시 END-->
			
			<div class="my-3"></div>
			
			<!-- 시작가, 즉시구매가, 입잘하기 표시 START  -->
			<c:if test="${not empty principal }">
				<div class="row justify-content-around">
					<div class="col-2">
						<button class="kdg-detailPost-btn" disabled>시작가격: ${post.actionBoardSP}</button>
					</div>
	      			<div class="col-2">
						<button id="" class="kdg-detailPost-btn" >${post.immediatly}원으로 즉시구매 클릭</button>
					</div>
	      			<div class="col-2">
	      				<button id="" class="kdg-detailPost-btn" >입찰하기 클릭</button>
	      			</div>
				</div>
			</c:if>
			<!-- 시작가, 즉시구매가, 입잘하기 표시 END  -->
		</div>
	</div>
</section>
<!-- End Contact Section -->

 

아직 뷰가 완성된건 아니지만,,, 일단 어디부분에 어떤 기능을 넣을지 정도만 만들어놓은 상태이다.

참고로 나중에 댓글 기능도 넣을거라서 아마 Controller 부분과 jsp 부분에 코드 추가,수정이 이뤄질 예정이다.

 


★ 이슈사항

1. 문제

ajax에서 controller로 값을 넘기는 도중

" Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'multi' for method parameter type MultipartHttpServletRequest is not present] " 라는 에러가 발생했다. 해당 에러는 폼데이터에서 MultipartHttpServletRequest 로 파일데이터가 안넘어와서 발생한 에러라고 한다.

해결

@RequestParam("uploadFile") List<MultipartFile> multi
요청받는 파라미터의 이름을 명시해주고 파일이 여러개 넘어올수 있기 때문에 리스트로 받아준다.

 

2. 문제

사용자가 이미지를 올리지 않았을땐 multipart객체가 없기 때문에 컨트롤러 부분에서 @RequestParam으로 값을 받을수 없고, 에러가 발생하게 됐다.

해결
js부분에서 form데이터에 값을 추가할때 만약 file을 추가하지 않았다면 최소한 한개의 파일을 추가하도록 변경.

 

3. 문제

멀티파트 객체뿐만 아니라 @RequestParam으로 받는 매개변수들이 제대로 안넘어오면 서비스쪽에서 db에 insert할때 에러 발생(엔티티를 만들때 컬럼 설정을 nullable = false 로 해놨기때문).

해결

값이 제대로 입력되지 않아서 JPA를 이용한 insert가 정상적으로 이뤄지지 않을 경우

DataIntegrityViolationException 라는 예외가 발생하게 된다.

그래서 DataIntegrityViolationException예외가 발생했을때 이전에 만들었던 GlobalExceptionHandler에서 처리되도록 했다. 반환값은 new ResponseEntity<>(message, HttpStatus.BAD_REQUEST); 로 만들어서 만약 Ajax 쪽에서 (jqXHR.status == 400)로 받게 됐을때 모든값을 제대로 입력하라는 메세지를 띄우도록 했다.

 

GlobalExceptionHandler.java

@ExceptionHandler(DataIntegrityViolationException.class)
	public ResponseEntity<String> dataIntegrityViolationError(){
		String message = "값이 제대로 입력되지 않았습니다.(DataIntegrityViolationException)";
		return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST);
}

 

 

★ 다음에 할꺼!

경매 게시판 리스트 페이징.

경매 글 상세보기에서 좋아요 구현

결제 시스템 구현하기! ( 오래 걸릴 예정,,,,ㅠ)