본문 바로가기
개발/Spring

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

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

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 Bootstrap Summernote is a JavaScript library that helps you create WYSIWYG editors online.summernote.org 

greed-yb.tistory.com

Summernote를 이용한 게시판 구현 연장선이다

 

 

 


 

1. 게시판 목록

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

 

[SpringBoot] DataTable(JavaScript) - 적용하기

이전글 이어서https://greed-yb.tistory.com/251 [SpringBoot] Thymeleaf - layout 적용하기https://greed-yb.tistory.com/218#google_vignette [Spring] Bootstrap SB Admin 2 설치하기 https://greed-yb.tistory.com/209 [SpringBoot+IntelliJ+Oracle+Thymel

greed-yb.tistory.com

게시판 목록은 이전글을 참고하길 바란다

 

게시판 목록페이지 javascript.
function boardInit(data){
    $("#board_dataTable").DataTable({
        data: data,                                                     // 가져온 데이터
        "paging": true,                                                 // 페이징
        "lengthMenu": [[10,30, 50, 100, -1], [10,30, 50, 100, "All"] ], // 목록 개수
        "pageLength": 10,                                               // 보여지는 기본 개수
        "searching": true,                                              // 검색 기능
        "info": true,                                                   // 정보 표시
        // "scrollX": false,                                               // 가로 스크롤(true , false)
        // "scrollY": false,                                               // 새로 스크롤(false , px 단위 : 200 )
        "ordering": true,                                               // 정렬 기능
        "order": [0 , "DESC"],                                           // 정렬 기준
        "language": {                                                   // 언어 설정
            decimal:        "",
            emptyTable:     "검색된 데이터가 없습니다.",
            info:           "Showing _START_ to _END_ of _TOTAL_ entries",
            infoEmpty:      "Showing 0 to 0 of 0 entries",
            infoFiltered:   "(filtered from _MAX_ total entries)",
            infoPostFix:    "",
            thousands:      ",",
            lengthMenu:     " _MENU_ ",
            loadingRecords: "Loading...",
            processing:     "Processing...",
            search:         "Search",
            zeroRecords:    "항목이 존재하지 않습니다",
            paginate: {
                "first":      "처음",
                "last":       "마지막",
                "next":       "다음",
                "previous":   "이전"
            },
            aria: {
                sortAscending:  ": activate to sort column ascending",
                sortDescending: ": activate to sort column descending"
            }
        },
        "columnDefs": [
        ],
        "columns":[
            {
                data: "no",
                render: function (data, type, row){
                    return data;
                }
            },
            {
                data: "title",
                render: function (data, type, row){
                    return '<a href="/companyBoardRead/'+ row.no +'"' +' style="text-decoration-line: none; color: rgba(0,0,0,0.22)">' + data + '</a>';

                }
            },
            {
                data: "writer",
                render: function (data, type, row){
                    return data;
                }
            },
            {
                data: "created",
                render: function (data, type, row){
                    return data;
                }
            },
            {
                data: "views",
                render: function (data, type, row){
                    return data;
                }
            },
        ]
    })
}

 

dataTable 로 그려줄 data column 명을 재정의 했고

글 제목을 눌러서 상세보기 페이지로 넘어갈 거라 <a태그> 를 추가하였다

 

 

게시판 목록 html
    <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"  style="text-align: right">
                    <button class="btn btn-secondary" id="writeBtn" type="button" onclick="writePageBtn()">글쓰기</button>
                </div>
                <div class="card-body">
                    <div class="table-responsive">
                        <table class="table table-bordered" id="board_dataTable" style="width: 98%">
                            <thead>
                            <tr>
                                <th>No</th>
                                <th>제목</th>
                                <th>작성자</th>
                                <th>작성날짜</th>
                                <th>조회수</th>
                            </tr>
                            </thead>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

 

 


 

2. 게시판 글 상세 보기

상세 보기 페이지 - read.html
<!-- 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-body">
                <form class="user" id="form" name="form">
                    <div class="form-group row">
                        <div class="col-sm-10 mb-3 mb-sm-0">
                            <input type="text" class="form-control form-control-user" id="title" name="title" readonly th:value="${vo.title}">
                        </div>
                        <div class="col-sm-2">
                            <input type="text" class="form-control form-control-user" id="writer" name="writer" readonly th:value="${vo.writer}">
                            <input type="hidden" id="id" name="id" readonly th:value="${vo.id}">
                        </div>
                    </div>
                    <div class="card shadow mb-4" style="min-height:400px; height: 100%; min-width: 400px; width: 100%" th:utext="${vo.content}"></div>
                    <div class="card shadow mb-4">
                        <a th:href="@{/upload/downloadFile/{filePath}(filePath = ${vo.filePath})}"><span id="fileName" th:text="${vo.fileName}"></span></a>
                    </div>
                    <div class="form-group">
                        <div style="text-align: center">
                            <button type="button" class="btn btn-info" onclick="backBtn()">뒤로</button>
                            <button type="button" class="btn btn-primary" id="updateBtn"  th:value="${vo.no}" onclick="updatePage(this)">수정</button>
                            <button type="button" class="btn btn-danger" id="dBtn" onclick="deleteBtn(this)" th:value="${vo.no}">삭제</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/companyBoardRead.js}"></script>
</script>

 

 

상세보기 - read.js
// 유저 정보
let user_info = [];

// 뒤로가기
function backBtn(){
    window.history.back();
}

function updatePage(e) {
    if(!confirm("수정 하시겠습니까?")){
        return false;
    }
    location.href = "/companyBoardUpdate/"+e.value;
}

// 글 삭제
function deleteBtn(e){
    if(!confirm("게시글을 삭제하시겠습니까?")){
        return false;
    }
    let boardNo = e.value;

    $.ajax({
        url : "/companyBoard/delete",
        type : "DELETE",
        dataType : "text",
        data : {"no" : boardNo},
        success : function (data) {
            if(data == 'success'){
                location.href = "/companyBoard"
            }else{
                alert("장애가 발생하였습니다.");
            }
        },
        error(e){
            console.log("error : "+ e);
        }
    });
}

// 유저 정보 가져오기
function userInfo(){
    $.ajax({
        url : "/userInfo",
        type : "GET",
        success : function (data) {
            if(data != null){
                user_info = data;

// 권한작업을 아직 하지 않아서 작성자ID와 로그인ID가 맞지 않으면 수정 삭제 버튼을 없애준다
                if($("#id").val() != user_info.id){
                    $("#updateBtn").remove();
                    $("#deleteBtn").remove();
                }
            }
        },
        error(e){
            console.log("error : "+ e);
        }
    });
}

$(document).ready(function (){
    // 유저 정보 가져오기
    userInfo();
});

 

 

UserController - 유저 정보
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/userInfo")
    public UserVo userInfo(HttpServletRequest request , HttpServletResponse response) throws Exception{
        String test = String.valueOf(request.getUserPrincipal().getName());
        UserVo vo = userService.getUserById(test);
        return vo;
    } 
 }

페이지 시작 시 유저 정보를 가져온다

 

 

WebController - 웹이동
@Controller
public class WebController {

    @Autowired
    private CompanyBoardService companyBoardService;
    
    // 사내게시판
    @GetMapping("/companyBoard")
    public String companyBoardPage() throws Exception{
        return "companyBoard";
    }
    // 사내 게시판 글 읽기
    @GetMapping("/companyBoardRead/{no}")
    public String companyBoardRead(@PathVariable String no , Model model) throws Exception{
        CompanyBoardVo vo = companyBoardService.read(no);
        model.addAttribute("vo" , vo);
        return "companyBoardRead";
    }
    
    // 사내 게시판 글 수정
    @GetMapping("/companyBoardUpdate/{no}")
    public String companyBoardUpdate(@PathVariable String no , Model model) throws Exception{
        model.addAttribute("no" , no);
        return "companyBoardUpdate";
    }
   
}

페이지가 열리면서 데이터를 넣어주려고 model 을 이용하였다

수정페이지 부분에서는 ajax를 이용하여 데이터를 바인딩했는데 

본인이 편한 대로 하면 된다

 

 

 

 CompanyBoardController - 게시판 
@RestController
@Tag(name="Swagger Company_Boards", description = "Company_Boards")
public class CompanyBoardController {

    @Autowired
    private CompanyBoardService companyBoardService;
    
    
    @Operation(summary = "사내게시판 글 삭제", description = "사내게시판 작성된 글을 삭제한다.")
    @DeleteMapping("/companyBoard/delete")
    public String companyBoardWriteDelete(@RequestParam String no) throws Exception{
        CompanyBoardVo vo = companyBoardService.read(no);

        try {
            companyBoardService.delete(no);
            // 파일 업로드 경로
            Path FILE_ROOT = Paths.get("./").toAbsolutePath().normalize();
            String uploadPath = FILE_ROOT.toString() + "/upload/image/";
            Path path = Paths.get(uploadPath, vo.getFilePath());
            Files.delete(path);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "fail";
        }
    }
    
	@Operation(summary = "사내게시판 수정 글 정보", description = "사내게시판 수정 글 정보를 가져온다.")
    @GetMapping("/companyBoard/updateInfo")
    public CompanyBoardVo companyBoardUpdateInfo(@RequestParam String no) throws Exception{
        CompanyBoardVo vo = companyBoardService.read(no);
        return vo;
    }


    @Operation(summary = "사내게시판 글 수정", description = "사내게시판 수정 글을 수정한다.")
    @PostMapping("/companyBoard/writeUpdate")
    public String companyBoardWriteUpdate(@RequestBody CompanyBoardVo vo) throws Exception{
        try {
            companyBoardService.writeUpdate(vo);
            return "success";
        }catch (Exception e){
            e.printStackTrace();
            return "fail";
        }
    }

}

 

 

 

 

vo
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Schema(description = "사내게시판 정보")
public class CompanyBoardVo {

    @Schema(description = "글 번호" , nullable = false)
    private Integer no;
    @Schema(description = "제목" , nullable = false)
    private String title;

    @Schema(description = "작성자" , nullable = false)
    private String writer;

    @Schema(description = "아이디" , nullable = false)
    private String id;

    @Schema(description = "내용" , nullable = false)
    private String content;
    
    @Schema(description = "작성시간" , nullable = false)
    private String created;

    @Schema(description = "수정시간" , nullable = false)
    private String updated;

    @Schema(description = "조회수" , nullable = false)
    private Integer views;

    @Schema(description = "파일명" , nullable = false)
    private String fileName;
    @Schema(description = "파일경로" , nullable = false)
    private String filePath;
}

lombok 을 사용 안 한다면 getter , setter와 toString을 따로 생성해 주면 된다

service
public interface CompanyBoardService {
    /**
     * DataTable 사내게시판 리스트
     * @return
     * @throws Exception
     */
    List<CompanyBoardVo> list() throws Exception;


    /**
     * 사내게시판 게시글 저장
     * @param vo
     * @throws Exception
     */
    void writeSave(CompanyBoardVo vo) throws Exception;

    /**
     * 사내게시판 게시글 읽기
     * @param no
     * @return
     * @throws Exception
     */
    CompanyBoardVo read(String no) throws Exception;

    /**
     * 사내게시판 게시글 삭제
     * @param no
     * @throws Exception
     */
    void delete(String no) throws Exception;

    /**
     * 사내게시판 게시글 수정
     * @param vo
     * @throws Exception
     */
    void writeUpdate(CompanyBoardVo vo) throws Exception;

}

 

 

serviceImpl
@Service
public class CompanyBoardServiceImpl implements CompanyBoardService {

    @Autowired
    private CompanyBoardMapper companyBoardMapper;

    @Override
    public List<CompanyBoardVo> list() throws Exception {
        return companyBoardMapper.list();
    }

    @Override
    public void writeSave(CompanyBoardVo vo) throws Exception {
        companyBoardMapper.writeSave(vo);
    }

    @Override
    public CompanyBoardVo read(String no) throws Exception {
        return companyBoardMapper.read(no);
    }

    @Override
    @Transactional
    public void delete(String no) throws Exception {
        companyBoardMapper.delete(no);
    }

    @Override
    public void writeUpdate(CompanyBoardVo vo) throws Exception {
        companyBoardMapper.writeUpdate(vo);
    }
}

 

 

mapper
@Mapper
public interface CompanyBoardMapper {

    /**
     * DataTable 사내게시판 리스트
     * @return
     * @throws Exception
     */
    List<CompanyBoardVo> list() throws Exception;

    /**
     * 사내게시판 게시글 저장
     * @param vo
     * @throws Exception
     */
    void writeSave(CompanyBoardVo vo) throws Exception;

    /**
     * 사내게시판 게시글 읽기
     * @param no
     * @return
     * @throws Exception
     */
    CompanyBoardVo read(String no) throws Exception;

    /**
     * 사내게시판 게시글 삭제
     * @param no
     * @throws Exception
     */
    void delete(String no) throws Exception;

    /**
     * 사내게시판 게시글 수정
     * @param vo
     * @throws Exception
     */
    void writeUpdate(CompanyBoardVo vo) throws Exception;
}

 

 

mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.practice.mapper.board.CompanyBoardMapper">

    <select id="list" resultType="com.example.practice.vo.board.CompanyBoardVo">
        SELECT
            NO,
            TITLE,
            WRITER,
            ID,
            CONTENT,
            CREATED,
            UPDATED,
            VIEWS
        FROM COMPANY_BOARD
        ORDER BY NO DESC
    </select>

    <insert id="writeSave" parameterType="com.example.practice.vo.board.CompanyBoardVo">
        INSERT INTO COMPANY_BOARD(
                                  NO,
                                  TITLE,
                                  WRITER,
                                  ID,
                                  CONTENT,
                                  VIEWS,
                                  FILE_NAME,
                                  FILE_PATH
        )VALUES(
                (SELECT NVL(Max(NO), 0) + 1 AS NO FROM COMPANY_BOARD),
                #{title},
                #{writer},
                #{id},
                #{content},
                0,
             #{fileName},
             #{filePath}
             )
    </insert>


    <select id="read" resultType="com.example.practice.vo.board.CompanyBoardVo">
        SELECT
            NO,
            TITLE,
            WRITER,
            CONTENT,
            ID,
            CREATED,
            UPDATED,
            VIEWS,
            FILE_NAME,
            FILE_PATH
        FROM COMPANY_BOARD
        WHERE NO = #{no}
    </select>


    <delete id="delete">
        DELETE FROM COMPANY_BOARD WHERE NO = #{no}
    </delete>


    <update id="writeUpdate" parameterType="com.example.practice.vo.board.CompanyBoardVo">
        UPDATE COMPANY_BOARD SET
                                 TITLE = #{title} , CONTENT = #{content} , UPDATED = SYSDATE , FILE_NAME = #{fileName} , FILE_PATH = #{filePath}
        WHERE NO = #{no}
    </update>
</mapper>

 

 

하단에 파일 첨부도 만들었는데 이전글에 추가가 되지 않았다

수정페이지에 있는 코드를 참고하길 바란다

 

 

 

 

 

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

 

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

https://greed-yb.tistory.com/266 [SpringBoot] Summernote Editor(게시판 글 상세보기) 적용하기 - 2https://greed-yb.tistory.com/265 [SpringBoot] Summernote Editor(이미지,동영상 삽입) 적용하기Summernote Editor 다운로드 주소https

greed-yb.tistory.com

다음글 - 게시판 글 수정하기

댓글