Initial commit
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
= 결재/메일
|
||||
|
||||
== Knox REST API 연계 서비스 신청
|
||||
.이 내용은 Knox결재외에 Knox메일 등 다른 Knox Portal REST API 연계 서비스 이용시에도 공통으로 해당되는 내용이다.
|
||||
|
||||
* SDL은 Knox Rest API를 이용하여 결재/메일 등의 서비스를 제공한다. 따라서 우선 Knox 연계서비스를 신청하고 시스템 아이디와 토큰을 발급받아야 한다.
|
||||
|
||||
* 시스템 아이디와 Token 발급이 완료되면, API 샘플 테스트도 가능하다.
|
||||
|
||||
//-
|
||||
|
||||
. Knox Portal Support내 연계 신청(Knox Dev Center)을 통하여 연계 신청을 진행한다.
|
||||
|
||||
NOTE: 스테이지 연계 신청한 Knox Portal 계정에 한하여 운영 연계신청이 가능하므로, 중복신청 방지 및 일관된 관리를 위해 현업 담당자가 신청하는 것을 권장한다.
|
||||
|
||||
[start=2]
|
||||
. Knox Dev Center내 연계 신청 가이드 메뉴를 참고하여 스테이지 연계부터 신청을 진행한다.
|
||||
. 스테이지 연계 신청 및 승인이 완료되면 API 연계 ID/Token 발급 및 API 구독이 완료되고,
|
||||
신청자에게 메일로 관련 내용이 통보된다.
|
||||
. SDL에서 제공하는 Knox 연계 모듈을 이용한 서비스를 개발하여 연계 테스트를 진행한다.
|
||||
. 스테이지 연계 테스트가 완료되면 운영도 스테이지와 동일하게 시스템을 통해 신청하여 테스트한다.
|
||||
|
||||
:leveloffset: +1
|
||||
|
||||
include::결재메일/결재.adoc[leveloffset]
|
||||
|
||||
include::결재메일/Knox상신.adoc[leveloffset]
|
||||
|
||||
include::결재메일/Knox결재상태동기화.adoc[leveloffset]
|
||||
|
||||
include::결재메일/결재관리.adoc[leveloffset]
|
||||
|
||||
include::결재메일/결재경로관리.adoc[leveloffset]
|
||||
|
||||
include::결재메일/결재양식관리.adoc[leveloffset]
|
||||
|
||||
include::결재메일/내부결재.adoc[leveloffset]
|
||||
|
||||
include::결재메일/대리결재.adoc[leveloffset]
|
||||
|
||||
include::결재메일/메일.adoc[leveloffset]
|
||||
|
||||
include::결재메일/보낸메일이력.adoc[leveloffset]
|
||||
|
||||
include::결재메일/메일그룹관리.adoc[leveloffset]
|
||||
|
||||
include::결재메일/메일상태조회.adoc[leveloffset]
|
||||
|
||||
include::결재메일/메일양식관리.adoc[leveloffset]
|
||||
|
||||
include::결재메일/임직원.adoc[leveloffset]
|
||||
|
||||
include::결재메일/주소록.adoc[leveloffset]
|
||||
|
||||
include::결재메일/메신저.adoc[leveloffset]
|
||||
|
||||
include::결재메일/MHTML변환.adoc[leveloffset]
|
||||
@@ -0,0 +1,57 @@
|
||||
= Knox 결재 상태 동기화
|
||||
|
||||
== 결재 Batch 설정
|
||||
|
||||
Knox 결재 문서는 10분마다 배치가 실행되어 동기화 되고 있다.
|
||||
|
||||
* config.properties : 동기화 배치 실행 주기 설정
|
||||
|
||||
[source,properties]
|
||||
----
|
||||
## Knox Approval Sync
|
||||
knox.approval.sync.cron=0 0/10 * * * ?
|
||||
----
|
||||
|
||||
* QuartzConfig.java: Scheduler 등록
|
||||
* KnoxSyncBatchConfig.java: 배치 Job, Trigger 등록
|
||||
* KnoxSyncBatchExecutor.java: 서비스 호출
|
||||
|
||||
== 결재 동기화 로직
|
||||
|
||||
배치에서 실행되는 동기화 로직을 구현한 서비스는 knoxApprovalSyncServiceImpl.java 파일의 synchronizeKnox() 메소드 이며 동기화 로직은 간략하게 아래와 같다.
|
||||
|
||||
. SDL 결재 테이블에서 결재 진행중인 문서 목록 대상 조회.
|
||||
. 대상 목록의 결재ID로 Knox REST를 조회하여 Revision이 변경되었는지 확인.
|
||||
. Revision 변경된 대상에 대해서 결재 정보 동기화 처리.
|
||||
. 동기화 완료 후 결재 후처리 진행.
|
||||
|
||||
결재 동기화 시 오류가 발생한 결재 문건은 결재정보 테이블(TN_CF_APPROVAL)에 결재 동기화 상태값(APPROVAL_FAULT)이 false로 Update되며 다음 배치 실행시 대상으로 선정되지 않는다. +
|
||||
이런 경우 이벤트 로그 테이블(TN_CF_APPROVAL_EVENT)의 로그를 확인하여 다시 동기화 해야 한다면 결재 관리 상세화면에서 개별 동기화가 가능하다.
|
||||
|
||||
image::approvalSync_01.png[]
|
||||
|
||||
== 결재 전후처리
|
||||
|
||||
결재 동기화 처리전 또는 처리후 결재 문서 상태에 대한 비지니스를 처리할 수 있도록 인터페이스를 제공하고 있다.
|
||||
|
||||
ApprovalInterceptor 를 결재 문서별로 상속 받는 구현 클래스를 만들면 된다.
|
||||
|
||||
* 구현 예시(KnoxApprovalSampleInterceptor.java)
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Log4j2
|
||||
@ApprovalDocumentType(names = {"knoxSample"})
|
||||
public class KnoxApprovalSampleInterceptor implements ApprovalInterceptor {
|
||||
|
||||
@Autowired
|
||||
KnoxApprovalSampleService knoxApprovalSampleService;
|
||||
|
||||
@Override
|
||||
public void afterSubmit(Approval approval, List<ApprovalStep> approvalStepList, Map<String, Object> attribute) {
|
||||
log.debug("execute ApprovalSampleInterceptor afterSubmit");
|
||||
//Knox 상신 후 문서 상태 업데이트
|
||||
knoxApprovalSampleService.updateSampleApprovalDocument(approval.getDbDocId(), ApprovalDocStatus.INPROCESS);
|
||||
}
|
||||
-- 생략 --
|
||||
----
|
||||
@@ -0,0 +1,124 @@
|
||||
= Knox 상신
|
||||
|
||||
== 결재 Entity Class 생성 및 설정
|
||||
|
||||
결재 기능을 구현해야 하는 업무의 Entity Class를 생성하고 Annotation을 설정하고 기본 결재 Class를 상속 받는다.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApprovalDocument(docType = "knoxSample", description = "Sample Approval Doc. (Knox)", approvalClass = Approval.class, templateEngine = TemplateEngineType.VELOCITY,
|
||||
templateFile = "/templates/approval/approval-sample.vm", docSecuType = ApprovalDocSecuType.PERSONAL,
|
||||
isArbitrary = true, bodyType = ApprovalBodyType.MIME)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class KnoxApprovalSampleDocument extends DefaultApprovalDocument {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String docId;
|
||||
private String contents;
|
||||
private String creator;
|
||||
}
|
||||
----
|
||||
|
||||
업무프로세스 Entity Class 에 @ApprovalDocument 을 달아 주고 DefaultApprovalDocument를 상속 받아야만 결재 Object 라는 것을 결재 모듈에서 알수 있다.
|
||||
|
||||
=== @ApprovalDocument
|
||||
|
||||
@ApprovalDocument를 이용해 결재 문서의 속정을 정의한다.
|
||||
|
||||
[cols=5]
|
||||
|====
|
||||
|props명
|
||||
|필수여부
|
||||
|Type
|
||||
|Default
|
||||
|설명
|
||||
|
||||
|docType
|
||||
|필수
|
||||
|String
|
||||
|
|
||||
|사용자가 식별할수 있는 이름
|
||||
|
||||
|description
|
||||
|필수
|
||||
|String
|
||||
|
|
||||
|결재 문서에 대한 설명
|
||||
|
||||
|approvalClass
|
||||
|필수
|
||||
|class
|
||||
|Approval.class
|
||||
|결재 문서를 DB에 저장할때 사용하는 클래스명
|
||||
|
||||
|templateEngine
|
||||
|필수
|
||||
|TemplateEngineType
|
||||
|VELOCITY
|
||||
|결재 본문 파싱에 필요한 템플릿 엔진. THYMELEAF, VELOCITY 2개의 템플릿 엔진을 사용할 수 있다.
|
||||
|
||||
|templateFile 또는 templateKey
|
||||
|필수
|
||||
|String
|
||||
|
|
||||
|templateFile: 템플릿 파일 이름을 경로와 확장자 포함해서 설정한다. +
|
||||
tempageKey: 결재양식 관리 메뉴에서 지정한 key
|
||||
|
||||
|docSecuType
|
||||
|
|
||||
|ApprovalDocSecuType
|
||||
|PERSONAL
|
||||
|결재 문서의 보안 형태
|
||||
|
||||
|isBodyModify
|
||||
|
|
||||
|boolean
|
||||
|true
|
||||
|문서의 기본 결재 본문 수정 true일 때만 UI에서 수정 가능
|
||||
|
||||
|
||||
|isRouteModify
|
||||
|
|
||||
|boolean
|
||||
|true
|
||||
|문서의 기본 결재 경로 수정 true일 때만 UI에서 수정 가능
|
||||
|
||||
|isArbitrary
|
||||
|
|
||||
|boolean
|
||||
|false
|
||||
|문서의 기본 결재 전결 true일 때만 UI에서 수정 가능
|
||||
|
||||
|bodyType
|
||||
|
|
||||
|ApprovalBodyType
|
||||
|MIME
|
||||
|전송할 문서의 형태 TEXT, HTML, MIME
|
||||
|
||||
|isInternalApproval
|
||||
|
|
||||
|boolean
|
||||
|false
|
||||
|true: 내부결재, false: Knox결재
|
||||
|
||||
|====
|
||||
|
||||
== 결재 본문 등록
|
||||
|
||||
결재 본문은 VELOCITY 또는 THYMELEAF 로 등록 할 수 있다. +
|
||||
SDL에서 제공하는 결재 샘플에서는 VELOCITY 파일로 제공하고 있으며 vm 파일을 작성하고, +
|
||||
위의 Entity Class 샘플에서와 같이 `@ApprovalDocument` 의 `templateEngine=엔진타입` 과 `templateFile="템플릿파일경로"` 값을 등록한다.
|
||||
|
||||
|
||||
== 결재 상신
|
||||
|
||||
=== 결재 상신 UI
|
||||
|
||||
샘플 문서를 등록 하고나면 상세하면 하단에 결재 경로를 설정할 수 있는 결재 스텝을 입력 받는 Component가 표시된다. +
|
||||
결재 문서 화면 개발시 이 Component를 붙여서 상신 기능을 구현한다.
|
||||
|
||||
image::approvalSumit_01.png[]
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
= MHTML 변환
|
||||
|
||||
== 개요
|
||||
프로젝트에서 메일발송이나 결재상신한 후 방화벽 밖(모바일)에서 조회시 이미지, css가 적용되지 않는 문제를 해결하기 위해 MHT로 변환
|
||||
|
||||
.MHT 변환 결과 예
|
||||
[source, text]
|
||||
----
|
||||
Date: Fri, 9 Aug 2019 14:55:20 +0900 (KST)
|
||||
Message-ID: <963810424.1.1565330120404@DESKTOP-TUEGPQT>
|
||||
Subject: =?UTF-8?B?66mU64m0IOq2jO2VnCDrp4zro4wg7JiI7KCVIOyViOuCtA==?=
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/related;
|
||||
boundary="----=_Part_0_398737318.1565330120140"
|
||||
|
||||
------=_Part_0_398737318.1565330120140
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
PCFkb2N0eXBlIGh0bWw+DQo8aHRtbD4NCjxoZWFkPg0KPG1ldGEgY2hhcnNldD0idXRmLTgiPg0K
|
||||
PHRpdGxlPuuplOuJtCDqtoztlZwg66eM66OMIOyYiOyglSDslYjrgrQ8L3RpdGxlPg0KPC9oZWFk
|
||||
Pg0KDQo8Ym9keT4NCjxkaXYgc3R5bGU9IndpZHRoOjEwMCU7YmFja2dyb3VuZC1jb2xvcjojZmZm
|
||||
(중략)
|
||||
------=_Part_0_398737318.1565330120140
|
||||
Content-Type: application/octet-stream
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-ID: <simbol.png>
|
||||
|
||||
iVBORw0KGgoAAAANSUhEUgAAAQEAAAEBCAYAAAB47BD9AAAwaUlEQVR42u3deZxddX3/8QuC4kPt
|
||||
Rqu2iK222pYW2yogCNbyE81MVRSFIEuAewmBsGQl+zqZmexhExEV0AQCSSDrZLZkJjOZzGRmQhAM
|
||||
RXYqbrVaFZHJTOCe+/29v+fc7z3nnn35nnPP8v3j9XAeLWVScz/P7/ee73fu5AghuSD90+Pt5Trk
|
||||
(중략)
|
||||
------=_Part_0_398737318.1565330120140--
|
||||
----
|
||||
@@ -0,0 +1,220 @@
|
||||
= 결재
|
||||
|
||||
== 개요
|
||||
SDL에서는 Knox결재와 동기화 되는 결재 모듈을 제공하고 있는데, 여기서는 Knox Portal REST API 연계 서비스 신청 및 Knox결재 서비스 연계 부분에 대하여 설명한다. 결재화면 및 기능구현 설명은 해당 가이드를 참조한다.
|
||||
|
||||
=== Knox결재 연계 설정
|
||||
. Knox REST API 연계 서비스 신청이 되었다면, 발급받은 `system-id`, `token` 값을 설정한다.
|
||||
|
||||
.knox.properties (스테이지)
|
||||
[source,properties]
|
||||
----
|
||||
knox.system-id=xxxxxxxxxxx
|
||||
knox.token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
knox.address.prefix=openapi.samsung.net
|
||||
knox.approval-service=/approval/api/v2.0/approvals
|
||||
----
|
||||
|
||||
.knox.properties (운영)
|
||||
[source,properties]
|
||||
----
|
||||
knox.system-id=xxxxxxxxxxx
|
||||
knox.token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // <1>
|
||||
knox.address.prefix=openapi.samsung.net,openapi.w1.samsung.net,openapi.w2.samsung.net # <2>
|
||||
knox.approval-service=/approval/api/v2.0/approvals
|
||||
----
|
||||
<1> 토큰 (comma(,)로 구분, 거점 순서와 동일)
|
||||
<2> 거점 (국내, 구주, 미주)
|
||||
|
||||
[start=2]
|
||||
. 결재화면 및 기능구현 설명은 해당 가이드를 참조한다.
|
||||
|
||||
* <<_knox_상신,Knox상신>>
|
||||
|
||||
|
||||
NOTE: 개발자의 IP도 반드시 Knox stage 방화벽에 등록하여야만 개발자 PC에서 상신이 된다. 또한 Knox 스테이지(http://www.stage.samsung.net) 에 개발자와 결재자의 계정도 생성 해야만 개발을 진행 할 수 있다.
|
||||
|
||||
== API
|
||||
KnoxApprovalController는 Knox에서 제공하는 Approval API를 직접 연결하는 API를 제공한다.
|
||||
시스템의 비즈니스 로직을 거치지 않고 Knox API를 직접 호출 하기 때문에 서비스 호출에 문제가 있는지 파악하는데 유용하다.
|
||||
|
||||
NOTE: KnoxApprovalController에서 제공하는 API URI은 Knox REST Service의 API URI와 같다.
|
||||
|
||||
. Knox 일반 결재 상신 +
|
||||
POST /knox/approvals/submit +
|
||||
* 파라미터는 KnoxApproval 클래스를 참고한다.
|
||||
|
||||
* attachments는 첨부파일, knoxApprovalStr는 KnoxApproval의 Json String 값이다.
|
||||
[source,java]
|
||||
----
|
||||
@PostMapping("/submit")
|
||||
public KnoxApproval submitGeneral(@Parameter(value = "첨부파일", required = true) MultipartFile attachments,
|
||||
@Parameter(value = "상신정보", required = true) String knoxApprovalStr) throws IOException {
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
KnoxApproval knoxApproval = objectMapper.readValue(knoxApprovalStr, KnoxApproval.class);
|
||||
|
||||
String serverLocation = Account.currentUser().getServerLocation();
|
||||
if (StringUtils.isEmpty(serverLocation)) serverLocation = "KR";
|
||||
|
||||
String fileId = fileManagerService.store(attachments);
|
||||
List<Resource> fileList = new ArrayList<>();
|
||||
fileList.add(fileManagerService.getResource(fileId));
|
||||
|
||||
return knoxApprovalService.submit(knoxApproval, fileList, serverLocation);
|
||||
}
|
||||
----
|
||||
|
||||
[start=2]
|
||||
. Knox 보안 결재 상신 +
|
||||
POST /knox/approvals/secu-submit +
|
||||
* 파라미터는 일반 결재 상신과 같지만 보안문서타입이 "CONFIDENTAIL" 이다.
|
||||
[source,java]
|
||||
----
|
||||
knoxApproval.setDocSecuType("CONFIDENTAIL");
|
||||
----
|
||||
|
||||
[start=3]
|
||||
. Knox 결재 상세 상황 조회 +
|
||||
GET /knox/approvals/{apInfId}/detail +
|
||||
* 결재 연계 ID로 결재 문서의 정보를 상세 조회한다.
|
||||
|
||||
. Knox 결재 본문 조회 +
|
||||
GET /knox/approvals/{apInfId}/content +
|
||||
* 결재 연계 ID로 결재 문서의 본문을 조회한다.
|
||||
|
||||
. Knox 결재 상황 조회 +
|
||||
POST /knox/approvals/status +
|
||||
* 결재문서의 진행 상태를 조회한다.
|
||||
* 복수개의 결재 연계 ID를 요청하여 각각 해당하는 문서변경횟수와 결재상태정보를 응답받는다. 이를 이용하여 결재문서 동기화시 변경된 건에 대해 결재 상태를 업데이트 한다.
|
||||
[source,java]
|
||||
----
|
||||
public List<KnoxApprovalStatus> getStatus(@RequestBody List<KnoxApprovalStatus> knoxApprovalStatusList) {
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. Knox 결재 연계 ID 조회 +
|
||||
POST /knox/approvals/apinfids +
|
||||
* 결재 ID로 결재 연계 ID를 조회한다.
|
||||
[source,java]
|
||||
----
|
||||
public KnoxApproval getApInfIds(@RequestParam String apId) {
|
||||
----
|
||||
|
||||
[start=7]
|
||||
. Knox 상신함 리스트 조회 +
|
||||
POST /knox/approvals/submission +
|
||||
* 상신자가 상신한 정보를 조회한다.
|
||||
[source,java]
|
||||
----
|
||||
public List<KnoxApproval> getApInfIdInfos(@RequestParam String epId) {
|
||||
----
|
||||
|
||||
[start=8]
|
||||
. Knox 연계 이력 조회 +
|
||||
GET /knox/approvals/apinfidinfos +
|
||||
* 요청 시스템에서 상신된 결재문서의 연계 이력을 조회한다.
|
||||
|
||||
. Knox 상신 취소 +
|
||||
POST /knox/approvals/{apInfId}/cancel +
|
||||
* 결재 문서를 상신취소한다.
|
||||
|
||||
. Knox 완결 처리 +
|
||||
POST /knox/approvals/{apInfId}/autoprogress +
|
||||
* 결재문서를 완결처리한다.
|
||||
|
||||
|
||||
== KnoxApprovalService
|
||||
|
||||
KnoxApprovalService는 시스템에서 결재 문서를 상신 할때 필요한 API들을 제공한다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
/**
|
||||
* 일반 상신, 보안 상신
|
||||
* @param knoxApproval
|
||||
* @param attachments
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
KnoxApproval submit(KnoxApproval knoxApproval, List<Resource> attachments, String locale);
|
||||
|
||||
/**
|
||||
* 취소
|
||||
* @param apInfId
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
KnoxApproval cancel(String apInfId, String opinion, String locale);
|
||||
|
||||
/**
|
||||
* 완결 처리
|
||||
* @param apInfId
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
KnoxApproval autoProgress(String apInfId, String flag, String timeZone, String locale);
|
||||
|
||||
/**
|
||||
* 결재 상황 조회
|
||||
* @param knoxApprovalStatusList
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
List<KnoxApprovalStatus> getStatus(List<KnoxApprovalStatus> knoxApprovalStatusList, String locale);
|
||||
|
||||
/**
|
||||
* 결재상세상황조회
|
||||
* @param apInfId
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
KnoxApproval getDetail(String apInfId, String locale);
|
||||
|
||||
|
||||
/**
|
||||
* 결재본문조회
|
||||
* @param apInfId
|
||||
* @param locale
|
||||
* @return
|
||||
*/
|
||||
KnoxApproval getContent(String apInfId, String locale);
|
||||
|
||||
/**
|
||||
* 결재연계ID조회
|
||||
* @param apId
|
||||
* @return
|
||||
*/
|
||||
KnoxApproval getApInfIds(String apId, String locale);
|
||||
|
||||
/**
|
||||
* 상신함리스트조회
|
||||
* @param epId
|
||||
* @return
|
||||
*/
|
||||
List<KnoxApproval> getSubmission(String epId, String locale);
|
||||
|
||||
/**
|
||||
* 연계이력조회
|
||||
* @param endDate yyyyMMddHHmm 형식
|
||||
* @param page 페이지 처리
|
||||
* @param duration 단위 : 분 / 최소 1분 ~ 최대 60분
|
||||
* @return
|
||||
*/
|
||||
List<KnoxApproval> getApInfIdInfos(String endDate, String page, String duration, String locale);
|
||||
----
|
||||
|
||||
각각의 메서드들은 Knox Rest 연계 서비스에서 요구하는 가이드대로 REST 형식을 갖추어 필요한 로직들을 수행한다.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
= 결재 경로 관리
|
||||
|
||||
== 개요
|
||||
시스템의 결재 문서 샘플을 RUNTIME 동안 가지고 있다가 결재시에 사용한다.
|
||||
|
||||
* ApprovalManager.java -> ApprovalDocument.java -> SampleApprovalDocument.java
|
||||
. ApprovalManager : @ApprovalDocument라는 어노테이션이 달린 클래스를 찾는다.
|
||||
. SampleApprovalDocument : 결재경로 관리 목록에 결재 문서 샘플을 보여준다.
|
||||
|
||||
== Table
|
||||
* 결재 경로 : TN_CF_DYNAMIC_APPROVAL_PATH
|
||||
* 필수 결재자 : TN_CF_REQUIRED_APPROVAL_USER
|
||||
|
||||
== API
|
||||
.ApprovalController.java
|
||||
|
||||
. 시스템 전체 결재 문서 조회 +
|
||||
GET /approval/approval-doc-types
|
||||
|
||||
. 결재 경로 조회 +
|
||||
GET /approval/dynamic-approval-paths/{docType} +
|
||||
Query ID : selectDynamicApprovalPath
|
||||
* 기본결재 경로 목록을 보여준다.
|
||||
|
||||
. 결재 경로 저장 +
|
||||
POST /approval/dynamic-approval-paths/{docType} +
|
||||
Query ID : deleteDynamicApprovalPath, insertDynamicApprovalPath
|
||||
|
||||
. 필수 결재자 목록 조회 +
|
||||
GET /approval/required-approval-users/{docType} +
|
||||
Query ID : selectRequiredApprovalUserList
|
||||
* 필수 결재자 목록을 보여준다.
|
||||
|
||||
. 필수 결재자 저장 +
|
||||
POST /approval/required-approval-users/{docType} +
|
||||
Query ID : deleteRequiredApprovalUser, insertRequiredApprovalUser
|
||||
|
||||
== 화면
|
||||
지정된 문서타입에 따른 결재경로를 관리기능 > 결재/메일 관리 > 결재경로 관리를 통해 지정할 수 있다.
|
||||
|
||||
image::front_07_04.png[]
|
||||
* 지정된 문서타입 목록을 확인할 수 있다.
|
||||
|
||||
image::front_07_05.png[]
|
||||
* 문서타입을 선택 후 해당 문서에 대한 기본 결재경로와 필수 결재자를 추가할 수 있다.
|
||||
* 결재 상신시 결재자 목록에 지정된 기본결재 경로가 자동 추가되며, 지정된 필수 결재자가 있는 경우 추가하라는 알림을 준다. (다수중 1인 가능)
|
||||
@@ -0,0 +1,35 @@
|
||||
= 결재 관리
|
||||
|
||||
== 개요
|
||||
결재 관리를 통해 내부/knox 결재 목록을 확인할 수 있다.
|
||||
|
||||
== Table
|
||||
* 결재 : TN_CF_APPROVAL
|
||||
* 결재 이벤트 : TN_CF_APPROVAL_EVENT
|
||||
* 결재 스텝 : TN_CF_APPROVAL_STEP
|
||||
|
||||
== API
|
||||
.ApprovalController.java
|
||||
|
||||
. 시스템 전체 결재 문서 조회 +
|
||||
GET /approval/approval-doc-types +
|
||||
* 결재경로 관리의 문서타입을 가져온다.
|
||||
|
||||
. 시스템 전체 결재 목록 조회(페이징) +
|
||||
GET /approval/approval-with-paging +
|
||||
Query ID : selectApprovalPagingList
|
||||
|
||||
. 결재 상세정보 조회 +
|
||||
GET /approval/{approvalRequestId} +
|
||||
Query ID : selectApprovalStep, selectApprovalEventList
|
||||
|
||||
== 화면
|
||||
image::front_07_01.png[]
|
||||
|
||||
=== 검색 조건
|
||||
** 구분 : knox 결재/ 내부결재
|
||||
** 상태 : 결재중/완료/후완결/반려/취소
|
||||
** 문서명 : 결재양식 목록
|
||||
** 상신자 : 결재 요청한 상신자
|
||||
** 동기화 상태 : 성공/실패
|
||||
** 기간 : 결재요청 기간
|
||||
@@ -0,0 +1,56 @@
|
||||
= 결재 양식 관리
|
||||
|
||||
== 개요
|
||||
결재 양식을 관리한다.
|
||||
|
||||
== Table
|
||||
* 템플릿 : TN_CF_TEMPLATE
|
||||
|
||||
== API
|
||||
.TemplateController.java
|
||||
|
||||
. 템플릿 목록 조회(페이징) +
|
||||
GET /templates-with-paging/group/{templateGroupCode} +
|
||||
Query ID : selectTemplatePagingList
|
||||
* TEMPLATE_GROUP_CODE 컬럼의 'APPROVAL'을 조회한다.
|
||||
|
||||
. 템플릿 상세 조회 +
|
||||
GET /templates/group/{templateGroupCode}/key/{templateKey} +
|
||||
Query ID : selectTemplate
|
||||
|
||||
. 템플릿 상세 조회 (By ID) +
|
||||
GET /templates/{id} +
|
||||
Query ID : selectTemplate
|
||||
* 양식의 상세 내용을 조회 하거나 팝업 미리보기를 할 수 있다.
|
||||
|
||||
. 템플릿 Key 중복 체크 +
|
||||
GET /templates/dup-check/group/{templateGroupCode}/key/{templateKey} +
|
||||
Query ID : selectTemplate
|
||||
* 저장 전 템플릿 Key 중복 여부를 검사한다.
|
||||
|
||||
. 템플릿 등록 +
|
||||
POST /templates/group/{templateGroupCode} +
|
||||
Query ID : insertTemplate
|
||||
* 양식을 저장한다.
|
||||
|
||||
. 템플릿 수정 +
|
||||
POST /templates/{id} +
|
||||
Query ID : updateTemplate
|
||||
|
||||
. 템플릿 삭제 +
|
||||
DELETE /templates/{id} +
|
||||
Query ID : deleteTemplate
|
||||
|
||||
== 화면
|
||||
결재양식을 관리기능 > 결재/메일관리 > 결재양식 관리를 통해 볼 수 있다.
|
||||
|
||||
image::front_07_06.png[]
|
||||
* 등록된 결재양식 목록을 결재양식 관리를 통해 볼 수 있다.
|
||||
* 목록의 Key 컬럼을 클릭하여 결재양식 정보를 수정할 수 있다.
|
||||
* 팝업 미리보기 컬럼을 클릭하여 등록된 결재양식의 첨부파일을 확인 할 수 있다.
|
||||
|
||||
image::front_07_07.png[]
|
||||
* Key : 유니크한 결재양식 키를 지정(영문, 숫자만 가능)
|
||||
* 제목 : 결재양식의 제목
|
||||
* 설명 : 결재양식의 설명
|
||||
* 첨부파일 : 결재양식 첨부파일
|
||||
@@ -0,0 +1,111 @@
|
||||
= 내부 결재
|
||||
|
||||
== 개요
|
||||
SDL에서 제공하는 내부결재 기능. +
|
||||
결재를 위한 문서 Entity등록과 내부결재 후처리를 위한 Interceptor 클래스 구현으로 간단하게 결재 기능을 구현할 수 있다. +
|
||||
샘플로 제공하는 화면의 결재 스텝을 지정하는 Component를 필요한 결재 문서 화면에 적용하여 사용할 수 있으며 샘플 Controller를 참조하여 문서와 결재 스텝 목록을 저장하여 approvalService.submit 메소드를 호출하면 된다.
|
||||
|
||||
== Table
|
||||
* 결재 정보 : TN_CF_APPROVAL
|
||||
* 결재 스텝 정보 : TN_CF_APPROVAL_STEP
|
||||
* Sample Document : TN_CF_SAMPLE_APPROVAL_INTERNAL_DOCUMENT
|
||||
|
||||
== API
|
||||
.ApprovalController.java
|
||||
|
||||
. 상신함 목록 조회(내부 결재) +
|
||||
GET /internal-approvals/submit +
|
||||
Query ID : selectInternalApprovalPagingListBySubmit
|
||||
* 사용자가 상신한 내부결재 문서를 조회한다.
|
||||
|
||||
. 미결함 목록 조회(내부 결재) +
|
||||
GET /internal-approvals/not-approve +
|
||||
Query ID : selectInternalApprovalPagingListByNotApprove
|
||||
* 미결중인 내부결재 문서를 조회한다.
|
||||
|
||||
. 기결함 목록 조회(내부 결재) +
|
||||
GET /internal-approvals/approved +
|
||||
Query ID : selectInternalApprovalPagingListByApproved
|
||||
* 사용자가 결재 완료한 내부결재 문서를 조회한다.
|
||||
|
||||
. 통보함 목록 조회(내부 결재) +
|
||||
GET /internal-approvals/notice +
|
||||
Query ID : selectInternalApprovalPagingListByNotice
|
||||
* 사용자가 통보 대상인 결재문서를 조회한다.
|
||||
|
||||
. 내부결재 문서 상신 취소 +
|
||||
POST /internal-approvals/{approvalRequestId}/cancel +
|
||||
Service : cancelInternalApproval
|
||||
Query ID : updateInternalApprovalStep
|
||||
* 결재 문서 상신을 취소한다.
|
||||
|
||||
. 내부결재 문서 결재 승인 또는 합의 +
|
||||
POST /internal-approvals/{approvalRequestId}/confirm +
|
||||
Service : confirmInternalApproval
|
||||
Query ID : updateInternalApprovalStep
|
||||
* 결재 문서 승인 또는 합의 한다.
|
||||
|
||||
. 내부결재 문서 반려 +
|
||||
POST /internal-approvals/{approvalRequestId}/reject +
|
||||
Service : rejectInternalApproval
|
||||
Query ID : updateInternalApprovalStep
|
||||
* 결재 문서를 반려 한다.
|
||||
|
||||
.ApprovalSampleController.java
|
||||
|
||||
. 내부결재 문서 목록 조회(페이징) +
|
||||
GET /internal-approval/sample-document-with-paging +
|
||||
Query ID : selectSampleApprovalDocumentPagingList
|
||||
* Sample Document 문서 목록을 조회한다.
|
||||
|
||||
. 내부 결재 문서 상신(Sample Document) +
|
||||
POST /internal-approval/submit/sample-document
|
||||
* Knox 결재 정보 동기화 로직을 제외한 다른 부분은 Knox 결재 상신과 동일하다.
|
||||
* 동기화 배치 로직 대상에서 제외된다.
|
||||
* 내부결재 전후 처리 로직은 문서 타입별로 Interceptor 클래스를 구현하여 처리한다.
|
||||
[source,java]
|
||||
----
|
||||
@Log4j2
|
||||
@ApprovalDocumentType(names = {"internalSample"})
|
||||
public class InternalApprovalSampleInterceptor implements ApprovalInterceptor {
|
||||
|
||||
@Autowired
|
||||
InternalApprovalSampleService internalApprovalSampleService;
|
||||
@Override
|
||||
public void afterSubmit(Approval approval, List<ApprovalStep> approvalStepList, Map<String, Object> attribute) {
|
||||
log.debug("execute ApprovalSampleInterceptor afterSubmit");
|
||||
//상신 후 문서 상태 업데이트
|
||||
internalApprovalSampleService.updateSampleApprovalDocument(approval.getDbDocId(), ApprovalDocStatus.INPROCESS);
|
||||
}
|
||||
--생략--
|
||||
}
|
||||
----
|
||||
|
||||
== 화면
|
||||
|
||||
. 내부결재 문서 상신함
|
||||
|
||||
image::internalApproval_01.png[]
|
||||
image::internalApproval_05.png[]
|
||||
* 사용자가 상신한 내부결재 문서 목록을 조회한다.
|
||||
* 상세화면에 진입하여 상신취소가 가능하다.
|
||||
|
||||
[start=2]
|
||||
. 내부결재 문서 미결함
|
||||
|
||||
image::internalApproval_02.png[]
|
||||
image::internalApproval_06.png[]
|
||||
* 사용자의 결재 차순에 있는 내부결재 문서 목록을 조회한다.
|
||||
* 상세화면에 진입하여 승인/합의 또는 반려가 가능하다.
|
||||
|
||||
[start=3]
|
||||
. 내부결재 문서 기결함
|
||||
|
||||
image::internalApproval_03.png[]
|
||||
* 사용자의 결재 완료한 내부결재 문서 목록을 조회한다.
|
||||
|
||||
[start=4]
|
||||
. 내부결재 문서 통보함
|
||||
|
||||
image::internalApproval_04.png[]
|
||||
* 결재 완료 후 사용자에게 통보된 내부결재 문서 목록을 조회한다.
|
||||
@@ -0,0 +1,35 @@
|
||||
= 대리 결재
|
||||
|
||||
== 개요
|
||||
내부결재에 적용되는 대리결재자를 지정하는 기능을 제공한다. +
|
||||
(Knox 결재 대리결재는 Knox portal에서 지정 가능함) +
|
||||
|
||||
== Table
|
||||
* 결재 정보 : TN_CF_APPROVAL_DELEGATE
|
||||
|
||||
== API
|
||||
.ApprovalController.java
|
||||
|
||||
. 대리결재자 조회 +
|
||||
GET /approval/approver-delegate +
|
||||
Query ID : selectApproverDelegate
|
||||
* 사용자의 대리결재자를 조회한다.
|
||||
|
||||
. 대리결재자 저장 +
|
||||
POST /approval/approver-delegate +
|
||||
Query ID : updateApproverDelegate, insertApproverDelegate
|
||||
* 사용자의 대리결재자를 저장한다.(등록 또는 변경)
|
||||
|
||||
. 대리결재자 삭제 +
|
||||
DELETE /approval/approver-delegate +
|
||||
Query ID : deleteApproverDelegate
|
||||
* 사용자의 대리결재자를 삭제한다.
|
||||
|
||||
== 화면
|
||||
|
||||
. 대리결재자 조회 및 지정
|
||||
|
||||
image::approverDelegate_01.png[]
|
||||
|
||||
* 사용자 정보 > 대리결재 메뉴를 통해서 대리결재자 등록이 가능하다.
|
||||
* 기등록된 대리결재자가 존재한다면 대리결재자 팝업 상단에 표시되며 삭제하거나 다른 사용자를 선택하여 변경가능 하다.
|
||||
@@ -0,0 +1,85 @@
|
||||
= 메신저
|
||||
|
||||
== 개요
|
||||
Knox Portal에서 제공하는 메신저 관련 Rest API 를 이용한 연계 서비스 제공
|
||||
|
||||
=== Knox REST API 연계 서비스 신청
|
||||
Knox REST API 연계 서비스 신청은 <<_knox_rest_api_연계_서비스_신청,Knox REST API 연계 서비스 신청>> 항목을 참조한다.
|
||||
|
||||
=== Knox Rest 메신저 연계 설정
|
||||
메일, 결재 Knox Rest API 연계와 마찬가지로 연계를 위한 사전 준비가 되었다면, knox.properties 에 메신저 관련 설정이 되어 있는지 확인한다.
|
||||
|
||||
.knox.properties
|
||||
[source,properties]
|
||||
----
|
||||
knox.messenger.contact-service=/messenger/contact/api/v1.0
|
||||
knox.messenger.msgctx-service=/messenger/msgctx/api/v1.0
|
||||
knox.messenger.message-service=/messenger/message/api/v1.0
|
||||
----
|
||||
|
||||
=== Knox 메신저 연계 서비스
|
||||
REST를 통해서 메신저와 연계하는 서비스로 주요 메서드는 KnoxMessengerService 인터페이스에 정의되어 있다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public interface KnoxMessengerService {
|
||||
|
||||
/**
|
||||
* 디바이스 ID 조회 : 사용자 ID 와 맵핑되는 단말의 ID 값
|
||||
* @return 디바이스 ID
|
||||
*/
|
||||
String getDeviceId();
|
||||
|
||||
/**
|
||||
* 메시지 암호화 키 조회 : 메시지를 암호화하기 위한 키 값
|
||||
* @param deviceId 디바이스 ID
|
||||
* @return 메시지 암호화 키
|
||||
*/
|
||||
String getKey(String deviceId);
|
||||
|
||||
/**
|
||||
* Knox Potal login ID를 이용하여 Knox Messenger 수신자들을 조회한다.
|
||||
* @param deviceId 디바이스 ID
|
||||
* @param singleIds 수신자 Knox ID 리스트
|
||||
* @return Knox Messenger 수신자 ID 리스트
|
||||
*/
|
||||
List<String> getUserIds(String deviceId, List<String> singleIds);
|
||||
|
||||
/**
|
||||
* 공지 메시지를 발신할 대화방이 없는 경우 신규 대화방 생성을 요청한다.
|
||||
* @param deviceId 디바이스 ID
|
||||
* @param key 암호화 키
|
||||
* @param userIds Knox Messenger 수신자 ID 리스트
|
||||
* @return 대화방 생성 요청 응답 결과
|
||||
*/
|
||||
Map<String, Object> createChatroom(String deviceId, String key, List<String> userIds);
|
||||
|
||||
/**
|
||||
* Message 서버 API 에서 필요한 암호화된 바디를 만들기 위한 function
|
||||
* @param key - msgCtx 를 통해 전달받은 key 값
|
||||
* @param body - 암호화 해야 될 String
|
||||
* @return 암호화된 String
|
||||
*/
|
||||
String encrypt(String key, String body);
|
||||
|
||||
/**
|
||||
* Response 로 전달된 암호화 body 를 복호화 하기 위한 function <br/>
|
||||
* 암호화의 역순으로 Base64 복호화 -> AES256 복호화
|
||||
* @param body - 암호화 되어 있는 body
|
||||
* @return 복호화된 response String
|
||||
*/
|
||||
String decrypt(String body);
|
||||
|
||||
/**
|
||||
* 신규 생성한 대화방 또는 기존에 사용중인 대화방에 공지 메시지를 발송한다.
|
||||
* @param deviceId 디바이스 ID
|
||||
* @param key 암호화 키
|
||||
* @param chatroomId 생성된 대화방 ID
|
||||
* @param chatMsg 메시지 내용
|
||||
* @return 메시지 발신 요청 응답 결과
|
||||
*/
|
||||
Map<String, Object> chat(String deviceId, String key, String chatroomId, String chatMsg);
|
||||
}
|
||||
----
|
||||
|
||||
자세한 API 스펙은 Swagger API 문서의 knox-messenger-controller 항목을 참고한다.
|
||||
@@ -0,0 +1,75 @@
|
||||
= 메일
|
||||
|
||||
=== Knox REST API 연계 서비스 신청
|
||||
Knox REST API 연계 서비스 신청은 <<_knox_rest_api_연계_서비스_신청,Knox REST API 연계 서비스 신청>> 항목을 참조한다.
|
||||
|
||||
=== Knox메일 연계 설정
|
||||
Knox REST API 연계 서비스 신청이 되었다면, 발급받은 `system-id`, `token` 값을 설정한다.
|
||||
|
||||
.knox.properties (스테이지)
|
||||
[source,properties]
|
||||
----
|
||||
knox.system-id=xxxxxxxxxxx
|
||||
knox.token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
knox.address.prefix=openapi.samsung.net
|
||||
knox.mail-service=/mail/api/v2.0
|
||||
----
|
||||
|
||||
.knox.properties (운영)
|
||||
[source,properties]
|
||||
----
|
||||
knox.system-id=xxxxxxxxxxx
|
||||
knox.token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // <1>
|
||||
knox.address.prefix=openapi.samsung.net,openapi.w1.samsung.net,openapi.w2.samsung.net # <2>
|
||||
knox.mail-service=/mail/api/v2.0
|
||||
----
|
||||
<1> 토큰 (comma(,)로 구분, 거점 순서와 동일)
|
||||
<2> 거점 (국내, 구주, 미주)
|
||||
|
||||
=== KnoxMailService
|
||||
Knox REST API 연계를 통해서 메일발신, 상태조회등을 하는 서비스로 주요 메서드는 KnoxMailService 인터페이스에 정의되어 있다.
|
||||
|
||||
KnoxMailService는 시스템에서 Knox 메일 서비스와 연계할 때 필요한 API들을 제공한다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public interface KnoxMailService {
|
||||
/**
|
||||
* Knox 메일 발신
|
||||
* @param sendMail 발신 메일 정보 (첨부파일정보(AttachFile) 포함)
|
||||
* @return Knox 메일 아이디
|
||||
*/
|
||||
String sendMail(SendMail sendMail);
|
||||
|
||||
/**
|
||||
* Knox 메일 발신
|
||||
* @param sendMail 발신 메일 정보
|
||||
* @param attachResources 메일 첨부가 파일 리소스일 경우
|
||||
* @return Knox 메일 아이디
|
||||
*/
|
||||
String sendMail(SendMail sendMail, List<Resource> attachResources);
|
||||
|
||||
/**
|
||||
* Knox 메일별 수신상태 조회
|
||||
* @param mailIds Knox 메일 아이디 리스트
|
||||
* @param sendMail 메일 정보
|
||||
* @return Knox 메일 상태
|
||||
*/
|
||||
MailStatus[] getDeliveryStatusCount(List<String> mailIds, SendMail sendMail);
|
||||
|
||||
/**
|
||||
* Knox 메일 수신인별 수신상태 조회
|
||||
* @param mailId Knox 메일 아이디
|
||||
* @param sendMail 메일 정보
|
||||
* @return Knox 메일 수신인별 수신상태 정보
|
||||
*/
|
||||
Recipient[] getDeliveryStatus(String mailId, SendMail sendMail);
|
||||
}
|
||||
----
|
||||
|
||||
각각의 구현 메서드들은 Knox Rest 연계 서비스에서 요구하는 가이드대로 REST 형식을 갖추어 필요한 로직들을 수행한다.
|
||||
|
||||
자세한 API 스펙은 Swagger API 문서의 knox-mail-controller 항목을 참고한다. +
|
||||
메일 발송 샘플 코드는 MailSampleController 소스코드를 참고한다.
|
||||
|
||||
IMPORTANT: Knox 연계 메일 발송을 위해서는 발신자/수신자 모두 Knox 계정이 존재해야만 테스트가 가능하다.
|
||||
@@ -0,0 +1,161 @@
|
||||
= 메일 그룹 관리
|
||||
|
||||
== 개요
|
||||
|
||||
메일 그룹 및 맵핑 정보 관리 기능 제공.
|
||||
|
||||
== UI Design & Function
|
||||
|
||||
=== 메일 그룹 목록(MailGroupList.vue)
|
||||
|
||||
메일 그룹 등록, 수정 및 삭제가 가능하다.
|
||||
|
||||
image::mailGroupList.png[mailGroupList.png]
|
||||
|
||||
* 기능 설명
|
||||
. 메일 그룹 목록 조회
|
||||
. 메일 그룹 상세정보 조회
|
||||
. 메일 그룹 등록 : 등록 버튼 클릭 시 메일 그룹 입력 popup 호출.
|
||||
. 메일 그룹 수정 : checkbox 선택 후 수정 버튼 클릭 시 변경을 위한 popup 호출.
|
||||
. 메일 그룹 삭제 : checkbox 선택 후 삭제 버튼 클릭 시 삭제.
|
||||
|
||||
|
||||
=== 메일 그룹 맵핑 수정(MailGroupMappEdit.vue)
|
||||
|
||||
메일 그룹 맵핑 목록 등록 및 삭제가 가능하다.
|
||||
|
||||
image::mailGroupMappEdit.png[mailGroupMappList.png]
|
||||
|
||||
* 기능 설명
|
||||
. 메일 그룹 맵핑 목록 조회
|
||||
. 메일 그룹 맵핑 저장
|
||||
.. 사용자 추가 : 버튼 클릭 시 사용자 추가 popup 호출.
|
||||
.. 역할 추가 : 버튼 클릭 시 역할 추가 popup 호출.
|
||||
.. 업무그룹 추가 : 버튼 클릭 시 업무그룹 추가 popup 호출.
|
||||
|
||||
== API & Service
|
||||
|
||||
=== API
|
||||
|
||||
* API : MailGroupController.java
|
||||
|
||||
. 메일 그룹 목록 조회 : GET /mail-group-with-paging
|
||||
. 메일 그룹 상세정보 조회 : GET /mail-group/{mailGroupId}
|
||||
. 메일 그룹 등록 : POST /mail-group
|
||||
. 메일 그룹 수정 : PUT /mail-group
|
||||
. 메일 그룹 삭제 : DELETE /mail-group
|
||||
. 메일 그룹 맵핑 목록 조회 : GET /mail-group-mapp-with-paging
|
||||
. 메일 그룹 맵핑 저장 : POST /mail-group-mapp/{mailGroupId}
|
||||
|
||||
* Service : MailGroupServiceImpl.java
|
||||
|
||||
. 메일 그룹 맵핑 저장 +
|
||||
맵핑 정보 저장 시 기등록 되어 있는 맵핑 목록 삭제 후
|
||||
전체 목록을 다시 저장하도록 구현.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
@Transactional
|
||||
public void saveMailGroupMapp(String mailGroupId, List<MailGroupMapp> mailGroupMappList) {
|
||||
|
||||
mailGroupDao.deleteMailGroupMapp(mailGroupId);
|
||||
|
||||
for(MailGroupMapp mailGroupMapp : mailGroupMappList) {
|
||||
mailGroupMapp.setMailGroupId(mailGroupId);
|
||||
mailGroupDao.insertMailGroupMapp(mailGroupMapp);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
== Entity Table & SQL
|
||||
|
||||
=== Entity Table
|
||||
|
||||
* TN_CF_MAIL_GROUP : 메일 그룹
|
||||
* TN_CF_MAIL_GROUP_MAPP : 메일 그룹 맵핑
|
||||
|
||||
=== SQL
|
||||
|
||||
. 메일 그룹 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectMailGroupPagingList" parameterType="java.util.HashMap" resultMap="mailGroupResult">
|
||||
SELECT T.*
|
||||
FROM (SELECT ROW_NUMBER() OVER(ORDER BY LABEL ASC) ROWNUM,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=2]
|
||||
. 메일 그룹 상세정보 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectMailGroup" parameterType="java.util.HashMap" resultMap="mailGroupResult">
|
||||
SELECT <include refid="columnMailGroup" />,
|
||||
(SELECT USER_NAME
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=3]
|
||||
. 메일그룹 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertMailGroup" parameterType="java.util.HashMap">
|
||||
INSERT INTO <include refid="tableMailGroup" />
|
||||
(<include refid="columnMailGroup" />)
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. 메일그룹 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateMailGroup" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableMailGroup" />
|
||||
SET LABEL = #{label},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. 메일그룹 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteMailGroup" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableMailGroup" />
|
||||
SET DELETED = '1',
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. 메일그룹 맵핑 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteMailGroup" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableMailGroup" />
|
||||
SET DELETED = '1',
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=7]
|
||||
. 메일그룹 맵핑 저장
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteMailGroupMapp" parameterType="java.util.HashMap">
|
||||
DELETE FROM <include refid="tableMailGroupMapp" />
|
||||
WHERE MAIL_GROUP_ID = #{mailGroupId}
|
||||
</delete>
|
||||
|
||||
<insert id="insertMailGroupMapp" parameterType="java.util.HashMap">
|
||||
INSERT INTO <include refid="tableMailGroupMapp" />
|
||||
(<include refid="columnMailGroupMapp" />)
|
||||
--생략--
|
||||
----
|
||||
@@ -0,0 +1,87 @@
|
||||
= 메일 상태 조회
|
||||
|
||||
== 개요
|
||||
Knox REST 메일수신상황조회 API와 연계하여 발신한 메일의 상태를 조회할 수 있다.
|
||||
|
||||
=== Knox메일 연계 설정
|
||||
Knox REST 메일수신상황조회를 위해서는 Knox메일 연계 설정이 되어 있어야 한다. +
|
||||
Knox메일 연계 설정은 <<_메일,메일>> 항목의 <<_knox메일_연계_설정,Knox메일 연계 설정>> 항목을 참조한다.
|
||||
|
||||
=== 메일별 수신 상황 카운트 조회
|
||||
발신한 메일의 메일 아이디값을 이용하여 발신한 메일을 수신한 수신자들의 개봉상태를
|
||||
요약한 카운트 정보조회
|
||||
|
||||
Service:: KnoxMailService
|
||||
|
||||
Method::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
* Knox 메일별 수신상태 조회
|
||||
* @param mailIds Knox 메일 아이디 리스트
|
||||
* @param sendMail 메일 정보
|
||||
* @return Knox 메일 상태
|
||||
*/
|
||||
MailStatus[] getDeliveryStatusCount(List<String> mailIds, SendMail sendMail);
|
||||
----
|
||||
* SendMail 객체에 senderId (발신자 EP ID) 값 설정 필수
|
||||
|
||||
=== 수신인 별 수신 상황 조회
|
||||
발신한 메일의 메일 아이디값을 이용하여 발신한 메일의 수신자 별 수신 상태 정보를 조회
|
||||
|
||||
Service:: KnoxMailService
|
||||
|
||||
Method::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
* Knox 메일 수신인별 수신상태 조회
|
||||
* @param mailId Knox 메일 아이디
|
||||
* @param sendMail 메일 정보
|
||||
* @return Knox 메일 수신인별 수신상태 정보
|
||||
*/
|
||||
Recipient[] getDeliveryStatus(String mailId, SendMail sendMail);
|
||||
----
|
||||
* SendMail 객체에 senderId (발신자 EP ID) 값 설정 필수
|
||||
|
||||
=== 사용 예
|
||||
.SentMailHistoryServiceImpl
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
public Map<String, Object> getSentMail(String mailId) {
|
||||
Map<String, Object> rtnMap = new HashMap<>();
|
||||
MailStatus mailStatus = new MailStatus();
|
||||
List<Recipient> recipientList;
|
||||
|
||||
List<String> mailIds = new ArrayList<>();
|
||||
mailIds.add(mailId);
|
||||
SendMail sendMail = sentMailHistoryDao.getSentMail(mailId);
|
||||
|
||||
try {
|
||||
// 14일 이전 문서는 Knox에서 조회
|
||||
mailStatus = knoxMailService.getDeliveryStatusCount(mailIds, sendMail)[0]; // <1>
|
||||
recipientList = Arrays.asList(knoxMailService.getDeliveryStatus(mailId, sendMail)); // <2>
|
||||
|
||||
if (ObjectUtils.isNotEmpty(recipientList)) {
|
||||
// 메일 수신 상태 동기화
|
||||
updateRecipient(mailId, recipientList);
|
||||
} else {
|
||||
recipientList = sentMailHistoryDao.getRecipientList(mailId);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error(ex.getMessage());
|
||||
//Knox 에서 메일을 조회 할수 없을 경우, 14일이 경과 되었거나 Mail Box를 못찾을 경우
|
||||
recipientList = sentMailHistoryDao.getRecipientList(mailId);
|
||||
}
|
||||
|
||||
rtnMap.put("mailStatus", mailStatus);
|
||||
rtnMap.put("recipientList", recipientList);
|
||||
rtnMap.put("sendMail", sendMail);
|
||||
return rtnMap;
|
||||
}
|
||||
----
|
||||
<1> Knox 메일별 수신자들의 개봉상태 카운트 조회
|
||||
<2> Knox 수신인 별 메일 수신 상황 조회
|
||||
@@ -0,0 +1,58 @@
|
||||
= 메일 양식 관리
|
||||
|
||||
== 개요
|
||||
메일 양식을 관리한다. +
|
||||
<<_결재_양식_관리,결재 양식 관리>>와 비교해서 'MAIL'로 조회하는 것 외에 동일하다.
|
||||
|
||||
== Table
|
||||
* 템플릿 : TN_CF_TEMPLATE
|
||||
|
||||
== API
|
||||
.TemplateController.java
|
||||
|
||||
. 템플릿 목록 조회(페이징) +
|
||||
GET /templates-with-paging/group/{templateGroupCode} +
|
||||
Query ID : selectTemplatePagingList
|
||||
* TEMPLATE_GROUP_CODE 컬럼의 'MAIL'을 조회한다.
|
||||
|
||||
. 템플릿 상세 조회 +
|
||||
GET /templates/group/{templateGroupCode}/key/{templateKey} +
|
||||
Query ID : selectTemplate
|
||||
|
||||
. 템플릿 상세 조회 (By ID) +
|
||||
GET /templates/{id} +
|
||||
Query ID : selectTemplate
|
||||
* 양식의 상세 내용을 조회 하거나 팝업 미리보기를 할 수 있다.
|
||||
|
||||
. 템플릿 Key 중복 체크 +
|
||||
GET /templates/dup-check/group/{templateGroupCode}/key/{templateKey} +
|
||||
Query ID : selectTemplate
|
||||
* 저장 전 템플릿 Key 중복 여부를 검사한다.
|
||||
|
||||
. 템플릿 등록 +
|
||||
POST /templates/group/{templateGroupCode} +
|
||||
Query ID : insertTemplate
|
||||
* 양식을 저장한다.
|
||||
|
||||
. 템플릿 수정 +
|
||||
POST /templates/{id} +
|
||||
Query ID : updateTemplate
|
||||
|
||||
. 템플릿 삭제 +
|
||||
DELETE /templates/{id} +
|
||||
Query ID : deleteTemplate
|
||||
|
||||
== 화면
|
||||
|
||||
메일양식을 관리기능 > 결재/메일관리 > 메일양식 관리를 통해 할 수 있다.
|
||||
|
||||
image::mailTemplate.png[]
|
||||
* 등록된 메일양식 목록을 메일양식 관리를 통해 볼 수 있다.
|
||||
* 목록의 Key 컬럼을 클릭하여 메일양식 정보를 수정할 수 있다.
|
||||
* 팝업 미리보기 컬럼을 클릭하여 등록된 메일양식의 첨부파일을 확인 할 수 있다.
|
||||
|
||||
image::mailTemplate1.png[]
|
||||
* Key : 유니크한 메일양식 키를 지정(영문, 숫자만 가능)
|
||||
* 제목 : 메일양식의 제목
|
||||
* 설명 : 메일양식의 설명
|
||||
* 첨부파일 : 메일양식 첨부파일
|
||||
@@ -0,0 +1,19 @@
|
||||
= 보낸 메일 이력
|
||||
|
||||
== 개요
|
||||
메일 발송 내역 및 수신 상태를 조회할 수 있다.
|
||||
|
||||
=== 보낸 메일 목록
|
||||
|
||||
image::sentMailHistory.png[]
|
||||
|
||||
<1> 기간, 제목별 검색 가능
|
||||
<2> 클릭시 상세 내역 조회
|
||||
|
||||
=== 보낸 메일 상세 정보
|
||||
|
||||
image::sentMailHistoryInfo.png[]
|
||||
|
||||
<1> 보낸 메일의 수신인별 개봉 여부
|
||||
<2> 보낸 메일 본문 확인 가능
|
||||
<3> 수신인과 수신상태 정보 목록
|
||||
@@ -0,0 +1,93 @@
|
||||
= 임직원
|
||||
|
||||
== 개요
|
||||
Knox Portal에서 제공하는 임직원 관련 Rest API 를 이용한 연계 서비스 제공
|
||||
|
||||
=== Knox REST API 연계 서비스 신청
|
||||
Knox REST API 연계 서비스 신청은 <<_knox_rest_api_연계_서비스_신청,Knox REST API 연계 서비스 신청>> 항목을 참조한다.
|
||||
|
||||
=== Knox임직원 연계 설정
|
||||
Knox REST API 연계 서비스 신청이 되었다면, 발급받은 `system-id`, `token` 값을 설정한다.
|
||||
|
||||
.knox.properties (스테이지)
|
||||
[source,properties]
|
||||
----
|
||||
knox.system-id=xxxxxxxxxxx
|
||||
knox.token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
knox.address.prefix=openapi.samsung.net
|
||||
knox.emp-service=/employee/api/v2.0
|
||||
----
|
||||
|
||||
.knox.properties (운영)
|
||||
[source,properties]
|
||||
----
|
||||
knox.system-id=xxxxxxxxxxx
|
||||
knox.token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // <1>
|
||||
knox.address.prefix=openapi.samsung.net,openapi.w1.samsung.net,openapi.w2.samsung.net # <2>
|
||||
knox.emp-service=/employee/api/v2.0
|
||||
----
|
||||
<1> 토큰 (comma(,)로 구분, 거점 순서와 동일)
|
||||
<2> 거점 (국내, 구주, 미주)
|
||||
|
||||
=== Knox임직원 API 연계 서비스
|
||||
Knox REST API 연계를 통해서 임직원 및 조직 정보 조회 기능을 제공하는 서비스로 주요 메서드는 KnoxUserService 인터페이스에 정의되어 있다.
|
||||
|
||||
KnoxUserService는 시스템에서 임직원 및 조직 정보를 조회할때 필요한 API들을 제공한다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public interface KnoxUserService {
|
||||
|
||||
/**
|
||||
* Knox 임직원 조회(By EpId)
|
||||
* @param epId EP ID
|
||||
* @return Knox 사용자
|
||||
*/
|
||||
Employee[] getKnoxEmployeesByEpId(String epId);
|
||||
|
||||
/**
|
||||
* Knox 임직원 조회(By UserName)
|
||||
* @param userName 사용자 이름
|
||||
* @return Knox 사용자
|
||||
*/
|
||||
Employee[] getKnoxEmployeesByUserName(String userName);
|
||||
|
||||
/**
|
||||
* Knox 임직원 조회(By KnoxId)
|
||||
* @param knoxId Knox ID
|
||||
* @return Knox 사용자
|
||||
*/
|
||||
Employee[] getKnoxEmployeesByKnoxId(String knoxId);
|
||||
|
||||
/**
|
||||
* Knox 임직원 조회(By Email)
|
||||
* @param email 이메일
|
||||
* @return Knox 사용자
|
||||
*/
|
||||
Employee[] getKnoxEmployeesByEmail(String email);
|
||||
|
||||
/**
|
||||
* Knox 조직도 조회(By CompanyCode)
|
||||
* @param companyCode 회사 코드
|
||||
* @return Knox 조직도
|
||||
*/
|
||||
Organization[] getKnoxOrganizationsByCompanyCode(String companyCode);
|
||||
|
||||
/**
|
||||
* Knox 조직도 조회(By DepartmentCode)
|
||||
* @param companyCode 회사 코드
|
||||
* @param departmentCode 부서 코드
|
||||
* @return Knox 조직도
|
||||
*/
|
||||
Organization[] getKnoxOrganizationsByDepartmentCode(String companyCode, String departmentCode);
|
||||
|
||||
/**
|
||||
* Knox 직급 조회
|
||||
* @param companyCode 회사 코드
|
||||
* @return Knox 직급
|
||||
*/
|
||||
Title[] getKnoxTitles(String companyCode);
|
||||
}
|
||||
----
|
||||
|
||||
자세한 API 스펙은 Swagger API 문서의 knox-user-controller 항목을 참고한다.
|
||||
@@ -0,0 +1,132 @@
|
||||
= 주소록
|
||||
|
||||
== 개요
|
||||
Knox Portal에서 제공하는 연락처 관련 Rest API 를 이용한 연계 서비스 제공
|
||||
|
||||
=== Knox REST API 연계 서비스 신청
|
||||
Knox REST API 연계 서비스 신청은 <<_knox_rest_api_연계_서비스_신청,Knox REST API 연계 서비스 신청>> 항목을 참조한다.
|
||||
|
||||
=== Knox Rest 연락처 연계 설정
|
||||
메일, 결재 Knox Rest API 연계와 마찬가지로 연계를 위한 사전 준비가 되었다면, knox.properties 에 연락처 관련 설정이 되어 있는지 확인한다.
|
||||
|
||||
.knox.properties
|
||||
[source,properties]
|
||||
----
|
||||
knox.pims-service=/pims/contacts/api/v2.0
|
||||
----
|
||||
|
||||
=== Knox 연락처 연계 서비스
|
||||
REST를 통해서 연락처를 연계하는 서비스로 주요 메서드는 KnoxContactService 인터페이스에 정의되어 있다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public interface KnoxContactService {
|
||||
|
||||
/**
|
||||
* 연락처 그룹 생성
|
||||
* @param contactGroupDto
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactGroupDto createGroup(ContactGroupDto contactGroupDto, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 그룹 수정
|
||||
* @param groupId
|
||||
* @param contactGroupDto
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactGroupDto updateGroup(String groupId, ContactGroupDto contactGroupDto, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 그룹 삭제
|
||||
* @param groupId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
String deleteGroup(String groupId, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 그룹 조회
|
||||
* @param groupId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactGroupDto getGroup(String groupId, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 그룹 목록 조회
|
||||
* @param pubType
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactGroupDto[] getGroups(String pubType, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 생성
|
||||
* @param contactDto
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactDto createCard(ContactDto contactDto, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 수정
|
||||
* @param contactId
|
||||
* @param contactDto
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactDto updateCard(String contactId, ContactDto contactDto, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 삭제
|
||||
* @param contactId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
String deleteCard(String contactId, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 조회
|
||||
* @param contactId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactDto getCard(String contactId, String userId);
|
||||
|
||||
/**
|
||||
* 연락처 목록 조회
|
||||
* @param pubType
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
ContactDto[] getCards(String pubType, String userId);
|
||||
|
||||
/**
|
||||
* KNOX REST GET MAPPING
|
||||
* @param <T>
|
||||
* @param methodName
|
||||
* @param params
|
||||
* @param paths
|
||||
* @param classType
|
||||
* @return
|
||||
*/
|
||||
<T> T contactsGet(String methodName, MultiValueMap<String, String> params, Map<String, String> paths, Class<T> classType);
|
||||
|
||||
/**
|
||||
* KNOX REST POST MAPPING
|
||||
* @param <T>
|
||||
* @param methodName
|
||||
* @param bodyMap
|
||||
* @param params
|
||||
* @param paths
|
||||
* @param classType
|
||||
* @return
|
||||
*/
|
||||
<T> T contactsPost(String methodName, Map<String, Object> bodyMap, MultiValueMap<String, String> params, Map<String, String> paths, Class<T> classType);
|
||||
}
|
||||
----
|
||||
|
||||
자세한 API 스펙은 Swagger API 문서의 knox-contact-controller 항목을 참고한다.
|
||||
@@ -0,0 +1,15 @@
|
||||
= 공통기능
|
||||
|
||||
include::사용자관리.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리.adoc[leveloffset=+1]
|
||||
|
||||
include::결재메일.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리.adoc[leveloffset=+1]
|
||||
|
||||
include::글로벌지원.adoc[leveloffset=+1]
|
||||
|
||||
include::파일서비스.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,7 @@
|
||||
= 글로벌 지원
|
||||
|
||||
include::글로벌지원/Timezone.adoc[leveloffset=+1]
|
||||
|
||||
include::글로벌지원/다국어서비스.adoc[leveloffset=+1]
|
||||
|
||||
include::글로벌지원/번역(Utrans).adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,34 @@
|
||||
= Timezone
|
||||
|
||||
== 개요
|
||||
사용자의 Timezone을 관리한다.
|
||||
|
||||
== Table
|
||||
* 사용자 : TN_CF_USER
|
||||
** TIME_ZONE_CODE, TIME_ZONE_ID 컬럼 사용
|
||||
|
||||
== Timezone 저장
|
||||
|
||||
* 사용자가 시스템에 최초 등록시 저장. +
|
||||
그 이후에는 '타임존 저장' API를 사용하여 저장한다.
|
||||
- 사용자가 시스템에 처음으로 SSO 로그인하여 사용자 등록시 epTray 연계된 타임존 정보를 가져와서 저장 (없을 경우 config.properties의 default 값)
|
||||
|
||||
== API
|
||||
.UserController.java
|
||||
|
||||
. 타임존 목록 조회 +
|
||||
GET /auth/users/timezone
|
||||
* 타임존은 java.util.TimeZone 라이브러리를 사용하기 때문에 DB에 타임존 목록이 저장되어 있지 않으며, +
|
||||
서머타임(일광 절약 시간제, DST(Daylight Saving Time))을 따로 계산하지 않아도 자동으로 목록에서 보여준다.
|
||||
|
||||
. 타임존 저장 +
|
||||
PUT /auth/users/timezone
|
||||
|
||||
== 화면
|
||||
사용자의 Timezone을 설정하는 기능으로, Timezone을 설정하게 되면 Local Storage의 *user.timeZoneId, user.timeZoneCode* 에 저장된다. +
|
||||
|
||||
TopMenu - 표준시간을 통해 접근 가능.
|
||||
|
||||
image::timezone.png[]
|
||||
|
||||
NOTE: SideMenu(Right Side) 이용 시 MainOffsider.vue 내 'timezone' 부분에 구현되어 있다.
|
||||
@@ -0,0 +1,56 @@
|
||||
= 다국어 서비스
|
||||
|
||||
== 개요
|
||||
message-common.properties를 사용하여 시스템에 다국어를 지원한다. +
|
||||
한글, 영문을 기본으로 서비스한다. +
|
||||
기본언어인 한글은 message-common_ko_KR.properties을 파일명으로 하고 영문은 '_en_US'를 붙여서 사용한다.
|
||||
|
||||
== 설정
|
||||
=== 프론트엔드 기본언어 값 설정
|
||||
..env
|
||||
[source,properties]
|
||||
----
|
||||
# To set default Language for I18n (ko_KR, en_US, zh_CN, etc..)
|
||||
VITE_DEFAULT_LANG=ko_KR
|
||||
----
|
||||
|
||||
=== 백엔드 기본언어 및 다국어 설정
|
||||
.config.properties
|
||||
[source,properties]
|
||||
----
|
||||
## Language Set
|
||||
default-language=ko_KR
|
||||
language-set=ko_KR,en_US
|
||||
----
|
||||
|
||||
* 예) 시스템에서 프랑스어를 추가하고자 할 때 방법
|
||||
. config.properties의 language-set에 fr_FR을 추가
|
||||
+
|
||||
[source,properties]
|
||||
----
|
||||
## Language Set(프랑스어 추가)
|
||||
language-set=ko_KR,en_US,fr_FR
|
||||
----
|
||||
|
||||
. message-common_fr_FR.properties 파일을 생성
|
||||
. 메세지의 프랑스어 버전 작성
|
||||
|
||||
NOTE: 메세지들만 추가되므로 메뉴관리, 역할관리, 업무그룹관리 등 다국어 컬럼(LABEL_JSON)을 지원하는 table 데이터의 경우 직접 입력하여야 한다.
|
||||
|
||||
== API
|
||||
.MessageBundleController.java
|
||||
|
||||
. 로케일별로 메세지를 조회 +
|
||||
GET /noauth/messages
|
||||
|
||||
. 설정한 모든 언어의 메세지를 조회 +
|
||||
GET /noauth/messages/all
|
||||
|
||||
== 화면
|
||||
한국어,영어 중 원하는 언어로 변경하여 화면을 나타내는 기능으로, 언어를 설정하게 되면 User Token에 저장 되어 로그아웃을 하게 되더라도 마지막에 변경된 언어로 설정된다.
|
||||
|
||||
TopMenu - 언어선택을 통해 접근 가능.(단, 다국어 지원 되는 영역에 한해서만 지원).
|
||||
|
||||
image::language.png[]
|
||||
|
||||
NOTE: SideMenu(Right Side) 이용 시 MainOffsider.vue 내 'language' 부분에 구현되어 있다.
|
||||
@@ -0,0 +1,20 @@
|
||||
= 번역(Utrans)
|
||||
|
||||
== 개요
|
||||
Utrans API를 호출하여 한국어 및 영어 등 언어로 번역하는 기능.
|
||||
|
||||
image::utrans.png[]
|
||||
|
||||
=== 지원 되는 언어방향
|
||||
- 유럽어 : 러시아어,스페인어,독일어,프랑스어,이탈리아어,포르투갈어
|
||||
|===
|
||||
|Source |Target
|
||||
|한국어 | 영어, 중국어, 베트남어, 일어, 유럽어
|
||||
|영어 | 유럽어
|
||||
|중국어 | 유럽어
|
||||
|유럽어 | 유럽어
|
||||
|===
|
||||
|
||||
=== 기능별 설명
|
||||
- 번역하기 : 번역된 내용을 오른쪽 창에 표시
|
||||
- 복사하기 : 번역 결과 값을 클립보드에 복사
|
||||
@@ -0,0 +1,19 @@
|
||||
= 보안관리
|
||||
|
||||
include::보안관리/약관관리.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/개인정보사용이력관리.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/개인정보취급자권한변경이력.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/EUGDPR.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/관리자IP관리.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/문자열암복호화.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/SQLInjection.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/XSS.adoc[leveloffset=+1]
|
||||
|
||||
include::보안관리/비밀번호관리.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,7 @@
|
||||
= EU GDPR
|
||||
|
||||
== 개요
|
||||
. GDPR이란? +
|
||||
2018년 5월 25일부터 시행되는 EU(유럽연합)의 개인정보보호 법령이며, 동 법령 위반시 과징금 등 행정처분이 부과될 수 있어 EU와 거래하는 우리나라 기업도 이 법에 위반되지 않도록 주의할 필요가 있다.
|
||||
. EU와 거래하는 시스템은 이용약관동의 관리에서 '유럽연합 개인정보보호 규정'이라는 약관을 추가하여 사용자의 동의를 받아야 한다.
|
||||
* 세부적인 내용은 <<_약관_관리,약관 관리>> 매뉴얼을 참조한다.
|
||||
@@ -0,0 +1,39 @@
|
||||
= SQL Injection
|
||||
|
||||
== 개요
|
||||
표준개발라이브러리에서는 MyBatis의 PreparedStatement 를 사용한 value injection을 원칙으로 사용하기 때문에 java 혹은 jsp에서 sql을 만들지 않는다면 근본적으로 sql injection이 발생하지 않는다.
|
||||
단, ${} 매핑을 사용 할 경우 SDLComparator를 사용해야 한다.
|
||||
|
||||
=== SQL Injeciton 공격 예
|
||||
MyBatis에서는 #{}, ${} 두 변수 형태를 제공한다. #{}의 경우엔 SQL Injection 공격이 불가하지만 ${}는 값이 직접 매핑되기 때문에 SQL Injeciton에 노출되어 있다.
|
||||
|
||||
${}에 매핑될 값은 사용자가 입력값(파라미터를 변조할수 있는)을 사용할 경우 보안 취약점에 노출되게 된다.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="getPerson" parameterType="string" resultType="org.application.vo.Person">
|
||||
SELECT * FROM PERSON WHERE NAME = #{name} AND PHONE LIKE '${phone}';
|
||||
</select>
|
||||
----
|
||||
|
||||
위의 경우
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
SELECT * FROM PERSON WHERE NAME = ? and PHONE LIKE 'A%'; DELETE FROM PERSON; --'
|
||||
----
|
||||
|
||||
실행이 가능하다.
|
||||
|
||||
따라서, ${}에 매핑될 값은 조작이 불가능 하도록 사용자 입력값을 코드로 입력 할 수 있도록 하고, 서버에서 코드에 맞는 스트링을 조합해서 실행 할 수 있도록 해야한다.
|
||||
|
||||
=== SDLComparator 적용
|
||||
SDL에서는 개발자의 시큐어 코딩 실수로 인한 SQL Injection 실행 방지를 위해 MyBatis에서 변수 매핑 전에 허용된 문자만 사용 할 수 있도록 함수를 제공하고 있다.
|
||||
|
||||
.사용방법
|
||||
[source,xml]
|
||||
----
|
||||
<if test="@com.samsung.SdlComparator@isNotEmptyForDynamicSql(orderBy)">
|
||||
ORDER BY ${orderBy}
|
||||
</if>
|
||||
----
|
||||
@@ -0,0 +1,21 @@
|
||||
= XSS 방지
|
||||
|
||||
== 개요
|
||||
XSS(Cross Site Scripting)는 JavaScript등으로 작성된 악성 스크립트 코드를 웹 게시판 등에 삽입해 세션을 가로채거나 공격자가 의도한대로 행동하도록 만드는 공격이다.
|
||||
|
||||
=== XSS 방지 적용
|
||||
표준개발라이브러리에서는 HTML태그를 허용하는 게시판에 대해 화이트리스트를 선정하여 해당 태그만 허용되도록 하고 있다.
|
||||
|
||||
.사용방법
|
||||
[source,java]
|
||||
----
|
||||
@Operation(summary = "게시글 상세조회")
|
||||
@GetMapping("/posts/{postId}")
|
||||
public Post getPost(
|
||||
@Parameter("게시글 ID") @PathVariable String postId) {
|
||||
Post post = postService.getPost(postId);
|
||||
post.setPostDetail(SdlHtmlPolicy.POLICY_DEFINITION.sanitize( post.getPostDetail())); // <1>
|
||||
return post;
|
||||
}
|
||||
----
|
||||
<1> 게시글 본문을 SdlHtmlPolicy.POLICY_DEFINITION에 정의된 정책을 기반으로 허용된 태그만 가능하도록 처리
|
||||
@@ -0,0 +1,93 @@
|
||||
= 개인정보 사용 이력 관리
|
||||
|
||||
== 개요
|
||||
사용자 정보를 조회한 이력을 남긴다. 관련 법에 따라 일정기간 동안 보관한다.
|
||||
|
||||
=== 사용자 정보 조회 이력 관리
|
||||
* UserController에서 사용자 정보 관련 메서드 호출시, UserService의 writeUserHistoryLog 메서드를 호출하고 있다.
|
||||
* log4j2.xml에 설정한 파일에 이력이 남는다.
|
||||
|
||||
.UserController.class
|
||||
[source,java]
|
||||
----
|
||||
@Operation(summary = "사용자 목록 조회")
|
||||
@GetMapping("/auth/users")
|
||||
public PagingResult<User> getUserPagingList( @ModelAttribute UserSearchDto searchDto) {
|
||||
|
||||
PagingResult<User> resultPage = userService.getUserPagingList(searchDto);
|
||||
|
||||
// 개인정보조회 이력 남김
|
||||
userService.writeUserHistoryLog(resultPage);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 조회 (by EP ID)")
|
||||
@GetMapping("/auth/users/{userId}")
|
||||
public User userInfo(@Parameter(description = "EP ID", required = true) @PathVariable(required = true) String userId) {
|
||||
|
||||
User userInfo = userService.getUserById(userId);
|
||||
|
||||
// 개인정보조회 이력 남김
|
||||
userService.writeUserHistoryLog(userInfo);
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
----
|
||||
|
||||
.UserServiceImpl.class
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
public void writeUserHistoryLog(Object returnValue) {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||
String requestUri = request.getRequestURI();
|
||||
String requestMethod = request.getMethod();
|
||||
User user = Account.currentUser();
|
||||
if(ObjectUtils.isNotEmpty(user)) { // 로그인된 사용자
|
||||
try {
|
||||
HistoryLog log = new HistoryLog();
|
||||
log.setLogId(idGenService.getNextStringId());
|
||||
log.setNodeId(nodeId);
|
||||
if(ObjectUtils.isNotEmpty(user)) {
|
||||
log.setWorkerId(user.getUserId());
|
||||
log.setWorkerName(user.getUserName());
|
||||
}
|
||||
log.setWorkDatetime(DateTime.now().toString());
|
||||
log.setRemoteAddr(webUtil.getClientIp(request));
|
||||
log.setRequestMethod(requestMethod);
|
||||
log.setRequestUri(requestUri);
|
||||
log.setApiResult(returnValue);
|
||||
|
||||
String jsonVal = mapper.writeValueAsString(log);
|
||||
USER_HISTORY_LOG.info(jsonVal);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.log4j2.xml
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="INFO">
|
||||
<Appenders>
|
||||
<RollingFile name="UserHistoryAppender" fileName="/logs/history/user-history-${date:yyyy-MM-dd}-${hostName}.log"
|
||||
filePattern="/logs/history/user-history-%d{yyyy-MM-dd}-${hostName}.log">
|
||||
<PatternLayout>
|
||||
<Pattern>%d %-5p [%t] %-17c{2} \(%13F:%L\) - %m%n</Pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
|
||||
</Policies>
|
||||
</RollingFile>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="UserHistoryLog" level="INFO" additivity="false">
|
||||
<AppenderRef ref="UserHistoryAppender"/>
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
----
|
||||
@@ -0,0 +1,195 @@
|
||||
= 개인정보 취급자 권한변경이력
|
||||
|
||||
== 개요
|
||||
역할 및 업무그룹의 권한 정보를 변경한 이력을 남긴다.
|
||||
|
||||
=== 역할 권한 변경 이력 로깅
|
||||
* AOP를 이용하여 RoleService의 사용자 역할 권한 추가/수정/삭제 메서드가 호출될때 이력을 남긴다.
|
||||
* log4j2.xml에 설정한 파일에 이력이 남는다.
|
||||
|
||||
.RoleHistoryLoggingAspect.class
|
||||
[source,java]
|
||||
----
|
||||
@Aspect
|
||||
@Component
|
||||
@Log4j2
|
||||
public class RoleHistoryLoggingAspect extends HistoryLoggingSupport{
|
||||
|
||||
private static final Logger ROLE_HISTORY_LOG = LogManager.getLogger("RoleHistoryLog");
|
||||
|
||||
@Value("${node-id}")
|
||||
private String nodeId;
|
||||
|
||||
private final IdGenService idGenService;
|
||||
|
||||
public RoleHistoryLoggingAspect(IdGenService idGenService) {
|
||||
this.idGenService = idGenService;
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.role.impl.RoleServiceImpl.insertUserRoleList(..))")
|
||||
public void insertUserRolePointcut() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.role.impl.RoleServiceImpl.updateUserRoleList(..))")
|
||||
public void updateUserRolePointcut() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.role.impl.RoleServiceImpl.deleteUserRoleList(..)) || execution(* com.samsung.role.impl.RoleServiceImpl.deleteUserRole(..))")
|
||||
public void deleteUserRolePointcut() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@After(value = "insertUserRolePointcut() || updateUserRolePointcut() || deleteUserRolePointcut()")
|
||||
public void writeRoleHistoryLog() {
|
||||
writeHistoryLog(ROLE_HISTORY_LOG, idGenService, nodeId);
|
||||
}
|
||||
}
|
||||
----
|
||||
.HistoryLoggingSupport.class
|
||||
[source,java]
|
||||
----
|
||||
public class HistoryLoggingSupport {
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
protected WebUtil webUtil;
|
||||
|
||||
public void writeHistoryLog(Logger logger, IdGenService idGenService, String nodeId) {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||
String requestUri = request.getRequestURI();
|
||||
String requestMethod = request.getMethod();
|
||||
User user = Account.currentUser();
|
||||
if(ObjectUtils.isNotEmpty(user)) { // 로그인된 사용자
|
||||
try {
|
||||
HistoryLog log = new HistoryLog();
|
||||
log.setLogId(idGenService.getNextStringId());
|
||||
log.setNodeId(nodeId);
|
||||
if(ObjectUtils.isNotEmpty(user)) {
|
||||
log.setWorkerId(user.getUserId());
|
||||
log.setWorkerName(user.getUserName());
|
||||
}
|
||||
log.setWorkDatetime(DateTime.now().toString());
|
||||
log.setRemoteAddr(webUtil.getClientIp(request));
|
||||
log.setRequestMethod(requestMethod);
|
||||
log.setRequestUri(requestUri);
|
||||
|
||||
String jsonVal = mapper.writeValueAsString(log);
|
||||
logger.info(jsonVal);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.log4j2.xml
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="INFO">
|
||||
<Appenders>
|
||||
<RollingFile name="RoleHistoryAppender" fileName="/logs/history/role-history-${date:yyyy-MM-dd}-${hostName}.log"
|
||||
filePattern="/logs/history/role-history-%d{yyyy-MM-dd}-${hostName}.log">
|
||||
<PatternLayout>
|
||||
<Pattern>%d %-5p [%t] %-17c{2} \(%13F:%L\) - %m%n</Pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
|
||||
</Policies>
|
||||
</RollingFile>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="RoleHistoryLog" level="INFO" additivity="false">
|
||||
<AppenderRef ref="RoleHistoryAppender"/>
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
----
|
||||
|
||||
=== 업무그룹 권한 변경 이력 로깅
|
||||
* AOP를 이용하여 WorkGroupService서비스의 업무그룹 권한 추가/수정/삭제 메서드가 호출될때 이력을 남긴다.
|
||||
* log4j2.xml에 설정한 파일에 이력이 남는다.
|
||||
|
||||
.WorkgroupHistoryLoggingAspect.class
|
||||
[source,java]
|
||||
----
|
||||
@Aspect
|
||||
@Component
|
||||
@Log4j2
|
||||
public class WorkgroupHistoryLoggingAspect extends HistoryLoggingSupport {
|
||||
|
||||
private static final Logger WORKGROUP_HISTORY_LOG = LogManager.getLogger("WorkgroupHistoryLog");
|
||||
|
||||
@Value("${node-id}")
|
||||
private String nodeId;
|
||||
|
||||
private final IdGenService idGenService;
|
||||
|
||||
public WorkgroupHistoryLoggingAspect(IdGenService idGenService) {
|
||||
this.idGenService = idGenService;
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.workgroup.impl.WorkgroupServiceImpl.insertWorkgroupRoleList(..))")
|
||||
public void insertWorkgroupRoleList() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.workgroup.impl.WorkgroupServiceImpl.insertWorkgroupMenuList(..))")
|
||||
public void insertWorkgroupMenuList() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.workgroup.impl.WorkgroupServiceImpl.updateWorkgroupRoleList(..))")
|
||||
public void updateWorkgroupRoleList() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.workgroup.impl.WorkgroupServiceImpl.updateWorkgroupMenuList(..))")
|
||||
public void updateWorkgroupMenuList() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.workgroup.impl.WorkgroupServiceImpl.deleteWorkgroupRoleList(..))")
|
||||
public void deleteWorkgroupRoleList() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.samsung.workgroup.impl.WorkgroupServiceImpl.deleteWorkgroupMenuList(..))")
|
||||
public void deleteWorkgroupMenuList() {
|
||||
// Do nothing because pointcut
|
||||
}
|
||||
|
||||
@After(value = "insertWorkgroupRoleList() || insertWorkgroupMenuList() || updateWorkgroupRoleList() || updateWorkgroupMenuList() || deleteWorkgroupRoleList() || deleteWorkgroupMenuList()")
|
||||
public void writeWorkgroupHistoryLog() {
|
||||
writeHistoryLog(WORKGROUP_HISTORY_LOG, idGenService, nodeId);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.log4j2.xml
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="INFO">
|
||||
<Appenders>
|
||||
<RollingFile name="WorkgroupHistoryAppender"
|
||||
fileName="/logs/history/workgroup-history-${date:yyyy-MM-dd}-${hostName}.log"
|
||||
filePattern="/logs/history/workgroup-history-%d{yyyy-MM-dd}-${hostName}.log">
|
||||
<PatternLayout>
|
||||
<Pattern>%d %-5p [%t] %-17c{2} \(%13F:%L\) - %m%n</Pattern>
|
||||
</PatternLayout>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
|
||||
</Policies>
|
||||
</RollingFile>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="WorkgroupHistoryLog" level="INFO" additivity="false">
|
||||
<AppenderRef ref="WorkgroupHistoryAppender"/>
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
----
|
||||
@@ -0,0 +1,11 @@
|
||||
= 관리자 IP 관리
|
||||
|
||||
== 관리자 IP 관리
|
||||
관리자용 계정의 IP를 관리
|
||||
|
||||
image::ipMgmt.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 삭제 : 등록된 계정을 삭제
|
||||
- 추가 : 관리자용 계정 정보를 등록하기 위해 Row를 추가
|
||||
- 저장 : 추가된 계정 정보를 저장
|
||||
@@ -0,0 +1 @@
|
||||
= 리소스 접근 제어
|
||||
@@ -0,0 +1,10 @@
|
||||
= 문자열 암/복호화
|
||||
|
||||
== 개요
|
||||
필요한 부분에 대하여 암/복호화를 적용하고 있다.
|
||||
|
||||
=== 해쉬 알고리즘
|
||||
ID/Password 로그인의 경우 Password에 대하여 해쉬 알고리즘(org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder)을 적용하고 있다.
|
||||
|
||||
=== 전자서명
|
||||
Knox Portal EpTray를 통한 로그인의 경우 전자서명된 ssoData를 시스템에 있는 개인키(Private Key)를 기반으로 검증한다.
|
||||
@@ -0,0 +1 @@
|
||||
= 불법탈취(Hijacking) 방지
|
||||
@@ -0,0 +1,23 @@
|
||||
= 비밀번호 관리
|
||||
|
||||
== 개요
|
||||
ID/PW 로그인시 사용하는 비밀번호의 변경 및 초기화가 가능하다.
|
||||
|
||||
=== 비밀번호 변경
|
||||
로그인된 상태에서 비밀번호 변경이 가능하다.
|
||||
|
||||
image::pwdChange.png[]
|
||||
|
||||
=== 비밀번호 초기화
|
||||
로그아웃된 상태에서 ID/PWD 로그인시 비밀번호 초기화를 할 수 있다. +
|
||||
이메일로 임시 비밀번호가 발송된다.
|
||||
|
||||
[cols=2*a]
|
||||
|===
|
||||
|
|
||||
.로그인 화면
|
||||
image::pwdReset_01.png[]
|
||||
|
|
||||
.비밀번호 초기화 화면
|
||||
image::pwdReset_02.png[]
|
||||
|===
|
||||
@@ -0,0 +1,28 @@
|
||||
= 약관 관리
|
||||
|
||||
== 이용약관동의 관리
|
||||
이용약관동의관리 화면을 관리한다.
|
||||
|
||||
image::termsConditionList.png[]
|
||||
|
||||
== 이용약관동의 등록
|
||||
|
||||
등록 화면에서 새로운 약관 생성이 가능하다.
|
||||
|
||||
구분은 **공통코드**의 `TERMS` 정보를, 언어는 `TERMS_LANG` 정보를 참조한다.
|
||||
|
||||
image::termsConditionDetail_Reg.png[]
|
||||
|
||||
== 이용약관동의 목록 추가
|
||||
|
||||
그룹코드 관리에서 약관동의 목록 추가가 가능하다.
|
||||
|
||||
image::termsCodeDetail.png[]
|
||||
|
||||
신규 사용자 로그인 시 화면에 아래와 같이 나타난다.
|
||||
|
||||
== 신규사용자 이용약관 동의
|
||||
|
||||
신규 사용자 로그인 시 화면에 아래와 같이 나타난다.
|
||||
|
||||
image::termsCondition_New.png[]
|
||||
@@ -0,0 +1,27 @@
|
||||
= 사용자 관리
|
||||
|
||||
include::사용자관리/사용자관리.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/부서관리_Knox.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/부서관리_사용자정의.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/장기미사용자.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/사용권한신청.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/외부사용자관리.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/권한관리기간배치.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/역할관리.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/업무그룹관리.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/메뉴관리.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/QuickMenu관리.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/Sitemap.adoc[leveloffset=+1]
|
||||
|
||||
include::사용자관리/로그인.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,16 @@
|
||||
= My Menu 관리
|
||||
|
||||
== My Menu 관리
|
||||
사용자가 자주 사용하는 메뉴를 등록하여 사용자 임의로 만들어서 빠른 이동을 목적으로 하는 Menu 목록.
|
||||
|
||||
TopMenu - My Menu 에 구현되어 있다.
|
||||
|
||||
image::MyMenu.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
|
||||
image::MyMenu_add.png[]
|
||||
메뉴의 BreadCrumb 영역 내 메뉴명 우측 별 모양을 클릭하여 MyMenu에 등록 또는 해제
|
||||
|
||||
|
||||
NOTE: SideMenu(Right Side) 이용 시 MainOffsider.vue 내 'QuickMenu' 에 구현되어 있다.
|
||||
@@ -0,0 +1,13 @@
|
||||
= Sitemap
|
||||
|
||||
== 개요
|
||||
로그인한 사용자의 권한에 맞는 전체메뉴를 볼 수 있다.
|
||||
|
||||
TopMenu - 사이트맵 부분에 구현되어 있다.
|
||||
|
||||
== 화면
|
||||
권한에 따른 전체 메뉴 목록을 나열한 화면으로 메뉴 이동 가능
|
||||
|
||||
image::siteMap.png[]
|
||||
|
||||
NOTE: SideMenu(Right Side) 이용 시 MainOffsider.vue 내 'sitemap' 부분에 구현되어 있다.
|
||||
@@ -0,0 +1,129 @@
|
||||
= 권한 관리
|
||||
|
||||
== 권한 체크 로직
|
||||
|
||||
권한 체크는 AuthorizationInterceptor에서 사용자가 가지고 있는 메뉴의 권한 목록을 기반으로 URL Base 로 체크가 되고 있다.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
-- 생략 --
|
||||
String httpMethod = request.getMethod();
|
||||
String decodedUrl = WebUtil.getDecodedRequestUrl(request, request.getRequestURI().substring(request.getContextPath().length()));
|
||||
|
||||
String queryString = "";
|
||||
if (decodedUrl.indexOf('?') > -1) {
|
||||
String[] uri = decodedUrl.split("\\?");
|
||||
decodedUrl = uri[0];
|
||||
queryString = uri[1];
|
||||
}
|
||||
|
||||
for (Api api : authMenuList) {
|
||||
if (api.getHttpMethod().equals(httpMethod) && checkUriMatch(api.getApiPath(), api.getApiParameters(), decodedUrl, queryString)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw new AuthorizationException("API 권한 없음");
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public boolean checkUriMatch(String apiPath, String apiParameters, String decodedUrl, String queryString){
|
||||
Map<String, String> requiredParamMap = new HashMap<>();
|
||||
if( StringUtils.isNotEmpty(apiParameters)){
|
||||
requiredParamMap = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(apiParameters);
|
||||
}
|
||||
|
||||
UriTemplate uriTemplate = new UriTemplate(apiPath);
|
||||
if ( uriTemplate.matches(decodedUrl) ){
|
||||
if( requiredParamMap.size() == 0 ) return true;
|
||||
for (Map.Entry<String, String> entry : requiredParamMap.entrySet()) {
|
||||
log.debug("Required Param : " + entry.getKey() + " Value : " + entry.getValue());
|
||||
Map<String, String> requestParamMap = getRequestParam(queryString);
|
||||
if( !requestParamMap.containsKey(entry.getKey()) || !(entry.getValue()).equals(requestParamMap.get(entry.getKey()))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== 권한별 메뉴 가져오기
|
||||
|
||||
SPA(Single Page Application) 방식으로 최초 메인화면 진입시에 사용자의 권한에 맞는 메뉴 목록을 조회하여 보여준다. +
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class MenuController {
|
||||
--생략--
|
||||
@Operation(summary = "사용자의 권한이 있는 메뉴 조회")
|
||||
@GetMapping("/menus-user-auth")
|
||||
public List<Menu> getMenuListByAuth() {
|
||||
return menuService.getMenuListByAuth(Account.currentUser().getUserId());
|
||||
}
|
||||
--생략--
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
일반 사용자와 시스템 어드민 사용자의 권한 메뉴 조회 서비스가 분리 되어 있다.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Service
|
||||
public class MenuServiceImpl implements MenuService {
|
||||
--생략--
|
||||
@Override
|
||||
public List<Menu> getMenuListByAuth(String userId) {
|
||||
if (Account.currentUser().isSystemAdminUser()) return this.getMenuListByAdminAuth();
|
||||
else return this.getMenuListByUserAuth(userId);
|
||||
}
|
||||
--생략--
|
||||
}
|
||||
----
|
||||
|
||||
접속중인 상태에서 권한이 변경될 경우 화면 재진입(다시 로그인 또는 브라우저 새로고침)을 통해서 변경된 권한의 메뉴 목록을 다시 로드할 수 있다.
|
||||
|
||||
== 권한에 따른 UI Control
|
||||
|
||||
SDL에서는 역할관리에서 정의해놓은 역할별로 4개의 권한을 지정할 수 있다. +
|
||||
최초 화면 진입시 조회한 사용자가 접근 가능한 메뉴 목록에 해당 메뉴에 대한 권한 목록을 함께 가지고 있으며 화면에서 버튼에 directive로 지정함으로써 show/hide 처리를 자동으로 처리한다.
|
||||
|
||||
역할관리를 통한 메뉴별 권한 등록
|
||||
|
||||
image::front_01_03.png[]
|
||||
vue 내에서 authorization directive를 통해 버튼 별 권한을 정의한다.(배열로 정의할 경우 여러개 중에 하나만 매칭되도 show처리 됨)
|
||||
|
||||
* 여러개의 권한을 둘 경우
|
||||
|
||||
[source, html]
|
||||
----
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
v-authorization="['UPDATE', 'EXECUTE']"
|
||||
@click="onApprWrite"
|
||||
>
|
||||
{{ $t('sdl.approval.label.APPROVE') }}
|
||||
</button>
|
||||
----
|
||||
* 하나의 권한을 둘 경우
|
||||
|
||||
[source, html]
|
||||
----
|
||||
<button type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
v-authorization="'UPDATE'"
|
||||
@click="onApprWrite"
|
||||
>
|
||||
{{ $t('sdl.approval.label.APPROVE') }}
|
||||
</button>
|
||||
----
|
||||
* 관리자일 경우는 directive 지정 상관 없이 무조건 show 처리된다.
|
||||
@@ -0,0 +1,24 @@
|
||||
= 권한관리 기간 배치
|
||||
|
||||
== 만료 권한 삭제
|
||||
배치를 실행하여 사용자 및 역할에 매핑된 권한 중 만료된 권한을 삭제한다.
|
||||
|
||||
.config.properties
|
||||
[source,properties]
|
||||
----
|
||||
batch.user.auth-expired.cron=0 10 02 * * ?
|
||||
----
|
||||
|
||||
== 권한만료 예정 안내 메일 발송
|
||||
배치를 실행하여 권한만료 예정인 사용자를 대상으로 안내메일을 발송한다.
|
||||
|
||||
.config.properties
|
||||
[source,properties]
|
||||
----
|
||||
batch.user.auth-expired-mailing.cron=0 30 02 * * ?
|
||||
----
|
||||
|
||||
== Spring 환경설정
|
||||
* QuartzConfig.java: Scheduler 등록
|
||||
* UserBatchConfig.java: 배치 Job, Trigger 등록
|
||||
* UserBatchExecutor.java: 서비스 호출
|
||||
@@ -0,0 +1,185 @@
|
||||
= 로그인
|
||||
|
||||
SDL은 Knox EpTray, ID/PW, ADFS 로그인을 지원한다.
|
||||
|
||||
== EpTray
|
||||
Knox EpTray 로그인은 Chrome, Edge 등 멀티 브라우저에서 가능하다.
|
||||
사용자가 Knox에 로그인을 한 후 시스템에 접속을 하게 되면 EpTray값을 이용해 시스템 로그인을 한다.
|
||||
|
||||
NOTE: Knox EpTray SSO 를 위한 연계 신청이 필요하다. (스테이지/운영) +
|
||||
자세한 내용 안내 및 문의는 Knox Support (매뉴얼 > KnoxPortal NewEpTray 연계 가이드) 를 참조한다.
|
||||
|
||||
=== EpTray 적용
|
||||
연계신청 과정에서 생성되는 *rsaprivkey8.pem* 파일을 +
|
||||
config.properties의 login.sso.knox-tray-private-key-path 경로에 넣어준다.
|
||||
|
||||
CAUTION: *rsaprivkey8.pem* 파일이 없는 경우 아래와 같은 에러가 발생하므로 주의한다.
|
||||
|
||||
image::newEpTrayUtil.png[Can not load new EpTray Private Key.png]
|
||||
|
||||
=== LoginPage.vue
|
||||
|
||||
UI 에서 사용자 정보가 없을 경우 LoginPage.vue 페이지로 이동하게 되고 인증 절차를 시작한다.
|
||||
|
||||
EpTray 연계를 위한 메서드
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
async loginByNewEpTray() {
|
||||
let loginResult = false;
|
||||
SDLUtil.showLoadingBar(true);
|
||||
await this.socConnect().then((data) => {
|
||||
this.newEpTrayKey = data;
|
||||
this.websocket.close();
|
||||
}).catch((err) => {
|
||||
SDLUtil.alert(err);
|
||||
});
|
||||
SDLUtil.showLoadingBar(false);
|
||||
|
||||
if (this.newEpTrayKey) {
|
||||
if (this.newEpTrayKey.result === 'success') {
|
||||
const { userInfo, key } = this.newEpTrayKey;
|
||||
loginResult = await this.loginNewEpTray({ userInfo, key });
|
||||
} else {
|
||||
SDLUtil.alert(`sdl.epTray.error.${this.newEpTrayKey.errorCode}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loginResult) {
|
||||
if (this.$route.query.token) {
|
||||
loginService.setToken(this.$route.query.token);
|
||||
document.location = `${SDLUtil.WEB_CONTEXT_PATH}/`;
|
||||
}
|
||||
}
|
||||
this.afterLoginProcess(loginResult);
|
||||
},
|
||||
----
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
socConnect() {
|
||||
return new Promise(((resolve, reject) => {
|
||||
const server = new WebSocket('wss://localhost:29283'); // <1>
|
||||
this.websocket = server;
|
||||
server.onopen = () => {
|
||||
server.send('{"rqtype":"getknoxsso","token":"","data":"KCC60TRAY0072"}'); // <2>
|
||||
server.onmessage = (event) => {
|
||||
const socketData = JSON.parse(event.data);
|
||||
if (socketData.rpcode === 'RETURN_SUCCESS') {
|
||||
resolve(JSON.parse(socketData.data));
|
||||
} else {
|
||||
this.websocket.close();
|
||||
let err;
|
||||
if (socketData.rpcode === 'EMPTY_BOX') err = 'sdl.epTray.rpCode.EMPTY_BOX';
|
||||
else err = `sdl.epTray.rpCode.${socketData.detail}`;
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
};
|
||||
server.onerror = () => {
|
||||
const err = 'sdl.epTray.rpCode.CONNECTION_FAILED';
|
||||
reject(err);
|
||||
};
|
||||
}));
|
||||
},
|
||||
----
|
||||
<1> WebSocket HOST 정보
|
||||
<2> 발급받은 연계용 system ID
|
||||
|
||||
=== LoginController.java
|
||||
|
||||
LoginController 에서는 EpTray값의 정보를 이용해 사용자를 로그인 한다.
|
||||
이때 시스템 사용자가 아닐 경우, 등록 화면으로 이동하고, 정상적인 시스템 사용자일 경우
|
||||
Token을 발급한다.
|
||||
|
||||
GET /noauth/login/new-eptray
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@PostMapping("/noauth/login/new-eptray")
|
||||
@Operation(value = "", description = "knox new eptray 로그인")
|
||||
public JwtResponse loginByNewEptray(HttpServletRequest request, HttpServletResponse response, @RequestParam String encodeUserInfo, @RequestParam String encodeAesKey ) throws AccountException {
|
||||
loginInterceptorExecutor.applyPreLogin(request, response);
|
||||
User user = authenticationService.loginByNewEpTray(encodeUserInfo, encodeAesKey);
|
||||
user.setRecentLoginIp(webUtil.getClientIp(request));
|
||||
JwtResponse jwtResponse = new JwtResponse(jwtUtil.generateToken(user), user.getUserId());
|
||||
user.setJwt(jwtResponse.getJwtToken().split("\\.")[2]);
|
||||
loginInterceptorExecutor.applyPostLogin(request, response, user);
|
||||
return jwtResponse;
|
||||
}
|
||||
----
|
||||
|
||||
== ADFS 로그인
|
||||
|
||||
NOTE: ADFS 통합인증 사이트 (https://adsso.sec.samsung.net) 에서 신청 후 사용해야 한다. +
|
||||
자세한 내용은 해당 사이트를 참조하거나 전자통합인증3 (nextsso3@samsung.com) 으로 문의 한다.
|
||||
|
||||
SDL에서는 ADFS 통합인증 사이트의 가이드를 참조하여 AD 로그인 샘플을 제공하고 있으며, 각 프로젝트 환경에 맞게 수정하여 사용한다.
|
||||
|
||||
=== 참고할 샘플 파일
|
||||
* LoginPage.vue
|
||||
* AdLoginController.java
|
||||
* onelogin.saml.properties
|
||||
|
||||
== 로그인/아웃 전,후 처리
|
||||
|
||||
로그인 전과 후, 로그아웃 전과 후 비즈니스 로직이 필요한 경우 LoginInterceptor를 상속받아 구현한다.
|
||||
|
||||
LoginInterceptor 클래스는 아래와 같은 Interface를 제공한다.
|
||||
[source, java]
|
||||
----
|
||||
/**
|
||||
* 로그인 전
|
||||
*/
|
||||
default void preLogin(HttpServletRequest request, HttpServletResponse response) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후
|
||||
*/
|
||||
default void postLogin(HttpServletRequest request, HttpServletResponse response, User user) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 전
|
||||
*/
|
||||
default void preLogout(HttpServletRequest request, HttpServletResponse response, User user) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 후
|
||||
*/
|
||||
default void postLogout(HttpServletRequest request, HttpServletResponse response, User user) {
|
||||
}
|
||||
----
|
||||
|
||||
LoginInterceptor를 구현한 클래스는 LoginInterceptorExecutor에 의해 실행되며 Spring Bean으로 등록하고 SpringWebCofig에 아래와 같이 LoginInterceptorExecutor에 등록한다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public LoginInterceptorExecutor loginInterceptorExecutor() {
|
||||
List<LoginInterceptor> loginInterceptorList = new ArrayList<>();
|
||||
loginInterceptorList.add(userUpdateInterceptor());
|
||||
loginInterceptorList.add(loginOutLogInterceptor());
|
||||
return new LoginInterceptorExecutor(loginInterceptorList);
|
||||
}
|
||||
----
|
||||
|
||||
아래는 로그인 후 사용자 정보를 update 하는 UserUpdateInterceptor의 일부분이다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Override
|
||||
public void postLogin(HttpServletRequest request, HttpServletResponse response, User user) {
|
||||
|
||||
String recentLoginIp = user.getRecentLoginIp();
|
||||
Date recentLoginDatetime = user.getRecentLoginDatetime();
|
||||
user.setLastLoginDate(recentLoginDatetime);
|
||||
user.setLastLoginIp(recentLoginIp);
|
||||
user.setRecentLoginDatetime(new Date());
|
||||
user.setRecentLoginIp(webUtil.getClientIp(request));
|
||||
user.setLastActivityTime(System.currentTimeMillis());
|
||||
userService.updateUser(user);
|
||||
}
|
||||
----
|
||||
@@ -0,0 +1,248 @@
|
||||
= 메뉴 관리
|
||||
|
||||
== 개요
|
||||
|
||||
메뉴와 하위 Page 및 API 목록을 관리하는 화면이다. +
|
||||
|
||||
NOTE: 신규 메뉴를 등록 하거나 메뉴정보를 수정 또는 삭제할 경우 콜백함수를 통해서 메뉴관련 된 모든 캐시가 초기화 된다.
|
||||
|
||||
== UI Design & Function
|
||||
|
||||
=== 메뉴 관리
|
||||
|
||||
메뉴를 등록, 수정 및 관리 할 수 있는 메뉴 트리를 제공한다.
|
||||
|
||||
image::menuManagement_01.png[]
|
||||
|
||||
* 기능 설명
|
||||
. 메뉴 트리 목록 조회
|
||||
. 메뉴 상세정보 조회 : 트리에서 메뉴 선택시 메뉴 상세정보를 조회 한다.
|
||||
. 메뉴 등록 : 현재 선택된 메뉴 하위에 새로운 메뉴를 추가 한다.
|
||||
. 메뉴 수정 / 삭제 : 현재 선택된 메뉴를 수정 또는 삭제 한다.
|
||||
. 메뉴 이동 : 트리에서 Drag&Drop 기능으로 선택한 메뉴를 이동할 수 있다.
|
||||
|
||||
=== 페이지 관리
|
||||
|
||||
image::menuManagement_02.png[]
|
||||
|
||||
* 기능 설명
|
||||
. Page 목록 조회 : 메뉴 트리에서 메뉴 선택 시 해당 메뉴에 맵핑되어 있는 페이지 목록 조회.
|
||||
. Page 등록, 수정, 삭제
|
||||
|
||||
NOTE: 권한 타입은 READ, UPDATE, EXECUTE, DOWNLOAD 로 구성되어 있으며 사용자가 가지고 있는 해당 메뉴에 대한 권한 종류로 UI의 권한 제어를 하고 있다. +
|
||||
자세한 내용은 <<_화면별_권한처리,화면별 권한처리>> 매뉴얼을 참고한다.
|
||||
|
||||
=== API 관리
|
||||
|
||||
image::menuManagement_03.png[]
|
||||
|
||||
* 기능 설명
|
||||
. API 목록 조회
|
||||
. API 등록
|
||||
. API 수정
|
||||
. API 삭제
|
||||
|
||||
=== SDL 게시판 Page, API 프리셋 추가
|
||||
|
||||
image::menuManagement_04.png[]
|
||||
|
||||
* 기능 설명
|
||||
. 게시판 선택 및 Page, API 자동 추가
|
||||
|
||||
== API & Service
|
||||
|
||||
=== API
|
||||
|
||||
MenuController.java +
|
||||
Menu와 해당 메뉴에 Mapping 되어 있는 Page, API의 등록, 수정, 삭제 및 조회하는 API를 포함하고 있다.
|
||||
|
||||
* 주요 기능 API 목록
|
||||
. 메뉴 트리 목록 조회 : GET /auth/menus/menu-mgmt/level
|
||||
. 메뉴 상세정보 조회 : GET /auth/menus/{menuId}
|
||||
. 메뉴 등록 : POST /auth/menus
|
||||
. 메뉴 수정 : PUT /auth/menus
|
||||
. 메뉴 삭제 : DELETE /auth/menus/{menuId}
|
||||
. 메뉴 이동 : PUT /auth/menus/move-menu
|
||||
. Page 목록 조회 : GET /auth/menus/{menuId}/pages
|
||||
. Page 등록 : POST /auth/menus/{menuId}/pages
|
||||
. Page 수정 : PUT /auth/menus/{menuId}/pages
|
||||
. Page 삭제 : DELETE /auth/menus/{menuId}/pages
|
||||
. API 목록 조회 : GET /auth/menus/{menuId}/apis
|
||||
. API 등록 : POST /auth/menus/{menuId}/apis
|
||||
. API 수정 : PUT /auth/menus/{menuId}/apis
|
||||
. API 삭제 : DELETE /auth/menus/{menuId}/apis
|
||||
|
||||
== Entity Table & SQL
|
||||
|
||||
=== Entity Table
|
||||
|
||||
* TN_CF_SYS_RESOURCE : 리소스
|
||||
* TN_CF_SYS_RESOURCE_MAPP : 리소스 맵핑
|
||||
* TN_CF_MENU : 메뉴
|
||||
* TN_CF_PAGE : Page
|
||||
* TN_CF_API : API
|
||||
|
||||
=== SQL
|
||||
|
||||
. 메뉴 트리 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectMenuLevel" parameterType="java.util.HashMap" resultMap="menuTreeInfo">
|
||||
SELECT *
|
||||
FROM (
|
||||
<include refid="conditionMenuLevel"/>
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=2]
|
||||
. 메뉴 상세정보 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectMenu" parameterType="java.util.HashMap" resultMap="menuResult">
|
||||
SELECT M.MENU_ID, M.LABEL,
|
||||
M.MENU_LEVEL, M.MENU_SEQUENCE,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=3]
|
||||
. 메뉴 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertMenu" parameterType="menu">
|
||||
INSERT INTO <include refid="tableMenu" />
|
||||
(MENU_ID, LABEL,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. 메뉴 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateMenu" parameterType="menu">
|
||||
UPDATE <include refid="tableMenu" />
|
||||
SET LABEL = #{label},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. 메뉴 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteMenu" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableMenu" />
|
||||
SET USE_YN = 0,
|
||||
DELETE_YN = 1
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. 메뉴 이동
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateParentMenuId" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="sysResource.tableSysResource" />
|
||||
SET UPPER_SYS_RESOURCE_ID = #{parentMenuId}
|
||||
--생략--
|
||||
----
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateMenuSequence" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableMenu" />
|
||||
SET MENU_SEQUENCE = #{moveToMenuSequence}
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=7]
|
||||
. Page 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectPageList" parameterType="java.util.HashMap" resultMap="pageResult">
|
||||
SELECT P.PAGE_ID, SA.AUTHORIZATION_ID, P.PAGE_NAME,
|
||||
P.PAGE_PATH, P.COMPONENT_NAME, P.DEFAULTED, P.USE_YN
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=8]
|
||||
. Page 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertPage" parameterType="page">
|
||||
INSERT INTO <include refid="tablePage" />
|
||||
(PAGE_ID, PAGE_NAME, PAGE_PATH,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=9]
|
||||
. Page 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updatePage" parameterType="page">
|
||||
UPDATE <include refid="tablePage" />
|
||||
SET PAGE_NAME = #{pageName},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=10]
|
||||
. Page 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deletePage" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tablePage" />
|
||||
SET USE_YN = 0,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=11]
|
||||
. API 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectApiList" parameterType="java.util.HashMap" resultMap="apiResult">
|
||||
SELECT A.API_ID, SA.AUTHORIZATION_ID, A.API_NAME,
|
||||
A.API_URL, A.HTTP_METHOD, A.SERVER_ID, A.USE_YN
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=12]
|
||||
. API 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertApi" parameterType="api">
|
||||
INSERT INTO <include refid="tableApi" />
|
||||
(API_ID, API_NAME, API_URL, API_PATH, API_PARAMETERS, HTTP_METHOD, SERVER_ID, USE_YN, DELETE_YN)
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=13]
|
||||
. API 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateApi" parameterType="api">
|
||||
UPDATE <include refid="tableApi" />
|
||||
SET API_NAME = #{apiName},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=14]
|
||||
. API 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteApi" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableApi" />
|
||||
SET USE_YN = 0,
|
||||
--생략--
|
||||
----
|
||||
@@ -0,0 +1,83 @@
|
||||
= 부서관리(Knox)
|
||||
|
||||
NOTE: 해당 기능 사용 필요시 Knox API를 통해 연계 해야 한다.
|
||||
|
||||
== Table
|
||||
* 부서(Knox) : TN_CF_DEPT_LDAP
|
||||
|
||||
== API
|
||||
.KnoxDepartmentController.java
|
||||
|
||||
* 부서의 데이터가 많으므로 목록에 1레벨만 불러오고 1레벨의 부서를 클릭시 "부서 관리(Knox)(하위 레벨 부서)" API를 실행하여 하위 부서를 조회한다. +
|
||||
부서 관리(사용자정의) 화면에서 부서매핑을 추가할 때도 사용한다.
|
||||
|
||||
. 부서 관리(Knox)(1레벨 부서) +
|
||||
GET /admin/knox-department +
|
||||
Query ID : selectKnoxDepartmentList
|
||||
|
||||
. 부서 관리(Knox)(하위 레벨 부서) +
|
||||
GET /admin/knox-department/{upperDeptCode}
|
||||
Query ID : selectKnoxDepartmentListByUpperCode
|
||||
|
||||
== 부서 Tree 구조
|
||||
|
||||
부서관리를 tree 구조로 보여지게 하기 위해, DB의 데이터가 parent 관계를 구성해야 한다.
|
||||
LDAP 부서관리는 TN_CF_DEPT_LDAP 테이블의 dept_code(Child) 와 upper_dept_code(Parent) 관계로 tree 구조를 만들게 되는데 시스템의 최상위 dept code에는 ROOT를 입력해야 한다.
|
||||
내부적으로 LDAP 부서 코드의 Root 밑의 1 level을 찾는 query가 *ROOT* 라는 코드를 찾도록 되어 있다.
|
||||
|
||||
|===
|
||||
|DEPT_CODE |DEPT_NAME |DEPT_LEVEL |UPPER_DEPT_CODE
|
||||
|
||||
|C00001
|
||||
|정보전략
|
||||
|1
|
||||
|ROOT
|
||||
|
||||
|C000011
|
||||
|정보전략 부서1
|
||||
|2
|
||||
|C00001
|
||||
|
||||
|C00002
|
||||
|마케팅
|
||||
|1
|
||||
|ROOT
|
||||
|
||||
|C000021
|
||||
|마케팅 부서1
|
||||
|2
|
||||
|C00002
|
||||
|===
|
||||
|
||||
사용자정의 부서관리는 TN_CF_DEPT_SELF 테이블의 SELF_DEPT_CODE(Child) 와 UPPER_SELF_DEPT_CODE(Parent)
|
||||
관계로 tree 구조를 이루며, 시스템의 최상위 SELF_DEPT_CODE는 DEPT를 입력해야 한다.
|
||||
|
||||
|===
|
||||
|DEPT_CODE |DEPT_NAME |DEPT_LEVEL |UPPER_DEPT_CODE
|
||||
|
||||
|C00001
|
||||
|정보전략
|
||||
|1
|
||||
|DEPT
|
||||
|
||||
|C000011
|
||||
|정보전략 부서1
|
||||
|2
|
||||
|C00001
|
||||
|
||||
|C00002
|
||||
|마케팅
|
||||
|1
|
||||
|DEPT
|
||||
|
||||
|C000021
|
||||
|마케팅 부서1
|
||||
|2
|
||||
|C00002
|
||||
|===
|
||||
|
||||
== 부서 목록
|
||||
|
||||
TN_CF_DEPT_LDAP 테이블에 저장되어 있는 부서정보를 트리형태로 보여준다.
|
||||
|
||||
image::deptMgmt(Knox).png[deptMgmt(Knox),300,400]
|
||||
@@ -0,0 +1,79 @@
|
||||
= 부서관리(사용자정의)
|
||||
|
||||
== Table
|
||||
* 부서(사용자정의) : TN_CF_DEPT_SELF
|
||||
* 부서매핑 : TN_CF_DEPT_MAPPING
|
||||
|
||||
== API
|
||||
.CustomDepartmentController.java
|
||||
|
||||
. 부서관리(사용자 정의) 목록 조회 +
|
||||
GET /admin/department/custom +
|
||||
Query ID : selectCustomDepartmentList
|
||||
|
||||
. 부서 하위 레벨 조회 +
|
||||
GET /admin/department/custom/dept-level-sub +
|
||||
Query ID : selectCustomDepartmentSubList
|
||||
* 부서의 데이터가 많으므로 목록에 1레벨만 불러오고 1레벨의 부서를 클릭시 "부서 하위 레벨 조회" API를 실행하여 하위 부서를 조회한다.
|
||||
|
||||
. 부서 상세정보 조회 +
|
||||
GET /admin/department/custom/dept-infos +
|
||||
Query ID : selectCustomDepartmentList, selectDeptMapping
|
||||
* 부서 정보와 부서 매핑을 함께 불러온다.
|
||||
|
||||
. 부서 저장 +
|
||||
POST /admin/department/custom +
|
||||
Query ID : insertCustomDepartment
|
||||
* 등록된 부서가 있는지 중복체크하고 없으면 저장한다.
|
||||
|
||||
. 부서 수정 +
|
||||
PUT /admin/department/custom/{selfDeptCode} +
|
||||
Query ID : updateCustomDepartment
|
||||
* 부서명, 정렬순서, 설명만 수정 가능하다.
|
||||
|
||||
. 부서 이동 +
|
||||
PUT /admin/department/custom +
|
||||
Query ID : updateDeptUpperDeptCode, updateDeptSequence
|
||||
* 부서는 Drag & Drop 으로 원하는 곳에 이동하여 저장할 수 있다.
|
||||
|
||||
. 부서 매핑 조회 +
|
||||
GET /admin/department/custom/mappings/{selfDeptCode} +
|
||||
Query ID : selectDeptMapping
|
||||
|
||||
. 부서 매핑 저장 +
|
||||
POST /admin/department/custom/mappings/{selfDeptCode} +
|
||||
Query ID : insertDeptMapping
|
||||
* DB에 저장된 mapping과 화면에서 추가한 mapping을 비교하여 DB에 없는 mapping만 insert 한다.
|
||||
|
||||
. 부서 매핑 삭제 +
|
||||
DELETE /admin/department/custom/mappings/{selfDeptCode}/{deptCode} +
|
||||
Query ID : deleteDeptMapping
|
||||
|
||||
== 부서관리(사용자정의) 기본 정보
|
||||
사용자의 임의로 부서를 등록 및 해당되는 부서의 Knox 부서 매핑.
|
||||
|
||||
image::deptList.png[]
|
||||
|
||||
=== 기본정보 필드 설명
|
||||
- 부서코드 : 부서에 부여되는 코드(Unique)
|
||||
- 부서명 : 부서명칭
|
||||
- 부서레벨 : 부서에 부여되는 Level이며 최상위 Department 부서는 0 level이다.
|
||||
- 정렬순서 : 같은 레벨 상 나오는 부서의 순서
|
||||
- 설명 : 부서에 대한 설명
|
||||
|
||||
=== 기본정보 기능별 설명
|
||||
- 삭제 : 부서목록의 부서를 선택 후 삭제 버튼 클릭시 선택된 부서 삭제(하위 부서 포함)
|
||||
- 추가 : 추가하고자 하는 상위 부서를 선택 후 추가 버튼을 클릭시 Tree 구조에 New Document 생성
|
||||
- 저장 : 추가로 생성된 또는 선택된 부서 정보를 저장
|
||||
|
||||
== 부서 매핑
|
||||
해당 부서에 대한 Knox 부서 매핑 정보.
|
||||
|
||||
=== 부서 매핑 정보 필드 설명
|
||||
- 부서코드 : Knox 부서에 부여되는 코드(Unique)
|
||||
- 부서명 : Knox 부서명칭
|
||||
- 삭제 : 삭제 실행 버튼
|
||||
|
||||
=== 기능별 설명
|
||||
- 추가 : Knox 부서 Popup 호출
|
||||
- 저장 : 호출된 Knox 부서 Popup 화면에서 추가하고자 하는 부서 매핑 정보에 저장.
|
||||
@@ -0,0 +1,23 @@
|
||||
= 사용권한 신청/승인
|
||||
|
||||
== 개요
|
||||
사용자가 시스템에 가입시 시스템 사용 권한을 승인/승인 취소/삭제를 하여 시스템을 사용/미사용하게 한다.
|
||||
|
||||
== Table
|
||||
* 사용자 : TN_CF_USER
|
||||
|
||||
== API
|
||||
.UserController.java
|
||||
|
||||
. 사용자 승인 +
|
||||
PUT /auth/users/status/confirm +
|
||||
Query ID : updateUser
|
||||
|
||||
. 사용자 승인취소 +
|
||||
PUT /auth/users/status/inactive +
|
||||
Query ID : updateUser
|
||||
|
||||
. 사용자 삭제 +
|
||||
DELETE /auth/users +
|
||||
Query ID : updateUser
|
||||
* DeleteMapping이지만 DB 삭제가 아닌 ACTIVE_FLAG = 0, DELETED = 1 로 업데이트 한다.
|
||||
@@ -0,0 +1,30 @@
|
||||
= 사용자 관리
|
||||
|
||||
== 사용자 관리 화면
|
||||
사용자 목록을 조회하며 시스템 사용 신청 대기자에 대한 사용 승인 및 취소가 가능하다. +
|
||||
사용자 조회 목록의 Row를 클릭시 사용자 상세 정보 화면으로 이동할 수 있다.
|
||||
|
||||
image::userMgmt.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 엑셀다운로드 : 조회 조건과 동일한 조회 결과 값을 담은 엑셀 파일을 다운로드 하는 기능.
|
||||
- 삭제 : 선택된 사용자를 삭제 처리하는 기능.
|
||||
* 삭제된 사용자의 row 데이터는 남아 있지만 사용자의 정보(EP ID, 사용자 이름 제외)는 모두 삭제 된다.
|
||||
- 승인취소 : 선택된 사용자를 승인취소 처리하는 기능.
|
||||
- 승인 : 선택된 사용자를 승인 처리하는 기능.
|
||||
* 세부적인 내용은 <<_사용권한_신청승인,사용권한 신청/승인>> 매뉴얼을 참조한다.
|
||||
|
||||
== 사용자 상세 조회 화면
|
||||
사용자의 상세정보표시 및 조회한 사용자에 대한 역할 및 메뉴 추가/삭제가 가능하다.
|
||||
|
||||
image::userInfo.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 사용자-역할 삭제 : 선택된 역할을 삭제
|
||||
- 사용자-역할 추가 : 선택된 역할을 추가하는 기능. 역할 팝업이 호출 되며, 사용자에게 부여하고 싶은 역할을 선택하여 확인 버튼 클릭시 사용자-역할 Grid에 추가된다.
|
||||
- 사용자-역할 저장 : 추가 또는 수정된 역할이 있을 경우, 저장 기능 수행.
|
||||
- 사용자-메뉴 삭제 : 선택된 메뉴를 삭제
|
||||
- 사용자-메뉴 추가 : 선택된 메뉴를 추가하는 기능. 메뉴 팝업이 호출 되며, 사용자에게 부여하고 싶은 메뉴을 선택하여 확인 버튼 클릭시 사용자-메뉴 Grid에 추가된다.
|
||||
* 메뉴의 Page, API 권한 타입(READ, UPDATE, EXECUTE, DOWNLOAD) 중 필요한 권한을 check하여 등록한다.
|
||||
* 권한 타입에 대한 설명은 메뉴관리 가이드를 참고한다. <<_메뉴_관리,메뉴 관리>>
|
||||
- 사용자-메뉴 저장 : 추가 또는 수정된 메뉴이 있을 경우, 저장 기능 수행
|
||||
@@ -0,0 +1,228 @@
|
||||
= 업무 그룹 관리
|
||||
|
||||
== 개요
|
||||
|
||||
메일 그룹 및 맵핑 정보 관리 기능 제공.
|
||||
|
||||
== UI Design & Function
|
||||
|
||||
=== 업무 그룹 목록(WorkgroupList.vue)
|
||||
|
||||
업무별로 업무그룹을 만들고 해당 업무그룹에서 사용가능한 메뉴를 선택, 해당 메뉴 사용 가능한 Role 또는 User를 추가한다.
|
||||
|
||||
image::workgroupList.png[업무 그룹 목록]
|
||||
|
||||
* 기능 설명
|
||||
. 업무그룹 목록 조회
|
||||
. 업무그룹 상세정보 조회
|
||||
. 업무그룹 등록 : 업무그룹 정보를 입력하는 Popup이 호출 되며, 정보 기입 후 저장 버튼을 클릭하여 등록한다.
|
||||
. 업무그룹 수정 : 선택된(checkbox : true) 업무그룹 정보를 수정하는 Popup이 호출 되며 역할 정보 기입 후 저장 버튼을 클릭하여 수정한다.
|
||||
. 업무그룹 삭제 : 선택된(checkbox : true) 역할 목록을 삭제한다.
|
||||
|
||||
=== 업무그룹-메뉴(WorkgroupMenuInfo.vue)
|
||||
|
||||
업무그룹에서 사용 가능한 메뉴를 관리 하는 화면. +
|
||||
메뉴 등록 및 삭제가 가능하며 메뉴별 권한 설정이 가능하다.
|
||||
|
||||
image::workgroupMenuInfo.png[업무그룹-메뉴]
|
||||
|
||||
* 기능 설명
|
||||
. 업무그룹-메뉴 목록 조회
|
||||
. 업무그룹-메뉴 등록 : 메뉴를 선택하는 Popup이 호출 되며 선택 후 저장 버튼을 클릭하여 등록한다.
|
||||
. 업무그룹-메뉴 수정 : 권한을 선택(checkbox : true)하여 해당 메뉴에 대한 권한을 수정한다.
|
||||
. 업무그룹-메뉴 삭제 : 선택된(checkbox : true) 메뉴 목록을 삭제한다.
|
||||
|
||||
=== 업무그룹-역할(WorkgroupMenuInfo.vue)
|
||||
|
||||
선택한 업무그룹에 역할 또는 사용자를 관리하는 화면. +
|
||||
역할과 사용자를 추가 또는 삭제할 수 있다.
|
||||
|
||||
image::workgroupRoleInfo.png[업무그룹-역할]
|
||||
|
||||
* 기능 설명
|
||||
. 업무그룹-역할 목록 조회
|
||||
. 업무그룹-역할 등록
|
||||
.. 사용자 등록 : 사용자 추가 버튼 클릭 시 사용자를 선택할 수 있는 Popup이 호출 되며 선택 후 저장 버튼을 클릭하여 등록 한다.
|
||||
.. 역할 등록 : 역할 추가 버튼 클릭 시 역할을 선택할 수 있는 Popup이 호출 되며 선택 후 저장 버튼을 클릭하여 등록 한다.
|
||||
. 업무그룹-역할 수정 : 등록된 사용자의 업무그룹 만료일을 수정 한다.
|
||||
. 업무그룹-역할 삭제 : 선택된(checkbox : true) 역할(사용자) 목록을 삭제한다.
|
||||
|
||||
== API & Service
|
||||
|
||||
=== API
|
||||
|
||||
* API : WorkgroupController.java
|
||||
|
||||
. 업무그룹 목록 조회 : GET /auth/workgroups-with-paging
|
||||
. 업무그룹 상세정보 조회 : GET /auth/workgroups/{workgroupId}
|
||||
. 업무그룹 등록 : POST /auth/workgroups
|
||||
. 업무그룹 수정 : PUT /auth/workgroups/{workgroupId}
|
||||
. 업무그룹 삭제 : DELETE /auth/workgroups
|
||||
. 업무그룹-메뉴 목록 조회 : GET /auth/workgroups/{workgroupId}/menus
|
||||
. 업무그룹-메뉴 등록 : POST /auth/workgroups/{workgroupId}/menus
|
||||
. 업무그룹-메뉴 수정 : PUT /auth/workgroups/{workgroupId}/menus
|
||||
. 업무그룹-메뉴 삭제 : DELETE /auth/workgroups/{workgroupId}/menus
|
||||
. 업무그룹-역할 목록 조회 : GET /auth/workgroups/{workgroupId}/roles
|
||||
. 업무그룹-역할 등록 : POST /auth/workgroups/{workgroupId}/roles
|
||||
. 업무그룹-역할 수정 : PUT /auth/workgroups/{workgroupId}/roles
|
||||
. 업무그룹-역할 삭제 : DELETE /auth/workgroups/{workgroupId}/roles
|
||||
|
||||
* Service : WorkgroupServiceImpl.java
|
||||
|
||||
. 업무그룹-메뉴 목록 조회. +
|
||||
메뉴의 전체 경로 셋팅 및 권한별 row 데이터를 메뉴 row 데이터로 가공한다.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
public List<WorkgroupMenuDto> getWorkgroupMenuList(String workgroupId) {
|
||||
|
||||
List<WorkgroupMenuDto> workgroupMenuDtoList = new ArrayList<>();
|
||||
List<String> authorizationIdList;
|
||||
String beforeMenuId = "";
|
||||
-- 생략 --
|
||||
----
|
||||
|
||||
== Entity Table & SQL
|
||||
|
||||
=== Entity Table
|
||||
|
||||
* TN_CF_WORKGROUP : 업무그룹
|
||||
* TN_CF_WORK_AUTHORIZATION : 업무그룹 메뉴 권한 맵핑
|
||||
* TN_CF_WORKGROUP_ROLE : 업무그룹 역할 맵핑
|
||||
|
||||
=== SQL
|
||||
|
||||
. 업무그룹 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectWorkgroupPagingList" parameterType="java.util.HashMap" resultMap="workgroupResult">
|
||||
SELECT *
|
||||
FROM (SELECT ROW_NUMBER() OVER ( <if test="@com.samsung.SdlComparator@isNotEmptyForDynamicSql(orderBy)">ORDER BY ${orderBy}</if> ) ROWNUM,
|
||||
<include refid="columnsWorkgroup" />
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=2]
|
||||
. 업무그룹 상세정보 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectWorkgroup" parameterType="java.util.HashMap" resultMap="workgroupResult">
|
||||
SELECT <include refid="columnsWorkgroup" />
|
||||
FROM <include refid="tableWorkgroup" />
|
||||
<include refid="conditionWorkgroup" />
|
||||
</select>
|
||||
----
|
||||
|
||||
[start=3]
|
||||
. 업무그룹 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertWorkgroup" parameterType="workgroup">
|
||||
INSERT INTO <include refid="tableWorkgroup" />
|
||||
(<include refid="columnsWorkgroup"/>)
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. 업무그룹 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateWorkgroup" parameterType="workGroup">
|
||||
UPDATE <include refid="tableWorkgroup" />
|
||||
SET WORKGROUP_NAME = #{workgroupName},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. 업무그룹 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteWorkgroup" parameterType="java.util.HashMap">
|
||||
UPDATE <include refid="tableWorkgroup" />
|
||||
SET DELETE_YN = 1,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. 업무그룹-메뉴 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectWorkgroupMenuList" parameterType="java.util.HashMap" resultMap="workgroupMenuResult">
|
||||
WITH TREE_TABLE AS (SELECT SR.UPPER_SYS_RESOURCE_ID, M.MENU_ID,
|
||||
CONVERT(NVARCHAR(100), M.LABEL) LABEL,
|
||||
CONVERT(NVARCHAR(100), M.LABEL_JSON) LABEL_JSON,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=7]
|
||||
. 업무그룹-메뉴 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertWorkgroupMenu" parameterType="workgroupMenu">
|
||||
INSERT INTO <include refid="tableWorkAuthorization" />
|
||||
(WORKGROUP_ID, SYS_RESOURCE_ID, AUTHORIZATION_ID,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=8]
|
||||
. 업무그룹-메뉴 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteWorkgroupMenu" parameterType="workgroupMenu">
|
||||
DELETE FROM <include refid="tableWorkAuthorization" />
|
||||
WHERE SYS_RESOURCE_ID = #{sysResourceId}
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=9]
|
||||
. 업무그룹-역할 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectRoleWorkgroupList" parameterType="java.util.HashMap" resultMap="workgroup.workgroupResult">
|
||||
SELECT W.WORKGROUP_ID, W.WORKGROUP_NAME, W.DESCRIPTION,
|
||||
W.LABEL
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=10]
|
||||
. 업무그룹-역할 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertWorkgroupRole" parameterType="workgroupRole">
|
||||
INSERT INTO <include refid="tableWorkgroupRole" />
|
||||
(WORKGROUP_ID, USER_ROLE_ID, FROM_DATE, THRU_DATE, ROLE_ID, USER_ID,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=11]
|
||||
. 업무그룹-역할 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateWorkgroupRole" parameterType="workgroupRole">
|
||||
UPDATE <include refid="tableWorkgroupRole" />
|
||||
SET THRU_DATE = #{thruDate},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=12]
|
||||
. 업무그룹-역할 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteWorkgroupRole" parameterType="java.util.HashMap">
|
||||
DELETE FROM <include refid="tableWorkgroupRole" />
|
||||
WHERE WORKGROUP_ID = #{workgroupId}
|
||||
--생략--
|
||||
----
|
||||
@@ -0,0 +1,207 @@
|
||||
= 역할 관리
|
||||
|
||||
== 개요
|
||||
|
||||
역할을 등록하고, 해당 역할에 사용자 및 업무그룹을 Mapping 한다.
|
||||
|
||||
== UI Design & Function
|
||||
|
||||
=== 역할 목록(RoleList.vue)
|
||||
|
||||
역할 등록, 수정 및 조회. +
|
||||
조회된 목록의 사용자/업무그룹 컬럼을 클릭하여 역할-사용자, 역할-업무그룹 상세 화면으로 이동한다.
|
||||
|
||||
image::roleList.png[역할 관리]
|
||||
|
||||
* 기능 설명
|
||||
. 역할 목록 조회
|
||||
. 역할 등록 : 역할 정보를 입력하는 Popup이 호출 되며 정보 기입 후 저장 버튼을 클릭하여 등록한다.
|
||||
. 역할 수정 : 선택된(checkbox : true) 역할 정보를 수정하는 Popup이 호출 되며, 정보 기입 후 저장 버튼을 클릭하여 수정한다.
|
||||
. 역할 삭제 : 선택된(checkbox : true) 역할 목록을 삭제한다.
|
||||
. 엑셀 다운로드 : 조회 조건과 동일한 역할 목록을 엑셀 파일로 다운로드 한다.
|
||||
|
||||
=== 역할-사용자 관리(RoleUserInfo.vue)
|
||||
|
||||
선택한 역할에 대한 사용자 추가. +
|
||||
역할에 대한 사용자가 많을 경우를 대비하여 상단에 페이징 처리 되는 Grid를, 하단에는 사용자를 추가하는 Grid로 나누어 화면을 구성된다.
|
||||
|
||||
image::roleUserInfo.png[역할-사용자 관리]
|
||||
|
||||
* 기능 설명
|
||||
. 역할-사용자 목록 조회
|
||||
. 역할-사용자 등록 : 추가 버튼 클릭시 사용자 조회 Popup이 호출 되며 사용자 선택 후 추가된 하단의 사용자 목록에서 저장한다.
|
||||
. 역할-사용자 수정 : 기등록된 상단 사용자 목록에서 역할 적용 기간을 수정한다.
|
||||
. 역할-사용자 삭제 : 기등록된 상단 사용자 목록에서 선택된(checkbox : true) 사용자 목록을 삭제한다.
|
||||
|
||||
=== 역할-업무그룹 관리(RoleWorkgroupInfo.vue)
|
||||
|
||||
선택한 역할에 대한 업무그룹 추가.
|
||||
|
||||
image::roleWorkgroupInfo.png[역할-업무그룹 관리]
|
||||
|
||||
* 기능 설명
|
||||
. 역할-업무그룹 목록 조회
|
||||
. 역할-업무그룹 등록 : 추가 버튼 클릭시 업무그룹 조회 Popup이 호출 되며 업무그룹을 선택 하여 등록 할 수 있다.
|
||||
. 역할-업무그룹 삭제 : 기등록된 상단 업무그룹 목록에서 선택된(checkbox : true) 업무그룹 목록을 삭제한다.
|
||||
|
||||
== API & Service
|
||||
|
||||
=== API
|
||||
|
||||
* API : RoleController.java
|
||||
|
||||
. 역할 목록 조회 : GET /roles-with-paging
|
||||
. 역할 상세정보 조회 : GET /roles/{roleId}
|
||||
. 역할 등록 : POST /roles
|
||||
. 역할 수정 : PUT /roles
|
||||
. 역할 삭제 : DELETE /roles
|
||||
. 역할-사용자 목록 조회 : GET /roles/{roleId}/users-with-paging
|
||||
. 역할-사용자 등록 : POST /roles/{roleId}/users
|
||||
. 역할-사용자 수정 : PUT /roles/{roleId}/users
|
||||
. 역할-사용자 삭제 : DELETE /roles/{roleId}/users
|
||||
. 역할-업무그룹 목록 조회 : GET /roles/{roleId}/workgroups
|
||||
. 역할-업무그룹 등록 : POST /roles/{roleId}/workgroups
|
||||
. 역할-업무그룹 삭제 : DELETE /roles/{roleId}/workgroups
|
||||
|
||||
== Entity Table & SQL
|
||||
|
||||
=== Entity Table
|
||||
|
||||
* TN_CF_ROLE : 역할
|
||||
* TN_CF_USER_ROLE : 역할-사용자 맵핑
|
||||
* TN_CF_WORKGROUP_ROLE : 역할-업무그룹 맵핑
|
||||
|
||||
=== SQL
|
||||
|
||||
. 역할 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectRolePagingList" parameterType="java.util.HashMap" resultMap="roleResult">
|
||||
SELECT *
|
||||
FROM (SELECT
|
||||
ROW_NUMBER() OVER ( <if test="@com.samsung.SdlComparator@isNotEmptyForDynamicSql(orderBy)">ORDER BY ${orderBy}</if> ) ROWNUM,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=2]
|
||||
. 역할 상세정보 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectRole" parameterType="java.util.HashMap" resultMap="roleResult">
|
||||
SELECT <include refid="columnsRole" />
|
||||
FROM <include refid="tableRole" />
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=3]
|
||||
. 역할 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertRole" parameterType="role">
|
||||
INSERT INTO <include refid="tableRole" />
|
||||
(<include refid="columnsRole" />)
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. 역할 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateRole" parameterType="role" flushCache="true" >
|
||||
UPDATE <include refid="tableRole" />
|
||||
SET ROLE_NAME = #{roleName},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. 역할 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="deleteRole" parameterType="role" flushCache="true" >
|
||||
UPDATE <include refid="tableRole" />
|
||||
SET DELETE_YN = 1,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. 역할-사용자 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectRoleUserPagingList" parameterType="java.util.HashMap" resultMap="roleUserResult">
|
||||
SELECT *
|
||||
FROM (SELECT
|
||||
ROW_NUMBER() OVER ( <if test="@com.samsung.SdlComparator@isNotEmptyForDynamicSql(orderBy)">ORDER BY ${orderBy}</if> ) ROWNUM,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=7]
|
||||
. 역할-사용자 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertUserRole" parameterType="userRole">
|
||||
INSERT INTO <include refid="tableUserRole" />
|
||||
(ROLE_ID, USER_ID, FROM_DATE, THRU_DATE,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=8]
|
||||
. 역할-사용자 수정
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<update id="updateUserRole" parameterType="userRole">
|
||||
UPDATE <include refid="tableUserRole" />
|
||||
SET THRU_DATE = #{thruDate},
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=9]
|
||||
. 역할-사용자 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteUserRole" parameterType="java.util.HashMap">
|
||||
DELETE FROM <include refid="tableUserRole" />
|
||||
WHERE USER_ID = #{userId}
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=10]
|
||||
. 역할-업무그룹 목록 조회
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<select id="selectRoleWorkgroupList" parameterType="java.util.HashMap" resultMap="workgroup.workgroupResult">
|
||||
SELECT W.WORKGROUP_ID, W.WORKGROUP_NAME, W.DESCRIPTION,
|
||||
W.LABEL
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=11]
|
||||
. 역할-업무그룹 등록
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<insert id="insertWorkgroupRole" parameterType="workgroupRole">
|
||||
INSERT INTO <include refid="tableWorkgroupRole" />
|
||||
(WORKGROUP_ID, USER_ROLE_ID, FROM_DATE, THRU_DATE, ROLE_ID, USER_ID,
|
||||
--생략--
|
||||
----
|
||||
|
||||
[start=12]
|
||||
. 역할-업무그룹 삭제
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<delete id="deleteWorkgroupRole" parameterType="java.util.HashMap">
|
||||
DELETE FROM <include refid="tableWorkgroupRole" />
|
||||
WHERE WORKGROUP_ID = #{workgroupId}
|
||||
--생략--
|
||||
----
|
||||
@@ -0,0 +1,15 @@
|
||||
= 외부 사용자 관리
|
||||
|
||||
== 개요
|
||||
ID/PW 회원가입을 통해서 등록한 사용자 목록을 조회하는 화면이다.
|
||||
|
||||
== 화면
|
||||
외부 사용자 목록 조회 및 엑셀 다운로드 기능을 제공한다.
|
||||
|
||||
NOTE: 사용자 테이블(TN_CF_USER)의 외부 사용자 여부(EXTERNAL_FLAG) 정보를 통해서 확인할 수 있다. +
|
||||
사용자 관리 메뉴에서 관리자 승인이 된 사용자 정보만 조회할 수 있다.
|
||||
|
||||
image::externalUserList.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 엑셀다운로드 : 조회 조건과 동일한 조회 결과 값을 담은 엑셀 파일을 다운로드 하는 기능.
|
||||
@@ -0,0 +1,25 @@
|
||||
= 장기미사용자
|
||||
|
||||
== 개요
|
||||
일정 기간 시스템에 로그인(TN_CF_USER.RECENT_LOGIN_DATETIME)을 하지 않은 사용자는 장기 미사용자로 관리한다.
|
||||
|
||||
== 설명
|
||||
장기 미사용자 관리 배치를 실행하면서 3개월간 로그인 하지 않은 사용자를 조회한다. +
|
||||
|
||||
* config.properties 파일에서 개월수(기본 3개월) 및 배치 실행시간을 변경 할 수 있다.
|
||||
[source,properties]
|
||||
----
|
||||
batch.user.long-term.month=3
|
||||
batch.user.long-term-check.cron=0 10 00 * * ?
|
||||
----
|
||||
|
||||
* 조회된 장기 미사용자에게 매핑된 메뉴, 업무그룹, 역할을 삭제하고 장기 미접속자로 상태를 변경한다. 사용자 관리 재직상태 컬럼(TN_CF_USER.EXPIRE_STATUS_CODE)에서 확인 할 수 있다.
|
||||
|
||||
== 배치 설정
|
||||
|
||||
=== Spring 환경설정
|
||||
* QuartzConfig.java: Scheduler 등록
|
||||
* UserBatchConfig.java: 배치 Job, Trigger 등록
|
||||
* UserBatchExecutor.java: 서비스 호출
|
||||
|
||||
SDL의 배치 작업은 Quartz 를 이용하여 구현하고 있다. 자세한 내용은 <<_작업_스케쥴링, 작업 스케쥴링>> 및 <<_quartz_clustering_with_jdbc_jobstore, Quartz Clustering with JDBC-JobStore>> 을 참고한다.
|
||||
@@ -0,0 +1,23 @@
|
||||
= 시스템 관리
|
||||
|
||||
include::시스템관리/개발툴.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/게시판관리.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/공지사항알림.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/메인페이지.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/링크사이트.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/일괄작업관리.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/작업스케쥴링.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/캐시관리.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/코드관리.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/업무담당자관리.adoc[leveloffset=+1]
|
||||
|
||||
include::시스템관리/주요전화번호관리.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,3 @@
|
||||
= 개발툴/빌드 스크립드
|
||||
|
||||
SDL을 이용한 웹 어플리케이션 개발에 필요한 툴과 빌드, 배포 방법은 <<_설치,설치>>를 참고한다.
|
||||
@@ -0,0 +1,116 @@
|
||||
= 게시판 관리
|
||||
|
||||
== 개요
|
||||
|
||||
SDL에서 제공하는 게시판 관리 기능으로 공지사항(메인화면 공지팝업 연동)이나 FAQ 게시판 등 Community 성격으로 게시글 등록 및 답변기능을 사용할 수 있는 게시판 기능이다. +
|
||||
관리 기능에서 게시판 등록 및 관리를 할 수 있으며 사용자용과 관리자용 메뉴를 따로 만들어 권한별로 게시판 기능을 사용할 수 있도록 제공한다.
|
||||
|
||||
== Table
|
||||
|
||||
* 게시판 : TN_CF_BOARD
|
||||
* 게시판 분류 : TN_CF_BOARD_CLASSIFICATION
|
||||
* 게시판 컬럼 : TN_CF_BOARD_COLUMN
|
||||
|
||||
== API
|
||||
|
||||
.BoardController.java
|
||||
|
||||
. 게시판 목록 조회(페이징) +
|
||||
GET /boards-with-paging +
|
||||
Query ID : selectBoardPagingList
|
||||
|
||||
. 게시판 상세정보 조회 +
|
||||
GET /boards/{boardId} +
|
||||
Query ID : selectBoard
|
||||
|
||||
. 게시판 등록 +
|
||||
POST /boards +
|
||||
Query ID : insertBoard, insertBoardClassification, insertBoardColumn
|
||||
* 게시판 등록 시 등록화면에서 추가한 게시판 분류 목록과 default 컬럼 정보가 저장된다.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
@Transactional
|
||||
public void insertBoard(Board board) {
|
||||
|
||||
// 게시판 저장.
|
||||
boardDao.insertBoard(board);
|
||||
|
||||
// 게시판 분류 저장.
|
||||
if (!board.getClassifications().isEmpty()) {
|
||||
for (BoardClassification boardClassification : board.getClassifications()) {
|
||||
boardClassification.setBoardId(board.getBoardId());
|
||||
boardClassification.setClassificationId(idGenService.getNextStringId());
|
||||
boardDao.insertBoardClassification(boardClassification);
|
||||
}
|
||||
}
|
||||
|
||||
// 게시판 컬럼 저장.
|
||||
boardDao.insertBoardColumn(board.getBoardId());
|
||||
}
|
||||
----
|
||||
|
||||
[start=5]
|
||||
. 게시판 수정 +
|
||||
PUT /boards/{boardId} +
|
||||
Query ID : updateBoard, updateBoardClassification, updateBoardColumn
|
||||
* 게시판 수정시 변경된 게시판 분류 목록과 컬럼 정보가 수정된다.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateBoard(Board board) {
|
||||
|
||||
// 게시판 수정.
|
||||
boardDao.updateBoard(board);
|
||||
|
||||
// 게시판 분류 수정.
|
||||
List<BoardClassification> beforeClassifications = boardDao.getBoardClassificationList(board.getBoardId());
|
||||
List<String> beforeClassificationIdList = new ArrayList<>();
|
||||
-- 생략 --
|
||||
}
|
||||
----
|
||||
|
||||
[start=6]
|
||||
. 게시판 삭제 +
|
||||
DELETE /boards/{boardId} +
|
||||
Query ID : deleteBoard
|
||||
|
||||
== 화면
|
||||
|
||||
. 게시판 목록 화면
|
||||
|
||||
image::boardManagement_01.png[]
|
||||
|
||||
* 게시판 목록을 확인할 수 있다.
|
||||
* 해당 게시판의 등록된 게시글 등록건수와 현재 사용 여부등의 정보를 보여준다.
|
||||
* 등록 버튼 클릭 시 등록화면으로 이동되며 게시판 명 클릭 시 게시판 수정화면으로 이동된다.
|
||||
|
||||
[start=2]
|
||||
. 게시판 등록 화면
|
||||
|
||||
image::boardManagement_02.png[]
|
||||
|
||||
* 게시판을 등록할 수 있다.
|
||||
* 게시판 세부기능 속성
|
||||
** 에디터 사용여부 : CafeNote 등 에디터를 사용할지 선택('미사용' 선택 시 textarea 태그로 구현된다)
|
||||
** 이미지파일사용 : 이미지 파일 등록 시 본문에 이미지를 표시한다.(사진형 게시판일 경우 필수사용)
|
||||
** 메인 공지팝업 : 게시글 등록 시 메인 공지사항 팝업창에 해당 기간동안 게시글이 노출된다.
|
||||
** 게시글 '공지'라벨 표시 : 게시글 등록 시 게시글 목록 상단에 공지 게시글로 노출된다.
|
||||
|
||||
[start=3]
|
||||
. 게시판 상세정보 수정 화면
|
||||
|
||||
image::boardManagement_03.png[]
|
||||
|
||||
* 게시판 세부기능 속성 변경이 가능하다.
|
||||
* '공지팝업 제한' 기능은 해당 게시판의 공지게시글을 일시적으로 제한할 수 있는 기능이다.
|
||||
|
||||
image::boardManagement_04.png[]
|
||||
|
||||
* 게시판의 검색 조건을 선택할 수 있다.
|
||||
* 게시글 목록화면에서 보여줄 정보를 선택 할수 있다.
|
||||
** 각 컬럼의 사용 여부에 따라서 넓이가 합산 100%로 변경된다.
|
||||
* 게시글 목록 정렬 기준을 선택 할 수 있으며 작성일 내림차순이 기본으로 선택된다.
|
||||
@@ -0,0 +1,29 @@
|
||||
= 공지사항 알림 뱃지
|
||||
|
||||
== 개요
|
||||
관리자가 작성한 공지사항이 등록되면 메인페이지에 알림이 보인다.
|
||||
|
||||
== Table
|
||||
* 게시판 : TN_CF_BOARD
|
||||
* 게시글 : TN_CF_POST
|
||||
* 게시판 분류 : TN_CF_BOARD_CLASSIFICATION
|
||||
* 사용자 : TN_CF_USER
|
||||
* 공지사항 확인 : TN_CF_NOTICE_CHECKED
|
||||
|
||||
== API
|
||||
.BoardController.java
|
||||
|
||||
. 공지사항 팝업 게시글 목록 조회(메인화면 용) +
|
||||
GET /notice-popup-posts +
|
||||
Query ID : selectNoticePopupPostList
|
||||
|
||||
. 공지사항 팝업 게시글 체크 +
|
||||
POST /check-notice +
|
||||
Query ID : selectNoticeChecked, insertNoticeChecked
|
||||
|
||||
== 화면
|
||||
* MainTopMenu.vue
|
||||
|
||||
image::notice_alarm.png[800,600]
|
||||
|
||||
image::notice.png[800,600]
|
||||
@@ -0,0 +1 @@
|
||||
= 공통 페이지
|
||||
@@ -0,0 +1,39 @@
|
||||
= 링크 사이트
|
||||
|
||||
== 개요
|
||||
링크 사이트를 관리한다.
|
||||
|
||||
== Table
|
||||
* 링크사이트 : TN_CF_LINK_SITE
|
||||
|
||||
== API
|
||||
.LinkSiteController.java
|
||||
|
||||
. 링크사이트 목록 조회(페이징) +
|
||||
GET /linksite/linksites-with-paging +
|
||||
Query ID : selectLinkSitePagingList
|
||||
|
||||
. 링크사이트 등록 +
|
||||
POST /linksite/linksites +
|
||||
Query ID : insertLinkSite
|
||||
* LINK_SITE_ID는 IdGenService를 사용하여 유니크한 ID를 만들어서 등록한다.
|
||||
|
||||
. 링크사이트 수정 +
|
||||
PUT /linksite/linksites +
|
||||
Query ID : updateLinkSite
|
||||
|
||||
. 링크사이트 삭제 +
|
||||
DELETE /linksite/linksites +
|
||||
Query ID : deleteLinkSite
|
||||
|
||||
== 화면
|
||||
|
||||
외부 링크사이트를 관리기능 > 기타관리 > 링크사이트 관리를 통해 할 수 있다.
|
||||
|
||||
image::front_05_01.png[]
|
||||
|
||||
* 구분 : 공통코드 type이 'LINKSITE'인 항목
|
||||
* 사이트명(기본) : 기본 사이트명을 입력한다.
|
||||
* 사이트명(locale) : 다국어별 사이트명을 입력한다.
|
||||
* 설명 : 사이트에 대한 설명을 입력한다.
|
||||
* URL : 사이트의 URL을 등록한다.
|
||||
@@ -0,0 +1,12 @@
|
||||
= 메인 페이지
|
||||
|
||||
== 구성
|
||||
* Header Section (TopMenu)
|
||||
** Navigation - My Menu(즐겨찾기) | navigation(결재함, 게시판, 샘플 페이지, 관리기능)
|
||||
** RightSide - 메인공지팝업 | 번역, 통합검색, 사이트맵 | 표준시간, 언어선택 | 프로필
|
||||
+
|
||||
NOTE: 통합검색의 경우 검색솔루션 등을 통해 따로 구현해야함.
|
||||
* MainContainer Section - 메인 컨텐츠(공지사항, FAQ, 연락처 등)
|
||||
* Footer Section - 개인정보취급방침, 이용약관
|
||||
|
||||
image::mainPage.png[]
|
||||
@@ -0,0 +1,24 @@
|
||||
= 업무 담당자 관리
|
||||
|
||||
== 개요
|
||||
주요 업무 담당자 페이지를 관리한다.
|
||||
|
||||
NOTE: 정적 HTML 로 만든 페이지를 업로드 하여 그대로 보여주고 싶을 때 사용한다. (업무 담당자 관리, 주요 연락처 관리)
|
||||
|
||||
== 화면
|
||||
image::front_06_01.png[]
|
||||
* 등록된 업무 담당자 페이지 목록을 볼 수 있다.
|
||||
* 목록의 Key 컬럼을 클릭하여 업무 담당자 페이지 정보를 수정할 수 있다.
|
||||
* 팝업 미리보기를 클릭하여 등록된 업무 담당자 페이지를 확인 할 수 있다.
|
||||
|
||||
image::front_06_02.png[]
|
||||
* Key: Unique한 키를 지정(영문, 숫자만 가능)
|
||||
* 제목: 업무 담당자 페이지 제목
|
||||
* 설명: 업무 담당자 페이지 설명
|
||||
* 첨부파일: 업무 담당자 HTML 페이지 (html, htm 확장자)
|
||||
|
||||
image::front_06_02_01.png[]
|
||||
* 메뉴관리에서 메뉴를 등록하고 Page/API 를 추가한다.
|
||||
- Page Path에 업무 담당자 관리에서 등록한 Key를 포함한다.
|
||||
- Vue Component는 SampleStaff.vue 를 참조하여 생성 후 등록한다.
|
||||
- API URL에 업무 담당자 조회 API URL을 등록한다. (/html-templates/group/{templateGroupCode=STAFF}/key/{templateKey})
|
||||
@@ -0,0 +1,23 @@
|
||||
= 일괄 작업 관리 및 이력 조회
|
||||
|
||||
== 일괄작업 관리
|
||||
* 일괄작업 관리를 위해서는 **구현 Service** (com.samsung..*Impl.*) 의 메서드에 `@BatchJob` annotation이 설정되어 있어야 한다.
|
||||
* 관리기능 > 일괄작업 관리> 일괄작업 관리 메뉴에서 아래와 같이 설정해야만 이력관리가 남게 된다.
|
||||
|
||||
image::batchJobMgmt.png[]
|
||||
|
||||
image::batchJobMgmtUpdate.png[]
|
||||
|
||||
* 일괄작업 관리 화면에서 관리하고자 하는 Batch 정보를 위와 같이 입력한다.
|
||||
** 구분 : 그룹코드 BATCHGUBUN 에 등록한 공통코드명을 입력한다.
|
||||
** 작업명 : Batch 작업명을 입력한다.
|
||||
** 작업클래스 : com.samsung.accesslog.impl.SysUseLogMngImpl.loadBatchData과 같이 Batch 실행시 실행되는 Package명을 포함한 클래스명 및 메소드명을 입력한다. (`@BatchJob` annotation에 설정한 값)
|
||||
** URL : Batch를 직접 실행하기 위한 URL을 입력한다. 여기에 입력한 URL은 일괄작업이력 화면에서 해당 배치실행 버튼을 클릭하였을때 Call 된다.
|
||||
URL을 입력할 경우에는 해당 Request를 처리할 Controller를 구현해야 한다.
|
||||
|
||||
== 일괄작업 이력
|
||||
일괄작업 관리에 등록한 작업이 수행되면 일괄작업 이력에 아래와 같이 나타난다.
|
||||
|
||||
image::batchJobLogList.png[]
|
||||
* 배치가 실행되는 시작 시간과 종료시간, 소요시간, 작업결과 등이 나타난다.
|
||||
* 작업이 실패 했을경우 실행 할 수 있는 배치실행 버튼이 나타난다.(일괄작업관리 등록시 URL을 등록해야 나타남).
|
||||
@@ -0,0 +1,52 @@
|
||||
= 작업 스케쥴링
|
||||
|
||||
== 개요
|
||||
SDL의 작업 스케쥴링은 http://www.quartz-scheduler.org/documentation/[Quartz]를 이용하여 구현하고 있다.
|
||||
수행할 작업(Job)을 등록하고 Trigger에 Job을 추가한 후 Scheduler에 Trigger(s)를 설정한다.
|
||||
|
||||
=== 스케줄러 설정 예
|
||||
.config.properties
|
||||
[source,properties]
|
||||
----
|
||||
batch.user.long-term-check.cron=0 10 00 * * ?
|
||||
----
|
||||
|
||||
.UserBatchConfig
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class UserBatchConfig {
|
||||
|
||||
@Value("${batch.user.long-term-check.cron}")
|
||||
private String batchUserLongTermCheckCron;
|
||||
|
||||
/**
|
||||
* 장기 미사용자 관리 Job
|
||||
*/
|
||||
@Bean
|
||||
public JobDetail batchUserLongTermCheckJob() {
|
||||
return JobBuilder
|
||||
.newJob(UserBatchExecutor.class) // <1>
|
||||
.withIdentity("batchUserLongTermCheck") // <2>
|
||||
.withDescription("User LongTerm Check Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 장기 미사용자 관리 Trigger
|
||||
*/
|
||||
@Bean
|
||||
public CronTriggerFactoryBean batchUserLongTermCheckTrigger() {
|
||||
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
|
||||
trigger.setJobDetail(batchUserLongTermCheckJob()); // <3>
|
||||
trigger.setCronExpression(batchUserLongTermCheckCron); // <4>
|
||||
return trigger;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
<1> 서비스를 수행할 Job Class
|
||||
<2> Job 구분 명
|
||||
<3> Job 등록
|
||||
<4> Cron 표현식 설정
|
||||
@@ -0,0 +1,22 @@
|
||||
= 주요 전화 번호 관리
|
||||
|
||||
== 개요
|
||||
주요 전화번호 페이지를 관리한다.
|
||||
|
||||
== 화면
|
||||
|
||||
image::front_06_03.png[]
|
||||
* 등록된 주요 전화번호 페이지 목록을 볼 수 있다.
|
||||
* 리스트의 Key 컬럼을 클릭하여 주요 전화번호 페이지 정보를 수정할 수 있다.
|
||||
* 팝업 미리보기를 클릭하여 등록된 주요 전화번호 페이지를 확인 할 수 있다.
|
||||
|
||||
image::front_06_04.png[]
|
||||
* Key : Unique한 키를 지정(영문, 숫자만 가능)
|
||||
* 제목 : 주요 전화번호 페이지 제목
|
||||
* 설명 : 주요 전화번호 페이지 설명
|
||||
* 첨부파일 : 주요 전화번호 HTML 파일 (html, htm 확장자)
|
||||
|
||||
[%hardbreaks]
|
||||
* 메뉴관리에서 메뉴를 등록하고 Page/API 를 추가한다.
|
||||
- Page Path에 주요 전화번호 관리에서 등록한 Key를 포함한다.
|
||||
- API URL에 주요 전화번호 조회 API URL을 등록한다. (/html-templates/group/{templateGroupCode=CONTACT}/key/{templateKey})
|
||||
@@ -0,0 +1,6 @@
|
||||
= 캐시 관리
|
||||
|
||||
== 개요
|
||||
시스템 전체 캐시를 초기화 한다.
|
||||
|
||||
image::cacheMgmt.png[캐시 관리]
|
||||
@@ -0,0 +1,76 @@
|
||||
= 코드 관리
|
||||
|
||||
== 개요
|
||||
그룹 코드를 등록하고 그룹 코드의 공통 코드를 등록하여 추가, 수정 및 삭제를 관리한다. +
|
||||
groupcodes는 그룹 코드를 나타내고, commcodes는 공통 코드를 나타낸다. 그룹 코드가 상위, 공통 코드가 그룹 코드의 하위 개념이다.
|
||||
|
||||
== Table
|
||||
* 그룹 코드 : TC_CF_COMM_CODE_TYPE
|
||||
* 공통 코드 : TC_CF_COMM_CODE
|
||||
|
||||
== API
|
||||
.CommCodeController.java
|
||||
|
||||
. 그룹 코드 목록 조회 +
|
||||
GET /commcode/groupcodes-with-paging +
|
||||
Query ID : selectGroupCodePagingList
|
||||
.. 검색조건에는 코드, 코드명, 설명이 있다. +
|
||||
"그룹 코드 목록 조회(페이징)"을 사용하며 각 조건에 맞게 쿼리를 실행한다. +
|
||||
특히 코드명은 한글, 영어, 중국어에 상관없이 입력한 값의 대소문자를 가리지 않고 1글자만 입력해도 검색이 된다.
|
||||
.. orderBy는 기본이 COMM_CODE_TYPE_CODE(코드명) 이다.
|
||||
|
||||
. 그룹 코드 등록 +
|
||||
POST /commcode/groupcodes
|
||||
** 그룹 코드는 20자 이내로 입력해야 한다. 영문자와 숫자, 특수문자만 사용할 수 있고 한글은 입력이 안 되게 정규식을 사용하여 프론트단에서 유효성 체크를 한다.
|
||||
|
||||
. 그룹 코드 삭제 +
|
||||
DELETE /commcode/groupcodes/{groupCode}
|
||||
.. 그룹 코드는 TC_CF_COMM_CODE_TYPE.DELETE_YN 컬럼 값을 true로 업데이트 하는 식으로 삭제한다.
|
||||
... 먼저 그룹 코드에 추가되어 있는 공통 코드의 하위를 전부 조회하여 찾은 후 공통 코드 전부 TC_CF_COMM_CODE.DELETE_YN 컬럼을 true로 업데이트 한다.
|
||||
.... Query ID : updateCommCodeDeleted
|
||||
... 그 후 그룹 코드의 DELETE_YN 컬럼을 true로 업데이트 하여 삭제한다.
|
||||
.... Query ID : updateGroupCode
|
||||
|
||||
. 공통 코드 추가 +
|
||||
POST /commcode/groupcodes/{groupCode}/commcodes
|
||||
** 추가시에 중복체크하고 없으면 등록하는데 TC_CF_COMM_CODE.CODE_ID는 IdGenService를 사용하여 유니크한 ID를 만들어서 등록한다.
|
||||
|
||||
. 공통 코드 삭제 +
|
||||
DELETE /commcode/groupcodes/{groupCode}/commcodes/{commCodeId}
|
||||
** 삭제하려는 공통 코드의 자신과 자식들 코드를 조회하여 DELETE_YN 컬럼 값을 true로 업데이트 하는 식으로 삭제한다.
|
||||
*** Query ID : updateCommCodeDeleted
|
||||
|
||||
== 화면
|
||||
그룹 코드 및 그룹코드에 대한 공통 코드를 추가, 수정 및 삭제 기능을 수행하여 관리한다. +
|
||||
|
||||
image::commonCodeList.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 등록 : 코드 정보를 등록하는 화면으로 이동
|
||||
|
||||
== 코드 등록
|
||||
그룹 코드를 등록 +
|
||||
* 영문자와 숫자, '-', '_' 2개의 특수문자만 사용하여 등록 가능하다.
|
||||
|
||||
image::commonCodeDetail_Reg.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 목록 : 코드 관리 화면으로 이동
|
||||
- 저장 : 그룹 코드 정보를 저장
|
||||
|
||||
== 코드 상세 정보
|
||||
그룹 코드의 상세 정보를 수정, 삭제하고 그에 대한 공통 코드를 추가, 수정, 삭제하여 관리
|
||||
|
||||
image::commonCodeDetail.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
==== 그룹코드 정보
|
||||
- 엑셀다운로드 : 조회 조건과 동일한 조회 결과 값을 담은 엑셀 파일을 다운로드 하는 기능
|
||||
- 목록 : 코드 관리 화면으로 이동
|
||||
- 삭제 : 그룹코드를 삭제
|
||||
- 저장 : 수정된 그룹코드 상세 정보를 수정
|
||||
|
||||
==== 공통코드 목록
|
||||
- 추가 : 공통코드를 등록하는 Popup 호출
|
||||
- 수정 : 공통코드를 수정하는 Popup 호출
|
||||
- 삭제 : 공통코드를 삭제
|
||||
@@ -0,0 +1,15 @@
|
||||
= 이력 관리
|
||||
|
||||
include::이력관리/시스템로깅.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리/SQLLogging.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리/로거관리.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리/메뉴활용도.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리/메뉴사용이력.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리/파일다운로드이력.adoc[leveloffset=+1]
|
||||
|
||||
include::이력관리/로그인이력.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1 @@
|
||||
= SQL Logging
|
||||
@@ -0,0 +1,9 @@
|
||||
= 로거(Logger) 관리
|
||||
|
||||
== 개요
|
||||
시스템 Logger 내역을 조회 및 log level을 변경할 수 있다.
|
||||
|
||||
image::loggerMgmt.png[로거 관리]
|
||||
|
||||
<1> Logger name별 설정된 configuration 내역(log4j2.xml)을 조회할 수 있다.
|
||||
<2> Logger name별로 log level 을 변경할 수 있다.
|
||||
@@ -0,0 +1,70 @@
|
||||
= 로그인 이력
|
||||
|
||||
== 개요
|
||||
사용자가 시스템에 로그인한 이력을 조회
|
||||
|
||||
== Table
|
||||
* 로그인 이력 : TN_CF_LOGIN_OUT
|
||||
* 사용자 : TN_CF_USER
|
||||
|
||||
== API
|
||||
.HistoryController.java
|
||||
|
||||
. 로그인 이력 목록 조회 +
|
||||
GET /history/login-out-logs
|
||||
Query ID : selectListLoginOut
|
||||
|
||||
== 화면
|
||||
사용자가 시스템에 로그인한 이력을 조회
|
||||
|
||||
image::loginHistory.png[]
|
||||
|
||||
== LoginOutLogInterceptor
|
||||
|
||||
시스템에 사용자가 로그인/아웃을 하면 LoginOutLogInterceptor에 의해 사용자 정보가 저장된다.
|
||||
|
||||
LoginOutLogInterceptor는 LoginInterceptor의 구현클래스로 LoginInterceptor의 자세한 설명은 <<_로그인, 로그인>>의 **로그인/아웃 전,후 처리**를 참고한다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Override
|
||||
public void postLogin(HttpServletRequest request, HttpServletResponse response, User user) {
|
||||
LoginOut loginOut = new LoginOut();
|
||||
|
||||
if (StringUtils.isNotEmpty(user.getJwt())) {
|
||||
loginOut.setToken(user.getJwt().substring(user.getJwt().lastIndexOf('.') + 1));
|
||||
}
|
||||
loginOut.setUserId(user.getUserId());
|
||||
loginOut.setLoginTime(new Date());
|
||||
loginOut.setUseIp(user.getRecentLoginIp());
|
||||
loginOut.setReqType("WEB");
|
||||
|
||||
loginOutLogService.insertLogin(loginOut);
|
||||
}
|
||||
----
|
||||
|
||||
로그인 시 사용자 ID, 로그인 시간, IP 등을 TN_CF_LOGIN_OUT 테이블에 저장한다.
|
||||
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Override
|
||||
public void preLogout(HttpServletRequest request, HttpServletResponse response, User user) {
|
||||
LoginOut loginOut = new LoginOut();
|
||||
|
||||
if (StringUtils.isNotEmpty(user.getJwt())) {
|
||||
loginOut.setToken(user.getJwt().substring(user.getJwt().lastIndexOf('.') + 1));
|
||||
}
|
||||
loginOut.setUserId(user.getUserId());
|
||||
loginOut.setLoginTime(user.getRecentLoginDatetime());
|
||||
loginOut.setLogoutTime(new Date());
|
||||
loginOut.setUseIp(webUtil.getClientIp(request));
|
||||
loginOut.setReqType("WEB");
|
||||
|
||||
loginOutLogService.updateLogout(loginOut);
|
||||
|
||||
userService.updateUserJwt(Map.of("userId", user.getUserId(), "lastLogoutDate", new Date(), "jwt", ""));
|
||||
}
|
||||
----
|
||||
로그아웃 전에는 TN_CF_LOGIN_OUT에 정보를 저장하는 것 외에 사용자의 jwt 값을 초기화 한다.
|
||||
중복 로그인 방지를 위해 jwt값을 이용한다.
|
||||
@@ -0,0 +1,24 @@
|
||||
= 메뉴 사용 이력
|
||||
|
||||
== 개요
|
||||
시스템에 접속한 사용자의 메뉴 사용 이력을 조회
|
||||
|
||||
== Table
|
||||
* 메뉴 사용 이력 : TN_CF_MENU_USE_HISTORY
|
||||
|
||||
== API
|
||||
.HistoryController.java
|
||||
|
||||
. 일별 메뉴 사용 이력 조회 +
|
||||
GET /history/menu-use-by-date +
|
||||
Query ID : selectMenuUseHistoryByDatePagingList
|
||||
|
||||
. 월별 메뉴 사용 이력 조회 +
|
||||
GET /history/menu-use-by-month +
|
||||
Query ID : selectMenuUseHistoryByMonthPagingList
|
||||
|
||||
== 화면
|
||||
image::menuUserHistoty.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 엑셀다운로드 : 조회 조건과 동일한 조회 결과 값을 담은 엑셀 파일을 다운로드 하는 기능
|
||||
@@ -0,0 +1,22 @@
|
||||
= 메뉴 활용도
|
||||
|
||||
== 개요
|
||||
메뉴 별로 메뉴 사용 이력을 분석해서 월별, 일별로 활용률을 계산한다.
|
||||
|
||||
== Table
|
||||
* 공통코드 : TC_CF_COMM_CODE
|
||||
* 메뉴사용주기 : TC_CF_MENU_USE_PERIOD
|
||||
* 메뉴 : TN_CF_MENU
|
||||
* 메뉴활용도 집계 : TS_CF_MENU_USE_MM
|
||||
|
||||
== API
|
||||
.MenuUtilizationController.java
|
||||
|
||||
. 메뉴 활용도 목록 조회 +
|
||||
GET /menuutilization/menu-utilizations +
|
||||
Query ID : selectMenuUtilizationList
|
||||
|
||||
== 화면
|
||||
메뉴에 대한 사용 주기, Hit, 활용도를 조회
|
||||
|
||||
image::menuUtilHistory.png[]
|
||||
@@ -0,0 +1,22 @@
|
||||
= 시스템 로깅
|
||||
|
||||
== 개요
|
||||
로그인/아웃, 시스템 접속, 파일다운로드 등의 시스템 로그를 남기고 있다.
|
||||
|
||||
=== Login/out 로그
|
||||
로그인 후와 로그아웃 전에 LoginOutLogInterceptor를 통해 로그를 남긴다.
|
||||
|
||||
NOTE: 로그인/아웃 전후에 추가할 로직이 있다면 LoginInterceptor 인터페이스를 상속받아 구현한다.
|
||||
|
||||
관련 테이블:: TN_CF_LOGIN_OUT
|
||||
|
||||
=== Access 로그
|
||||
시스템 접속시 LoggingInterceptor를 통해 AccessLog를 남겨 db(또는 file)에 저장한다. +
|
||||
일배치로 메뉴사용이력 및 메뉴활용도 데이터를 집계한다.
|
||||
|
||||
관련 테이블:: TN_CF_SYS_USE_LOG
|
||||
|
||||
=== File Download 로그
|
||||
첨부파일, 엑셀파일 등을 다운로드시 fileManagerService의 saveFileDownloadLog 메서드를 호출하여 파일다운로드 로그를 남긴다.
|
||||
|
||||
관련 테이블:: TN_CF_SYS_USE_LOG
|
||||
@@ -0,0 +1,24 @@
|
||||
= 파일 다운로드 이력
|
||||
|
||||
== 개요
|
||||
사용자가 엑셀 다운로드 하거나 이미지 파일이 있는 게시판을 이용했거나 첨부파일을 다운로드 한 목록을 보여준다.
|
||||
|
||||
== Table
|
||||
* 시스템 사용 로그 : TN_CF_SYS_USE_LOG
|
||||
* 사용자 : TN_CF_USER
|
||||
|
||||
== API
|
||||
.HistoryController.java
|
||||
|
||||
. 파일 다운로드 이력 목록 조회 +
|
||||
GET /history/file-download-logs +
|
||||
Query ID : selectFileDownloadLogPagingList
|
||||
* 파일 다운로드 이력의 LOG_FLAG는 TN_CF_SYS_USE_LOG 테이블 LOG_FLAG 컬럼의 '5' 이다.
|
||||
|
||||
== 화면
|
||||
엑셀 다운로드 기능이 있는 특정 화면에서 엑셀 다운로드 실행에 대한 이력을 조회.
|
||||
|
||||
image::fileDownloadHistory.png[]
|
||||
|
||||
=== 기능별 설명
|
||||
- 엑셀다운로드 : 조회 조건과 동일한 조회 결과 값을 담은 엑셀 파일을 다운로드 하는 기능
|
||||
@@ -0,0 +1,5 @@
|
||||
= 파일서비스
|
||||
|
||||
include::파일서비스/엑셀다운로드및업로드.adoc[leveloffset=+1]
|
||||
|
||||
include::파일서비스/파일업다운로드.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,99 @@
|
||||
= 엑셀 다운로드 및 업로드
|
||||
|
||||
== 개요
|
||||
|
||||
SDL 6.0은 사용자 관리, 외부 사용자 관리, 역할 관리, 메뉴 사용 이력, 파일 다운로드 이력, 코드 관리, 링크사이트 관리에서 엑셀 다운로드를 지원한다. +
|
||||
그 외 메뉴에서 엑셀 다운로드 기능을 적용하려면 <<_공통컴포넌트_유틸,공통컴포넌트 & 유틸>>의 <<_excel_download_button,Excel Download Button>>, <<_excel_upload_button,Excel Upload Button>> 매뉴얼을 참고한다.
|
||||
|
||||
== API
|
||||
.ExcelController.java
|
||||
|
||||
. 엑셀 다운로드 +
|
||||
GET /excel/excel-download +
|
||||
|
||||
. 엑셀 업로드 +
|
||||
POST /excel/excel-upload
|
||||
|
||||
== 엑셀 다운로드
|
||||
|
||||
=== excel.xml 설정
|
||||
sdl-base/src/main/resources/excel 폴더에 다운로드 할 기능의 xml 양식을 만든다. +
|
||||
다국어 적용(message.properties)이 된다.
|
||||
|
||||
. 대외비 표기여부, 시트보호 여부, 제목
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<CONFIDENTIAL>true</CONFIDENTIAL> <!--> 대외비 표기여부(false : 태그작성 x) <-->
|
||||
<PROTECTION>true</PROTECTION> <!--> 시트보호 여부(false : 태그작성 x) <-->
|
||||
<PASSWORD>sdl</PASSWORD> <!--> 시트보호 암호(PROTECTION = false : 태그작성 x) <-->
|
||||
<TITLE>sdl.excel.user.title</TITLE> <!--> 제목 <-->
|
||||
----
|
||||
|
||||
[start=2]
|
||||
. 문서 Comment 출력
|
||||
.. 코멘트를 여러개 작성 가능
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<COMMENTS>
|
||||
<COMMENT>
|
||||
<COMMENT_LABEL>sdl.excel.accessLog.comment</COMMENT_LABEL> <!--> 코멘트 <-->
|
||||
<COMMENT_FONT_COLOR>10</COMMENT_FONT_COLOR> <!--> 코멘트 글자색 <-->
|
||||
</COMMENT>
|
||||
<COMMENT>
|
||||
<COMMENT_LABEL>2번째 코멘트</COMMENT_LABEL>
|
||||
<COMMENT_FONT_COLOR>10</COMMENT_FONT_COLOR>
|
||||
</COMMENT>
|
||||
</COMMENTS>
|
||||
----
|
||||
|
||||
[start=3]
|
||||
. 헤더
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<HEADERS>
|
||||
<HEADER>
|
||||
<HEADER_LABEL>NO.</HEADER_LABEL> <!--> 컬럼명 <-->
|
||||
<ROWSPAN>3</ROWSPAN> <!--> 열병합 <-->
|
||||
<HEADER_FONT_COLOR>8</HEADER_FONT_COLOR> <!--> 컬럼 글자색 <-->
|
||||
<HEADER_BACKGROUND_COLOR>44</HEADER_BACKGROUND_COLOR> <!--> 컬럼 배경색 <-->
|
||||
</HEADER>
|
||||
<HEADER>
|
||||
<HEADER_LABEL>sdp.user.label.compName</HEADER_LABEL>
|
||||
<COLSPAN>3</COLSPAN> <!--> 행병합 <-->
|
||||
</HEADER>
|
||||
</HEADERS>
|
||||
----
|
||||
|
||||
[start=4]
|
||||
. 컬럼
|
||||
.. 첫번째 Row 에 No. 필드 넣을 경우 No. 필드에 대한 <COLUMN>를 작성하지 않아도 된다.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<COLUMNS>
|
||||
<COLUMN>
|
||||
<FIELD_NAME>compName</FIELD_NAME> <!--> HEADER_LABEL에 대응하는 엔티티명 <-->
|
||||
<COLUMN_WIDTH>15</COLUMN_WIDTH> <!--> 컬럼 너비 <-->
|
||||
<CELL_ALIGN>LEFT</CELL_ALIGN> <!--> 셀 정렬 <-->
|
||||
<COLUMN_LOCKING>true</COLUMN_LOCKING> <!--> 셀 잠금 여부(false : 태그작성 x) <-->
|
||||
<COLUMN_HIDDEN>true</COLUMN_HIDDEN> <!--> 셀 숨김 여부(false : 태그작성 x) <-->
|
||||
<COLUMN_TYPE>Number</COLUMN_TYPE> <!--> 날짜 형식(Date), 숫자 형식(Number)으로 출력 <-->
|
||||
<CELL_FONT_COLOR>10</CELL_FONT_COLOR> <!--> 컬럼 글자색 <-->
|
||||
<BACKGROUND_COLOR>13</BACKGROUND_COLOR> <!--> 컬럼 배경색 <-->
|
||||
</COLUMN>
|
||||
</COLUMNS>
|
||||
----
|
||||
IMPORTANT: 간혹 다운로드 받은 엑셀에 초록색 경고가 뜨는데 이것은 DB에 문자로 저장된 숫자를 불러오기 때문이다. +
|
||||
따라서 <COLUMN_TYPE>을 'Number'로 하면 엑셀에서도 숫자로 잘 나올 것이다.
|
||||
|
||||
== 엑셀 업로드
|
||||
|
||||
=== excelUploadSample.xml 설정
|
||||
SampleExcelUpload.vue 파일에 샘플로 엑셀 업로드가 구현되어 있다.
|
||||
|
||||
* 업로드할 데이터의 양식에 맞추어 excel.xml을 만들고 업로드 한다.
|
||||
** 업로드 excel.xml 파일은 다운로드 excel.xml 파일과 같은 경로에 만든다.
|
||||
** 날짜 형식 데이터가 업로드되지 않는 경우 `yyyy-MM-dd` 형태로 입력하거나 텍스트 서식으로 입력한다.
|
||||
@@ -0,0 +1,211 @@
|
||||
= 파일 업/다운로드
|
||||
|
||||
== 개요
|
||||
클라이언트의 파일 업/다운로드 API 호출을 처리한다.
|
||||
|
||||
=== 파일 업로드
|
||||
단일 파일 업로드를 처리한다. 하나의 파일만 업로드하는 경우가 아니라면 보통은 멀티 파일 업로드를 이용한다.
|
||||
|
||||
=== 멀티 파일 업로드
|
||||
|
||||
====
|
||||
컨트롤러에서 파일 업로드 서비스를 호출한다.
|
||||
|
||||
.FileManagerController
|
||||
[source,java]
|
||||
----
|
||||
@PostMapping("/resource/attachments/multifile-upload")
|
||||
public List<AttachFile> uploadMultiFile(
|
||||
@Parameter(description = "MultipartFile[]", required = true) @RequestParam(required = true) MultipartFile[] files,
|
||||
@Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType) {
|
||||
|
||||
return fileManagerService.save(downloadType, Arrays.asList(files));
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
====
|
||||
서비스에서 지정된 경로에 파일 리소스를 저장하고, 업로드된 파일 정보를 DB 에 저장한다.
|
||||
|
||||
.FileManagerServiceImpl
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
* 기본 업로드 패스 설정 값
|
||||
*/
|
||||
@Value("${common.upload-path}")
|
||||
private String fileUploadPath;
|
||||
|
||||
/**
|
||||
* 업로드 루트 하위 폴더 자릿수 설정 값
|
||||
*/
|
||||
@Value("${common.upload.directory-name-len}")
|
||||
private int directoryNameLen;
|
||||
|
||||
/**
|
||||
* 사용자지정 업로드 패스 설정 여부
|
||||
*/
|
||||
@Value("${custom.upload-path.enabled}")
|
||||
private boolean customUploadPathEnabled;
|
||||
|
||||
/**
|
||||
* 사용자지정 업로드 패스 설정 값
|
||||
*/
|
||||
@Value("${custom.upload-path}")
|
||||
private String customUploadPaths;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<AttachFile> save(String downloadType, List<MultipartFile> files) {
|
||||
return this.save(downloadType, null, files);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<AttachFile> save(String downloadType, String refId, List<MultipartFile> files) {
|
||||
List<AttachFile> attachFileList = new ArrayList<>();
|
||||
|
||||
files.stream().forEach(file -> {
|
||||
AttachFile attachFile = this.save(downloadType, refId, file);
|
||||
attachFileList.add(attachFile);
|
||||
});
|
||||
|
||||
return attachFileList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AttachFile save(String downloadType, String refId, MultipartFile file) {
|
||||
String fileId = this.store(file, downloadType); // <1>
|
||||
|
||||
AttachFile attachFile = new AttachFile();
|
||||
attachFile.setDownloadType(downloadType);
|
||||
attachFile.setFilePathName(this.rootUploadPath.toString());
|
||||
attachFile.setFileExtensionName(fileId);
|
||||
attachFile.setFileName(file.getOriginalFilename());
|
||||
attachFile.setFileMimeTypeName(file.getContentType());
|
||||
attachFile.setFileSize(file.getSize());
|
||||
attachFile.setOwnerObjectPkId(refId);
|
||||
|
||||
return fileManagerDao.insertFileInfo(attachFile); // <2>
|
||||
}
|
||||
----
|
||||
<1> 파일 리소스 저장
|
||||
<2> 업로드된 파일 정보 DB저장
|
||||
====
|
||||
|
||||
====
|
||||
파일 리소스 저장 경로는 기본 업로드 패스 값(common.upload-path)과 하위 디렉토리 길이 설정 값(common.upload.directory-name-len)에 따라 결정된다. +
|
||||
기본 업로드 패스외에 사용자정의 업로드 패스 설정도 가능하다.
|
||||
|
||||
.config.properties
|
||||
[source,properties]
|
||||
----
|
||||
## File Attach Configuration
|
||||
common.upload-path=/NAS/SDL/upload // <1>
|
||||
common.upload.directory-name-len=2 // <2>
|
||||
# custom upload path 설정
|
||||
custom.upload-path.enabled=false // <3>
|
||||
custom.upload-path=\ // <4>
|
||||
notice=/NAS/SDL/upload/notice,\
|
||||
faq=/NAS/SDL/upload/faq
|
||||
----
|
||||
<1> 기본 업로드 패스 설정
|
||||
<2> 업로드 패스 하위 디렉토리 길이 +
|
||||
파일명(UUID)에서 이 길이 만큼 잘라서 기본 업로드 패스 하위 디렉토리가 생성된다.
|
||||
<3> 사용자정의 업로드 패스 사용 여부
|
||||
<4> 파일 컴포넌트별 사용자정의 업로드 패스 설정 (custom.upload-path.enabled=true 일 경우 적용됨)
|
||||
====
|
||||
|
||||
=== 파일 다운로드
|
||||
컨트롤러에서 파일 다운로드 서비스를 호출한다.
|
||||
|
||||
.FileManagerController
|
||||
[source,java]
|
||||
----
|
||||
@GetMapping("/resource/attachments/file-download/{fileId}")
|
||||
public ResponseEntity<Resource> downloadFile(
|
||||
@Parameter(description = "File ID", required = true) @PathVariable String fileId,
|
||||
@Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType,
|
||||
HttpServletRequest request) {
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
if (StringUtils.isBlank(fileId)) {
|
||||
throw new FileManagerException("No File ID");
|
||||
}
|
||||
|
||||
AttachFile attachFile = fileManagerService.getAttachFile(fileId, downloadType); // <1>
|
||||
if (attachFile == null) {
|
||||
throw new FileManagerException("Cannot find file info: " + fileId);
|
||||
}
|
||||
Resource resource = fileManagerService.getResource(attachFile.getFileExtensionName(), attachFile.getFilePathName()); // <2>
|
||||
|
||||
String contentType = attachFile.getFileMimeTypeName();
|
||||
if (StringUtils.isBlank(contentType)) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
}
|
||||
|
||||
String fileName = attachFile.getFileName(); // <3>
|
||||
String encodeFileName = null;
|
||||
// 다운로드 파일명 UTF-8 인코딩
|
||||
encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
|
||||
// 다운로드 이력 로깅
|
||||
fileManagerService.saveFileDownloadLog(fileName, startTime, request); // <4>
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header("Content-Transfer-Encoding", "binary")
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodeFileName + "\"")
|
||||
.body(resource);
|
||||
}
|
||||
----
|
||||
<1> 파일 정보 조회
|
||||
<2> 파일 리소스 가져오기
|
||||
<3> 파일 업로드 시점의 파일명
|
||||
<4> 파일 다운로드 이력 로깅
|
||||
|
||||
=== 멀티 파일 다운로드
|
||||
여러 파일을 zip 파일 형태로 다운로드할 수 있도록 제공한다.
|
||||
|
||||
.FileManagerController
|
||||
[source,java]
|
||||
----
|
||||
@Value("${common.download.zipfilename}")
|
||||
private String zipFileName;
|
||||
|
||||
@GetMapping("/resource/attachments/multifile-download")
|
||||
public ResponseEntity<ByteArrayResource> downloadMultiFile(
|
||||
@Parameter(description = "File IDs", required = true) @RequestParam(required = true) String[] fileIds,
|
||||
@Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType,
|
||||
@Parameter(description = "zip 파일명", required = false) @RequestParam(required = false) String zipFileName,
|
||||
HttpServletRequest request) {
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
if (fileIds == null || fileIds.length == 0) {
|
||||
throw new FileManagerException("No File ID(s)");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(zipFileName)) {
|
||||
zipFileName = this.zipFileName;
|
||||
}
|
||||
|
||||
// 다운로드 파일명 UTF-8 인코딩
|
||||
String fileName = null;
|
||||
fileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
|
||||
// 다운로드 이력 로깅
|
||||
fileManagerService.saveFileDownloadLog(zipFileName, startTime, request);
|
||||
|
||||
ByteArrayResource baResource = fileManagerService.getByteArrayResource(fileIds, downloadType);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentLength(baResource.contentLength())
|
||||
.contentType(MediaType.parseMediaType("application/zip"))
|
||||
.header("Content-Transfer-Encoding", "binary")
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + ".zip\";")
|
||||
.body(baResource);
|
||||
}
|
||||
----
|
||||
Reference in New Issue
Block a user