글을 작성하면서 구현을 하다가 (1/), (2/), (3/)까지는 이어졌는데, 갑자기 구현에  신경을 쓰느라 이번 글에서는 어느 정도 완성된 버전에서 다시 기술한다.

맥은 어느 정도 유지가 되지만, 이전 글에서 스무드하게 이어지지 못해  이해에 어려움이 있을 수도 있을 것 같다.

1. 게시판 관리 기능 중 "게시판 목록 보기"

Header의 메뉴 창에서 "관리자" 메뉴를 선택하면 "게시판 목록 보기" 창으로 이동하게 된다.  url은 "localhost:8080/board/manager/list" 이다. 즉, 게시판 목록 보기 기능을 구현한 것이다.

1.1 BoardController에서 url /manager/list를 처리

  @RequestMapping(value="/list", method=RequestMethod.GET)

  public void list(Criteria cri, Model model) throws Exception {    // View로는 기본적으로 list.jsp가 사용됨

    List<BoardVO> list = service.list(cri);    // 게시판 목록정보를 가져 온다.

    for(int i = 0;i < list.size();i++) {           // 게시판의 게시글 수를 가져와 저장한다.

     BoardVO board = list.get(i);

board.setNofArticle(aService.listCount());    //

    }

    model.addAttribute("list", list);         // 게시판 목록 정보를 model에 저장한다.

    PageMaker pm = new PageMaker();     // 페이징 처리를 위해 필요한 정보를 담을 클래스를 인스턴스화

    pm.setCri(cri);                                  // 페이징 처리를 위한 기준을 저장 (cri는 기본값이 있지만, 사용

    pm.setTotalCount(service.listCount());    // 자가 파라미터로 제공 가능. 전체 게시판의 수를 얻는다.

    model.addAttribute("pageMaker", pm); // 페이징 정보를 model에 저장

  }                                                     // view는 기본적으로 list.jsp

1.2 list.jsp : "게시판 목록 보기" view

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>게시판 목록 보기</title>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/common.css"/>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/boardlist.css"/>

</head>

<body>

<header> <!-- 헤더 영역 정의 -->

  <jsp:include page="../common/header.jsp" flush="false"/>

</header>

<section id="wrap"> <!-- 본문 영역 정의 -->

  <section id="lnav"> <!-- 사이드 메뉴 영역 정의 -->

    <jsp:include page="../common/lnav.jsp" flush="false"/>

  </section>

  <section id="content"> <!-- 내용 영역 정의 -->

    <h1>게시판 목록 보기</h1>

    <table border="1">

      <c:if test="${pageMaker.totalCount > 0}">

<tr>

  <td colspan="6">

    ${pageMaker.cri.pageStart}-${pageMaker.cri.pageEnd}

    [${pageMaker.cri.page}/${pageMaker.totalPageCount}]

  </td>

</tr>

      </c:if>


      <c:choose>

<c:when test="${list.size() == 0}">

  <tr>

    <td colspan="6">

      게시판이 없습니다.

    </td>

  </tr>

</c:when>

<c:otherwise>

  <tr>

    <td style="text-align: center" width="6%">번호</td>

    <td style="text-align: center" width="21%">게시판 이름</td>

    <td style="text-align: center" width="13%">게시판 유형</td>

    <td style="text-align: center" width="32%">URL</td>

    <td style="text-align: center" width="17%">생성일</td>

    <td style="text-align: center" width="11%">게시글 수</td>

  </tr>

  <c:forEach var="board" items="${list}">

    <tr>

      <td>${board.board_id}</td>

      <td>

<c:set var="query" value="board_id=${board.board_id}&page=${pageMaker.cri.page}

 &perPageNum=${pageMaker.cri.perPageNum}"/>

<a href="<c:url value="manager/read?${query}"/>">${board.board_name}</a>

      </td>

      <td>${board.board_type}</td>

      <td>${board.url}</td>

      <td><fmt:formatDate value="${board.creating_date}" pattern="yyyy-MM-dd" /></td>

      <td>${board.nofArticle}</td>

    </tr>

  </c:forEach>

  <tr>

    <td colspan="6">

      <c:if test="${pageMaker.prev == true}">

<a href="<c:url value="/manager/list?page=${pageMaker.startPage-1}&perPageNum=

${pageMaker.cri.perPageNum}"/>">이전</a>

      </c:if>

      <c:forEach var="pno" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">

<a href="<c:url value="/manager/list?page=${pno}&perPageNum=

${pageMaker.cri.perPageNum}" />">[${pno}]</a>

      </c:forEach>

      <c:if test="${pageMaker.next == true}">

<a href="<c:url value="/manager/list?page=${pageMaker.endPage+1}

&perPageNum=${pageMaker.cri.perPageNum}"/>">다음</a>

      </c:if>

    </td>

  </tr>

</c:otherwise>

      </c:choose>

      <tr>

        <td colspan="6">

  <a href="write">게시판 만들기</a>

        </td>

      </tr>

    </table>

  </section>

</section>

<footer> <!-- 풋터 영역 정의 -->

<jsp:include page="../common/footer.jsp" flush="false"/>

</footer>

</body>

</html>


아래의 그림과 같은 내용이 보여진다. (물론 처음에는 아무것도 없겠지만... 지금은 게시판 1개를 추가한 것)


1.3 boardlist.css : 위 그림과 같은 테이블 형태의 게시판 목록을 보여주도록 꾸며주는 파일

@CHARSET "UTF-8";

#content table {

width: 100%;

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

margin-top: 20px;

}

#content td {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

padding: 5px;

}


2. 게시판 생성 : "게시판 만들기" 메뉴 동작

2.1 BoardController /manager/write 동작

  위 list.jsp 파일에서 "게시판 만들기" 버튼을 누르면 BoardController로 /manager/list가 전달된다.

 @RequestMapping(value="/write", method=RequestMethod.GET)

  public String write() {

    return "/manager/writeForm";    // writeForm.jsp를 보여준다.

  }


2.2 writeForm.jsp : 게시판 추가를 위한 Form 입력

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>게시판 만들기</title>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/common.css"/>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/boardwrite.css"/>

</head>

<body>

<header> <!-- 헤더 영역 정의 -->

  <jsp:include page="../common/header.jsp" flush="false"/>

</header>

<section id="wrap"> <!-- 본문 영역 정의 -->

  <section id="lnav"> <!-- 사이드 메뉴 영역 정의 -->

    <jsp:include page="../common/lnav.jsp" flush="false"/>

  </section>

  <section id="content"> <!-- 내용 영역 정의 -->

    <h2>게시판 만들기</h2>

    <form action="<c:url value='write' />" method="post">

      게시판 이름: <input type="text" name="board_name" size="20"/> <br/>

      게시판 유형: <select name="board_type">

<option value="기본">기본</option>

<option value="Q&A">Q&#38A</option>

<option value="이미지">이미지</option>

<option value="공지사항">공지사항</option>

<option value="자료실">자료실</option>

<option value="방명록">방명록</option>

      </select><br>

      url : <input type="text" name="url" size="40"><br>

      비밀글 허용여부 : <input type="radio" name="secret" value="T">&nbsp;감추기&nbsp;&nbsp;&nbsp;

        <input type="radio" name="secret" value="F" checked="checked">&nbsp;보여주기<br>

      읽기 권한 : <select name="read_allow">

<option value="all">모두 허용</option>

<option value="login">로그인 후</option>

<option value="customer">고객</option>

<option value="business">상인</option>

<option value="admin">관리자</option>

      </select><br>

      쓰기 권한 : <select name="write_allow">

<option value="all">모두 허용</option>

<option value="login">로그인 후</option>

<option value="customer">고객</option>

<option value="business">상인</option>

<option value="admin">관리자</option>

      </select><br>

      댓글 쓰기 권한 : <select name="reply_allow">

<option value="all">모두</option>

<option value="login">로그인 후</option>

<option value="customer">고객</option>

<option value="business">상인</option>

<option value="admin">관리자</option>

      </select><br>

      수정 권한 : <select name="modify_allow">

<option value="all">모두</option>

<option value="login">로그인 후</option>

<option value="customer">고객</option>

<option value="business">상인</option>

<option value="writer">작성자</option>

<option value="admin">관리자</option>

      </select><br>

      삭제 권한 : <select name="remove">

<option value="writer">작성자</option>

<option value="admin">관리자</option>

      </select><br>

      다운로드 권한 : <select name="download">

<option value="all">모두</option>

<option value="login">로그인 후</option>

<option value="customer">고객</option>

<option value="business">상인</option>

<option value="admin">관리자</option>

      </select><br>

      업로드 권한 : <select name="upload">

<option value="all">모두</option>

<option value="login">로그인 후</option>

<option value="customer">고객</option>

<option value="business">상인</option>

<option value="admin">관리자</option>

      </select><br>

      첨부 파일 수 : <input type="number" name="nAttach" min="0" max="5"><br>

      첨부 파일 크기제한(한 개) : <select name="aSize">

<option value="0">0</option>

<option value="50KB">50KB</option>

<option value="200KB">200KB</option>

<option value="1MB">1MB</option>

<option value="10MB">10MB</option>

<option value="100MB">100MB</option>

      </select><br>

      내용 표시 형식 : <select name="display_format">

<option value="1">제목만 표시</option>

<option value="2">내용 일부 표시</option>

<option value="3">내용 전부 표시</option>

      </select><br>

      게시판 설명: <br/>

       <textarea name="board_desc" cols="80" rows="3"></textarea>

      <br/>

      <input id="addBtn" type="submit" value="전송" />

    </form>

  </section>

</section>

<footer> <!-- 풋터 영역 정의 -->

  <jsp:include page="../common/footer.jsp" flush="false"/>

</footer>

</body>

</html>



2.3 boardwrite.css : 위 그림과 같은 "게시판 만들기" view의 디자인 제공

@CHARSET "UTF-8";

#content form {

margin-top: 20px;

line-height: 1.5;

}

#content textarea {

line-height: 1.5;

}

#content #addBtn {

width: 200px;

font-size: 14px;

padding: 10px;

background-color: blue;

color: white;

border-radius: 5px 5px 5px 5px;

}


2.4 BoardController /manager/write (Post) 동작

  @RequestMapping(value="/write", method=RequestMethod.POST)

  public String writePro(BoardVO board) {    // 게시판 정보를 입력 받아

service.add(board);                         // 데이터베이스에 저장하고

return "redirect:/manager/list";          // 게시판 목록 보기로 이동

  }


2.5 게시판 추가 시 메뉴 변경

- 게시판을 추가하면 그에 따라 메뉴도 추가해 주어야 한다. 메뉴는 header.jsp에서 javascript로 처리를 한다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!-- 게시판 관리 기능에 의하여 게시판의 추가, 삭제, 변경 등에 대한 처리를 하는 javascript 파일 -->

<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery-1.11.3.min.js"></script>

<script type="text/javascript" charset="utf-8" src="${pageContext.request.contextPath}/resources/js/header.js"></script>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/header.css"/>

<script type="text/javascript" charset="utf-8">

  sessionStorage.setItem("contextpath", "${pageContext.request.contextPath}");

</script>

<nav>

  <div id="gnbMenu">

    <ul id="gnb">

      <li class="item" id="item1">

<a href="" class="menu">지식창고</a>

<div class="sub" id="sub1">

  <ul class="subGnb" id="subGnb1">

    <!-- 게시판 추가로 인해 게시판 보기 메뉴가 추가될 영역 (javascript로 처리 : header.js)

    <li><a href="${pageContext.request.contextPath}/article/list?board_id=1&p=1">시 모음</a></li>

    -->

  </ul>

</div>

      </li>

      <li class="item" id="item2">

<a class="menu" href="<c:url value="/manager/list"/>">관리자</a>

      </li>

    </ul>

  </div>

</nav>


2.6 header.js

$(function() {

  var ctx = getContextPath();    // 프로젝트의 contextPath를 가져오는 부분

  getMenuInfo();                    // 서버로부터 메뉴처리를 위한 게시판 정보를 가져 온다.


  var gnbArea = document.getElementById("gnb");

  var menuItemAll = gnbArea.getElementsByTagName("li");

  var menuItem = new Array();

  var currentItem, prevItem;

  for(i=0;i<menuItemAll.length;i++){

    if((menuItemAll[i].className).indexOf("item") > -1){

      menuItem.push(menuItemAll[i]);

    }

  }


  for(i=0;i<menuItem.length;i++){

    var link = menuItem[i].getElementsByTagName("a")[0];

    var layer = menuItem[i].getElementsByTagName("div")[0];

    var subLinks = menuItem[i].getElementsByTagName("div")[0].getElementsByTagName("a");


    link.onmouseover = layer.onmouseover = link.onfocus = function(e){

      currentItem = this.parentNode;

      if(prevItem){

removeClass(prevItem,"on");

      }

      addClass(currentItem,"on");

      prevItem = currentItem;

    }

    link.onmouseout = layer.onmouseout = function(e){

      removeClass(this.parentNode,"on");

    }

    link.onkeydown = function(e){

      if (event.shiftKey && event.keyCode == 9){

  removeClass(this.parentNode,"on");

      }

    }

    for(j=0;j<subLinks.length;j++){

      subLinks[subLinks.length-1].onblur = function(e){

        removeClass(this.parentNode.parentNode.parentNode.parentNode,"on");

      }

    }

  }


  // 서버로부터 게시판에 대한 메뉴 처리를 위해 게시판 정보를 가져오는 부분

  function getMenuInfo() {        // 서버로 /manager/getMenuInfo 요청

    $.get(ctx + "/manager/getMenuInfo", function(data, status) {

      var str = "";

      $(data).each(function() {

        str += "<li><a href='" + ctx + "/article/list?board_id=" + this.board_id + "'>" + this.board_name + "</a></li>";

      });

      $('#subGnb1').html(str);

    });

  }


  // 프로젝트의 contextPath를 구하는 함수 : sessionStorage를 사용 (보통 header.jsp에서 script를 사용하여

  // sessionStorage에 contextPath를 저장하고, 이 파일에서 그것을 이용한다.

  function getContextPath() {

    return sessionStorage.getItem("contextpath");

  }

  // 액션 : 해당 클래스를 보여주기 위하여 on 클래스를 추가하는 루틴

  function addClass(ele, cls){

    var eleCln = ele.className;

    if(eleCln.indexOf(cls) == -1){

      ele.className = eleCln + " " + cls;

    }

  }


  // 해당 클래스를 안보이게 처리하기 위해 on class를 삭제하는 루틴

  function removeClass(ele, cls){

    var eleCln = ele.className;

    ele.className = eleCln.split(" " + cls)[0];

  }

});


2.7 서버에서 /manager/getMenuInfo 처리하는 부분

header.js에서 메뉴 처리를 위해 서버로 게시판 정보를 요청하는 /manager/getMenuInfo 부분이 있는데, 이에 대한 서버의 처리 부분이다.

  @RequestMapping(value="/getMenuInfo", method=RequestMethod.GET)

  public @ResponseBody List<MenuVO> getMenuInfo() {

List<MenuVO> list = service.getMenuInfo();

return list;

  }


- MenuVO 정의 : 메뉴 처리를 위해 필요한 정보를 나타낸다. (board_id, url, board_name)

package com.example.board.domain;


public class MenuVO {

private int board_id;             // 게시판 아이디 (게시판을 고유하게 식별)

private String url;                 // 게시글을 관리하기 위한 페이지로 이동을 위한 URL 정보

private String board_name;    // 메뉴에 표시되는 글

// getter, setter, toString() 등은 생략...

}


2.8 boardMapper.xml : 데이터베이스에서 MenuInfo를 추출하는 부분

  <!-- 게시판에 따른 메뉴 정보를 얻어내는 부분 -->

  <select id="getMenuInfo" resultType="com.example.board.domain.MenuVO">

    select board_id, url, board_name from board where board_id > 0 order by board_id desc

  </select>


Posted by 세상을 살아가는 사람
,

보통 테이블을 만들고 데이터를 넣기 위하여 SQL insert 구문에서 sequence의 nextval을 사용한다.

그런데 나는 이상하게 처음에 들어가는 값이 2부터 시작한다.

처음에 sequence를 정의할 때 start 값을 1로 주고 increment 값을 1로 하였기 때문에

새로운 row 데이터를 삽입하면 nextval이 2가 되어 처음부터 id가 2인 값부터 시작하는 것으로 판단이 된다.

어떻게 생각하면 당연한 것으로 생각이 되지만 id가 1부터 시작하는 것이 맞다고 생각이 든다.

그래서 1부터 시작하는 방법에 대하여 알아 보았다.

Sequence를 생성한 후

SQL> alter sequence board_sequence increment by -1 minvalue 0;

를 수행한다.

그러면 nextval의 값이 -1씩 증가하도록 설정하게 되고, nextval의 값이 0이 된다.

이때 다시 다음을 수행해 준다.

SQL> alter sequence board_sequence increment by 1 minvalue 0;

그러면 nextval의 값이 1부터 시작하게 된다.

다시 정리하면

1. sequence를 만든다.

2. SQL> alter sequence board_sequence increment by -1 minvalue 0; 를 수행

3. SQL> alter sequence board_sequence increment by 1 minvalue 0; 를 수행

4. 테이블에 데이터를 추가(insert)하면 id가 1부터 시작하게 된다.

Good Luck!

Posted by 세상을 살아가는 사람
,

이전에 기술한 내용은 다음과 같다.

- 게시판 관리기능 : 여러 개의 게시판이 필요할 때 (1/) : http://talanton.tistory.com/51

  + 요구사항

  + 데이터베이스 테이블 고려사항

- 게시판 관리기능 : 여러 개의 게시판이 필요할 때 (2/) : http://talanton.tistory.com/53

  + 새로운 스프링 프로젝트 bdmanager의 생성

  + pom.xml 설정 변경 : java-version 및 spring version의 설정

  + 프로젝트 설정 변경 : java-version 및 tomcat 8.5 사용 설정

  + 프로젝트의 Encoding을 UTF-8로 설정

  + 데이터베이스 테이블의 설정 : Oracle 11g를 사용


여기서는 화면설계와 게시판 관리기능에 대한 구현을 기술한다.

1. 화면 설계

 위 그림과 같이 두 개의 메뉴가 있고, 지식창고 메뉴를 누르면 서브 메뉴가 보이는 구조를 가진다.

- header : gnb로 지식창고와 관리자 메뉴를 가지고, 지식창고 메뉴를 누르면 게시판에 대한 서브 메뉴가 나타난다.

- section : lnav와 content라는 두개의 section으로 구성된다.

- footer : 영역만 표시한다. (중요하지 않음)


2. 관리자 메뉴 처리

  - "관리자" 메뉴를 누르면, 게시판 관리 기능의 "게시판 목록보기" 창으로 이동한다. (url /board/manager/list)

2.1 새로운 BoardController.java를 생성

package com.example.board.controller;


import javax.inject.Inject;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import com.example.board.service.BoardService;


@Controller

@RequestMapping("/manager")

public class BoardController {

private static final Logger logger = LoggerFactory.getLogger(BoardController.class);

@Inject

private BoardService service;    // 게시판 관리기능을 처리하기 위해 BoardService를 주입한다.

@RequestMapping(value="/list", method=RequestMethod.GET)

public void list(Model model) throws Exception {    // View로는 기본적으로 list.jsp가 사용된다.

model.addAttribute("list", service.list());         // 게시판 목록 정보를 가져온다.

}

}

2.2 BoardService.java 생성

package com.example.board.service;


import java.util.List;

import com.example.board.domain.BoardVO;


public interface BoardService {

public List<BoardVO> list() throws Exception;

}


2.3 BoardServiceImpl.java 생성

package com.example.board.service;


import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;

import com.example.board.domain.BoardVO;

import com.example.board.persistence.BoardDAO;


@Service        // Spring에서 동작하기 위해 Service annotation 사용

public class BoardServiceImpl implements BoardService {

@Inject

private BoardDAO dao; // 데이터베이스 처리를 위한 DAO를 주입


@Override

public List<BoardVO> list() throws Exception {

return dao.list(); // 게시판 목록 정보를 데이터베이스로부터 가져 온다.

}

}


2.4 BoardDao.java 생성

package com.example.board.persistence;


import java.util.List;

import com.example.board.domain.BoardVO;


public interface BoardDAO {

public List<BoardVO> list() throws Exception;

}


2.5 BoardDAOImpl.java 생성

package com.example.board.persistence;


import java.util.List;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;

import org.springframework.stereotype.Repository;

import com.example.board.domain.BoardVO;


@Repository        // Spring에서 동작하기 위해 Repository annotation 사용

public class BoardDAOImpl implements BoardDAO {

@Inject

private SqlSession session; // MyBatis 처리를 위한 SqlSession을 주입

private static String namespace = "com.example.board.mappers.BoardMapper";


@Override

public List<BoardVO> list() throws Exception { // MyBatis에서 게시판 목록 정보를 가져온다.

return session.selectList(namespace + ".list");

}

}


2.6 boardMapper.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.board.mappers.BoardMapper">

<select id="list" resultType="com.example.board.domain.BoardVO">

<![CDATA[

select 

board_id, board_name, board_type, url, secret, read_allow, write_allow, reply_allow, modify_allow, remove, download, upload, nAttach, aSize, display_format, creating_date, board_desc

from 

board 

where board_id > 0 

order by board_id desc, creating_date desc

]]>  

</select>

</mapper>


2.7 BoardVO.java

  게시판 정보에 대한 VO를 정의한다.

package com.example.board.domain;


import java.util.Date;


public class BoardVO {

private Integer board_id;

private String board_name;

private String board_type;

private String url;

private String secret;

private String read_allow;

private String write_allow;

private String reply_allow;

private String modify_allow;

private String remove;

private String download;

private String upload;

private Integer nAttach;

private String aSize;

private Integer display_format;

private Date creating_date;

private String board_desc;


        /* getter와 setter 및 toString()을 설정해 준다. (생략) */

}


3. 설정

3.1 데이터베이스에 대한 접근을 위한 설정

- pom.xml

    /* 오라클 데이터베이스 기능을 이용하기 위한 Maven Repository를 추가 */

    </properties>

    <!-- oracleDB -->

    <repositories>

<repository>

<id>mesir-repo</id>

<url>http://maven.jahia.org/maven2</url>

</repository>

    </repositories>

    <dependencies>


    /* 데이터베이스 처리를 위한 MyBatis, Oracle dependency 추가 *

    <!-- DB  -->

    <dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>4.3.1.RELEASE</version>

    </dependency>

    <dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.4.1</version>

    </dependency>

    <dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.3.1</version>

    </dependency>

    <dependency>

<groupId>oracle</groupId>

<artifactId>ojdbc14</artifactId>

<version>10.2.0.4</version>

    </dependency>


3.2 root-context.xml 변경

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns:context="http://www.springframework.org/schema/context"

  xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"

  xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring

    http://mybatis.org/schema/mybatis-spring-1.2.xsd

    http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd

    http://www.springframework.org/schema/context

    http://www.springframework.org/schema/context/spring-context-4.1.xsd">

  <!-- Root Context: defines shared resources visible to all other web components -->

  <!-- DB연결 -->

  <!-- 오라클 JDBC driver -->

  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

    <property name="driverClassName" value="oracle.jdbc.OracleDriver"></property>

    <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"></property>

    <property name="username" value="jspwork"></property>

    <property name="password" value="jspwork0"></property>

  </bean>

  <!-- 오라클과 마이바티스 연결 -->

  <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">

    <property name="dataSource" ref="dataSource"/>

    <property name="configLocation" value="classpath:/mybatis-config.xml"></property>

    <property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml"/>

  </bean>

  <!-- 마이바티스 -->

  <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">

    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>

  </bean>

  

  <!-- Service와 DAO에 대한 주입을 위해, Service와 DAO가 있는 경로를 설정 -->

  <context:component-scan base-package="com.example.board.persistence"></context:component-scan>

  <context:component-scan base-package="com.example.board.service"></context:component-scan>

</beans>


3.3 resources/mybatis-config.xml

  mapper에서 VO에 대한 경로를 알 수 있도록 mybatis-config에서 설정해 준다.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration

  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

  <typeAliases>

    <package name="com.example.board.domain"/>    

  </typeAliases>

</configuration>


4. view 관련 파일

4.1 home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ page session="false" %>

<html>

<head>

<title>Home</title>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/common.css"/>

</head>

<body>

<header>  <!-- 헤더 영영 정의 -->

<jsp:include page="common/header.jsp" flush="false"/>

</header>

<section id="wrap">    <!-- 본문 영역 정의 -->

<section id="lnav">    <!-- 사이드 메뉴 영영 정의 -->

<jsp:include page="common/lnav.jsp" flush="false"/>

</section>

<section id="content">    <!-- 내용 영역 정의 -->

</section>

</section>

<footer>    <!-- 풋터 영역 정의 -->

<jsp:include page="common/footer.jsp" flush="false"/>

</footer>

</body>

</html>


4.2 common.css

@CHARSET "UTF-8";

* {

margin: 0px;

padding: 0px;

border: 0;

}

html[lang^="ko"] {

font-family: "Nanum Gothic", "나눔 고딕", "NanumGothic", sans-serif;

}

header {

width: 980px;

height: 90px;

background-color: black;

margin: 0 auto;

}

/* content */

#wrap {

width: 980px;

margin: 0 auto;

margin-top: 5px;

min-height: 770px;

}

#lnav {

width: 178px;

float:left;

border: 1px solid black;

min-height: 768px;

}

#content {

width: 758px;

float:right;

border: 1px solid black;

padding: 20px;

min-height: 728px;

}

footer {

clear: both;

width: 980px;

height: 50px;

background-color: black;

margin: 0 auto;

margin-top: 5px;

}


4.3 header.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!-- 게시판 관리 기능에 의하여 게시판의 추가, 삭제, 변경 등에 대한 처리를 하는 javascript 파일 -->

<script type="text/javascript" charset="utf-8" src="${pageContext.request.contextPath}/resources/js/header.js"></script>

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/header.css"/>

<nav>

  <div id="gnbMenu">

    <ul id="gnb">

      <li class="item" id="item1">

<a href="" class="menu">지식창고</a>

<div class="sub" id="sub1">

  <ul class="subGnb" id="subGnb1">

    <!-- 새로운 게시판 추가 시, 추가될 li 영역 -->

<!-- 

    <li><a href="${pageContext.request.contextPath}/article/list?board_id=1&p=1">시 모음</a></li>

     -->

  </ul>

</div>

      </li>

      <li class="item" id="item2">

<a href="manager/list" class="menu">관리자</a>

      </li>

    </ul>

  </div>

</nav>


4.4 header.css

@CHARSET "UTF-8";

ul {list-style:none;}

a {vertical-align:top; text-decoration:none;}

a:hover {color:orange;}

#gnb {position:relative; width:870px; height: 70px; padding-top: 20px;}

#gnb:after {content:" "; display:block; clear:both;}

#gnb .item {width: 260px; text-align: center; float: left; margin-right:10px; height:20px;}

#gnb .item .menu {float:left; position:relative; width:160px; height:20px;}

#gnb .item .menu span {position:absolute; width:100%; height:20px;}

#gnb .item .sub {display:none; float:left; position:relative; padding:10px 0; font-size:12px;}

#gnb .item .sub li {float: left; margin:0 10px; text-align: center; }

#gnb .item.on .sub {display:block;}

#gnb .item #sub1 {width: 680px; margin-left: 0px;}

#gnb .item li { float: left; margin: 0 10px; }


4.5 footer.css

@CHARSET "UTF-8";

#foot {

margin: 0 auto;

text-align: center;

color: white;

padding-top: 15px;

}

Posted by 세상을 살아가는 사람
,

지난 게시글 "게시판 관리기능 : 여러 개의 게시판이 필요할 때 (1/)"에 이어서 실제로 프로젝트를 만들어서 구현을 해보는 글을 기술하고자 한다.

여기서는 다중 게시판 프로젝트 : bdmanager라는 이름으로 이를 기술한다.

1. 이클립스에서 새로운 스프링 MVC 프로젝트 bdmanager를 생성한다.

2. pom.xml을 변경 : Properties 중 java-version과 Spring version을 변경

  - Java-version 변경 : 1.6 -> 1.8    // java version은 새로운 버전을 사용

  - org.springframework-version 변경 : 3.1.1.RELEASE -> 4.1.7.RELEASE    // spring도 새로운 버전 사용

3. 이클립스에서 프로젝트 설정 변경

  - 프로젝트에서 우클릭 후 Properties 선택

  - Project Facets 선택

  - java : 1.6 -> 1.8로 변경        // 자바 버전을 새로운 것으로 맞추어 준다.

  - Runtimes에서 Apache Tomcat v8.5 선택하고 "Apply" 버튼 클릭    // Tomcat v8.5 사용

  - Java Build Path Libraries 확인    // 라이브러리를 맞추어 준다.

    + JRE System Library [jdk1.8.0_121] : 자바 버전에 따라 다름 (jdk8)

    + Apache Tomcat v8.5 [Apache Tomcat V8.5]

  - Java Compiler 1.8 확인 후 "OK" 버튼을 누른다.

4. Encoding UTF-8 설정

  - web.xml에 Encoding Filter 적용 : src/main/webapp/WEB-INF/web.xml

    <filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

     <param-name>forceEncoding</param-name>

     <param-value>true</param-value>

</init-param>

    </filter>

    <filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>/*</url-pattern>

    </filter-mapping>

  - views/home.jsp에 UTF-8 pageEncoding 추가

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

  -> 이렇게 하면 기본적인 프로젝트 설정 기본단계는 완료

5. 데이터베이스 테이블 생성

  - board table 생성    // 다중 게시판 운영을 위해 게시판에 대한 설정 정보를 저장하는 테이블

    /* board type

     *   기본 : 제목, 작성자, 비밀번호, 내용, 첨부 1

     *   갤러리 : 이미지 첨부만 가능, 첨부를 thumbnail로 처리

     *   자료실 : 첨부 5개까지 가능

     *   Q&A : 비밀글 표시 및 비밀번호 검사 후 내용 표시

     *   공지사항 : 글쓰기 버튼 비활성화, 관리자만 쓰기 가능

     *   방명록 : 쓰기, 삭제, 목록보기 기능

     * url    : 링크를 누르면 이동할 페이지의 주소

     * secret : 비밀글 표시 사용 여부

     * read_allow  : 읽기 권한 (all, login, customer, business, admin)

     * write_allow : 쓰기 권한 (all, login, customer, business, admin)

     * reply  : 댓글 권한 (all, login, customer, business, admin)

     * modify : 수정 권한 (writer, admin)

     * remove : 삭제 권한 (writer, admin)

     * download : 첨부물 다운로드 권한 (all, login, customer, business, admin)

     * upload : 첨부물 업로드 권한 (all, login, customer, business, admin)

     * nAttach : 첨부 파일 수

     * aSize : 첨부파일 하나의 크기 제약

     */


    create sequence board_sequence

increment by 1

start with 1

nomaxvalue

nocycle

cache 10;


    create table board (

board_id number(11) not null primary key,

board_name varchar2(40) not null,

board_type varchar2(20) not null,

url varchar2(100),

secret char(1) default 'F',

read_allow varchar2(10),

write_allow varchar2(10),

reply_allow varchar2(10),

modify_allow varchar2(10),

remove varchar2(10),

download varchar2(10),

upload varchar2(10),

nAttach number(1),

aSize varchar2(5),

display_format number(1) default 1,

creating_date timestamp default current_timestamp,

board_desc varchar2(100)

    );

  - article table : 게시글을 저장하는 테이블

    create sequence article_sequence

increment by 1

start with 1

nomaxvalue

nocycle

cache 10;


    /*

     * article : 게시글을 저장하는 테이블

     * - article_id : 게시글 ID

     * - board_id : 게시판을 구별하는 ID

     * - writer_name : 게시글 작성자 이름

     * - email : 게시글 작성자의 ID (ID를 email로 사용하는 경우)

     * - title : 게시글 제목

     * - author : 원 저자 (시와 같은 창작물을 게시할 때, 원 저자를 기술)

     * - password : 게시글을 관리(수정, 삭제 등)할 때 필요 (로그인 하지 않은 경우 작성자를 구분하는 방법)

     * - read_count : 게시글을 읽은 횟수

     * - comment_count : 댓글의 수

     * - like_count : 좋아요를 누른 횟수

     * - ref : 게시글과 댓글에 대한 순서를 보장하는데 사용 (보통 게시글 ID)

     * - setp : 댓글에 대한 순서를 나타냄

     * - depth : 댓글에 대한 depth 정보를 저장

     * - createdAt : 게시글 생성 시간

     * - modifiedAt : 게시글을 수정한 시간

     * - content : 게시글 내용

     * - ip : 게시글을 접근하는 사용자의 IP address

     * - secret : 비밀글 여부를 나타냄 (기본 : 'F' 비밀글이 아님)

     */

    create table article (

article_id number(11) not null primary key,

board_id number(11) default 0,

writer_name varchar2(30) not null,

email varchar2(30) not null,

title varchar2(100) not null,

author varchar2(30),

password varchar2(20) not null,

read_count number(8) default 0 not null,

comment_count number(4) default 0 not null,

like_count number(8) default 0 not null,

ref number(10) default 0 not null,

step number(3) default 0 not null,

depth number(3) default 0 not null,

createdAt timestamp(6) default current_timestamp,

modifiedAt timestamp(6) default current_timestamp,

content varchar2(4000) not null,

ip varchar2(20) not null,

secret char(1) default 'F'

    );


    /* 게시글은 특정 게시판에 속하므로 게시판에 의존하게 된다.

     * 따라서 다음과 같이 foreign key를 선언해 준다.

    */

    alter table article add constraint fk_board

    foreign key (board_id) references board (board_id);

Posted by 세상을 살아가는 사람
,

다음은 웹을 배우면서 홈 페이지를 개발하는 프로젝트를 수행한다. 이때 필요한 기능들을 기술해 본다.

  • 회원관리 기능 : 회원가입, 약관 및 개인정보수집 동의, 로그인, 로그아웃, 아이디/비밀번호 찾기, 회원탈퇴, 회원정보 관리(목록보기, 회원정보보기, 변경), 아이디 중복 검사, 아이디/비밀번호 입력제약 Audit 기능, 개인정보 암호화 저장

  • 게시판 기능 : 페이징 처리, 댓글 순서 관리, 목록보기, 게시글 쓰기, 게시글 수정, 게시글 삭제, 게시글 검색, 스마트 에디터 사용, 첨부파일 저장, 비밀글 기능, 게시판 종류 및 다른점 처리 (방문록, Q&A, 공지사항, 게시판, 자료실, FAQ), 게시판 권한관리(읽기, 쓰기, 답변, 첨부 허용여부 관리), 다중 게시판 만들기(게시판이 여러 개일 때 관리방법), 게시판 관리(목록보기, 추가, 변경, 삭제)

  • 메뉴 체계 및 링크 연동 : 헤더에 있는 nav 및 사이드바에 있는 lnav

    • nav : 몇 단으로 할 것인가? 마우스를 갖다 대면 전체 메뉴를 보여 줄 것인가? 아니면 1단 메뉴에 속한 2단 세부 메뉴를 보여줄 것인가?

    • lnav : nav와 연동되어 표시되는 방법은? 몇 단으로 구성할 것인가?

    • 관리자 측면에서는 메뉴의 관리를 할 것인가? 즉, 메뉴에 대한 추가, 삭제, 변경 및 목록보기, 보기를 허용할 것인지? 아니면 고정적으로 운영할 것인지 이다.

  • SNS 연동 기능 : 페이스북 로그인, 카카오톡 로그인, 트위터 로그인, 게시글 공유(카톡, 페이스북, 트위터), 좋아요, 별점

  • 반응형 웹 만들기 : 휴대폰에서 브라우저에서 접속할 때 동작

  • 실행환경 서버 관리(윈도우 서버, 리눅스 서버) : 네트워크, 하드웨어(CPU, 메모리, 하드디스크, 백업장치), 도메인관리, 호스팅 관리 , 실행환경 구축

  • 전자정부표준프레임 : 공통기능(모바일 포함), 하이브리드앱

  • HTML5 기능 : 비디오, 오디오, 캔버스, 드래그 & 드롭, 지오로케이션, 웹 스토리지, Indexed DB, 파일, 커뮤니케이션, 웹워커, 히스토리, 오프라인

  • Open API 연동 : Rest API, SOAP API 연동 (공공데이터 연동)

  • Open API 기능 제공 : REST API 제공

  • 주기적인 Job 수행 방법


Posted by 세상을 살아가는 사람
,

홈 페이지를 만들다보면 게시판을 여러 개를 만들 필요가 있다. 또한 게시판 마다 게시글의 읽기, 쓰기, 댓글달기, 첨부파일 달기 등의 권한을 달리 설정해주어야 하는 경우가 발생한다.

여기서는 그런 경우에 고려하여야 할 사항과 구현방법에 대하여 기술한다.

1. 요구사항

- 여러 개의 게시판을 운영한다. : 게시판 생성, 게시판 정보 변경, 게시판 삭제, 게시판 목록 보기, 게시판 정보 보기 등의 기능이 필요

- 게시판에 대한 속성 설정 : 읽기, 쓰기, 댓글달기, 비밀글 여부, 첨부파일 허용여부, 첨부파일 허용용량, 게시글 수정, 게시글 삭제

- 게시판 종류 : 기본, 갤러리, 자료실, Q&A, 공지사항, 방명록

  + 기본 게시판 : 제목, 작성자, 비밀번호, 내용, 첨부 1개

  + 갤러리 : 이미지 첨부만 가능, 첨부는 thumbnail로 처리

  + 자료실 : 첨부 파일은 5개까지 가능

  + Q&A : 비밀글 표시 및 비밀번호 검사 후 내용 표시

  + 공지사항 : 글쓰기 버튼 비활성화, 관리자만 쓰기 가능

  + 방명록 : 내용과 첨부 사진 올리기만 가능, 비밀글(쓰기, 답변)

여기에 기술된 요구사항은 대략적인 것으로 개발을 위해서는 구체적인 요구사항이 세분화 되어져야 한다.

2. 데이터베이스 테이블

2.1 board table

- board_id : 게시판을 식별하는 ID (정수)

- board_name : 게시판 이름

- board_type : 게시판 종류 (기본 게시판, 갤러리, 자료실, Q&A, FAQ, 공지사항, 방명록)

- url : 게시판에 접속하기 위한 URL 주소

- secret : 비밀글 여부

- read_allow : 읽기 허용 여부 (사용자 유형에 따라 제어 : 로그인 안했을 때, 로그인 했을 때, 관리자)

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- write_allow : 쓰기 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 사용자만 허용 : 별도의 쓰기 허용자 정보 저장 필요 (추후 고려)

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- reply_allow : 댓글 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- modify_allow : 수정 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 작성자만 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- delete_allow : 삭제 허용 여부

  + 작성자만 허용

  + 관리자만 허용

- download : 첨부파일 다운로드 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- upload : 첨부파일 업로드 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 사용자만 허용 (추후 고려)

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- nAttach : 첨부 가능한 파일의 수 (0~최대 5개)

- aSize : 첨부 가능한 파일의 용량 (최대 100MB까지)

  + 0, 50KB, 200KB, 1MB, 10MB, 100MB

- display_format : 본문 표시 여부

  + 1 : 제목만 표시

  + 2 : 본문(내용) 일부 표시

  + 3 : 본문(내용) 전부 표시

- board_desc : 게시판 설명

2.2 article table

- article_id : 게시글 id

- boardId : 게시판 id (하나의 테이블에 모든 게시판의 게시글을 저장하므로 게시판을 구분해 주어야 함)

- writer_name : 게시글 작성자 이름(O) -> 불필요할 수 있음(원 저자)

- email : 게시글 작성자 id (이메일 형식)

- title : 제목(O)

- author : 시와 같은 창작물을 게시할 때 원 저자를 입력(O)

- password : 비밀번호 (로그인 하지 않은 사용자가 게시글을 작성시, 추후 게시글 삭제 때 필요)

- read_count : 게시글을 읽은 횟수

- comment_count : 게시글에 대한 댓글 수

- like_count : 좋아요 수

- ref : 게시글과 댓글에 대한 정렬에 사용

- step : 게시글과 댓글에 대한 정렬에 사용

- depth : 게시글과 댓글에 대한 정렬에 사용

- createdAt : 게시글 작성 시간

- modifiedAt : 게시글 수정 시간

- content : 게시글 본문 내용

- ip : 사용자가 접속한 IP address

- secret : 비밀글 여부 (default false)

3. 추가로 고려되어야 할 사항

- 게시판 생성 후, 메뉴와의 연계방법 입니다. 동적으로 게시판을 생성하였더라도 그 게시판을 사용하려면 홈 페이지의 메뉴 체계와 동적으로 연계가 되어야 하는데 그 부분은 상당히 난이도가 있는 부분 같습니다. 전체 구조와도 연계가 되기 때문 입니다. 그 부분은 나중에 시간이 있을 때 고려하기로 합니다.


Posted by 세상을 살아가는 사람
,

웹 프로그래밍을 하다보면 회원가입과 로그인 기능을 구현하게 된다.

이때 비밀번호는 중요한 개인 정보로서 운영자도 알 수 없게 암호화하여 저장을 하는 것을 요구하고 있다.

여기서는 어떻게 하면 암호화하여 저장할 수 있는지 기술한다.

security.zip

첨부한 소스코드는 spring mvc 프로젝트로 만든 sample 코드이다.

파일 구성은 다음과 같다.

- HomeController.java : home()

  + 프로젝트가 시작되면 호출된다. home.jsp를 보여준다.

- home.jsp : 메인 화면으로 회원가입과 로그인에 대한 메뉴 버튼이 있다.

  + 회원가입 메뉴를 누르면 회원가입 입력창으로 이동

  + 로그인 메뉴를 누르면 로그인 입력창으로 이동

- joinForm.jsp : 회원가입 정보(아이디, 비밀번호)를 입력 받아 서버로 회워가입을 요청한다.

- HomeController.java : join() - GET

  + id와 pw를 입력 받고, pw를 암호화하여 임시로 저장하는 클래스에 저장한다.

  + joinSuccess.jsp를 보여준다.

- joinSuccess.jsp

  + 회원가입 성공을 알리고

  + 홈(메인)으로 이동하는 메뉴를 둔다.

- loginForm.jsp : 로그인에 필요한 정보(id, password)를 입력 받아 서버로 로그인을 요청한다.

- HomeController login() - Post

  + 로그인 정보 (id, pw)로 입력받은 것과

  + 임시로 저장한 암호화된 비밀번호를 가져와

  + 로그인 시 입력한 비밀번호와 암호화된 비밀번호를 이용하여 사용자에 대한 비밀번호 검증을 한다.

  + 성공하면 loginSuccess.jsp로 이동하고

  + 실패하면 loginFail.jsp로 이동한다.

- loginSuccess.jsp : 로그인 성공을 알리고, 홈으로 이동하는 메뉴를 보여준다.

- loginFail.jsp : 로그인 실패를 알리고 홈으로 이동하는 메뉴를 보여준다.

- Utilities.java

  + makeSha256BCrypt() : 비밀번호를 암호화 한다. 

  + checkSha256BCrypt() : 로그인시 입력한 비밀번호와 데이터베이스에 저장된 비밀번호를 사용하여 비밀번호 검증을 수행한다.

- SHA256.java : SHA 알고리즘을 수행한다.

- BCrypt.java : 암호화나 복호화를 수행한다.

Posted by 세상을 살아가는 사람
,

게시판의 본문을 블로그 작성하듯 도와주는 소프트웨어로 네이버에서 제공하는 스마트에디터가 있다. 이것을 스프링 MVC 프로젝트에서 적용하는 방법을 기술한다.

1. 이클립스에서 File>New>Spring Legacy Project>Spring MVC Project로 새로운 스프링 프로젝트 se를 생성한다.

  - 새로 만든 스프링 프로젝트는 기본적으로 "/"가 HomeController에서 동작하여 home.jsp를 처움에 보여준다.

2. home.jsp를 "게시글 쓰기" 형태로 수정한다.

1 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

 2 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

 3 <%@ page session="false" %>

 4 <html>

 5 <head>

 6  <title>Home</title>

 7  <script type="text/javascript" src="<c:url value="/resources/js/jquery-1.11.3.min.js"/>"></script>

 8  <script type="text/javascript" src="<c:url value="/resources/smarteditor/js/HuskyEZCreator.js"/>" charset="utf-8"></script>

 9  <script type="text/javascript" charset="utf-8" src="<c:url value="/resources/js/writeForm.js"/>"></script>

10  <link rel="stylesheet" href="<c:url value="/resources/css/common.css"/>"/>

11  <script type="text/javascript" charset="utf-8">

12  sessionStorage.setItem("contextpath", "${pageContext.request.contextPath}");

13  </script>

14 </head>

15 <body>

16 <form action="write" method="post" id="frm">

17  <table>

18  <tr>

19  <td>제목</td>

20 <td><input type="text" name="title" id="title" placeholder="제목"></td>

21 </tr>

22 <tr>

23 <td style="margin:0; padding:0;" colspan="2"><textarea name="smarteditor" id="smarteditor" rows="10" cols="100" style="width:100%; height:412px;"></textarea></td>

24 </tr>

26 </table>

27 <input id="addBtn" type="submit" value="전송">

28 </form>

29 </body>

30 </html>


1줄 : pageEncoding을 UTF-8로 설정

 7줄 : Ajax Operation을 위해 jquery js (jquery-1.11.3.min.js)를 사용

 8줄 : 스마트에디터를 사용하기 위해 필요

 9줄 : javascript를 정의하기 위해 writeForm.js를 사용

10줄 : 디자인을 깨끗하게 하기 위해 common.css를 사용

11줄~13줄 : contextPath를 javascript에서 사용하기 위해 sessionStorage에 저장

16줄 : form을 사용. "전송" 버튼을 누르면 javascript에서 textarea에 입력된 데이터를 가져와 request에 추가하도록 처리를 한다. 이후 서버로 입력 파라미터들이 전달되고 action=write가 서버에서 수행됨.

23줄 : 스마트에디터를 사용하여 본문을 입력하도록 정의해 준다.

27줄 : 전송 버튼을 누르면 javascript가 동작

3. smarteditor 파일들 : http://naver.github.io/smarteditor2/user_guide/ 참조

  - SmartEditor Basic 2.0은 JavaScript로 구현된 웹 기반의 WYSIWYG 편집기이다. 글꼴, 글자 크기, 줄 간격 등을 자유롭게 설정할 수 있으며, 단어 찾기/바꾸기와 같은 편리한 기능을 제공한다.

  - https://github.com/naver/smarteditor2에서 최신 버전을 다운로드

    -> 이 버전은 사진(이미지) 업로드 기능이 삭제된 것이다. 무슨 이유인지 모르겠지만 네이버가 이 기능을 배포판에서 빼 버렸다.

se.zip

  - 디렉토리 구조는 다음과 같다.

    + src/main/webapp/resources/smarteditor 디렉토리 밑에 다음과 같은 파일이 존재

    + css

    + img

    + js

    + photo_uploader

      * plugin

      * popup

        # attach_photo.js      // 사진 추가에 대한 javascript 처리 (서버와 연동하여 파일 저장)

        # callback.html         // 사진 추가에 대한 결과 처리

        # file_uploader.php    // 다중 사진(이미지) 본문 추가에 대한 서버 처리 루틴 (PHP 방식)

        # file_uploader_html5.php    // 단일 사진(이미지) 본문 추가에 대한 서버 처리 루틴 (PHP 방식)

        # jindo.fileuploader.js

        # jindo.min.js

        # photo_uploader.html        // popup 창에 대한 HTML 파일

    + smartEditor2Skin.html

    + 일부 파일 생략 (...)


4. writeForm.js

 1 $(function() {

 2   //전역변수선언

 3   var editor_object = [];

 4   var ctx = getContextPath();

 5

 6   nhn.husky.EZCreator.createInIFrame({    // 입력 창을 구성하는 부분을 구동

 7     oAppRef: editor_object,

 8     elPlaceHolder: "smarteditor",

 9     sSkinURI: ctx + "/resources/smarteditor/SmartEditor2Skin.html",

10     htParams : {

11       // 툴바 사용 여부 (true:사용/ false:사용하지 않음)

12       bUseToolbar : true,             

13       // 입력창 크기 조절바 사용 여부 (true:사용/ false:사용하지 않음)

14       bUseVerticalResizer : true,     

15       // 모드 탭(Editor | HTML | TEXT) 사용 여부 (true:사용/ false:사용하지 않음)

16       bUseModeChanger : true,

17       fOnBeforeUnload : function(){

18                

19       }

20     }

21   });

22    

23   //전송버튼 클릭이벤트

24   $("#addBtn").click(function(){

25     //id가 smarteditor인 textarea에 에디터에서 대입

26     editor_object.getById["smarteditor"].exec("UPDATE_CONTENTS_FIELD", []);

27         

28     // 이부분에 에디터 validation 검증 : 때에 따라 가비지 correction 등에 대한 처리 필요

29     var el = document.createElement('html');

30     el.innerHTML = editor_object.getById["smarteditor"].elPlaceHolder.value;    // 본문 정보 넣기

31     //폼 submit

32     $("#frm").submit();    // 서버로 전송 (게시글 추가 처리 요청)

33   });

34    

35   function getContextPath() {

36     return sessionStorage.getItem("contextpath");

37   }

38 });


6~21줄 : home.jsp에서 사용한 textarea 부분을 구성해 주는 부분

23~33줄 : textarea에 입력한 정보를 "smarteditor"란 parameter에 넣어주고 form 형태로 서버로 전송


5. 사진(이미지) 추가에 대한 동작 처리 : smarteditor/photo_uploader/popup/attach_photo.js

  - 사진(이미지)를 추가하는 방법은 하나의 이미지만 추가 하는 방법과 여러 개의 사진을 추가하는 방법으로 두 가지를 제공한다. 필요에 따라 자신에게 맞는 방법을 선택한다.

    + 아래 처럼 bSupportDragAndDropAPI 값을 true/false로 설정함에 따라 다르게 설정된다.

        //File API 지원 여부로 결정

function checkDragAndDropAPI(){

try{

if( !oNavigator.ie ){

if(!!oNavigator.safari && oNavigator.version <= 5){

bSupportDragAndDropAPI = false;

}else{

bSupportDragAndDropAPI = true; // multiple 파일 업로드

// bSupportDragAndDropAPI = false; // single 파일 업로드

}

} else {

bSupportDragAndDropAPI = false;

}

}catch(e){

bSupportDragAndDropAPI = false;

}

}

  - function html5Upload()    // 하나의 사진 이미지 추가에 대한 처리

    + popup 창이 뜨고, 다음을 선택한 후 확인 버튼을 누르면, 선택한 사진을 서버로 전송하는 부분

    + sUploadURL = '/se/file_uploader_html5';    // 사진 업로드 URL

  - function callFileUploader()    // 다중 사진 이미지 추가에 대한 처리

    + sUrl : '/se/file_uploader',    // 서버에서 처리하는 URL

    + sCallback : '/resources/smarteditor/photo_uploader/popup/callback.html',    // 처리 결과 응답

6. HomeController.java    // 사진 이미지 추가에 대한 처리

  - resources/upload 디렉토리에 사진 이미지 저장 (실제 저장되는 폴더는 workspace/.metadata/.plugins/\org.eclipse.wst.server.core\tmp1\wtpwebapps\se\resources\upload 밑에 저장됨

@RequestMapping(value="/file_uploader", method=RequestMethod.POST)

public void file_uploader(HttpServletRequest request, HttpServletResponse response) throws IOException, FileUploadException {

  request.setCharacterEncoding("utf-8");

  String return1="";

  String return2="";

  String return3="";

  String name = "";

  if (ServletFileUpload.isMultipartContent(request)){

    ServletFileUpload uploadHandler = new ServletFileUpload(new DiskFileItemFactory());

    //UTF-8 인코딩 설정

    uploadHandler.setHeaderEncoding("UTF-8");

    List<FileItem> items = uploadHandler.parseRequest(request);

    //각 필드태그들을 FOR문을 이용하여 비교를 합니다.

    for (FileItem item : items) {

      if(item.getFieldName().equals("callback")) {

return1 = item.getString("UTF-8");

      } else if(item.getFieldName().equals("callback_func")) {

return2 = "?callback_func="+item.getString("UTF-8");

      } else if(item.getFieldName().equals("Filedata")) {

//FILE 태그가 1개이상일 경우

if(item.getSize() > 0) {

  name = item.getName();

  String ext = item.getName().substring(item.getName().lastIndexOf(".")+1);

  //파일 기본경로

  String defaultPath = context.getRealPath("/");

  //파일 기본경로 _ 상세경로

  String path = defaultPath + "upload" + File.separator;

                

  File file = new File(path);

          //디렉토리 존재하지 않을경우 디렉토리 생성

          if(!file.exists()) {

            file.mkdirs();

          }

          //서버에 업로드 할 파일명(한글문제로 인해 원본파일은 올리지 않는것이 좋음)

          String realname = UUID.randomUUID().toString() + "." + ext;

          ///////////////// 서버에 파일쓰기 ///////////////// 

          InputStream is = item.getInputStream();

          OutputStream os=new FileOutputStream(path + realname);

          int numRead;

          byte b[] = new byte[(int)item.getSize()];

          while((numRead = is.read(b,0,b.length)) != -1){

            os.write(b,0,numRead);

          }

  if(is != null)  is.close();

  os.flush();

  os.close();

  ///////////////// 서버에 파일쓰기 /////////////////

  String root = request.getContextPath();

          return3 += "&bNewLine=true&sFileName="+name+"&sFileURL=" + root + "/upload/"+realname; // by ksseo

}else {

  return3 += "&errstr=error";

}

      }

    }

  }

  response.sendRedirect(return1+return2+return3);

}


  - 추후 가비지 correction이 진행되어야 함. (사진 이미지를 추가한 후 삭제할 경우, 또는 사진을 추가하면서 글을 작성하다가 다른 창으로 이동할 경우 등) -> writeForm.js에서 처리해 주어야 함


  - form에 대한 처리 : 데이터베이스에 게시글 정보 저장 (여기서는 생략...)

@RequestMapping(value = "/write", method = RequestMethod.POST)

public ModelAndView write(HttpServletRequest request) throws IOException, FileUploadException {

  String title = request.getParameter("title");

  String smarteditor = request.getParameter("smarteditor");


  ModelAndView model = new ModelAndView("write");

  model.addObject("title", title);

  model.addObject("smarteditor", smarteditor);

  return model;

}


7. pom.xml 고려사항 : file upload dependency 추가

<!-- file upload -->

<dependency>

  <groupId>commons-fileupload</groupId>

  <artifactId>commons-fileupload</artifactId>

  <version>1.3.1</version>

</dependency>


8. servlet-context.xml 고려사항 : multipartResolver 추가

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

  <beans:property name="maxUploadSize" value="52428800"></beans:property>

</beans:bean>


9. write.jsp : 게시글 추가 결과를 보여주는 뷰

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>홈 페이지</title>

</head>

<body>

게시글 작성 완료

제목 : ${title}<br>

본문 : <br>

${smarteditor}

</body>

</html>


10. common.css : 게시글 쓰기 CSS 파일

@CHARSET "UTF-8";

* {

margin: 0px;

padding: 0px;

border: 0;

}

html[lang^="ko"] {

font-family: "Nanum Gothic", "나눔 고딕", "NanumGothic", sans-serif;

}

/* content */

body {

width: 980px;

margin: 0 auto;

margin-top: 5px;

min-height: 770px;

}

form {

margin-top: 20px;

line-height: 2;

}

#addBtn {

width: 200px;

font-size: 14px;

padding: 10px;

background-color: blue;

color: white;

border-radius: 5px 5px 5px 5px;

margin-top:30px;

}

table {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

margin-top: 20px;

width: 100%;

}

td {

border: 1px solid black;

border-collapse: collapse;

border-spacing: 0px;

padding: 5px;

line-height: 1.5;

}


가비지 correction 과정은 추후 기술한다.

수고 하셨습니다.


참고 : http://hellogk.tistory.com/63

Posted by 세상을 살아가는 사람
,

구글 플레이 스토어에서 운영 중인 "클래식 음악 -600곡"의 2017년 1월 1달 동안 사용자들이 이용한 Top 5 (Play 및 다운로드)

Play Top 5

[1] 아베 마리아, 구노

[2] 전주곡 1권 중에서 No.8 La Fille aux Cheveux de Lin (arr. Hartmann), 드뷔시

[3] "카벨레리아 르스티카나" Intermezzo sinfonico, 마스카니

[4] 바흐 관현악 모음곡 3번 D장조 BWV 1068 II. Air, 바흐

[5] 클라리넷 5중주 A장조 KV 581 II. Larghetto, 모차르트

Download Top 5

[1] 피아노 소나타 14번 C sharp단조 op. 27-2 "월광" I. Adagio sostenuto, 베토벤

[2] 트로이메라이 - 어린이 정경 op. 15-7 (실내악 편곡), 슈만

[3] 자장가 D flat장조 op. 57, 쇼팽

[4] 동물의 사육제 Le Cygne, 생상스

[5] 타이스의 명상곡 (2010~2011년 시즌, 갈라쇼), 마스네


클래식음악 - 600곡 https://play.google.com/store/apps/details?id=com.talanton.music.player

Posted by 세상을 살아가는 사람
,

라즈베리파이를 이용해서 JSP tomcat 서버를 구동하려고 한다.

이때 Tomcat의 포트번호를 default 8080에서 80으로 변경하여 적용하는 방법을 기술한다.

리눅스나 윈도우에서는 포트번호 1024 이하는 일반 사용자가 사용하지 못하도록 binding 자체를 못하도록 하고 있다. 따라서 tomcat conf 디렉토리에서 server.xml을 변경하여 port 번호를 8080에서 80으로 변경하더하고 동작을 하지 않는다.

대신 포트번호는 8080 그대로 유지한채 iptables를 변경하여 port redirect를 수행하여 80 포트번호로 접속을 요구하면 8080 포트가 대신 동작을 하게하여 해결할 수 있다.

다음의 명령어를 라즈베리파이 터미널에서 수행하여 줌으로써 동일한 효과를 얻을 수 있다.

$ sudo iptables -A INPUT -i eth0 -p tcp --dport 80 -j ACCEPT

$ sudo iptables -A INPUT -i eth0 -p tcp --dport 8080 -j ACCEPT

$ sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080

참고 : http://stackoverflow.com/questions/10450045/why-does-tomcat-work-with-port-8080-but-not-80


Posted by 세상을 살아가는 사람
,