Initial commit

This commit is contained in:
2026-05-29 17:49:25 +09:00
commit 330105cb27
1081 changed files with 148694 additions and 0 deletions
@@ -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>> 을 참고한다.