📌 게시글 작성

🚩 테이블

먼저 게시글 테이블은 아래와 같이 구성되어있다.
Writer, CategoryId는 각각 Member와 Category테이블에 Join되어있고, Rating과 Status는 각각 물건의 상태와 게시글의 상태(판매여부 등)를 나타내며 Enum으로 구성되어있다.

🚩 Category

게시글 작성 Form에 접근하게 되면 Url에 해당 카테고리의 ID값이 같이 전송된다. 해당 Param값으로 Category를 셀렉트 시켜 사용자가 편리하게 이용할 수 있도록 구현하였다.

<div class="dropdown" id="divCategory">
            <label class="form-label" for="categoryName">종류</label>
            <select class="form-control" id="categoryName" name="categoryName">
                <option th:each="category : ${categoryList}"
                        th:value="${category.name}"
                        th:text="${category.name}"
                        th:selected="${category.id}==${#request.getParameter('category')}">
                </option>
            </select>
</div>



🚩 Price

이후 제목과 내용, 가격, 거래장소 까지 입력할 수 있게 구현하였다. 가격 부분은 숫자만 입력되게 해야한다. 따라서 사용자가 숫자이외에 다른 문자를 입력할 경우 공백으로 치환되게 하였다. 그리고 가독성을 높이기 위해 3자리 마다 콤마표시를 만들었다. 해당 로직은 JS로 구성하였다.

/*가격 콤마생성*/
$("#price").on("focusout", function()	{
    $(this).val( $(this).val().replace(",","") );
    $(this).val( $(this).val().replace(/[^-\.0-9]/gi,"") );
    $(this).val( $(this).val().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") );
});

Db에는 Price가 number로 되어있다. 따라서 해당 form을 Db로 Post할 때 가격에 콤마를 제거하고 보내야한다.

/*db로 보낼 때 콤마 제거*/
function eraseComma(){
    const temp = $("#price").val().replace(/,/gi,"");
    $("input[name=price]").val(temp);
}

🚩 Content

Summernote

게시글 Content는 위지윅 에디터를 적용시켜 구현하였다. 많은 에디터들 중에 Summernote를 사용하였다. 처음에는 가장 대중적인 Ck Editor를 사용하였는데, 이미지를 저장하는 부분에서 권한 문제에 부딪혀 변경하게 되었다.

$(document).ready(function () {

    $('#content').summernote({
        height: 500,                 // 에디터 높이
        minHeight: null,             // 최소 높이
        maxHeight: null,             // 최대 높이
        focus: true,                  // 에디터 로딩후 포커스를 맞출지 여부
        lang: "ko-KR",					// 한글 설정
        placeholder: '내용을 입력해 주세요',	//placeholder 설정
        callbacks: {	//여기 부분이 이미지를 첨부하는 부분
            onImageUpload: function (files) {
                uploadSummernoteImageFile(files[0], this);
            },
            onPaste: function (e) {
                let clipboardData = e.originalEvent.clipboardData;
                if (clipboardData && clipboardData.items && clipboardData.items.length) {
                    let item = clipboardData.items[0];
                    if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
                        e.preventDefault();
                    }
                }
            }
        }
    });
});

Summernote 이미지 업로드

이미지 업로드 방식은 다음과 같다. 먼저 Js로 Ajax요청을 보낸다

/*이미지 파일 업로드*/
function uploadSummernoteImageFile(file) {
    const data = new FormData();
    data.append("file", file);
    $.ajax({
        data: data,
        type: "POST",
        url: "/api/uploadSummernoteImageFile",
        contentType: false,
        processData: false,
        success: function (data) {
            //항상 업로드된 파일의 url이 있어야 한다.
            $("#content").summernote('insertImage', data.url);
        }
    });
}

이후 해당컨트롤러에서 서비스로 요청을 보내 로직 수행을 한다.
Controller

@ResponseBody
    @PostMapping("/api/uploadSummernoteImageFile")
    public HashMap<String, Object> uploadSummernoteImageFile(@RequestParam("file") MultipartFile multipartFile) {
        return boardService.boardImageUpload(multipartFile);
    }

Service
fileRoot는 이미지의 저장경로이다. 먼저 해당 파일명을 randomUUid를 이용하여 파일명을 랜덤으로 변경시키다. 이후 fileRoot에 해당 이미지를 저장시키고 성공하였으면 response코드로 success를 리턴한다.

/*summernote 이미지 첨부*/
    public HashMap<String, Object> boardImageUpload(MultipartFile multipartFile) {
        HashMap<String, Object> map = new HashMap<>();

        String fileRoot = "D:\\summernote_image\\";

        String originalFileName = multipartFile.getOriginalFilename(); // 오리지널 파일명
        String extension = originalFileName.substring(originalFileName.lastIndexOf(".")); //파일확장자
        String savedFileName = UUID.randomUUID() + extension; //저장될 파일 명

        File targetFile = new File(fileRoot + savedFileName);
        try {
            InputStream fileStream = multipartFile.getInputStream();
            FileUtils.copyInputStreamToFile(fileStream, targetFile);
            map.put("url", "/summernoteImage/" + savedFileName);
            map.put("responseCode", "success");
        } catch (IOException e) {
            FileUtils.deleteQuietly(targetFile); //저장된 파일 삭제
            map.put("responseCode", "error");
            e.printStackTrace();
        }
        return map;
    }

이후 정상적으로 업로드가 완료되었으면 ajax에서 아래와 같이 해당 링크를 연결하여 이미지를 첨부한다.

success: function (data) {
            //항상 업로드된 파일의 url이 있어야 한다.
            $("#content").summernote('insertImage', data.url);
        }

🚩 MyLat, MyLng

해당 게시물을 작성할 때에는 거래장소도 미리 정하도록 설정하였다. 거래장소는 카카오 지도 Api를 이용하였으며 카카오지도 Api에 대한 내용은 다른 포스트에서 자세히 다루도록 하겠다

거래 장소 정하기버튼을 누르면 Popup창을 띄우는 JS코드를 작성하였다.

$("#mapButton").on('click',function (){
    window.open("/board/map","map","width=900,height=550,left=300,top=100");
});

아래와 같은 창이 열린다

지도를 저장하는 로직은 아래와 같다

지도를 클릭 or 검색 or 현재위치 클릭 시 마커가 생성 -> 마커 클릭시 confirm창 생성 -> 확인 시 해당 좌표가 mylat, mylng에 저장되고 부모 Html로 전송 -> form을 post하면 해당 좌표가 Dto에 전달

마커 생성

  1. 지도 직접 클릭 시
/*지도 직접 클릭*/
kakao.maps.event.addListener(map, 'click', function (mouseEvent) {

    showInfoWindow(mouseEvent.latLng);

    // 마커를 클릭한 위치에 표시합니다
    marker.setPosition(mouseEvent.latLng);
    marker.setMap(map);

    myLat = mouseEvent.latLng.getLat();
    myLng = mouseEvent.latLng.getLng();
});
  1. 검색버튼을 통한 마커 생성
/*검색버튼 클릭*/
$("#searchBtn").on('click', function () {
    const keyword = $("#address").val()

    // 주소로 좌표를 검색합니다
    geocoder.addressSearch(keyword, function (result, status) {
        // 정상적으로 검색이 완료됐으면
        if (status === kakao.maps.services.Status.OK) {
            var coords = new kakao.maps.LatLng(result[0].y, result[0].x);

            showInfoWindow(coords);

            marker.setPosition(coords);
            marker.setMap(null);
            marker.setMap(map);

            // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
            map.setCenter(coords);

            myLat = coords.getLat();
            myLng = coords.getLng();
        }
    });
});
  1. 현재위치를 통한 마커 생성
/*현재위치 버튼 클릭*/
$("#location").on('click', function () {
    /*현재위치 받아오기*/

    function locationLoadSuccess(pos) {
        // 현재 위치 받아오기
        const currentPos = new kakao.maps.LatLng(pos.coords.latitude, pos.coords.longitude);

        showInfoWindow(currentPos);
        // 지도 이동(기존 위치와 가깝다면 부드럽게 이동)
        map.panTo(currentPos);

        // 마커 생성
        marker.setPosition(currentPos);

        // 기존에 마커가 있다면 제거
        marker.setMap(null);
        marker.setMap(map);

        myLat = currentPos.getLat();
        myLng = currentPos.getLng();
    };

    function locationLoadError(pos) {
        alert('위치 정보를 가져오는데 실패했습니다.');
    };

    navigator.geolocation.getCurrentPosition(locationLoadSuccess, locationLoadError);
});![](https://velog.velcdn.com/images%2Falstn_dev%2Fpost%2F2c10cdd6-282d-4fe7-8b69-5f014ac4e938%2Fimage.png)![](https://velog.velcdn.com/images%2Falstn_dev%2Fpost%2Fc4cb22b8-a162-4485-8b8a-3b6063cfb7ff%2Fimage.png)![](https://velog.velcdn.com/images%2Falstn_dev%2Fpost%2F3907c4ca-6fcc-40b6-b027-2293d45ac1d0%2Fimage.png)![](https://velog.velcdn.com/images%2Falstn_dev%2Fpost%2Fe9d9f56f-50f8-4641-a37a-b9ec4b23a2c1%2Fimage.png)

마커 클릭

//마커 클릭 이벤트
kakao.maps.event.addListener(marker, 'click', function (mouseEvent) {
    if (confirm("이 위치로 하시겠습니까?")) {
        alert("위치가 지정되었습니다");
        opener.document.getElementById("myLat").value = myLat;
        opener.document.getElementById("myLng").value = myLng;
        close();
    }
});

confirm 클릭

해당 값이 부모 클래스로 전달된다.

profile
논리적으로 사고하고 해결하는 것을 좋아하는 개발자입니다.

0개의 댓글