itsm_ysm/docs/SR_CHANGES_20260719.md

18 KiB

ITMS SR 소스 변경 상세 (변경 전 → 변경 후) — 2026-07-19

리뷰·감사 추적용. 자격증명·AES 암호문·프로파일 yml·키파일은 변경 대상 아님(미기재). 빌드: :api:compileJava :front:compileJava BUILD 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.javaSvcDeskReqController.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)는 미변경)

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/logo
  • front/.../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 유지)