로그 히스토리 조회

This commit is contained in:
pjs
2026-03-01 02:19:52 +09:00
parent 035a36f607
commit 8e1f495a0b
8 changed files with 555 additions and 0 deletions

View File

@@ -1 +1,2 @@
> Task :compileJava UP-TO-DATE
Terminate batch job (Y/N)?

View File

@@ -0,0 +1,29 @@
package com.madeu.crm.settings.log.ctrl;
import com.madeu.init.ManagerDraftAction;
import com.madeu.crm.settings.log.dto.LogHistoryDTO;
import com.madeu.crm.settings.log.svc.LogHistoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@RestController
public class LogHistoryController extends ManagerDraftAction {
@Autowired
private LogHistoryService logHistoryService;
@RequestMapping(value = "/logHistory/moveLogHistoryList.do")
public ModelAndView moveLogHistoryList() throws Exception {
return new ModelAndView("/crm/settings/log/logHistorySelectList");
}
@RequestMapping(value = "/logHistory/getLogHistoryList.do")
public LogHistoryDTO getLogHistoryList(@RequestBody LogHistoryDTO paramDTO) throws Exception {
return logHistoryService.getLogHistoryList(paramDTO);
}
}

View File

@@ -0,0 +1,53 @@
package com.madeu.crm.settings.log.dto;
import lombok.Data;
@Data
public class LogHistoryDTO {
// DB Column mapping
private String muLogHistoryId;
private String muMemberId;
private String visitIp;
private String visitOriginAgent;
private String visitAgent;
private String visitOs;
private String url;
private String func;
private String service;
private String funcName;
private String serviceName;
private String requestValue;
private String responseValue;
private String resultCode;
private String resultMsg;
private String writeDate;
private String writeTime;
private String cudFlag;
private String useYn;
private String regId;
private String regDate;
private String modId;
private String modDate;
private String tId;
private String tDate;
// 조회 결과 전용
private String rowNum;
// Search & UI Variables
private String searchKeyword;
private String searchType;
private String startDate;
private String endDate;
private Integer start;
private Integer limit;
private String sort;
private String dir;
// Response Mapping Variables
private String msgCode;
private String msgDesc;
private String success;
private int totalCount;
private Object rows;
}

View File

@@ -0,0 +1,16 @@
package com.madeu.crm.settings.log.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.dao.DataAccessException;
import com.madeu.crm.settings.log.dto.LogHistoryDTO;
import java.util.List;
@Mapper
public interface LogHistoryMapper {
List<LogHistoryDTO> selectTotalLogHistoryCount(LogHistoryDTO paramDTO) throws DataAccessException;
List<LogHistoryDTO> selectListLogHistory(LogHistoryDTO paramDTO) throws DataAccessException;
}

View File

@@ -0,0 +1,62 @@
package com.madeu.crm.settings.log.svc;
import com.madeu.constants.Constants;
import com.madeu.crm.settings.log.dto.LogHistoryDTO;
import com.madeu.crm.settings.log.mapper.LogHistoryMapper;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@Slf4j
@Service("LogHistoryViewService")
public class LogHistoryService {
@Autowired
private HttpSession session;
@Autowired
private LogHistoryMapper logHistoryMapper;
@Autowired
private MessageSource messageSource;
private String msg(String code) {
return messageSource.getMessage(code, null, Locale.getDefault());
}
public LogHistoryDTO getLogHistoryList(LogHistoryDTO dto) {
try {
// 로그 히스토리는 시스템 관리 기능 → 로그인 여부만 체크
String loginMemberId = (String) session.getAttribute("loginMemberId");
if (loginMemberId != null && !loginMemberId.isEmpty() && !"null".equals(loginMemberId)) {
dto.setUseYn("Y");
List<LogHistoryDTO> countList = logHistoryMapper.selectTotalLogHistoryCount(dto);
int total = countList.get(0).getTotalCount();
dto.setTotalCount(total);
if (total > 0) {
dto.setRows(logHistoryMapper.selectListLogHistory(dto));
} else {
dto.setRows(new ArrayList<>());
}
dto.setMsgCode(Constants.OK);
dto.setSuccess("true");
} else {
dto.setMsgCode(Constants.FAIL);
dto.setMsgDesc(msg("auth.error.select"));
}
} catch (Exception e) {
log.error("Error in getLogHistoryList", e);
dto.setMsgCode(Constants.FAIL);
dto.setMsgDesc(msg("error.select"));
}
return dto;
}
}

View File

@@ -0,0 +1,72 @@
<?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.madeu.crm.settings.log.mapper.LogHistoryMapper">
<select id="selectTotalLogHistoryCount" parameterType="com.madeu.crm.settings.log.dto.LogHistoryDTO" resultType="com.madeu.crm.settings.log.dto.LogHistoryDTO">
SELECT COUNT(*) AS "totalCount"
FROM MU_LOG_HISTORY MLH
WHERE MLH.USE_YN = 'Y'
<if test="searchKeyword != null and searchKeyword != ''">
AND (
MLH.FUNC_NAME LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
OR MLH.SERVICE_NAME LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
OR MLH.URL LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
OR MLH.MU_MEMBER_ID LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
)
</if>
<if test="resultCode != null and resultCode != ''">
AND MLH.RESULT_CODE = #{resultCode}
</if>
<if test="startDate != null and startDate != ''">
AND MLH.WRITE_DATE &gt;= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND MLH.WRITE_DATE &lt;= #{endDate}
</if>
</select>
<select id="selectListLogHistory" parameterType="com.madeu.crm.settings.log.dto.LogHistoryDTO" resultType="com.madeu.crm.settings.log.dto.LogHistoryDTO">
SELECT MLH.*
FROM (
SELECT MLH.*
,CAST(@RNUM:=@RNUM + 1 AS CHAR) AS "rowNum"
FROM (
SELECT MLH.MU_LOG_HISTORY_ID AS "muLogHistoryId"
,MLH.MU_MEMBER_ID AS "muMemberId"
,MLH.VISIT_IP AS "visitIp"
,MLH.URL AS "url"
,MLH.FUNC_NAME AS "funcName"
,MLH.SERVICE_NAME AS "serviceName"
,MLH.RESULT_CODE AS "resultCode"
,MLH.RESULT_MSG AS "resultMsg"
,DATE_FORMAT(MLH.WRITE_DATE, '%Y-%m-%d') AS "writeDate"
,MLH.WRITE_TIME AS "writeTime"
FROM MU_LOG_HISTORY MLH
WHERE MLH.USE_YN = 'Y'
<if test="searchKeyword != null and searchKeyword != ''">
AND (
MLH.FUNC_NAME LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
OR MLH.SERVICE_NAME LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
OR MLH.URL LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
OR MLH.MU_MEMBER_ID LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
)
</if>
<if test="resultCode != null and resultCode != ''">
AND MLH.RESULT_CODE = #{resultCode}
</if>
<if test="startDate != null and startDate != ''">
AND MLH.WRITE_DATE &gt;= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND MLH.WRITE_DATE &lt;= #{endDate}
</if>
ORDER BY MLH.WRITE_DATE DESC, MLH.WRITE_TIME DESC
LIMIT 18446744073709551615
) MLH, (SELECT @RNUM:=0) R
WHERE 1 = 1
) MLH
WHERE 1 = 1
LIMIT ${start}, ${limit}
</select>
</mapper>

View File

@@ -0,0 +1,205 @@
/****************************************************************************
* 로그 히스토리 목록 - Tabulator Grid
****************************************************************************/
let logHistoryTable;
let currentPage = 1;
let pageSize = 50;
/****************************************************************************
* 초기화
****************************************************************************/
$(document).ready(function () {
fn_initTable();
fn_initEvent();
fn_setDefaultDate();
fn_selectLogHistoryList(1);
});
/****************************************************************************
* 기본 날짜 설정 (오늘 기준 7일 전 ~ 오늘)
****************************************************************************/
function fn_setDefaultDate() {
let today = new Date();
let weekAgo = new Date();
weekAgo.setDate(today.getDate() - 7);
$("#searchEndDate").val(fn_formatDate(today));
$("#searchStartDate").val(fn_formatDate(weekAgo));
}
function fn_formatDate(date) {
let y = date.getFullYear();
let m = String(date.getMonth() + 1).padStart(2, "0");
let d = String(date.getDate()).padStart(2, "0");
return y + "-" + m + "-" + d;
}
/****************************************************************************
* Tabulator 초기화
****************************************************************************/
function fn_initTable() {
logHistoryTable = new Tabulator("#logHistoryGrid", {
height: "calc(100vh - 350px)",
layout: "fitColumns",
placeholder: "조회된 데이터가 없습니다.",
columns: [
{
title: "No",
field: "rowNum",
hozAlign: "center",
headerHozAlign: "center",
width: 60,
headerSort: false
},
{
title: "날짜",
field: "writeDate",
hozAlign: "center",
headerHozAlign: "center",
width: 110
},
{
title: "시간",
field: "writeTime",
hozAlign: "center",
headerHozAlign: "center",
width: 90
},
{
title: "기능명",
field: "funcName",
headerHozAlign: "center",
minWidth: 160
},
{
title: "서비스명",
field: "serviceName",
headerHozAlign: "center",
minWidth: 160
},
{
title: "URL",
field: "url",
headerHozAlign: "center",
minWidth: 200
},
{
title: "결과",
field: "resultCode",
hozAlign: "center",
headerHozAlign: "center",
width: 90,
formatter: function (cell) {
let val = cell.getValue();
if (val === "SUCCESS") {
return '<span class="result-success">SUCCESS</span>';
} else if (val === "ERROR") {
return '<span class="result-error">ERROR</span>';
}
return val || "";
}
},
{
title: "사용자ID",
field: "muMemberId",
hozAlign: "center",
headerHozAlign: "center",
width: 100
},
{
title: "접속IP",
field: "visitIp",
hozAlign: "center",
headerHozAlign: "center",
width: 130
}
]
});
}
/****************************************************************************
* 이벤트 바인딩
****************************************************************************/
function fn_initEvent() {
// 조회 버튼
$("#btnSearch").on("click", function () {
fn_selectLogHistoryList(1);
});
// Enter 키 검색
$("#searchKeyword").on("keyup", function (e) {
if (e.keyCode === 13) {
fn_selectLogHistoryList(1);
}
});
}
/****************************************************************************
* 로그 히스토리 목록 조회
****************************************************************************/
function fn_selectLogHistoryList(page) {
currentPage = page;
let start = (page - 1) * pageSize;
let paramData = {
"menuClass": menuClass,
"start": start,
"limit": pageSize,
"searchKeyword": $("#searchKeyword").val(),
"resultCode": $("#searchResultCode").val(),
"startDate": $("#searchStartDate").val(),
"endDate": $("#searchEndDate").val()
};
$.ajax({
url: encodeURI("/logHistory/getLogHistoryList.do"),
data: JSON.stringify(paramData),
dataType: "json",
contentType: "application/json",
type: "POST",
async: true,
success: function (data) {
if ("0" == data.msgCode) {
let totalCount = data.totalCount || 0;
$("#totalCount").text(totalCount.toLocaleString());
if (data.rows && data.rows.length > 0) {
logHistoryTable.setData(data.rows);
} else {
logHistoryTable.setData([]);
}
fn_setPagination(totalCount);
} else {
alert(data.msgDesc || "조회 중 오류가 발생하였습니다.");
}
},
error: function () {
alert("서버 통신 오류가 발생하였습니다.");
}
});
}
/****************************************************************************
* 페이지네이션
****************************************************************************/
function fn_setPagination(totalCount) {
let totalPages = Math.ceil(totalCount / pageSize);
if (totalPages < 1) totalPages = 1;
$("#logHistoryPagination").twbsPagination("destroy");
$("#logHistoryPagination").twbsPagination({
totalPages: totalPages,
startPage: currentPage,
visiblePages: 10,
initiateStartPageClick: false,
first: "<<",
prev: "<",
next: ">",
last: ">>",
onPageClick: function (event, page) {
fn_selectLogHistoryList(page);
}
});
}

View File

@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/homeLayout}">
<th:block layout:fragment="layout_css">
<link rel="stylesheet" href="/css/web/webPhotoDietSelectList.css">
<link href="https://unpkg.com/tabulator-tables@5.5.2/dist/css/tabulator_materialize.min.css" rel="stylesheet">
<style>
.log-search-area {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.log-search-area select,
.log-search-area input {
height: 38px;
padding: 0 12px;
border: 1px solid #E9ECF0;
border-radius: 5px;
font-size: 14px;
outline: none;
background: #fff;
}
.log-search-area input[type="date"] {
width: 150px;
}
.log-search-area input[type="text"] {
width: 250px;
}
.log-total {
font-size: 14px;
color: #666;
margin: 10px 0;
}
.log-total strong {
color: #333;
font-weight: 600;
}
.tabulator {
font-size: 13px;
}
.tabulator .tabulator-header .tabulator-col {
background: #f8f9fa;
}
.result-success {
color: #28a745;
font-weight: 600;
}
.result-error {
color: #dc3545;
font-weight: 600;
}
</style>
</th:block>
<th:block layout:fragment="layout_top_script">
<script>
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
</script>
</th:block>
<th:block layout:fragment="layout_content">
<div class="center_box">
<p class="page_title">로그 히스토리</p>
<div class="filter_box">
<div class="form_box">
<div class="log-search-area">
<input type="date" id="searchStartDate" />
<span>~</span>
<input type="date" id="searchEndDate" />
<select id="searchResultCode">
<option value="">전체 결과</option>
<option value="SUCCESS">SUCCESS</option>
<option value="ERROR">ERROR</option>
</select>
<div class="search_box">
<img src="/image/web/search_G.svg" alt="search" />
<input type="text" id="searchKeyword" placeholder="기능명 / 서비스명 / URL / 사용자ID" />
</div>
<button id="btnSearch" class="search_btn">조회</button>
</div>
</div>
</div>
<div class="log-total">
전체 <strong id="totalCount">0</strong>
</div>
<div id="logHistoryGrid"></div>
<!-- 페이지네이션 -->
<div class="page_box">
<nav aria-label="Page navigation" class="navigation">
<ul class="pagination" id="logHistoryPagination"></ul>
</nav>
</div>
</div>
</th:block>
<th:block layout:fragment="layout_script">
<script src="/js/web/jquery.twbsPagination.js" type="text/javascript"></script>
<script src="https://unpkg.com/tabulator-tables@5.5.2/dist/js/tabulator.min.js"></script>
<script src="/js/crm/settings/log/logHistorySelectList.js" type="text/javascript"></script>
</th:block>
</html>