18 KiB
ITMS SR 소스 변경 상세 (변경 전 → 변경 후) — 2026-07-19
리뷰·감사 추적용. 자격증명·AES 암호문·프로파일 yml·키파일은 변경 대상 아님(미기재). 빌드:
:api:compileJava :front:compileJavaBUILD SUCCESSFUL.
SR#1 잠긴 계정 비밀번호 초기화 차단 + 메일 미발송 결함
api/src/main/java/com/urpsys/itmsapi/sys/controller/UserManageController.java (관리자 초기화)
변경 전:
LinkedHashMap<String, Object> userInfoMap = userManageService.selectUser(requestMap);
if(CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(""))
{
resBody.put("resultCd" , "-1" );
resBody.put("resultMsg", "E-mail 이 존재하지 않습니다." );
return ResultData.ok(resBody);
}
변경 후:
LinkedHashMap<String, Object> userInfoMap = userManageService.selectUser(requestMap);
// SR#1 : 계정이 잠겨 있으면 초기화 차단 (잠금 해제 후 가능)
if(CommonUtil.nvl(userInfoMap.get("lockAt")).equals("Y"))
{
resBody.put("resultCd" , "-2" );
resBody.put("resultMsg", "계정이 잠겨 있습니다. [로그인인증제한해제]로 잠금을 먼저 해제한 후 비밀번호를 초기화하세요." );
return ResultData.ok(resBody);
}
if(CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(""))
{ ... 동일 ... }
api/src/main/java/com/urpsys/baseapi/main/controller/MainController.java (로그인화면 초기화)
변경 전:
else if(!CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(requestMap.get("userEmail")))
{
resBody.put("resultCd" , -6 );
resBody.put("resultMsg", "사용자정보가 일치하지 않습니다." );
return ResultData.ok(resBody);
}
String strResetPassword = CommonUtil.getRandomChar(10);
변경 후:
else if(!CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(requestMap.get("userEmail")))
{ ... 동일 ... }
// SR#1 : 계정 잠금 시 초기화 차단 + 안내
else if(CommonUtil.nvl(userInfoMap.get("lockAt")).equals("Y"))
{
resBody.put("resultCd" , -7 );
resBody.put("resultMsg", "로그인 시도 횟수 초과로 계정이 잠겼습니다. " + getAdminDisplay() + "에게 문의 바랍니다." );
return ResultData.ok(resBody);
}
String strResetPassword = CommonUtil.getRandomChar(10);
front/.../webapp/WEB-INF/jsp/main/PopPasswordReset.jsp (서버 안내 메시지 노출)
변경 전:
else
{
alert("비밀번호 초기화에 실패하였습니다. \n사용자정보을 확인해주시길 바랍니다.");
}
변경 후:
else
{
if(data.resultMsg != null && data.resultMsg != "") { alert(data.resultMsg); }
else { alert("비밀번호 초기화에 실패하였습니다. \n사용자정보을 확인해주시길 바랍니다."); }
}
SR#2, SR#3, D03 로그인 메시지(잠금/오입력 구분 + 관리자 성명 동적)
front/src/main/java/com/urpsys/basefront/controller/MainController.java
변경 전:
if(srError[0].equals("-1")) { strErrorMsg = "고객님의 아이디는 5회 로그인 오류로 인해 잠금처리 되었습니다. 시스템 관리자에게 연락해 주세요."; }
else if(srError[0].equals("-2")) { strErrorMsg = "아이디 혹은 비밀번호을 확인해주세요. 5회 로그인 오류인경우 아이디가 잠금처리가 됩니다."; }
else if(srError[0].equals("-3")) { strErrorMsg = "아이디 혹은 비밀번호을 확인해주세요. 5회 로그인 오류인경우 아이디가 잠금처리가 됩니다."; }
변경 후:
if(srError[0].equals("-1")) { strErrorMsg = "로그인 시도 횟수 초과로 계정이 잠겼습니다. " + getAdminDisplay() + "에게 문의 바랍니다."; }
else if(srError[0].equals("-2")) { strErrorMsg = "아이디 또는 패스워드가 잘못 되었습니다. 아이디와 패스워드를 정확히 입력해 주세요."; }
else if(srError[0].equals("-3")) { strErrorMsg = "아이디 또는 패스워드가 잘못 되었습니다. 아이디와 패스워드를 정확히 입력해 주세요."; }
추가(동적 관리자 표기 — 프로파일 yml custom.adminName/custom.adminTitle 미설정 시 "시스템 관리자"):
@Value("${custom.adminName:}") private String strAdminName;
@Value("${custom.adminTitle:}") private String strAdminTitle;
private String getAdminDisplay() {
String n = (strAdminName==null)?"":strAdminName.trim();
String t = (strAdminTitle==null)?"":strAdminTitle.trim();
if(n.equals("")) return "시스템 관리자";
return "시스템 관리자(" + n + (t.equals("")?"":" "+t) + ")";
}
(api baseapi/main/MainController에도 동일 getAdminDisplay()/@Value 추가 — SR#1 잠금 메시지용)
SR#7, SR#10 "등록" 버튼 → "임시저장"
front/.../jsp/itms/srm/{SvcDeskInsert,SvcDeskCopyInsert,ReportCheckInsert,ReportCheckCopyInsert,ReportIssueInsert,ReportIssueCopyInsert,ReportErrorInsert,ReportErrorCopyInsert,ReportOtherInsert,ReportOtherCopyInsert}.jsp (10파일)
변경 전(각 파일 등록/승인요청 옆의 단독 등록 버튼):
<button type="button" class="btn-etc" title="<spring:message code="button.create" />" onclick="fncXxxInsert(); return false;">
<spring:message code="button.create" />
</button>
변경 후:
<button type="button" class="btn-etc" title="임시저장" onclick="fncXxxInsert(); return false;">
임시저장
</button>
(등록/승인요청 버튼은 그대로 유지. 단독 신청 버튼만 있는 SvcDeskReqInsert.jsp는 제외.)
SR#8 중복 "새로만들기" 제거
front/.../jsp/itms/srm/ReportIssueDetail.jsp
변경 전(승인요청 블록 + 수정/삭제 블록에 각각 새로만들기 → 2개 표출):
<c:if test="${(privType eq 'mine' && report.inReportStatus ne 3) || privType eq 'super'}">
<!-- 새로만들기 기능 추가 -->
<button type="button" class="btn-etc" title="새로만들기" onclick="fncReportIssueCopyInsert(); return false;">새로만들기</button>
<button type="button" class="btn-etc" title="수정" onclick="fncReportIssueUpdate(); return false;">
변경 후(수정/삭제 블록의 중복 새로만들기 제거):
<c:if test="${(privType eq 'mine' && report.inReportStatus ne 3) || privType eq 'super'}">
<!-- SR#8 중복 '새로만들기' 제거 (상단 승인요청 블록의 새로만들기로 일원화) -->
<button type="button" class="btn-etc" title="수정" onclick="fncReportIssueUpdate(); return false;">
SR#9 조치예정자 직접선택 차단(신청) + 접수자/접수일시 서버제어(접수)
front/.../jsp/itms/srm/SvcDeskReqInsert.jsp (조치예정자 돋보기 제거)
변경 전:
<input type="text" name="vcChargeUser" id="vcChargeUser" style="width: 90%;" readonly value=""/>
<a href="<c:url value='/cmm/selectUserListPopup.do' />" ... onclick="fn_User_Search('조치예정', 'inChargeUser', '', 'vcChargeUser', '', '');return false;">
<img src="...btn_search.gif" alt="조치예정자 검색" title="조치예정자 검색"></a>
<input type="hidden" name="inChargeUser" id="inChargeUser" value="" >
변경 후:
<!-- SR#9-1 : 서비스 선택에 따라 자동배정, 돋보기 제거 -->
<input type="text" name="vcChargeUser" id="vcChargeUser" style="width: 60%;" readonly value=""/>
<span style="color:#888;">(자동배정)</span>
<input type="hidden" name="inChargeUser" id="inChargeUser" value="" >
api/.../itmsapi/srm/controller/SvcDeskController.java (접수자/접수일시 서버 강제)
변경 전: (클라이언트가 보낸 incidentVo 값을 그대로 서비스로 전달)
LinkedHashMap<String, Object> returnMap = svcDeskService.addIncident(incidentVoMap, approvalsList, fileList, reqApprovalMap);
변경 후:
// SR#9-2,9-3 : 접수자/접수일시 서버 권한값 강제
TokenInfo tokenInfoReg = CommonUtil.getAccessTokenInfo(request);
incidentVoMap.put("inRegUser" , tokenInfoReg.getUniqId());
incidentVoMap.put("vcRegUser" , tokenInfoReg.getUserName());
incidentVoMap.put("dtReceiptDatetime", CommonUtil.getKST("yyyy-MM-dd HH:mm"));
LinkedHashMap<String, Object> returnMap = svcDeskService.addIncident(incidentVoMap, approvalsList, fileList, reqApprovalMap);
(완료요청일시는 사용자 입력 성격상 유지)
SR#12 입력값 길이 검증 + 이중 결함(장애페이지 후 등록) 차단
api/.../itmsapi/srm/controller/SvcDeskController.java 및 SvcDeskReqController.java (등록 전 길이검증)
변경 전: 검증 없이 바로 등록 호출. 변경 후(양 컨트롤러 공통):
String strTitle = CommonUtil.nvl(incidentVoMap.get("vcIncidentTitle"));
String strBody = CommonUtil.nvl(incidentVoMap.get("txIncidentBody"));
if(strTitle.length() > 200) { resBody.put("resultCd","-2"); resBody.put("resultMsg","제목은 200자 이내로 입력해 주세요. (현재 "+strTitle.length()+"자)"); return ResultData.ok(resBody); }
if(strBody.length() > 60000){ resBody.put("resultCd","-2"); resBody.put("resultMsg","본문 내용이 너무 깁니다. 60,000자 이내로 입력해 주세요."); return ResultData.ok(resBody); }
front/.../itmsfront/srm/controller/SvcDeskController.java (커밋 후 메일 예외 격리)
변경 전:
if(returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(1, (int)returnMap.get("inIncidentSeq")); }
if(strReqApprovalYn.equals("Y") && returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(3, (int)returnMap.get("inIncidentSeq")); }
변경 후:
try {
if(returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(1, (int)returnMap.get("inIncidentSeq")); }
if(strReqApprovalYn.equals("Y") && returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(3, (int)returnMap.get("inIncidentSeq")); }
} catch(Exception mailEx) {
log.error("서비스 등록 후 메일발송 실패(등록은 정상): {}", mailEx.getMessage()); // 등록 결과에 영향 없음
}
front/.../jsp/itms/srm/{SvcDeskInsert,SvcDeskReqInsert}.jsp (검증 메시지 노출 + maxlength)
변경 전:
success: function(data) { alert("등록 완료 되었습니다."); fncSvcDeskList(); }
변경 후:
success: function(data) {
if(data.resultCd != "0") { alert(data.resultMsg); return; } // SR#12 검증 실패 안내
alert("등록 완료 되었습니다."); fncSvcDeskList();
}
그리고 제목 input에 maxlength="200" 추가.
SR#13 에디터 높이 무한 증가 → 최대높이
front/src/main/resources/static/js/tinymce_uritms_setting.js (drawEditorWrite/drawEditorRead 2곳)
변경 전:
height: 300,
min_height: 300, // 최소 높이
toolbar_mode: 'floating',
변경 후:
height: 300,
min_height: 300, // 최소 높이
max_height: 600, // SR#13 최대 높이 제한(초과 시 내부 스크롤)
toolbar_mode: 'floating',
SR#14 신청번호 부분검색 + SR#16 본인 신청건만 조회
api/.../mapper/.../itms/srm/SvcDeskReqMapper.xml
변경 전:
<sql id="searchFilter">
<if test="incidentReqNum != null and incidentReqNum != ''"> AND A.VC_REQ_INCIDENT_NUM = #{incidentReqNum} </if>
...
</sql>
변경 후:
<sql id="searchFilter">
<!-- SR#14 부분검색 + 바인딩 -->
<if test="incidentReqNum != null and incidentReqNum != ''"> AND A.VC_REQ_INCIDENT_NUM LIKE CONCAT('%', #{incidentReqNum}, '%') </if>
...
<!-- SR#16 일반 사용자 본인 신청건만 -->
<if test="reqUserFilter != null and reqUserFilter != ''"> AND A.IN_REQ_USER = #{reqUserFilter} </if>
</sql>
api/.../itmsapi/srm/controller/SvcDeskReqController.java (비관리자 필터 주입)
변경 후 추가:
String strRoleReq = CommonUtil.nvl(tokenInfo.getAuthorities());
if(strRoleReq.indexOf("ROLE_ADMIN") < 0) { requestMap.put("reqUserFilter", tokenInfo.getUniqId()); }
SR#19 오타 "없무권한" → "업무권한"
front/.../jsp/itms/error/errorNoAuth.jsp
변경 전: 시스템 관리자에게 없무권한 부여후 사용하시길 바랍니다.
변경 후: 시스템 관리자에게 업무권한 부여후 사용하시길 바랍니다.
SR#20 "인스턴트"(인스던트/인시던트) → "서비스"
front 모듈 전체 스윕 — JSP/JS/Java/XML 48파일 172건. 예:
- 변경 전:
<title> 인스던트 신청 </title>/confirm("인스던트 신청 처리를 하시겠습니까?")/<th> 인스던트 접수번호 </th> - 변경 후:
<title> 서비스 신청 </title>/confirm("서비스 신청 처리를 하시겠습니까?")/<th> 서비스 접수번호 </th>(명령:sed 's/인스던트/서비스/g',sed 's/인시던트/서비스/g'— "인스턴스"(instance)는 미변경)
SR#23 FOOTER 저작권 오타
front/.../jsp/main/inc/IncFooter.jsp
변경 전: COPYRIGHT © 20242 URP. All Right Reserved
변경 후: COPYRIGHT © 2024 URP. All Right Reserved
F01 보고서 양식명 전체 표시(툴팁)
front/.../jsp/itms/rem/ReportList.jsp
변경 전: <td><a href="javascript:fncSelectReport('${report.inReportForm}')" style="text-align: center;" >${report.vcFormName}</a></td>
변경 후: <td title="${report.vcFormName}"><a href="javascript:fncSelectReport('${report.inReportForm}')" title="${report.vcFormName}">${report.vcFormName}</a></td>
F03 서비스신청 서비스 항목 고정(SR기본요청양식)
front/.../jsp/itms/srm/SvcDeskReqInsert.jsp
변경 후 추가(select 아래):
$(function(){
var $s = $('#inServiceCode');
$s.find('option').each(function(){ if($.trim($(this).text())=='SR기본요청양식'){ $s.val($(this).val()); return false; } });
$s.css({'pointer-events':'none','background-color':'#f2f2f2'}).attr('tabindex','-1'); // 값은 제출, 변경 불가
});
F05 그룹아이디 기본값 KIC
front/.../jsp/itms/sys/UserSelectUpdt.jsp
변경 후 추가(select 아래):
$(function(){
var $g = $('#groupId');
if($g.length>0 && ($g.val()==null || $g.val()=='')) {
$g.find('option').each(function(){ if($.trim($(this).text())=='KIC'){ $g.val($(this).val()); return false; } });
}
});
F06 게시판 제목 클릭 진입
front/.../jsp/itms/srm/ReportCheckList.jsp
변경 전(제목 = 일반 텍스트):
<td title="${reportList.vcReportTitle}">
<c:choose><c:when test="${fn:length(reportList.vcReportTitle) > 25}">...</c:when><c:otherwise>...</c:otherwise></c:choose>
</td>
변경 후(제목 = 링크):
<td title="${reportList.vcReportTitle}">
<a href="javascript:fncSelectReportCheck('${reportList.inReportSeq}')">
<c:choose>...</c:choose>
</a>
</td>
F09 관리자 게시물/댓글 삭제 권한
front/.../itmsfront/bbs/controller/BBSAdminManageController.java
변경 후 추가: model.addAttribute("sessionAdminYn", (role.indexOf("ROLE_ADMIN")>-1)?"Y":"N");
front/.../jsp/itms/bbs/admin/NoticeInqire.jsp
변경 후: 작성자 삭제 블록 뒤에 관리자 전용 삭제 버튼 추가
<c:if test="${result.frstRegisterId != sessionUniqId && sessionAdminYn == 'Y'}">
<button ... onclick="fn_egov_delete_notice(); return false;">삭제</button>
</c:if>
댓글 삭제 조건 변경 전: <c:if test="${listCn.deleteYn eq 'Y' }"> → 변경 후: <c:if test="${listCn.deleteYn eq 'Y' || sessionAdminYn == 'Y' }">
F10 게시판 조회수/댓글수 표기
api/.../mapper/.../itms/bbs/BBSAdminManageMapper.xml
변경 후 추가(select 컬럼):
, ( SELECT COUNT(1) FROM TB_CMNT_INFO cc WHERE cc.NTT_ID = a.NTT_ID AND cc.USE_AT = 'Y' ) AS cmntCnt
front/.../jsp/itms/bbs/admin/NoticeList.jsp
헤더에 <th scope="col">댓글수</th>, 본문에 <td><c:out value="${result.cmntCnt}" /></td> 추가(빈목록 colspan 7/5/4 → 8/6/5).
F12 로고/파비콘 브랜딩 관리(KIC)
신규 front/.../basefront/controller/BrandingController.java
GET /branding/logo·/branding/favicon: 업로드본 우선, 없으면 번들 KIC 기본자산(webapp/branding/default/*) 폴백. 캐시무효화 헤더.GET /branding/brandingManageView.do: 관리자 화면(비관리자 → 메인 리다이렉트).POST /branding/upload.do: 관리자 검증 + 확장자 화이트리스트(logo: svg/png/jpg/gif, favicon: ico/png/svg/jpg/gif) + 2MB 제한 + 저장파일명 서버고정(kind.ext, 사용자 파일명 미사용→경로조작 차단) + canonical 경로 재확인.
front/.../basefront/config/SecurityConfig.java
변경 후 추가: .antMatchers("/branding/logo", "/branding/favicon").permitAll() (로그인 전 표출).
참조 변경(정적 로고/파비콘 → 동적 엔드포인트)
front/.../jsp/main/LoginUsr.jsp·LoginUsr_kic.jsp:${ctx}/images/main/logo.png→${ctx}/branding/logofront/.../jsp/tilesLayout/{uritms-base-main1,uritms-base-main2,uritms-base-popup1,uritms-popup1}.jsp:<link rel="shortcut icon" href="data:image/x-icon;,">→href="${pageContext.request.contextPath}/branding/favicon"front/.../jsp/main/inc/IncHeader.jsp:<h1 class="logo">에background-image:url('<c:url value="/branding/logo"/>')인라인 지정
신규 자산·화면
front/src/main/webapp/branding/default/logo.svg·favicon.jpg(KIC 공식 자산 번들)front/.../jsp/branding/brandingManage.jsp(업로드/미리보기 화면)
D04 검색 후 페이지네이션 검색조건 소실
front/.../jsp/itms/sys/AuthorGroupManage.jsp
변경 전: <ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="fn_egov_select_linkPage"/> (해당 함수 미정의 → 검색조건 유실)
변경 후: <ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="linkPage"/> (listForm 재제출 → searchCondition/searchKeyword 유지)