티스토리 뷰

Operation History


웹 페이지 안에서의 모든 기록으로, Article과 Article-Admin에 다 필요하다.

Operation History는 법적인 부분에서도 근거 자료로 활용할 수 있고, 마케팅 용도로도 활용할 수 있기때문에 반드시 수집을 해두어야 한다.

(네이버가 이걸 참 잘한다고....)


1. DB에 테이블을 만들자(테이블명: OPER_HIST)

1)HISTORY_ID : OPER_HIST의 PK (시퀀스도 만들어주어야 한다. 시퀀스 이름 : HISTORY_ID_SEQ)

2) IP : 클라이언트의 IP 주소를 담는 컬럼

3) MEMBER_ID : 클라이언트의 아이디를 담는 컬럼

4) CRT_DT : 클라이언트가 접속한 현재 시간을 담는 컬럼

5) URL : 클라이언트가 현재 머무르고 있는 URI

6) ACTION_CODE : 수행 코드

7) DESCRIPTION : 상세 수행 내용

8) ETC : 기타



2. Article 프로젝트에 history 패키지를 만든 후, vo, biz, dao 패키지를 만든다. (사용자는 Operation History를 볼 일이 없기 때문에 web 폴더가 필요 없다.) 프로젝트 구성도는 다음과 같다.


3. 이제 내용을 하나하나 채워간다.

먼저 VO부터!


OperationHistoryVO.java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

package com.ktds.smahn.history.vo;

import com.ktds.smahn.member.vo.MemberVO;

public class OperationHistoryVO extends MemberVO {

   private int historyId;

   private String ip;

   // private String memberId; (MemberVO에 정의되어 있음)

   private String createdDate;

   private String url;

   private String actionCode;

   private String description;

   private String etc;

   public int getHistoryId() {

       return historyId;

   }

   public void setHistoryId(int historyId) {

       this.historyId = historyId;

   }

   public String getIp() {

       return ip;

   }

   public void setIp(String ip) {

       this.ip = ip;

   }

   public String getCreatedDate() {

       return createdDate;

   }

   public void setCreatedDate(String createdDate) {

       this.createdDate = createdDate;

   }

   public String getUrl() {

       return url;

   }

   public void setUrl(String url) {

       this.url = url;

   }

   public String getActionCode() {

       return actionCode;

   }

   public void setActionCode(String actionCode) {

       this.actionCode = actionCode;

   }

   public String getDescription() {

       return description;

   }

   public void setDescription(String description) {

       this.description = description;

   }

   public String getEtc() {

       return etc;

   }

   public void setEtc(String etc) {

       this.etc = etc;

   }

}

Colored by Color Scripter

cs


OperationHistoryDAO.java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

package com.ktds.smahn.history.dao;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import com.ktds.smahn.history.vo.OperationHistoryVO;

import com.ktds.smahn.member.dao.Const;

import com.ktds.smahn.util.xml.XML;

public class OperationHistoryDAO {

   public void insertHistory(OperationHistoryVO historyVO) {

       

       loadOracleDriver();

       Connection conn = null;

       PreparedStatement stmt = null;

       try {

           conn = DriverManager.getConnection(Const.DB_URL, Const.DB_ID, Const.DB_PASSWORD);

           String query = XML.getNodeString("//query/operationHistory/insertHistory/text()");

           stmt = conn.prepareStatement(query);

           stmt.setString(1, historyVO.getIp());

           stmt.setString(2, historyVO.getMemberId());

           stmt.setString(3, historyVO.getUrl());

           stmt.setString(4, historyVO.getActionCode());

           stmt.setString(5, historyVO.getDescription());

           stmt.setString(6, historyVO.getEtc());

           

           stmt.executeUpdate();

       } catch (SQLException e) {

           throw new RuntimeException(e.getMessage(), e);

       } finally {

           closeDB(conn, stmt, null);

       }

       

   }

   

   private void loadOracleDriver() {

       try {

           Class.forName("oracle.jdbc.driver.OracleDriver");

       } catch (ClassNotFoundException e) {

           throw new RuntimeException(e.getMessage(), e);

       }

   }

   private void closeDB(Connection conn, PreparedStatement stmt, ResultSet rs) {

       if (rs != null) {

           try {

               rs.close();

           } catch (SQLException e) {

           }

       }

       if (stmt != null) {

           try {

               stmt.close();

           } catch (SQLException e) {

           }

       }

       if (conn != null) {

           try {

               conn.close();

           } catch (SQLException e) {

           }

       }

   }

}

Colored by Color Scripter



cs



OperationHistoryBiz.java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package com.ktds.smahn.history.biz;

import com.ktds.smahn.history.dao.OperationHistoryDAO;

import com.ktds.smahn.history.vo.OperationHistoryVO;

public class OperationHistoryBiz {

   

   private OperationHistoryDAO dao;

   

   public OperationHistoryBiz(){

       dao = new OperationHistoryDAO();

   }

   

   public void addHistory(OperationHistoryVO historyVO){

       

       dao.insertHistory(historyVO);

       

   }

}

Colored by Color Scripter

cs


그리고 추가적으로 필요한 클래스와 인터페이스들!

Description.java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package com.ktds.smahn.history.vo;

public interface Description {

   

   // 바뀌는 부분은 %s로, %s는 문자가 들어올 것이다 라는 String format

   public static final String VISIT_LOGIN_PAGE = "[%s] 가 로그인 페이지에 접근했습니다.";

   public static final String LOGIN = "[%s] 님이 로그인했습니다.";

   public static final String LOGIN_FAIL = "[%s]가 [%s]로 로그인을 시도했지만, 실패했습니다.";

   public static final String ALREADY_LOGIN = "[%s] 님이 이미 로그인되어, List 페이지로 이동합니다.";

   

   public static final String LIST = "[%s]님이 목록보기 페이지에 접근했습니다.";

   public static final String LIST_PAGING = "[%s]님이 [%s]번째 페이지로 이동했습니다.";

   public static final String LIST_SEARCH = "[%s]님이 목록보기 페이지에서 [%s]로 [%s]를 검색했습니다.";

   

   public static final String DETAIL = "[%s]님이 [%s]번째 글을 읽었습니다.";

   public static final String DETAIL_DESCRIPTION = "제목 : [%s]<br/>글쓴이 : [%s]<br/>내용: [%s]<br/>";

}

Colored by Color Scripter

cs


ActionCode.java

1

2

3

4

5

6

7

8

9

10

11

package com.ktds.smahn.history.vo;

/**

* Action Code를 상수로 저장해놓은 클래스

*

*/

public interface ActionCode {

   public static final String LOGIN = "MB_L";

   public static final String ARTICLE = "AR_L";

}

Colored by Color Scripter

cs


BuildDescription.java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

package com.ktds.smahn.history.vo;

public class BuildDescription {

   /**

    *

    * @param format

    * @param args 가변 인자(파라메터를 제한없이 넣을 수 있는)배열

    * @return

    */

   public static String get( String format, String ... args ) {

       // format : %s에 들어가는 것을 args로 바꿔줌

       String desc = String.format(format, args);

       return desc;

   

   }

}

Colored by Color Scripter

cs



3. 클래스를 다 만들었다면, 이제 jsp를 수정해준다.

Operation History 의 경우 서블릿이 없기 때문에 jsp에서 처리해준다.

서블릿에서는 request.getSession(); 이 jsp에서는 session과 동일하다. session만 쓰면 자동으로 session을 가져올 수 있다.

추가된 부분은 아래와 같다.


이해를 돕기위해 전체 코드도 첨부한다.

index.jsp

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

<%@page import="com.ktds.smahn.history.vo.ActionCode"%>

<%@page import="com.ktds.smahn.history.vo.Description"%>

<%@page import="com.ktds.smahn.history.vo.BuildDescription"%>

<%@page import="com.ktds.smahn.history.vo.OperationHistoryVO"%>

<%@page import="com.ktds.smahn.history.biz.OperationHistoryBiz"%>

<%@page import="com.ktds.smahn.member.vo.MemberVO"%>

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

   pageEncoding="UTF-8"%>

   

<%

/*

Operation History 의 경우 서블릿이 없기 때문에 jsp에서 처리해준다.

서블릿에서는    request.getSession();

jsp에서는 session만 쓰면 자동으로 session을 가져올 수 있다.

Ctrl+Shift+M : 자동 임포트

*/

   MemberVO member = (MemberVO) session.getAttribute("_MEMBER_");

   if ( member != null ){

       OperationHistoryVO historyVO = new OperationHistoryVO();

       /* 요청자의 원격 호스트를 가져오면 어떤 ip를 쓰는지 알수 있다. */

       historyVO.setIp(request.getRemoteHost());

       /* 로그인을 한 상태이기 때문에 세션에서 정보를 가져온다. */

       historyVO.setMemberId(member.getMemberId());

       historyVO.setUrl(request.getRequestURI());

       historyVO.setActionCode(ActionCode.LOGIN);

       historyVO.setDescription(BuildDescription.get(Description.ALREADY_LOGIN, member.getMemberId()));

       

       OperationHistoryBiz biz = new OperationHistoryBiz();

       biz.addHistory(historyVO);

       /* 로그인이 되어있다면 list 페이지로 이동 */

       response.sendRedirect("/list");

       return;

   }

   

   

   /* 로그인이 안되있을 경우는 index페이지로 */

   OperationHistoryVO historyVO = new OperationHistoryVO();

   /* 요청자의 원격 호스트를 가져오면 어떤 ip를 쓰는지 알수 있다. */

   historyVO.setIp(request.getRemoteHost());

   /* 로그인을 안한 상태라, 누군지 모른다 */

   historyVO.setMemberId("");

   historyVO.setUrl(request.getRequestURI());

   historyVO.setActionCode(ActionCode.LOGIN);

   historyVO.setDescription( BuildDescription.get( Description.VISIT_LOGIN_PAGE, request.getRemoteHost() ) );

   

   OperationHistoryBiz biz = new OperationHistoryBiz();

   biz.addHistory(historyVO);

%>

<jsp:include page="./WEB-INF/view/common/header.jsp"></jsp:include>

<jsp:include page="./WEB-INF/view/common/login.jsp"></jsp:include>

<jsp:include page="./WEB-INF/view/common/footer.jsp"></jsp:include>

Colored by Color Scripter

cs


이렇게 추가를 해주면 이제 맨 첫페이지에 접속을 할 때마다 기록이 OPER_HIST 테이블에 쌓이는 것을 확인할 수 있을 것이다.


4. 이런 식으로 모든 페이지마다 추가를 시켜줘야하는데, 동일한 코드를 매번 치는 것은 굉장히 비효율적이므로, 페이지에 도달하기 전 Filter에 내용을 적용하면 훨씬 효과적이다.

SessionCheckFilter.java 를 열어 아래 부분을 추가해준다.


이해를 돕기 위해 전체 코드도 첨부한다.


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

package com.ktds.smahn.filter;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import com.ktds.smahn.history.vo.OperationHistoryVO;

import com.ktds.smahn.member.vo.MemberVO;

/**

* 세션을 체크한다.

* Servlet Filter implementation class SessionCheckFilter

*/

public class SessionCheckFilter implements Filter {

   

   private List<String> whiteList;

   

   private List<String> staticResourceList;

   /**

    * 필터에서 생성자는 의미 없다.

    * Default constructor.

    */

   public SessionCheckFilter() {

       

       whiteList = new ArrayList<String>();

       //  게스트가 바로 통과할 수 있는 uri

       whiteList.add("/");

       whiteList.add("/doLogin");

       whiteList.add("/registerMember");

       whiteList.add("/addNewMember");

       // url 앞에 뜨는 아이콘

       whiteList.add("/favicon.ico");

       

       //resource를 jsp에서 보여주려면 따로 list를 만들어주어야 한다.

       staticResourceList = new ArrayList<String>();

       //                         /resource --> resourceImage 도 그냥 지나감.

       staticResourceList.add("/resource/");

   }

   /**

    * @see Filter#destroy()

    */

   public void destroy() {

   }

   /**

    * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)

    */

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

       

       //ServletRequest, ServletResponse : 인터페이스

       //HttpServletRequest, HttpServletResponse : 클래스

       // 인터페이스와 클래스는 상속관계, 즉 부모와 자식 관계

       HttpServletRequest req = (HttpServletRequest) request;

       

       String uri = req.getRequestURI();

       System.out.println(uri);

       

       

       

       if( !whiteList.contains(uri) ){

           

           boolean isURIResourceFile = false;

           

           for( String staticResource : staticResourceList ){

               if( uri.startsWith(staticResource) ) {

                   //사용자가 요청한 uri가 /resource로 시작한다면 그냥 지나가라.

                   isURIResourceFile = true;

                   break;

               }

           }

       

       

           if( !isURIResourceFile ){

           

               // whiteList에 uri가 없다면 세션을 체크해라.

               HttpSession session = req.getSession();

               

               // 데이터를 넣을때는 setAttribute, 가지고올때는 getAttribute를 쓴다.

               MemberVO member = (MemberVO) session.getAttribute("_MEMBER_");

               // 세션에 null이 있으면 로그인페이지로 redirect한다.

               if( member == null ){

                   HttpServletResponse res = (HttpServletResponse) response;

                   res.sendRedirect("/");

                   return;

               }

               else {

                   

                   // 매 페이지마다 Operation History의 공통내용을 VO에 저장한다.

                   OperationHistoryVO historyVO = new OperationHistoryVO();

                   historyVO.setIp(request.getRemoteHost());

                   historyVO.setMemberId(member.getMemberId());

                   historyVO.setUrl(req.getRequestURI());

                   

                   request.setAttribute("OperationHistoryVO", historyVO);

                   

               }

           }

       }

       

       

       // pass the request along the filter chain

       chain.doFilter(request, response);

   }

   /**

    * web.xml에 어떤 설정을 해 줄때만 가능하다.

    * @see Filter#init(FilterConfig)

    */

   public void init(FilterConfig fConfig) throws ServletException {

   }

}

Colored by Color Scripter

cs



이렇게 공통내용을 Session에서 처리해주면 다른 서블릿에는 이정도의 코드만 추가해주면 된다.

예를 들어 DetailServlet.java 에 추가한 것을 보면 아래와 같다.


빨간색 박스 부분만 더 넣어주면 이제 서블릿에 도달하기 전 필터에서 이미 historyVO에 내용이 담겨서 오기 때문에 달라지는 부분, 즉 Action Code와 Description, Etc 만 서블릿에서 처리해주면 된다.






이런식으로 매 페이지마다 클라이언트가 하는 모든 동작들을 수집해서 DB에 담아두는 것이다.!^^


공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함