728x90

서버에서 Json 형태로 프론트로 넘어오면 날짜 형식이 다음과 같이 변경돼서 날아온다.

"createDate""Jan 5, 2022, 4:25:48 PM"

원하는 날짜 형식으로 바꿔 보여주고 싶을 때 사용할 라이브러리

https://momentjs.com/

 

Moment.js | Home

Format Dates moment().format('MMMM Do YYYY, h:mm:ss a'); moment().format('dddd'); moment().format("MMM Do YY"); moment().format('YYYY [escaped] YYYY'); moment().format(); Relative Time moment("20111031", "YYYYMMDD").fromNow(); moment("20120620", "YYYYMMDD"

momentjs.com

사용법

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
  • 스크립트를 추가하고 사용하면된다.
moment(변수).format("YYYY-MM-DD HH:mm:ss")
  • 사용 문법

* 적용 프로젝트

https://black-mint.tistory.com/50

 

[Spring Boot] 댓글 기능 - REST API 규칙 적용

저번에 작성한 댓글 기능 구현 코드를 REST API 규칙에 맞게 수정하도록한다. * 이전 댓글 구현 글 https://black-mint.tistory.com/35 [Spring Boot] Ajax(비동기) 통신으로 댓글 구현 (+ Jquery 사용법) 게시판..

black-mint.tistory.com

 

728x90

저번에 작성한 댓글 기능 구현 코드를 REST API 규칙에 맞게 수정하도록한다.

 

* 이전 댓글 구현 글

https://black-mint.tistory.com/35

 

[Spring Boot] Ajax(비동기) 통신으로 댓글 구현 (+ Jquery 사용법)

게시판 댓글을 구현하는 도중 비동기 호출에 대해 알게 됐고, 이를 이용하려면 Ajax를 활용해야 한다는 정보를 얻었다. 비동기란? 비동기의 반대인 동기적 통신의 경우 절차적으로 일을 차례로

black-mint.tistory.com


1. REST(Representational State Transfer) API 란?

  • 네트워크 아키텍처 원리의 모음
  • 네트워크 아키텍처 : 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 의미
  • 예를 들어 학생

2. REST API 설계

  • URI는 정보의 자원을 표현
  • 자원에 대한 행위로 HTTP Method(GET, POST, PUT, PATCH, DELETE)로 표현
GET 데이터를 조회 (게시글 조회) GET /boards/1
게시글 중 1번 게시글을 조회
POST 데이터를 등록 (게시글 작성) POST /boards
게시글 등록
PATCH 데이터를 일부 수정 (게시글 수정) PATCH / boards/1
게시글 중 1번 게시글을 수정
PUT 데이터를 전체적으로 수정 PUT /boards/1
게시글 중 1번 게시글을 수정
DELETE 데이터를 삭제 DELETE /boards/1
게시글 중 1번 게시글을 삭제

3. REST API 규칙

  • 소문자로 작성하고 밑줄(_) 대신 하이픈(-)을 사용
  • 동사가 아닌 명사로 작성
  • 슬래시 구분자(/)는 계층 관계를 나타낼 때 사용하고 URI 마지막에 쓰지 않는다.
  • URI에 파일 확장자는 작성하지 않는다.

이 외 규칙이 많지만 이러한 2~3번 규칙을 잘 지켜서 만든 API를 RESTful API라고 한다.


4. 컨트롤러

@RequestMapping("/comments")
@RestController
public class CommentController {

    @Autowired
    private CommentService commentService;

    // 댓글 조회
    @GetMapping("/{boardId}")
    public List<Comment> getCommentList(@PathVariable(value = "boardId") Long boardId) throws Exception {
        List<Comment> comments = commentService.getCommentList(boardId);
        return comments;
    }

    // 댓글 작성
    @PostMapping("/{boardId}")
    public void commentWrite(@PathVariable(value = "boardId") Long boardId,
                             @RequestBody Comment comment,
                               Principal principal) throws Exception {
        String username = principal.getName();
        commentService.write(boardId, comment.getContent(), username);
    }

    // 댓글 수정
    @PatchMapping("/{commentId}")
    public void updateComment(@PathVariable(value = "commentId") Long commentId,
                              @RequestBody Comment comment) throws Exception {
        commentService.update(commentId, comment.getContent());
    }

    // 댓글 삭제
    @DeleteMapping("/{commentId}")
    public void deleteComment(@PathVariable(value = "commentId") Long commentId) throws Exception {
        commentService.delete(commentId);
    }
  • @RestController : 클래스 레벨에 선언하면 이전에 각 매핑마다 @ResponseBody 선언 했던 것을 하지 않아도 된다.
  • @ResponseBody : 해당 어노테이션이 선언된 매핑은 HTTP의 BODY에 자바 객체를 직접 반환한다.
  • @RequestBody : View에서 받아오는 JSON데이터를 파싱해준다. (변환할 데이터를 매핑 가능하도록 필드명 맞춰줘야함) 단, Content-Type이 multipart/form-data로 전달받을 때는 오류를 일으킨다. (주로 파일, 이미지를 전달받을 때)
  • @PathVariable : REST방식에서 주로 사용하며, @RequestParam과 유사. URI에 전달받을 변수를 지정

5. Ajax 통신 스크립트 코드

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
    <script th:inline="javascript">
        $(document).ready(function () {
            getComments()
        })

        function getComments() {
            var loginUsername = [[${ loginUser }]]
            var boardId = $('input[name=boardId]').val()
            var url = '/comments/' + boardId

            $.ajax({
                type: 'GET',
                url: url,
                success: function (response) {
                    var a = ''
                    var size = 0
                    $.each(response, function (key, value) {
                        size = size + 1;
                        a += '<hr /><div>'
                        a += '<input type="hidden" id="commentId" name="commentId" value="' + value.id + '">'
                        a += '<span id="writer" style="font-weight: bold;">' + value.writer + '</span>'
                        if (value.writer == loginUsername) {
                            a += '<ul name="commentChange" class="justify-content-end" style="display: inline;">'
                            a += '<li name="commentUpdate" type="button" style="display: inline; opacity: 0.7; font-size: small; margin-right: 5px" onclick="updateCommentForm(' + value.id + ')">수정</li>'
                            a += '<li name="commentDelete" type="button" style="display: inline; opacity: 0.7; font-size: small;" onclick="deleteComment(' + value.id + ')">삭제</li></ul>'
                        }
                        a += '<pre id="' + value.id + '" name="comment' + value.id + '" style="margin-bottom: 5px; font-size: large;">' + value.content + '</pre>'
                        a += '<p name="createDate' + value.id + '" style="margin-bottom: 5px; opacity: 0.5; font-size: small;">' + moment(value.createDate).format("YYYY-MM-DD HH:mm") + '</p></div>'
                    });
                    $("#count").html(size)
                    $("#comment").html(a)
                },
                error: function (response) {
                    console.log("getComments error : " + response)
                },
                complete: function () { }
            })
        }

        function insertComment() {
            var boardId = $('input[name=boardId]').val()
            var content = document.getElementById("content").value
            var param = {"content": content}
            var url = '/comments/' + boardId

            if (isEmpty(content) == true) {
                alert('댓글을 입력해주세요.')
                return false;
            } else {
                $.ajax({
                    contentType: 'application/json',
                    type: 'POST',
                    url: url,
                    data: JSON.stringify(param),
                    success: function (response) {
                        getComments()
                    },
                    error: function (response) {
                        console.log("insertComment error : " + response)
                    },
                })
            }
        }

        function updateCommentForm(id) {
            var commentId = id
            var content = document.getElementById(id).innerText

            $('ul[name=commentChange]').hide()
            $('pre[name=comment' + commentId + ']').contents().unwrap().wrap('<textarea id="newComment" class="form-control mt-2" name="updateContent" rows="4"></textarea>');
            $('p[name=createDate' + commentId + ']').contents().unwrap().wrap('<input name="update" type="button" class="me-2 mt-2 btn btn-primary" value="수정하기" onclick="updateComment(' + commentId + ')">');
            $('input[name=update]').after("<button class=\"me-2 mt-2 btn btn-primary\" onclick=\"getComments()\">취소</button>")
        }

        function updateComment(id) {
            var commentId = id
            var content = document.getElementById("newComment").value
            var param = {"content": content}
            var url = '/comments/' + commentId

            if (isEmpty(content) == true) {
                alert('댓글을 입력해주세요.')
                return false;
            } else {
                $.ajax({
                    contentType: 'application/json',
                    type: 'PATCH',
                    url: url,
                    data: JSON.stringify(param),
                    success: function (response) {
                        getComments()
                    },
                    error: function (response) {
                        console.log("updateComment error : " + response)
                    },
                    complete: function () { }
                })
            }
        }

        function deleteComment(id) {
            var commentId = id
            var url = '/comments/' + commentId

            if (confirm("정말 삭제하시겠습니까?")) {
                $.ajax({
                    type: 'DELETE',
                    url: url,
                    success: function (response) {
                        getComments()
                    },
                    error: function (response) {
                        console.log("deleteComment error : " + response)
                    },
                    complete: function () { }
                })
            } else {
                return;
            }
        }
        function isEmpty(strIn) {
            if (strIn === undefined) {
                return true;
            }
            else if (strIn == null) {
                return true;
            }
            else if (strIn == "") {
                return true;
            }
            else {
                return false;
            }
        }
    </script>
  • 이전 글과 바뀐 부분
    • Json으로 서버에 데이터를 보내주기 위해 contentType을 json으로 설정
    • url을 REST API 규칙에 맞춰 작성
    • data: json.stringify( ) : ajax 통신의 데이터 전달부분을 미리 만들어둔 json형식의 자바스크립트 변수를 서버로 전달
    • type을 REST API 규칙에 맞춰 작성
    • 날짜 포맷을 변경하기 위해 moment라이브러리 사용
    • https://black-mint.tistory.com/51
 

[JavaScript] 날짜 포맷 변경 라이브러리

서버에서 Json 형태로 프론트로 넘어오면 날짜 형식이 다음과 같이 변경돼서 날아온다. "createDate": "Jan 5, 2022, 4:25:48 PM" 원하는 날짜 형식으로 바꿔 보여주고 싶을 때 사용할 라이브러리 https://m

black-mint.tistory.com

 

* REST 관련 참고 

https://congsong.tistory.com/30?category=749196 

728x90

1. 자바스크립트 빈 문자열 검사 함수

function isEmpty(strIn)
{
    if (strIn === undefined)
    {
        return true;
    }
    else if(strIn == null)
    {
        return true;
    }
    else if(strIn == "")
    {
        return true;
    }
    else
    {
        return false;
    }
}

* 출처 : https://stackoverflow.com/questions/19592721/isempty-is-not-defined-error

 

isEmpty is not defined-error

I was getting an error as isEmpty is not defined .what i have to do .while alerting too it is not showing any alert. <!DOCTYPE html> <html> <head> ...

stackoverflow.com

2. 활용

        function insertComment() {
            var boardId = $('input[name=boardId]').val()
            var content = document.getElementById("content").value;

            if (isEmpty(content) == true) {
                alert('댓글을 입력해주세요.')
                return false;
            } else {
                $.ajax({
                    type: 'POST',
                    url: '/board/comment/write',
                    data: {
                        boardId: boardId,
                        content: content
                    },
                    success: function (response) {
                        getComments()
                        console.log("insert success")
                    },
                    error: function (response) {
                        console.log("insertComment error : " + response)
                    },
                })
            }
        }
  • 댓글 추가 ajax 코드
  • 댓글 내용 없이 작성버튼을 클릭하면 경고문을 띄우고 리턴.
728x90
$('input[name=a]').after("<button onclick=\"getComments()\">취소</button>")
  • $('선택 태그').after("")
728x90

1. 이름으로 태그 불러오기

$('ul[name=commentChange]').hide()
  • <ul> 태그의 name이 commentChange인 태그를 숨긴다.

2. 태그 숨기기

$('pre[name=comment' + commentId + ']').contents().unwrap().wrap('<p></p>')
  • <pre>태그의 name이 comment + commentId(댓글 번호, 특정 댓글 태그만 변경하기 위한 값) 인 태그를 <p>태그로 바꿈
728x90

조회수 구현 게시글을 클릭해서 포스트를 조회할 때 조회수를 올려주기만 하면 된다.

그러나 이렇게만 한다면 댓글 작성할 때나 같은 아이디로 다시 들어갈 때나 조회수가 계속 올라간다.

이를 방지하기 위해 쿠키를 이용해서 조회수를 구현하는 방법을 기록한다.

쿠키란?

  • 서버가 클라이언트에게 보내는 데이터 중 하나로써, 클라이언트는 받은 쿠키 데이터를 로컬영역에 저장한다.
  • 다음에 서버를 방문한다면 쿠키를 요청 헤더에 포함해서 서버에게 전달한다.

1. 게시판 DB

CREATE TABLE `tb_board` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `writer_id` int NOT NULL,
  `writer` varchar(20) NOT NULL,
  `delete_yn` varchar(1) DEFAULT 'N',
  `create_date` datetime DEFAULT NULL,
  `views` int DEFAULT '0',
  `image` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `writer_id` (`writer_id`),
  CONSTRAINT `tb_board_ibfk_1` FOREIGN KEY (`writer_id`) REFERENCES `tb_userinfo` (`id`) ON DELETE CASCADE
)
  • views : 조회수

2. View에 조회수 추가 (게시판 목록 html)

            <!-- contents -->
            <table class="table caption-top table-bordered table-hover">
                <caption>List</caption>
                <thead>
                    <tr>
                        <th class="text-center" width="50" scope="col">No</th>
                        <th class="text-center" width="950" scope="col">제목</th>
                        <th class="text-center" width="180" scope="col">작성자</th>
                        <th class="text-center" width="200" scope="col">작성일</th>
                        <th class="text-center" width="150" scope="col">조회수</th>
                    </tr>
                </thead>

                <tbody>
                    <tr th:each="board : ${boardList}">
                        <td class="mt-5 text-center" scope="row" th:text="${board.id}">1</td>
                        <td><a th:text="${board.title}" th:href="@{/board/post(id=${board.id})}" style="text-decoration:none; color:black;">제목</a></td>
                        <td class="text-center" th:text="${board.writer}">작성자</td>
                        <td class="text-center" th:text="${#dates.format(board.createDate, 'yyyy/MM/dd HH:mm')}">작성일</td>
                        <td class="text-center" th:text="${board.views}">조회수</td>
                    </tr>
                </tbody>
            </table>
  • 마지막 줄 th:text="${board.views}" : 조회수

3. Service

// 조회수 증가
    public void updateViews(Board board, String username, HttpServletRequest request,
                            HttpServletResponse response) throws Exception {
        Cookie[] cookies = request.getCookies();
        Map<String, String> mapCookie = new HashMap<>();

        if (request.getCookies() != null) {
            for (int i = 0; i < cookies.length; i++) {
                mapCookie.put(cookies[i].getName(), cookies[i].getValue());
            }

            String viewsCookie = mapCookie.get("views");
            String newCookie = "|" + board.getId();

            // 쿠키가 없을 경우 쿠키 생성 후 조회수 증가
            if (viewsCookie == null || !viewsCookie.contains(newCookie)) {
                Cookie cookie = new Cookie("views", viewsCookie + newCookie);
                response.addCookie(cookie);

                boardRepository.updateViews(board);
            }
        }
    }
  • request : 쿠키 정보를 얻을 HttpServletRequest 객체
  • response : 쿠키 정보를 추가할 HttpServletResponse 객체 
  • mapCookie.put : HashMap을 이용해 key value로 쿠키 값을 저장
  • viewsCookie : hashmap에 views라는 key값으로 저장된 value객체를 저장
  • newCookie : 새로운 쿠키를 추가할 때 사용 (id : 게시판 번호)
  • 쿠키 객체를 생성하고 추가할 쿠키 값을 초기화
    • 2번 게시글 조회 -> (views, null|2)
    • 3번 게시글 조회 -> (views, null|2|3) ...
  • response.addCookie를 통해 쿠키를 추가하고 조회수 1 증가
  • contains -> https://black-mint.tistory.com/39
 

[Java] String contains() 문자열 포함 여부

문자열 포함 여부 확인 방법을 기록한다. 1. 코드 String s = "hello World"; System.out.println("e : " + s.contains("e")); System.out.println("hello : " + s.contains("hello")); System.out.println("oW :..

black-mint.tistory.com

4. Controller

// 포스트 조회
@GetMapping("/post")
public String readPost(Model model, @RequestParam(required = false) Long id,
                       Principal principal, HttpServletRequest request,
                       HttpServletResponse response) throws Exception {
    String loginUser = principal.getName();
    Board board = boardService.contentLoad(id);

    boardService.updateViews(board, loginUser, request, response);

    model.addAttribute("board", board);
    model.addAttribute("loginUser", loginUser);

    return "board/post";
}
  • 쿠키를 사용하기 위해 HttpServletRequest request, HttpServletResponse response 를 파라미터로 받는다.
728x90

비밀번호 찾기를 시도한 사용자의 비밀번호를 초기화 시키고 초기화된 비밀번호를 사용자 이메일로 보낸다.

1. 인증 절차

  • 구글 계정 관리 -> 보안 -> 앱 비밀번호 -> [메일], [windows 컴퓨터] 2개 선택 후 생성
  • 여기서 나오는 비밀번호를 application.properties에 추가할 것이다.

2. 설정

  • gmail -> 톱니바퀴 클릭 후 모든 설정보기

톱니바퀴

  • 전달 및 모든 POP/IMAP 진입

  • POP 다운로드 -> 모든 메일에 POP 사용하기체크
  • IMAP 액세스 -> IMAP사용 체크
  • 변경사항 저장

3. 의존성 추가

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail'

 

4. SMTP 설정

# GoogleMail
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=사용자 구글 아이디
spring.mail.password=인증 비밀번호
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.smtp.auth=true
  • username=test2@gmail.com
  • password=아까 인증에서 비밀번호

5. MailService

@Autowired
private JavaMailSender javaMailSender;

// 메일을 통해 임시 비밀번호 전송
public void sendMail(String username, String userEmail, String temporaryPassword) {
    List<String> toUserList = new ArrayList<>();
    toUserList.add(userEmail);
    int userSize = toUserList.size();

    SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
    simpleMailMessage.setTo((String[]) toUserList.toArray(new String[userSize]));
    simpleMailMessage.setSubject("임시 비밀번호를 보내드립니다.");
    simpleMailMessage.setText(username + "님의 임시 비밀번호 : " + temporaryPassword);

    javaMailSender.send(simpleMailMessage);
}

 

* 참고 사이트

https://kitty-geno.tistory.com/43

 

Spring Boot | 메일 발송하기 (Google SMTP)

▶ 스프링 부트 메일 발송하기 (Google SMTP) 1. Google 홈페이지 > Google 계정 관리(우측상단) 2. 보안 > 앱 비밀번호 앱 비밀번호는 위에 2단계 인증을 해야 생성됩니다. 3. 메일, Windows 컴퓨터 4. 앱 비..

kitty-geno.tistory.com

 

728x90

1. 확인창

if (confirm("정말 삭제하시겠습니까?")) {
	// 확인 클릭시
} else { // 취소 클릭 시
	return;
}

2. 팝업창 띄우기

alert('hello')

 

728x90

name의 값을 가진 pre 태그를 p태그로 대체

$('pre[name=comment' + commentId + ']').contents().unwrap().wrap('<p></p>')

버튼에 onclick="함수" 걸어주고 함수 안에 작성해서 사용하면된다.

 

관련 프로젝트

https://black-mint.tistory.com/35

 

[Spring Boot] Ajax(비동기) 통신으로 댓글 구현 (+ Jquery 사용법)

게시판 댓글을 구현하는 도중 비동기 호출에 대해 알게 됐고, 이를 이용하려면 Ajax를 활용해야 한다는 정보를 얻었다. 비동기란? 비동기의 반대인 동기적 통신의 경우 절차적으로 일을 차례로

black-mint.tistory.com

 

728x90

1, 다른 태그 값 가져오기

var content = document.getElementById(id).innerText;

2. 사용자가 input 또는 textarea에 작성한 값 가져오기 (버튼 클릭시 실행)

var content = document.getElementById("newComment").value;

 

관련 프로젝트

https://black-mint.tistory.com/35

 

[Spring Boot] Ajax(비동기) 통신으로 댓글 구현 (+ Jquery 사용법)

게시판 댓글을 구현하는 도중 비동기 호출에 대해 알게 됐고, 이를 이용하려면 Ajax를 활용해야 한다는 정보를 얻었다. 비동기란? 비동기의 반대인 동기적 통신의 경우 절차적으로 일을 차례로

black-mint.tistory.com

 

+ Recent posts