반응형
SMALL

게시글 수정하기

게시글 수정 버튼을 누르면 네이버 스마트 에디터를 연결해놓은 페이지로 이동한다. 그리고 이전에 작성했던 내용이 나오고, 그 내용을 수정하면 다시 update 될 수 있게 하자.

사진은 수정 할수도, 안할 수도 있기 때문에 따로 분기처리를 해줘야할 것 같다.

[ 만들 기능 ]
1. 게시글 수정 버튼 누르면 수정페이지로 이동
2. 게시글 수정 페이지에 기존에 작성한 내용 띄우기
3. 게시글 수정 내용을 저장하면 DB에 업데이트(단, 사진은 분기처리)

1. 게시글 수정 버튼 누르면 수정페이지로 이동

 1) jsp

blogPost.jsp는 게시글 상세보기 페이지이다. 세션에 접속된 유저가 보고 있는 게시글의 작성자라면, 수정/삭제버튼이 보이게 해놓았다. 수정버튼을 누르면 postUpdateAction 페이지로 이동한다.

내 게시글이면 수정/삭제 버튼이 보인다.

<a class="badge bg-secondary text-decoration-none link-light" href="postUpdateAction.me?writer=${postObj.MEM_ID}&postno=${postObj.POST_NO}" id="post_update">수정</a>
<a class="badge bg-secondary text-decoration-none link-light" href="postDeleteAction.me?writer=${postObj.MEM_ID}&postno=${postObj.POST_NO}" id="post_delete">삭제</a>

 

 2) controller

두가지 링크가 필요하다.

첫번째, 게시글 수정 페이지에 기존에 저장한 내용을 띄워주는 페이지

두번째, 기존 내용을 수정하여 저장버튼을 누르면 수정한 내용을 update 해주는 페이지

둘 다 링크를 다르게 진행해준다. 수정은 게시글 insert 때와 동일하게 ajax로 할 것이므로 ajax 이름을 붙여주었다. 

//-------------------- 게시글 안에 수정 누르면 게시글 수정 페이지로 이동 ---------------------------
else if(command.equals("/member/postUpdateAction.me")) {
    //게시글 수정 페이지로 이동함
    forward = new ActionForward();
    //객체 생성
    forward.setRedirect(false);
    //이동 허락 안함
    forward.setPath( "./updateEdit.jsp"))
    //거기 주소는 updateEdit.jsp으로 해라.(이동할 주소 저장)
    action = new PostEditAction(); //edit 페이지에 기존 정보 출력
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "기존 내용 띄우기 실패"))
    }
}

//-------------------- 수정한 게시글을 저장하는 PostUpdateAction 페이지 생성 ---------------------------
else if(command.equals("/member/postUpdateAjax")) {
    action = new PostUpdateAction();
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "포스팅 수정 실패"))
    }
}

 3) Action1 - DB에 저장된 기존 내용을 띄우는 PostEditAction

또 다시 연결할 필요없이, 기존에 [게시글 상세페이지] 내용과 동일하다. post_info 정보를 전부 select 해온 게시글 상세페이지 DB 연결을 그대로 사용한다. get 방식으로 URL에 담아온 파라미터인 postno 를 DAO에 던져 게시글의 정보를 전부 불러온다.

[삭제] 버튼과 마찬가지로 작성자가 아닌 유저가 버튼을 활성화해서 눌렀을 경우를 대비하여 sessionId와 작성자를 비교한다.

public class PostEditAction implements Action {
	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		ActionForward forward = null; //어디로 갈지?

			forward = new ActionForward();
			
			//session을 써서 서버 생성함.
    		HttpSession session = req.getSession();
    		// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.

			//URL 주소 뒤에 붙은 쿼리스트링은 getParameter로 가져온다.
    		String sessionId = (String) session.getAttribute("id");
            System.out.println("수정 sessionId "+sessionId);
    		String post_writer = req.getParameter("writer");
    		int postNo = Integer.parseInt(req.getParameter("postno"));
            System.out.println("수정 postNo "+postNo);
    		

    		// 로그인 상태인지 확인 -> 아니면 로그인화면 고고
    		if (sessionId == null) {
    			forward = new ActionForward();
    			forward.setRedirect(true);
    			forward.setPath("./memberLogin.me");
    			
    		// 사용자가 작성자가 맞는지 확인 -> 아니면 alert를 띄우고 해당 포스트로 다시 이동
    		} else if (!sessionId.equals(post_writer)) {
    			resp.setContentType("text/html;charset=UTF-8");
    			PrintWriter out = resp.getWriter();
    			out.println("<script>");
    			out.println("alert('작성자가 아닌 사람은 게시글을 수정할 수 없습니다.')");
    			out.println("location.href='./postViewAction.me?postno="+postNo+"'");
    			out.println("</script>");
    			
    		}else {
					
			PostViewService postViewService = new PostViewService();
			//게시글 정보 띄워주는건 그냥 원래 게시글 상세페이지 보여줬던 정보 똑같이 가져오면 됨.
			PostBean postinfo = postViewService.getPostinfo(postNo);

			req.setAttribute("postinfo", postinfo);
			//수정페이지로 게시글 정보를 던져준다.
			forward.setPath("./updateEdit.jsp");
		
    		}
		return forward;
	}
}

3-1) updateEdit.jsp

제목 부분, 내용부분에 value 값으로 DB에서 가져온 기본 내용값을 넣어준다. textarea는 value 가 아닌 태그 안에 값을 넣어주면 된다.

주의할 점은 제이쿼리로 값을 보여지게 할 때 따음표로 묶어주는 것! 안묶어줬더니 제목이 잘려서 나오더라.(아래 결과창은 제목부분을 안묶어서 잘려나옴)

...  value='${postinfo.POST_TITLE}' >

file의 경우, 보안상 기본 값을 넣기 어려우므로 미리보기 이미지에만 띄워준다.

<div class="row">
<!-- 포스트 추가하는 곳-->
<!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
<!-- 파일 전송을 위해 encType = "multipart/form-data" -->
<form action="insertStudentInfoForm" id="form"  enctype="multipart/form-data" method="post">
	<!-- 제목 -->
  <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px'value='${postinfo.POST_TITLE}' >
  
  <div id="smarteditor">
  <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠.-->
	<textarea name="editorTxt" id="editorTxt" 
			  rows="20" cols="10" 
			  placeholder="내용을 입력하세요"
			  style='width: 600px'>${postinfo.POST_CONTENT}</textarea>
			  
  </div>
  <img id="preview" src="../resources/img/thumbnail/${postinfo.POST_THUMBNAIL}" alt="image" style="width:100px" />
  <input type="file" name="fileName" id='fileName' onchange="readURL(this);">
  <!-- 파일쪽엔 기본경로 불가(https://okky.kr/articles/260830) -->
  
  <input type='hidden' id='postno' value='${postinfo.POST_NO}'>
  <input type="button" value ="저장" onclick="submitPost()"/>
</form>
<!-- 포스트 추가하는 곳-->
</div>

수정 버튼을 누르면 DB에서 저장된 내용을 가져온다.

 4) Action2 - 수정한 내용을 DB에 update 하는 PostUpdateAction

PostUpdateAction는 Insert 하는 부분과 동일하다. 단, 사진 파일을 선택하지 않았을 경우(=null) 파일 이름은 업데이트 하지 않는다.

//파일 첨부를 한 경우에만 post 객체에 넣어준다. 없으면 sysout만 찍음.
if(fileName != null) {
	post.setPOST_THUMBNAIL(fileName);
}else {
	System.out.println("파일 첨부 안함");
}

 

PostUpdateAction.java 전체

더보기
package action;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import svc.PostUpdateService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 작성한 포스트 저장 처리를 위한 클래스이다.*/
public class PostUpdateAction implements Action { // Action을 implements 해줌

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		PostBean post = new PostBean(); //vo에 선언한 변수들 import한거.
		//작성한 게시글 내용을 저장하고 DB로 전달.

		ActionForward forward = null;

  
		//------- 파일 업로드 구현 --------------------------------------
	    //업로드 파일 사이즈
        int fileSize = 5*1024*1024;

        String uploadPath = req.getServletContext().getRealPath("/resources/img/thumbnail");
        		
        //git 링크는 이렇지만 실제 소스 경로는 위와 같다.
        //"C:\\Users\\User\\git\\BlogPrj\\Test1\\WebContent\\resources\\img\\thumbnail";

        System.out.println("uploadpath는?  "+uploadPath);
        try {
            //파일업로드
            MultipartRequest multi = new MultipartRequest(req, uploadPath, fileSize, "UTF-8", new DefaultFileRenamePolicy());
        
            //파일 이름 초기화
            String fileName  = "";
            
            //파일 이름 가져오기
            Enumeration<String> names = multi.getFileNames();
            if(names.hasMoreElements()) {
                String name = names.nextElement();
                fileName = multi.getFilesystemName(name);
            }

    		//---------------------------------------------	
            

    			int postNo = Integer.parseInt(multi.getParameter("postno"));
	    		post.setPOST_NO(postNo);
	    		//입력 목록 적어주기(vo에서 받아옴.)
	    		post.setPOST_TITLE(multi.getParameter("title"));
	    		
	    		//파일 첨부를 한 경우에만 post 객체에 넣어준다. 없으면 sysout만 찍음.
	    		if(fileName != null) {
		    		post.setPOST_THUMBNAIL(fileName);
	    		}else {
	    			System.out.println("파일 첨부 안함");
	    		}
	    		
	    		post.setPOST_CONTENT(multi.getParameter("content"));
	    		
	    		PostUpdateService postUpdateService = new PostUpdateService();
	    		boolean result = postUpdateService.updatePost(post); //vo에서 받은 변수 보내줌.
	    		//게시글 저장이 잘 되었는지 여부
	    		if(result) {
	                forward.setRedirect(true);
	                forward.setPath("myBlogAction.me");
	            }
    		
	     } catch (Exception e) {
	        e.printStackTrace();
	     }
	        return forward;
    }
}

 

4-1) updateEdit.jsp

컨트롤러에 보내줘야 하는 데이터는 수정한 제목, 내용, 썸네일 파일 이외에도 작성자id, 게시글 번호 도 있다.

<input type='hidden' id='postno' value='${postinfo.POST_NO}'>

이런식으로 hidden 타입으로 데이터를 출력해 함께 ajax로 보내주자.

ajax 통신 부분에서도 사진 파일을 선택하지 않았을 경우(=null) 파일 이름은 보내주지 않는다.

// 등록할 파일 리스트를 formData로 데이터 입력
var form = $('#form');
var formData = new FormData(form[0]);

//파일을 새로 첨부한 경우에만 파일명을 컨트롤러에 보내준다.
var fileCheck = $("#fileName").val();
if(fileCheck != ''){ //파일 첨부를 했을경우에만
	formData.append('files',fileObject[0]);
}else{
	console.log("사진파일 변경이 없으므로 기존 썸네일을 유지합니다.")
}

formData.append('title', $("#title")[0].value);
formData.append('content', content);
formData.append('postno', $("#postno")[0].value);

분기처리해서 사진파일이 없으면 안보내준다. 게시글 번호도 폼데이터에 넣어서 보내준다.

▼ updateEdit.jsp 전체

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 참고 : https://beforb.tistory.com/53 -->
    <!-- 참고 : https://www.wrapuppro.com/programing/view/ZPLdxEBiyJG38IG -->
    <!-- 에디터 : Naver SmartEditor 2.0 -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>게시글 작성</title>
        <!-- 제이쿼리 -->
		<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js"></script>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
      <script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog - 게시글 수정</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                    <!-- 포스트 추가하는 곳-->
                    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
                    <!-- 파일 전송을 위해 encType = "multipart/form-data" -->
				    <form action="insertStudentInfoForm" id="form"  enctype="multipart/form-data" method="post">
				    	<!-- 제목 -->
				    	
				      <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px' value='${postinfo.POST_TITLE}' >
				      
				      <div id="smarteditor">
				      <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠.-->
				        <textarea name="editorTxt" id="editorTxt" 
				                  rows="20" cols="10" 
				                  placeholder="내용을 입력하세요"
				                  style='width: 600px'>${postinfo.POST_CONTENT}</textarea>
				                  
				      </div>
				      <img id="preview" src="../resources/img/thumbnail/${postinfo.POST_THUMBNAIL}" alt="image" style="width:100px" />
				      <input type="file" name="fileName" id='fileName' onchange="readURL(this);">
				      <!-- 파일쪽엔 기본경로 불가(https://okky.kr/articles/260830) -->
				      
				     <input type='hidden' id='postno' value='${postinfo.POST_NO}' >
				      <input type="button" value ="저장" onclick="submitPost()"/>
				    </form>
              <!-- 포스트 추가하는 곳-->
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
	     <script>
	     
	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })
	    
	    
	    
	    //첨부한 파일 내용 전역변수로 가지고 있기
	    let fileObject = null;
	    //파일 선택 시 파일 내용 변수에 넣기
 	    $(document).ready(function() {
			$("#fileName").bind('change', function() {
				fileObject = this.files;
				//this.files[0].size gets the size of your file.
				//alert(this.files[0].size);
			});
		}); 
	    
 	   
	    /* 버튼 클릭 이벤트 */
	    function submitPost(){
	    	console.log(fileCheck);
		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
		
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
			 
				// 등록할 파일 리스트를 formData로 데이터 입력
				var form = $('#form');
				var formData = new FormData(form[0]);
				
				//파일을 새로 첨부한 경우에만 파일명을 컨트롤러에 보내준다.
			 	   var fileCheck = $("#fileName").val();
					if(fileCheck != ''){ //파일 첨부를 했을경우에만
					formData.append('files',fileObject[0]);
					}else{
						console.log("사진파일 변경이 없으므로 기존 썸네일을 유지합니다.")
					}
					
					formData.append('title', $("#title")[0].value);

					console.log($("#title")[0].value)
					formData.append('content', content);
					formData.append('postno', $("#postno")[0].value);
			
			
			 
			 
			 if(confirm('게시글을 수정하시겠습니까?')){
			 
			    //ajax 통신으로 서버로 보내 데이터 저장함
			    $.ajax({
		          url: "postUpdateAjax"
		          , data: formData
		          , type: 'POST'
		          , enctype : 'multipart/form-data'
		          , contentType : false //false 로 선언 시 content-type 헤더가 multipart/form-data로 전송되게 함
		          , processData : false //false로 선언 시 formData를 string으로 변환하지 않음
		          , success: function(data) {
		            console.log('success')
		            alert('저장되었습니다.')
		            location.href='./myBlogAction.me'
		          }
		          , error: function(jqXHR, textStatus, errorThrown) {
		            console.log(jqXHR)
		            alert('오류가 발생하였습니다.')
		          }
		        })
			}else{
				alert('취소되었습니다.');
			}
		  }
		}
	    
	    
	    /* 이미지 미리보기 */
	    function readURL(input) {
		  if (input.files && input.files[0]) {
		    var reader = new FileReader();
		    reader.onload = function (e) {
		      $('#preview')
		      .attr('src', e.target.result);
		    };
		    reader.readAsDataURL(input.files[0]);
		  }
		}
	    
	    
	  </script>
     
    </body>
</html>

5) Service

DB 연결한다.

package svc;

import static db.JdbcUtil.close;
import static db.JdbcUtil.commit;
import static db.JdbcUtil.getConnection;
import static db.JdbcUtil.rollback;

import java.sql.Connection;

import DAO.PostDAO;
import vo.PostBean;

/* 게시글을 DB에 수정후 저장하는 역할을 담당하는 클래스(Service) */
public class PostUpdateService {
	
	public boolean updatePost(PostBean post) {
		
		boolean updateSuccess = false; //수정완료 기본값 false
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		int updateCount = postDAO.updatePost(post);
		// DB추가 쿼리를 메소드로 구현.

		if (updateCount > 0) {
			updateSuccess = true;
			// 정상 처리 되었음을 호출했던 곳으로 리턴.
			commit(con); //DB 명령 완료 확인
		} else {
			rollback(con);
			//만약 제대로 동작하지 않았다면
			//쿼리문에 대해서 취소(rollback)
		}
		close(con);
		//연결되지 않았으면 연결 해제(DB와 Connection의 연결 해제)

		return updateSuccess;
	}
}

6) DAO

update를 한다.썸네일 파일이 없을 경우 sql문에 들어가는 값의 갯수도 달라진다. 조건문으로 분기처리 해주었다...(더 좋은 방법이 있을까?)

// =========================== 게시글 수정하는 SQL로직 ===============================
// postUpdateService에서  게시글 수정할 때 DB와 JSP를 연결할 때 인자로 쓰임.
public int updatePost(PostBean post) {

    int updateCount = 0;
    System.out.println("파일첨부" + post.getPOST_THUMBNAIL());

    //파일 이름이 있는 경우 --------------------------------------
    if (post.getPOST_THUMBNAIL() != null) {
        String sql = 
		"UPDATE post_info SET "
		+ "post_title = ?,"
		+ "post_thumbnail = ?,"
		+ "post_content = ?"
		+ "WHERE POST_NO = ?";

        try {
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, post.getPOST_TITLE());
            pstmt.setString(2, post.getPOST_THUMBNAIL());
            //pstmt.setInt(4, post.getPOST_VIDIO());
            pstmt.setString(3, post.getPOST_CONTENT());
            pstmt.setInt(4, post.getPOST_NO()); //파라미터로 가져온 POST_NO 찾기

            updateCount = pstmt.executeUpdate();
            System.out.println("게시글 수정");
        } catch (Exception ex) {
            System.out.println("게시글 수정 안됨" + ex);
        } finally {
            close(pstmt); // import static db.JdbcUtil.*;
        }

    } else {
        //파일 이름이 없는 경우 --------------------------------------
        String sql2 = 
			"UPDATE post_info SET "
			+ "post_title = ?,"
		//	+ "post_thumbnail = ?,"
			+ "post_content = ?"
			+ "WHERE POST_NO = ?";

        try {
            pstmt = con.prepareStatement(sql2);
            pstmt.setString(1, post.getPOST_TITLE());
            
            // pstmt.setString(2, post.getPOST_THUMBNAIL()); //썸네일 없음
            
            pstmt.setString(2, post.getPOST_CONTENT());
            pstmt.setInt(3, post.getPOST_NO()); //파라미터로 가져온 POST_NO 찾기

            updateCount = pstmt.executeUpdate();
            System.out.println("게시글 수정");
        } catch (Exception ex) {
            System.out.println("게시글 수정 안됨" + ex);

        } finally {
            close(pstmt); // import static db.JdbcUtil.*;
        }
    }

    // --------------------------------------
    return updateCount;
}

 

수정할 부분)

1. 테스트해보니 제목이 잘려서 나온다.(따옴표로 묶으면 됨)

2. 썸네일을 변경했을 경우 파일이 잘 저장되는지 다시 확인해봐야한다. -> 와...set 다음에 띄어쓰기 안되어있어서 안되었었음.

▼ SQL 문 쓸 때 띄어쓰기 누락 주의할 것.

에러발생

java.sql.SQLSyntaxErrorException: ORA-00971: missing SET keyword 에러 발생....

	String sql = 
	"UPDATE post_info SET" //set 다음에 띄어쓰기 없어서 안됨....;;
	+ "post_title = ?,"
	+ "post_thumbnail = ?,"
	+ "post_content = ?"
	+ "WHERE POST_NO = ?";

3. 메인페이지 context 내용을 가져와서 15자를 잘라 사용했는데 editor 때문에 글씨 미리보기가 안됨..

-> 정규식으로 태그를 걷어내려 했으나 잘 안걷어져서 일단은 텍스트가 보이게 만들어놓음. 추 후 변경할 예정.

 

결과창

 

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

내 블로그 페이지 만들기

이젠 반복작업이다. 저번 포스팅의 위젯을 그대로 따고, 내 블로그 페이지에는 세션에 있는 회원의 정보를 띄워주도록 하자.

[ 만들 기능 ]
1. 내 블로그 클릭하면 내가 쓴 게시글 목록이 나오게 하기
2. 게시글 목록을 클릭하면 게시글 상세페이지로 이동
3. 위젯에 내 프로필 사진과 ID를 띄움
4. 세션 ID = 게시글 쓴 사람 이면 삭제버튼이 보이게 하기
5. 게시글 삭제 기능 구현

1. 메인페이지에서 내 블로그 클릭하면 내가 쓴 게시글 목록이 나오게 하기

간단하게 링크를 걸어준다.

1) JSP

<li class="nav-item"><a class="nav-link" href="myBlogAction.me?id=${sessionScope.id}">MyBlog</a></li>

로그인한 '내' 블로그에 가는 것이므로 sessionScope.id를 사용해준다.

2) Controller

//-------------------- 내 블로그 페이지를 보여주는 myBlogAction 페이지 생성 ---------------------------
else if (command.equals("/member/myBlogAction.me")) {
    action = new MyBlogAction();
    // MyBlogAction, 내 블로그 페이지 표시.
    try {
        forward = action.execute(req, resp);
        System.out.println( "내 블로그 페이지 표시"))
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

3) Action

내 블로그에는 내가 쓴 게시글 목록을 띄울 것이다. sessionId가 있어야 내 블로그인 줄 알테니까 sessionId 생성 후 Service에 같이 던져준다.

package action;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import svc.MyPostListService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 내 블로그 표시를 위한 클래스이다.*/
public class MyBlogAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		//session을 써서 서버 생성함.
		HttpSession session = req.getSession();
		// 세션에서 id값 가지고 있기.
		String sessionId = (String) session.getAttribute("id");
		ActionForward forward = null; //어디로 갈지?

			// 내 게시글 목록 및 내용 표시.
			forward = new ActionForward();
			MyPostListService myPostListService = new MyPostListService();
			//배열로 게시글 목록을 가져온다. id는 같이 던져줘야 읽음.
			ArrayList<PostBean> myPostList = myPostListService.getMyPostList(sessionId);

			req.setAttribute("myPostList", myPostList);
			
			//mainPage로 게시글 목록을 던져준다.
			forward.setPath("./myBlog.jsp");
		
		return forward;
	}
}

4) Service

DB연결해서 커넥션 풀 열고 닫기

package svc;
import static db.JdbcUtil.close;
import static db.JdbcUtil.getConnection;
import java.sql.Connection;
import java.util.ArrayList;
import DAO.PostDAO;
import vo.PostBean;


/* 내 게시글를 띄울 시 DB와 JSP를 연결해주는 역할을 담당하는 클래스(Service) */
public class MyPostListService {
	
	public ArrayList<PostBean> getMyPostList(String sessionId) { //리스트이므로 배열로 가져온다.
		//커넥션 풀 : DB와 미리 연결해놓은 커넥션 객체들을 풀에 저장해뒀다가 클라에서 요청오면 객체 빌려주고 볼일 끝나면 다시 반납받아 풀에 저장하는 방식
		Connection con = getConnection();
		// DB와 연결을 얻어내고.
		PostDAO postDAO = PostDAO.getInstance();
		// DB접속용 DAO 객체 참조 얻어오기.(싱글톤 패턴 : 인스턴스가 1개만 생성됨)
		// import static db.JdbcUtil.*;
		postDAO.setConnection(con);
		// 얻어온 커넥션 저장. -> 연결해서 할일 함.
		ArrayList<PostBean> myPostList = postDAO.selectMyPostList(sessionId); //selectMyPostList는 PostDAO에서 받아온다.
		//커넥션으로 DB에서 게시글 목록 받아서 배열에 저장

		close(con);
		// 사용을 마친 커넥션 반환
		return myPostList;
		// 내 게시글 목록 리턴.
	}
}

5) DAO

내 블로그 게시글 목록을 가져온다. 목록이므로 미리보기처럼 보일 수 있게 게시글 내용은 15자 이내로 자르고, 회원 테이블에서 ID값과 pic을 가져와서 넣어준다.

//=========================== 내 블로그 게시글을 가져와 보여주는 SQL로직 ===============================
public ArrayList<PostBean > selectMyPostList(String sessionId) {
    // ArrayList: 객체 배열 비슷, 컬렉션 프로임워크
    // 여러 개의 게시글 정보를 저장한다.

    //디비에 저장된 내 게시글 목록을 확인하는 SQL문(DB 이름 확인하기***)
    String sql =  "select p.*,m.mem_id,m.mem_pic from post_info p join memberinfo m on p.mem_no = m.mem_no where m.mem_id = ?"

    ArrayList<PostBean> myPostList = new ArrayList<PostBean>();
    PostBean pb = null; //Bean(vo)은 그릇이다. 뭘 가져올지는 vo에 목록이 있다.

    try {
        pstmt = con.prepareStatement(sql);

        //세션 id값 가져와서 SQL 문에 넣어줌.
        pstmt.setString(1, sessionId);
        System.out.println( "sessionId : "+sessionId))

        rs = pstmt.executeQuery(); //executeQuery : resultSet 객체 반환
        //select는 executeQuery()를 사용한다.
        //쿼리문 처리결과 ResultSet의 객체인 rs에 저장.

        if (rs.next()) {
            //조회된 결과가 있다면 아래 문장 수행.
            do {
                //한 번 수행하고 또 수행할 게 있으면 수행.
                pb = new PostBean();
                //게시글 1개의 정보를 저장할 수 있는 PostBean 객체 생성.
                pb.setPOST_NO(rs.getInt( "POST_NO"))) //게시글번호
                pb.setMEM_NO(rs.getInt( "MEM_NO"))) //회원번호
                pb.setPOST_TITLE(rs.getString( "POST_TITLE"))) //게시글제목
                pb.setPOST_THUMBNAIL(rs.getString( "POST_THUMBNAIL"))) //게시글섬네일
                pb.setPOST_VIDEO(rs.getString( "POST_VIDEO"))) //게시글비디오					

                //--- 게시글 내용 가져와서 15자만 미리보기로 보여줌 ---
                String preStr=rs.getString( "POST_CONTENT"))
                if(preStr.length() > 30) {
                    preStr=preStr.substring(0,15)+ "..." //자르고 ... 붙이기
                };
                pb.setPOST_CONTENT(preStr); //게시글내용 미리보기
                //----------------------------------------

                pb.setVisit_cnt(rs.getInt( "Visit_cnt"))) //게시글조회수
                pb.setPOST_UPLOADTIME(rs.getString( "POST_UPLOADTIME"))) //게시글업로드타임

                //위젯에 띄울 내 회원정보
                pb.setMEM_id(rs.getString( "MEM_ID"))) //게시글 쓴 회원 id
                pb.setMEM_PIC(rs.getString( "MEM_PIC"))) //게시글 쓴 회원 사진

                //조회된 결과를 PostBean객체에 저장.
                myPostList.add(pb);
                //저장하면서 생성된 것을 이제 List에 담아냄.(ArrayList)
                //반복문이 실행될 때마다 게시글 1개씩 누적 시킴.
            }
            while (rs.next());
            //rs.next때문에 어레이 리스트 다음 값으로 넘어간다.(차례차례 읽어옴)
        }
    }
    catch (Exception ex) {
        System.out.println( "selectMyPostList 에러 : " + ex))
    }
    finally {
        close(rs);
        close(pstmt);
    }
    return myPostList;
}

6) VO

vo에 있는 PostBean의 정보는 이전글 참고. 아래 두가지를 추가했다.

private String MEM_ID;
private String MEM_PIC;

결과창

2. 게시글 목록을 클릭하면 게시글 상세페이지로 이동

동일하게 상세보기 페이지로 연결해주면 된다. 그럼 상세보기 페이지이지만 위젯 내역은 똑같이 보이니 페이지가 이동하지 않은 상태로 내용만 바뀐 것처럼 보인다.

  <a class="btn btn-primary" href="postViewAction.me?postViewAction.me?postno=${myPostList.POST_NO}">Read more →</a>

 

3. 위젯에 내 프로필 사진과 ID를 띄움

1번 항목 참조.

 

나만 내 게시글 삭제하기

 

4. 세션 ID = 게시글 쓴 사람 이면 삭제버튼이 보이게 하기

요걸 버튼으로 사용해보자.

세션 ID를 어떻게 가져올까 하다가...공부하는 겸 해서 자바스크립트 단에서 display : none 처리로 간단하게 해보기로 했다.

자바스크립트에 java 코드를 사용해본다. 서버 session에 있는 id값을 가져왔다.

<div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} 
by <b id="memId">${postObj.MEM_ID}</b></div>

<!--회원id를 b태그로 감싸서 id를 부여해줬다.-->

<script>
        var sessionId = '<%=(String)session.getAttribute("id")%>';
        var post_writer = document.getElementById('memId').innerText;
        console.log(typeof(sessionId),typeof(post_writer)); //string,string
        //세션 id가 포스트작성자가 아니면, 수정버튼과 삭제버튼 안보임.
        if (sessionId != post_writer){
        	 document.getElementById("post_update").style.display ='none';
        	 document.getElementById("post_delete").style.display ='none';
        	 console.log("내 게시글 아님. 수정 불가");
        };
		
</script>

세션 아이디와 다름
세션 아이디와 같음

물론 좋은 방법은 아니다.

말 그대로 css로 감춘 것이기 때문에 스크립트에 노출되어 있으며, display : none 속성을 브라우저에서 없애주면 버튼이 보여지게 된다.

개발자 도구에서 버튼 발굴(?) 가능

하지만 이렇게 한 이유는 귀찮은 건 아니고 자바스크립트에서 세션값을 가져오는 방법을 사용해보기 위함이며, 수정 화면에 이동할 시, session에 있는 사용자가 수정권한이 있는지 컨트롤러 단에서 다시 확인할 것이기 때문이다.

컨트롤러 단에서 이걸 어떻게 받을까 고민하다가 postNo와 같이 get 방식으로 mem_id값도 보내주기로 했다.(2번 참고)

 

5. 게시글 삭제 기능 구현

삭제기능만 우선 구현해보자.

1) JSP

삭제버튼 클릭 시 해당 게시글 번호와 작성자를 쿼리스트링으로 보내줌

<a class="badge bg-secondary text-decoration-none link-light" href="postDeleteAction.me?writer=${postObj.MEM_ID}&postno=${postObj.POST_NO}" id="post_delete">삭제</a>

get 방식으로 보낼 때 파라미터에 작성자 id값도 함께 보내준다. 컨트롤러 단에서 확인할 것이기 때문!!

쿼리스트링을 보낼 때에는 &로 묶어서 보내준다.

2) Controller

//-------------------- 내 게시글을 삭제하는 postDeleteAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postDeleteAction.me")) {
    action = new PostDeleteAction();
    // PostDeleteAction, 삭제 후 페이지 표시.
    try {
        forward = action.execute(req, resp);
        System.out.println( "게시글 삭제"))
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

3) Action

로그인 상태인지, 사용자가 게시글 작성자가 맞는지를 확인한다.

브라우저 alert를 띄우기 위해 resp.getWriter()를 이용해서 println으로 스크립트를 띄워주었다.

가져온 작성자 id값과 게시글 번호를 통해 분기한다.

package action;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import svc.PostDeleteService;
import vo.ActionForward;

public class PostDeleteAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		HttpSession session = req.getSession();
		String id = (String) session.getAttribute("id");
		// 로그인하면, id는 session에 저장되어 있다.
		String post_writer = req.getParameter("writer");
		int postNo = Integer.parseInt(req.getParameter("postno"));
		System.out.println("writer"+post_writer);

		ActionForward forward = null;
		// 로그인 상태인지 확인 -> 아니면 로그인화면 고고
		if (id == null) {
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./memberLogin.me");
			
		// 사용자가 작성자가 맞는지 확인 -> 아니면 alert를 띄우고 해당 포스트로 다시 이동
		} else if (!id.equals(post_writer)) {
			resp.setContentType("text/html;charset=UTF-8");
			PrintWriter out = resp.getWriter();
			out.println("<script>");
			out.println("alert('작성자가 아닌 사람은 게시글을 삭제할 수 없습니다.')");
			out.println("location.href='./postViewAction.me?postno="+postNo+"'");
			out.println("</script>");		

		//사용자가 작성자면 삭제가 가능하다.
		} else {
			//Service에 삭제한 게시물 번호를 함께 넘겨준다.
			PostDeleteService postDeleteService = new PostDeleteService();
			boolean deleteResult = postDeleteService.deletePost(postNo);
			
			// delete 하면 내 블로그로 이동
			if (deleteResult) {
				resp.setContentType("text/html;charset=UTF-8");
				PrintWriter out = resp.getWriter(); 
				out.println("<script>");
				out.println("alert('삭제되었습니다.');");
				out.println("location.href='./myBlogAction.me'");
				out.println("</script>");

			// delete 실패하면 alert 발생 후 해당 포스트로 다시 이동
			} else {
				resp.setContentType("text/html;charset=UTF-8");
				PrintWriter out = resp.getWriter(); 
				out.println("<script>");
				out.println("alert('삭제 실패');");
				out.println("location.href='./postViewAction.me?postno="+postNo+"'");
				out.println("</script>");
			}
		}
		return forward;	
	}
}

4) Service

DB연결해서 커넥션 풀 열고 닫기. 게시글 번호는 계속 가지고 간다.

package svc;

import static db.JdbcUtil.close;
import static db.JdbcUtil.commit;
import static db.JdbcUtil.getConnection;
import static db.JdbcUtil.rollback;
import java.sql.Connection;
import DAO.PostDAO;

public class PostDeleteService {

	public boolean deletePost(int postNo) {
		boolean deleteResult = false;
		Connection con = getConnection();
		PostDAO postDAO = PostDAO.getInstance();
		postDAO.setConnection(con);
		int deleteCount = postDAO.deletePost(postNo);

		//지우면 지웠다고 결과 1 리턴 아니면 롤백
		if (deleteCount > 0) { 
			commit(con);
			deleteResult = true;
		} else {
			rollback(con);
		}
		close(con);
		return deleteResult;
	}

}

5) DAO

내 블로그 게시글 목록을 가져온다. 목록이므로 미리보기처럼 보일 수 있게 게시글 내용은 15자 이내로 자르고, 회원 테이블에서 ID값과 pic을 가져와서 넣어준다.

//=========================== 게시글을 삭제하는 SQL로직 ===============================
// PostDeleteService에서 게시글 삭제 시 DB와 JSP를 연결할 때 인자로 쓰임.
public int deletePost(int postNo) {
    //우클릭 Create method
    // 해당 게시글을 삭제하는 SQL문(DB 이름 확인하기***)
    String sql =  "delete from post_info where post_No=?"
    int deleteCount = 0; //삭제 유무

    try {
        pstmt = con.prepareStatement(sql);
        pstmt.setInt(1, postNo); //가져온 게시글 번호 보내주기
        pstmt.executeUpdate();
        deleteCount = 1;
    }
    catch(Exception ex) {
        System.out.println( "삭제 안됨"))
        deleteCount = 0;
    }
    finally {
        close(rs);
        close(pstmt);
    }
    return deleteCount;
}

6) VO

vo에 있는 PostBean의 정보는 이전글 참고.

결과창 : 유저가 자기 게시글을 삭제함

css로 버튼을 감췄기 때문에 display:none을 풀어서(?) 삭제 버튼을 활성화시킬 수도 있다. 이 때 alert가 발생하면서 다시 해당 페이지로 이동한다.

결과창 : 유저가 다른사람 게시글을 삭제할 경우 alert 발생

 

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

게시글 목록 클릭 시 상세페이지 연결하기

게시글 목록을 클릭하면 해당 게시글로 이동하는 기능을 구현해보자.

[ 만들 기능 ]
1. 목록에 있는 게시글 클릭 시 postNo를 넘겨줌
2. 해당 postNo에 해당하는 상세페이지로 이동
3. 해당 상세페이지에 글쓴이의 정보가 위젯에 뜨게 하기

1. 메인페이지에서 게시글 클릭 시 해당 postNo 보내주기

상세보기 페이지로 파라미터 postno를 함께 넘겨준다.

<c:forEach var="postList" items="${postList}">
    <!-- Featured blog post -->
    <div class="card mb-4">
        <div class="card-body">
            <a href="#!"><img class="card-img-top" src="../resources/img/thumbnail/${postList.POST_THUMBNAIL}" alt="..."/></a>
            <div class="small text-muted">${postList.POST_UPLOADTIME}</div>
            <h2 class="card-title">${postList.POST_TITLE} ----- ${postList.MEM_NO}</h2>
            <p class="card-text">${postList.POST_CONTENT}</p>
            <!-- 상세보기 페이지로 파라미터 postno를 함께 넘겨준다. -->
            <a class="btn btn-primary" href="postViewAction.me?postno=${postList.POST_NO}">Read more →</a>
        </div>
    </div>
</c:forEach>

2. 상세페이지에 게시글 내용 출력

부트스트랩 이용(출처 : https://startbootstrap.com/template/blog-post)

부트스트랩으로 가져온 페이지에 게시글 목록 띄운 것과 동일한 방법으로 상세페이지를 띄운다.

1) JSP

<header class="mb-4">
	<!-- Post title-->
	<h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
	<!-- Post meta content-->
	<div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} by ${postObj.MEM_NO}</div>
	<!-- Post categories-->
	<a class="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
	<a class="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
</header>
<!-- Preview image figure-->
<figure class="mb-4"><img class="img-fluid rounded" src="../resources/img/thumbnail/${postObj.POST_THUMBNAIL}" alt="..." /></figure>
<!-- Post content-->
<section class="mb-5">
	<p class="fs-5 mb-4">${postObj.POST_CONTENT}</p>
	<p class="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
	<p class="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
	<h2 class="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
	<p class="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
	<p class="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
</section>
</article>

게시글 리스트와 구분해주려고 postObj라는 이름으로 쓰기로 함.

▼ blogPost.jsp 전체

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 부트스트랩 출처 : https://startbootstrap.com/template/blog-home -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>${postObj.POST_TITLE}</title>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                <div class="col-lg-8">
                    <!-- Post content-->
                    <article>
                        <!-- Post header-->
                        <header class="mb-4">
                            <!-- Post title-->
                            <h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
                            <!-- Post meta content-->
                            <div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} by ${postObj.MEM_NO}</div>
                            <!-- Post categories-->
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
                        </header>
                        <!-- Preview image figure-->
                        <figure class="mb-4"><img class="img-fluid rounded" src="../resources/img/thumbnail/${postObj.POST_THUMBNAIL}" alt="..." /></figure>
                        <!-- Post content-->
                        <section class="mb-5">
                            <p class="fs-5 mb-4">${postObj.POST_CONTENT}</p>
                            <p class="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
                            <p class="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
                            <h2 class="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
                            <p class="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
                            <p class="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
                        </section>
                    </article>
                    <!-- Comments section-->
                    <section class="mb-5">
                        <div class="card bg-light">
                            <div class="card-body">
                                <!-- Comment form-->
                                <form class="mb-4"><textarea class="form-control" rows="3" placeholder="Join the discussion and leave a comment!"></textarea></form>
                                <!-- Comment with nested comments-->
                                <div class="d-flex mb-4">
                                    <!-- Parent comment-->
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.
                                        <!-- Child comment 1-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors.
                                            </div>
                                        </div>
                                        <!-- Child comment 2-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                When you put money directly to a problem, it makes a good headline.
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <!-- Single comment-->
                                <div class="d-flex">
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence.
                                    </div>
                                </div>
                            </div>
                        </div>
                    </section>
                </div>
                <!-- Side widgets-->
                <div class="col-lg-4">
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">Search</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="Enter search term..." aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">Go!</button>
                            </div>
                        </div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">Categories</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">Web Design</a></li>
                                        <li><a href="#!">HTML</a></li>
                                        <li><a href="#!">Freebies</a></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">JavaScript</a></li>
                                        <li><a href="#!">CSS</a></li>
                                        <li><a href="#!">Tutorials</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Side widget-->
                    <div class="card mb-4">
                        <div class="card-header">Side Widget</div>
                        <div class="card-body">You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component!</div>
                    </div>
                </div>
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
    </body>
</html>

 

2) Controller

//-------------------- 게시글 상세보기 postViewAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postViewAction.me")) {
    action = new PostViewAction();
    // PostViewAction, 게시글 상세보기 페이지 표시.
    try {
        forward = action.execute(req, resp);
        System.out.println( "게시글 상세보기 표시"))
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

3) Action

그냥 게시글이기 때문에 session은 필요없다. 관리자가 못볼 글들은 아니기 때문에... 파라미터 값을 받아서 서비스로 넘겨준다.

package action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import svc.PostViewService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 게시글 상세보기 표시를 위한 클래스이다.*/
public class PostViewAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		ActionForward forward = null; //어디로 갈지?
			forward = new ActionForward();

			int ClickPostNo = Integer.parseInt(req.getParameter("postno"));
			//URL 주소 뒤에 붙은 쿼리스트링은 getParameter로 가져온다.
			System.out.println("클릭한 포스트 번호: "+ClickPostNo);
	
			PostViewService postViewService = new PostViewService();
			//서비스 svc에서 getPostinfo를 만들어준다. 게시글 번호 보내주자.
			PostBean postObj = postViewService.getPostinfo(ClickPostNo);
			req.setAttribute("postObj", postObj);
			forward.setPath("./blogPost.jsp");

		return forward;
	}
}

4) Service

DB연결해서 커넥션 풀 열고 닫기

package svc;
import static db.JdbcUtil.close;
import static db.JdbcUtil.getConnection;
import java.sql.Connection;
import DAO.PostDAO;
import vo.PostBean;


/* 게시글 상세페이지를 띄울 시 DB와 JSP를 연결해주는 역할을 담당하는 클래스(Service) */
public class PostViewService {
	
	public PostBean getPostinfo(int postNo) {
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		
		PostBean post = postDAO.selectPost(postNo); //selectPost는 postDAO에서 받아온다.

		close(con); //커넥션풀 닫음

		return post;
	}
}

5) DAO

postDAO에 게시글 목록을 뽑아주는 selectPostList가 이미 있다. 그 아래에 새롭게 selectPost를 생성해준다.

//=========================== 게시글 상세보기를 가져와 보여주는 SQL로직 ===============================
// PostViewService에서 게시글 상세보기정보를 볼때 DB와 JSP를 연결할 때 인자로 쓰임.
public PostBean selectPost(int postNo) {
    String sql = "select * from post_info where POST_NO=?""
    PostBean pb=null;

    try {
        pstmt=con.prepareStatement(sql);
        pstmt.setInt(1,postNo);
        rs=pstmt.executeQuery();

        if(rs.next()) {

            pb=new PostBean();

            //게시글 1개의 정보를 저장할 수 있는 PostBean 객체 생성.
            pb.setPOST_NO(rs.getInt( "POST_NO"))) //게시글번호
            pb.setMEM_NO(rs.getInt( "MEM_NO"))) //회원번호
            pb.setPOST_TITLE(rs.getString( "POST_TITLE"))) //게시글제목
            pb.setPOST_THUMBNAIL(rs.getString( "POST_THUMBNAIL"))) //게시글섬네일
            pb.setPOST_VIDEO(rs.getString( "POST_VIDEO"))) //게시글비디오
            pb.setPOST_CONTENT(rs.getString( "POST_CONTENT"))) //게시글 내용
            pb.setVisit_cnt(rs.getInt( "Visit_cnt"))) //게시글조회수
            pb.setPOST_UPLOADTIME(rs.getString( "POST_UPLOADTIME"))) //게시글업로드타임
        }
    }
    catch(Exception ex) {
        System.out.println( "getSelectPost 에러: " + ex))

    }
    finally {
        close(rs);
        close(pstmt);
    }
    return pb;
}

6) VO

vo에 있는 PostBean의 정보는 이전글 참고.

서버콘솔에서 포스트 번호 확인

3. 해당 상세페이지에 글쓴이의 정보가 위젯에 뜨게 하기

사실 연결은 게시글 목록 리스트 띄우는 것과 똑같이 부트스트랩 폼에 필요한 정보를 넣어주면 된다.

네비게이션을 따로 분리해서 띄워주는 방식을 사용할까 하다가, 결국 어떤 블로그 게시글을 클릭하면 그 글을 쓴 사람의 블로그로 이동된다는 사실을 깨닫고 이를 위젯으로 표시해주기로 했다.

글쓴이의 id값, 그리고 mem_pic으로 설정해준 멤버의 프로필 사진을 위젯에 띄워주자.

1) 위젯을 왼쪽으로 옮겨서 프로필 사진 나올 수 있게 하기

간단하게 div 위치를 수정해서 프로필이 나올 수 있게 한다. 메인페이지에 오른쪽에 위젯이 있다보니 차별점을 두고 싶었음.

게시글 출력되는 곳이 <div class="col-lg-8">로 되어있는데, 이를 <div class="col-lg-9">로 늘려주고 위젯 영역을 3으로 설정해주었다. 위젯이 그닥 클 필요는 없어보여서...

위젯에 사진 보이는 것은 기존 css를 이용해서 게시글 미리보기 이미지를 출력하는 class="card-img-top"를 사용했다.

▼blogPost.jsp 에 블로그 헤더 및 위젯 왼쪽으로 추가

    <!-- 블로그 헤더 및 블로그 배경화면 -->
        <header class="py-5 bg-light border-bottom mb-4" style="background-image: url('https://www.urbanbrush.net/web/wp-content/uploads/edd/2022/01/urbanbrush-20220105101328484351.jpg')">
            <div class="container">
                <div class="text-center my-5">
                    <h1 class="fw-bolder"> ${postObj.MEM_NO} 회원님의 블로그입니다.</h1>
                    <p class="lead mb-0">내가 쓴 글을 볼 수 있습니다.</p>
                </div>
            </div>
        </header>
        
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
            
            <!-- 왼쪽 블로그 소개 위젯-->
                <div class="col-lg-3">
                <!-- Side widget-->
                    <div class="card mb-4">
                    	<img class="card-img-top" src="../resources/img/blog/${postObj.MEM_PIC}" alt="..." />
                        <div class="card-header">${postObj.MEM_ID}</div>
                        <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다!</div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">카테고리?</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">내 블로그</a></li>
                                        <li><a href="#">오늘 ??명 방문 ></a></li>
                                        <li></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">새글작성</a></li>
                                        <li><a href="#">로그아웃</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">내 블로그에서 검색</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">검색</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-lg-9">
                    <!-- Post content-->
                    <article>
                        <!-- Post header-->
                        <header class="mb-4">
                            <!-- Post title-->
                            <h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
                            
                            
                           ...

2) 위젯과 블로그 배경에 멤버 정보 띄우기

기존에 게시글 상세보기 페이지의 SQL 문은 아래와 같다.

select * from post_info where POST_NO=?

그냥 게시글 번호가 맞는 포스트 정보를 전체 가져와서 사용했음.

post_info 테이블에서 FK로 mem_no를 연결해놓았기 때문에 멤버정보도 가지고 올 수 있음. join을 사용한다.

select p.*,m.mem_id,m.mem_pic from post_info p 
    left join memberinfo m on p.mem_no = m.mem_no where POST_NO=?

join으로 mem_no가 서로 같다는걸 표시해주고 그 중 post_no가 같은 게시글의 정보를 가져온다.

단, 게시글 정보 및 멤버id와 사진도 함께 가져옴. -> 일단 이것만 필요하니까.

VO에서 post_info 테이블 정보를 담아 가져오는데, 어차피 DAO에서 하나에 담아서 리턴쳐주기 때문에 VO에 변수를 하나 늘려주었다.

▼  PostBean.java에 내용 추가

   ...
    
	//블로그 게시글 네비에 표시하기 위해 추가
	private String MEM_ID;
	private String MEM_PIC;

	
	public String getMEM_ID() {
		return MEM_ID;
	}
	public void setMEM_id(String mEM_ID) {
		MEM_ID = mEM_ID;
	}
	public String getMEM_PIC() {
		return MEM_PIC;
	}
	public void setMEM_PIC(String mEM_PIC) {
		MEM_PIC = mEM_PIC;
	}
	
	...

▼  PostDAO.java에 회원정보 받아줌

...
pb.setPOST_UPLOADTIME(rs.getString("POST_UPLOADTIME")); //게시글업로드타임
				

// 가져올 회원 정보 추가
pb.setMEM_id(rs.getString("MEM_ID")); //게시글 쓴 회원 id
pb.setMEM_PIC(rs.getString("MEM_PIC")); //게시글 쓴 회원 사진

...

		}
		return pb;
	}

어차피 return은 PostBean에서 가져온 pb 변수이기 때문에 함께 담아서 보내주었다.

그럼 jsp 페이지에서 다른 변수들과 같은 방법으로 띄울 수 있음.

 blogPost.jsp에 내용 띄워줌

<h1 class="fw-bolder"> ${postObj.MEM_NO} 회원님의 블로그입니다.</h1>

...

<div class="card mb-4">
   <img class="card-img-top" src="../resources/img/blog/${postObj.MEM_PIC}" alt="..." />
   <div class="card-header">${postObj.MEM_ID}</div>
   <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다! 이미지는 일단 고정으로 박아뒀지롱...</div>
</div>

결과창

▼blogPost.jsp 전체

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 부트스트랩 출처 : https://startbootstrap.com/template/blog-home -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>${postObj.POST_TITLE}</title>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        
                <!-- 블로그 헤더 및 블로그 배경화면 -->
        <header class="py-5 bg-light border-bottom mb-4" style="background-image: url('https://www.urbanbrush.net/web/wp-content/uploads/edd/2022/01/urbanbrush-20220105101328484351.jpg')">
            <div class="container">
                <div class="text-center my-5">
                    <h1 class="fw-bolder"> ${postObj.MEM_NO} 회원님의 블로그입니다.</h1>
                    <p class="lead mb-0">내가 쓴 글을 볼 수 있습니다.</p>
                </div>
            </div>
        </header>
        
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
            
            <!-- 왼쪽 블로그 소개 위젯-->
                <div class="col-lg-3">
                <!-- Side widget-->
                    <div class="card mb-4">
                    	<img class="card-img-top" src="../resources/img/blog/${postObj.MEM_PIC}" alt="..." />
                        <div class="card-header">${postObj.MEM_ID}</div>
                        <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다! 이미지는 일단 고정으로 박아뒀지롱...</div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">카테고리?</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">내 블로그</a></li>
                                        <li><a href="#">오늘 ??명 방문 ></a></li>
                                        <li></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">새글작성</a></li>
                                        <li><a href="#">로그아웃</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">내 블로그에서 검색</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">검색</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-lg-9">
                    <!-- Post content-->
                    <article>
                        <!-- Post header-->
                        <header class="mb-4">
                            <!-- Post title-->
                            <h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
                            <!-- Post meta content-->
                            <div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} by ${postObj.MEM_ID}</div>
                            <!-- Post categories-->
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
                        </header>
                        <!-- Preview image figure-->
                        <figure class="mb-4"><img class="img-fluid rounded" src="../resources/img/thumbnail/${postObj.POST_THUMBNAIL}" alt="..." /></figure>
                        <!-- Post content-->
                        <section class="mb-5">
                            <p class="fs-5 mb-4">${postObj.POST_CONTENT}</p>
                            <p class="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
                            <p class="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
                            <h2 class="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
                            <p class="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
                            <p class="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
                        </section>
                    </article>
                    <!-- Comments section-->
                    <section class="mb-5">
                        <div class="card bg-light">
                            <div class="card-body">
                                <!-- Comment form-->
                                <form class="mb-4"><textarea class="form-control" rows="3" placeholder="Join the discussion and leave a comment!"></textarea></form>
                                <!-- Comment with nested comments-->
                                <div class="d-flex mb-4">
                                    <!-- Parent comment-->
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.
                                        <!-- Child comment 1-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors.
                                            </div>
                                        </div>
                                        <!-- Child comment 2-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                When you put money directly to a problem, it makes a good headline.
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <!-- Single comment-->
                                <div class="d-flex">
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence.
                                    </div>
                                </div>
                            </div>
                        </div>
                    </section>
                </div>
                <!-- Side widgets-->
                <div class="col-lg-4">
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">Search</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="Enter search term..." aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">Go!</button>
                            </div>
                        </div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">Categories</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">Web Design</a></li>
                                        <li><a href="#!">HTML</a></li>
                                        <li><a href="#!">Freebies</a></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">JavaScript</a></li>
                                        <li><a href="#!">CSS</a></li>
                                        <li><a href="#!">Tutorials</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Side widget-->
                    <div class="card mb-4">
                        <div class="card-header">Side Widget</div>
                        <div class="card-body">You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component!</div>
                    </div>
                </div>
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
    </body>
</html>

 

 

 

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

+ Recent posts

반응형
LIST