본문 바로가기
개발/Spring

[SpringBoot] Summernote Editor(게시판 글 수정하기) 적용하기 - 3

by 코딩하는 흰둥이 2024. 7. 15.

https://greed-yb.tistory.com/266

 

[SpringBoot] Summernote Editor(게시판 글 상세보기) 적용하기 - 2

https://greed-yb.tistory.com/265 [SpringBoot] Summernote Editor(이미지,동영상 삽입) 적용하기Summernote Editor 다운로드 주소https://summernote.org/#google_vignette Summernote - Super Simple WYSIWYG editorSuper Simple WYSIWYG Editor on Bo

greed-yb.tistory.com

목록, 상세 보기는 이전글 참고하길 바람

 

 

수정페이지 - html
<style>
    .input-file-button{
        padding: 7px 25px;
        background-color: rgba(255, 174, 0, 0.35);
        border-radius: 4px;
        color: white;
        cursor: pointer;
        line-height: 1.5;
    }
</style>


<!-- Begin Page Content -->
<div class="container-fluid">

    <!-- Page Heading -->
    <div class="d-sm-flex align-items-center justify-content-between mb-4">
        <h1 class="h3 mb-0 text-gray-800">사내 게시판</h1>
    </div>
    <!-- Content Row -->
    <div>
        <!-- DataTales Example -->
        <div class="card shadow mb-4">
            <div class="card-header py-3">
                <h5 class="m-0 font-weight-bold text-dark">글 작성</h5>
            </div>
            <div class="card-body">
                <form class="user" id="form" name="form" autocomplete="off" enctype="multipart/form-data">
                    <div class="form-group">
                        <input type="text" class="form-control form-control-user" id="title" name="title" placeholder="제목">
                        <input type="hidden" id="no" th:value="${no}">
                    </div>
                    <div class="form-group">
                        <textarea class="form-control" id="summernote" name="content" type="text"></textarea>
                    </div>
                    <div class="fileDiv" >
                        <input type="text" name="fileName" readonly>
                        <label class="input-file-button" for="inputFile">업로드</label>
                        <input type="file" id="inputFile" style="display:none" onchange="selectFile(this);"/>
                        <button type="button" class="btn btn-danger" onclick="deleteFile()"> 파일 삭제</button>
                        <!--                            <button type="button" class="btn btn-primary"> 파일 추가</button>-->
                    </div>
                    <div class="form-group">
                        <div style="text-align: center">
                            <button type="button" class="btn btn-secondary" onclick="updateBtn()">수정</button>
                            <button type="button" class="btn btn-info" onclick="backBtn()">뒤로</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>



<script>
// 스크립트 경로는 thymeleaf layout 를 사용중이라 본인에 맞게 바꿔야 한다
    <script th:src="@{/bootstrap/vendor/datatables/jquery.dataTables.min.js}"></script>
    <script th:src="@{/bootstrap/vendor/datatables/dataTables.bootstrap4.min.js}"></script>
    <script th:src="@{/bootstrap/js/companyBoardUpdate.js}"></script>

    <!-- summernote -->
    <script th:src="@{/bootstrap/js/summernote/summernote-lite.js}"></script>
    <script th:src="@{/bootstrap/js/summernote/lang/summernote-ko-KR.js}"></script>
    <link rel="stylesheet" th:href="@{/bootstrap/js/summernote/summernote-lite.css}">
    
</script>

파일 추가 버튼으로 첨부파일을 계속 추가하도록 하려고 했는데

당장 필요가 없고 하다 보니 하기 싫어져서... 패스했다

 

 

수정 .js
// 유저 정보
let user_info = [];
// 파일첨부
let ajaxFilePath = "";

// 게시글 정보
let board_info = [];

function backBtn(){
    window.history.back();
}

function updateBtn(){
    if(!confirm("수정 하시겠습니까?")){
        return false;
    }
    let ob = new Object();
    ob.no = $("#no").val();
    ob.title = $("#title").val();
    ob.content = $("#summernote").val();
    ob.fileName = $("input[name=fileName]").val();
    ob.filePath = ajaxFilePath;

    $.ajax({
       url : "/companyBoard/writeUpdate",
       type : "POST",
        dataType : "text",
        contentType : "application/json; charset=utf-8",
        data : JSON.stringify(ob),
        success : function (data){
           if(data == 'success'){
               location.href="/companyBoardRead/"+ob.no;
           }else{
               alert("저장 오류 발생");
           }
        }
    });
}

// 이미지 업로드
function imageUpload(file){
    let formData = new FormData();
    formData.append("file", file);

    $.ajax({
       url : "/upload/imageUpload",
       type : "POST",
       data : formData,
        // contentType, processData 설정 안하면 TypeError: Illegal invocation 오류가 발생한다
        contentType: false,
        processData: false,
       encType : "multipart/form-data",
       success : function (data) {
           // 글에 이미지 넣을때 크기 설정
           // $("#summernote").summernote("insertImage", "/uploadPath/image/"+data, function (data){
           //     data.css("width" , "100%");
           // });

           // 글에 이미지 넣기
           $("#summernote").summernote("insertImage", "/uploadPath/image/"+data);
       },
        error(e){
           console.log("error : "+ e);
        }
    });
}

// 이미지 삭제
function deleteImageFile(fileName) {
    let formData = new FormData();
    formData.append("file", fileName);

    $.ajax({
        url : "/upload/imageDelete",
        type : "POST",
        data : formData,
        // contentType, processData 설정 안하면 TypeError: Illegal invocation 오류가 발생한다
        contentType: false,
        processData: false,
        encType : "multipart/form-data"
    });
}


// 유저 정보
function userInfo(){
    $.ajax({
        url : "/userInfo",
        type : "GET",
        success : function (data) {
            if(data != null){
                user_info = data;
            }
        },
        error(e){
            console.log("error : "+ e);
        }
    });
}

// 게시글 정보
function companyBoardInfo(no){
    $.ajax({
        url : "/companyBoard/updateInfo",
        type : "GET",
        data : {"no" : no},
        success : function (data) {
            if(data != null){
                board_info = data;
                $("#title").val(board_info.title);
                $("#writer").val(board_info.writer);
                ajaxFilePath= board_info.filePath;
                $("input[name=fileName]").val(board_info.fileName);

                // summernote 에 데이터 insert
                $("#summernote").summernote("code", board_info.content);
            }
        },
        error(e){
            console.log("error : "+ e);
        }
    });
}


// 파일 첨부
function selectFile(e){
    let file = e.files[0];
    let fileName = e.closest('.fileDiv').firstElementChild;

    fileName.value = file.name;

    let formData = new FormData();
    formData.append("file", file);

    $.ajax({
        url : "/upload/imageUpload",
        type : "POST",
        data : formData,
        // contentType, processData 설정 안하면 TypeError: Illegal invocation 오류가 발생한다
        contentType: false,
        processData: false,
        encType : "multipart/form-data",
        success : function (data) {
            // UUID로 변경한 파일 명을 가져온다
            ajaxFilePath = data;
        },
        error(e){
            console.log("error : "+ e);
        }
    });



}

function deleteFile(){
    $("input[name=fileName]").val("");
    // 이미지라고 적었지만 파일은 다 삭제된다
    // 첨부파일 기능은 구현하지 않으려다가 넣어서 이름이 맞지 않는다
    deleteImageFile(ajaxFilePath);
}

$(document).ready(function (){

    // 유저정보 가져오기
    userInfo();

    // textarea summernote 적용하기
    $("#summernote").summernote({
        codeviewFilter: false,                              // 코드 보기 필터 비활성화
        codeviewIframeFilter: false,                        // 코드 보기 iframe 필터 비활성화

        height: 400,                                        // 에디터 높이
        minHeight: null,                                    // 최소 높이
        maxHeight: null,                                    // 최대 높이
        lang: "ko-KR",                                      // 에디터 한글 설정
        focus : true,                                       // 에디터 포커스 설정
        toolbar: [
            ['fontname', ['fontname']],                     // 글꼴 설정
            ['fontsize', ['fontsize']],                     // 글자 크기
            ['style', ['bold', 'italic', 'underline','strikethrough', 'clear']],  // 글자 스타일 설정
            ['color', ['forecolor','color']],               // 글자색
            ['table', ['table']],                           // 표 생성
            ['insert', ['picture', 'link','video']],        // 이미지, 링크 , 동영상
            ['para', ['ul', 'ol', 'paragraph']],            // 문단 스타일 설정
            ['height', ['height']],                         // 줄간격
            ['view', ['codeview','fullscreen', 'help']]     // 코드보기, 전체화면, 도움말
        ],
        fontNames: ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New','맑은 고딕','궁서','굴림체','굴림','돋음체','바탕체'], // 추가한 글꼴
        fontSizes: ['8','9','10','11','12','14','16','18','20','22','24','28','30','36','50','72'], // 추가한 폰트사이즈
        callbacks : {
            // 파일 업로드
            onImageUpload : function (files) {
                for(let i=0; i < files.length; i++){
                    // 이미지가 여러개일 경우
                    imageUpload(files[i]);
                }
            },
            // 파일 삭제
            onMediaDelete: function ($target){
                if(confirm("이미지를 삭제하시겠습니까?")){
                    let fileName = $target.attr('src').split('/').pop();
                    deleteImageFile(fileName);
                }
            }
        }
    });

    // 수정 글 정보 가져오기
    let no = $("#no").val();
    companyBoardInfo(no);
});

 

수정 페이지에 데이터 바인딩

 

수정이 정상적으로 되고 있다

 

 

 

 

파일 업로드 및 첨부파일 다운로드
package com.example.practice.util.imageUpload;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

@RestController
@Tag(name="Swagger upload", description = "upload")
@RequestMapping("/upload/*")
public class ImageUpload {

    // 파일 업로드 경로
    final Path FILE_ROOT = Paths.get("./").toAbsolutePath().normalize();
    private String uploadPath = FILE_ROOT.toString() + "/upload/image/";

    @Operation(summary = "이미지 업로드 ", description = "이미지를 서버에 업로드한다.")
    @PostMapping("/imageUpload")
    public ResponseEntity<?> imageUpload(@RequestParam MultipartFile file) throws Exception{
        try {
            // 업로드 파일의 이름
            String originalFileName = file.getOriginalFilename();

            // 업로드 파일의 확장자
            String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));

            // 업로드 된 파일이 중복될 수 있어서 파일 이름 재설정
            String reFileName = UUID.randomUUID().toString() + fileExtension;

            // 업로드 경로에 파일명을 변경하여 저장
            file.transferTo(new File(uploadPath, reFileName));

            // 파일이름을 재전송
            return ResponseEntity.ok(reFileName);
        }catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.badRequest().body("업로드 에러");
        }
    }


    @Operation(summary = "파일 삭제 ", description = "서버에 저장된 파일을 삭제한다.")
    @PostMapping("/imageDelete")
    public void imageDelete(@RequestParam String file) throws Exception{
        try {
            Path path = Paths.get(uploadPath, file);
            Files.delete(path);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Operation(summary = "첨부파일 다운로드 ", description = "첨부된 파일을 다운로드한다.")
    @GetMapping("/downloadFile/{filePath}")
    public void readFileResource(@PathVariable String filePath, HttpServletResponse response) throws Exception{

        File downloadFile=new File(uploadPath + filePath);

        byte fileByte[]= FileUtils.readFileToByteArray(downloadFile);

        response.setContentType("application/octet-stream");
        response.setContentLength(fileByte.length);

        response.setHeader("Content-Disposition","attachment;fileName=\""+ URLEncoder.encode(filePath,"UTF-8")+"\";");
        response.setHeader("Content-Transfer-Encoding","binary");

        response.getOutputStream().write(fileByte);
        response.getOutputStream().flush();
        response.getOutputStream().close();

    }


}

 

Controller , service, mapper, vo 는 이전글에 모두 입력을 해버려서

현재 페이지에서는 생략했다

 

 

 

Summernote로 간단하게 구현 테스트만 진행하려다가

이것저것 추가하면서 방향을 잡았더니

제대로 된 계획 및 설계가 없이 코딩을 하게 되었다

쿼리 부분만 봐도 defalut로 주면 될 값들도 중간중간 추가하다 보니

쿼리에서 직접 데이터를 넣어주고 있는 상황까지 나왔으니...

그냥 이렇게 구현하면 된다 정도만 보면 될 것이다

 

데이터를 바인딩하는 부분에서 model도 사용하고 ajax로 여러 번 controller를 갔다 온 것은

그저 여러 방법을 사용하고 싶었을 뿐 큰 이유는 없다

댓글