초기 셋팅

dev
박성영 5 months ago
parent 098845d875
commit 8d58c91b9e

@ -0,0 +1,23 @@
create table tb_cd_detail
(
CD_GROUP_ID varchar(20) not null comment '코드 그룹 ID',
CD_ID varchar(20) not null comment '코드 ID',
CD_NM varchar(100) not null comment '코드 이름',
CD_DC varchar(200) null comment '코드 설명',
SORT_ORDR int default 0 null comment '정렬 순서',
USE_YN varchar(1) not null comment '사용 여부',
ATTRIBUTE1 varchar(200) null comment '속성1',
ATTRIBUTE2 varchar(200) null comment '속성2',
ATTRIBUTE3 varchar(200) null comment '속성3',
ATTRIBUTE4 varchar(200) null comment '속성4',
ATTRIBUTE5 varchar(200) null comment '속성5',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
MDFCN_DTTM datetime null comment '수정 일시',
MDFR varchar(20) null comment '수정자',
primary key (CD_GROUP_ID, CD_ID),
constraint fk_cd_detail_group
foreign key (CD_GROUP_ID) references tb_cd_group (CD_GROUP_ID)
)
comment '코드 상세';

@ -0,0 +1,14 @@
create table tb_cd_group
(
CD_GROUP_ID varchar(20) not null comment '코드 그룹 ID'
primary key,
CD_GROUP_NM varchar(100) not null comment '코드 그룹 이름',
CD_GROUP_DC varchar(200) null comment '코드 그룹 설명',
USE_YN varchar(1) not null comment '사용 여부',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
MDFCN_DTTM datetime null comment '수정 일시',
MDFR varchar(20) null comment '수정자'
)
comment '코드 그룹';

@ -0,0 +1,15 @@
create table tb_group
(
GROUP_ID varchar(20) not null comment '그룹 ID'
primary key,
GROUP_NM varchar(100) not null comment '그룹 이름',
GROUP_DC varchar(200) null comment '그룹 설명',
SORT_ORDR int default 0 null comment '정렬 순서',
USE_YN varchar(1) not null comment '사용 여부',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
MDFCN_DTTM datetime null comment '수정 일시',
MDFR varchar(20) null comment '수정자'
)
comment '그룹';

@ -0,0 +1,14 @@
create table tb_group_role
(
GROUP_ID varchar(20) not null comment '그룹 ID',
ROLE_ID varchar(20) not null comment '역할 ID',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
primary key (GROUP_ID, ROLE_ID),
constraint fk_group_role_group
foreign key (GROUP_ID) references tb_group (GROUP_ID),
constraint fk_group_role_role
foreign key (ROLE_ID) references tb_role (ROLE_ID)
)
comment '그룹-역할 매핑';

@ -0,0 +1,30 @@
-- 로그인 로그 ID 시퀀스 생성
CREATE SEQUENCE IF NOT EXISTS seq_login_log_id START WITH 1 INCREMENT BY 1;
create table tb_login_log
(
LOG_ID BIGINT auto_increment not null comment '사용자 ID'
primary key,
USER_ID varchar(20) null comment '사용자 ID',
USER_ACNT varchar(20) null comment '사용자 계정',
LOGIN_DTTM datetime not null comment '로그인 시간',
IP_ADDR varchar(50) null comment 'IP 주소',
SUCCESS_YN varchar(1) not null comment '성공 여부',
FAIL_REASON varchar(100) null comment '실패 사유',
LOGIN_TYPE varchar(20) default 'WEB' null comment '로그인 유형',
SESSION_ID varchar(100) null comment '세션 ID',
USER_AGENT varchar(500) null comment '사용자 에이전트',
DEVICE_INFO varchar(100) null comment '접속 디바이스 정보',
REG_DTTM datetime default current_timestamp() null comment '등록 일시'
)
comment '로그인 로그';
create index IDX_TB_LOGIN_LOG_LOGIN_DTTM
on tb_login_log (LOGIN_DTTM);
create index IDX_TB_LOGIN_LOG_SUCCESS_YN
on tb_login_log (SUCCESS_YN);
create index IDX_TB_LOGIN_LOG_USER_ID
on tb_login_log (USER_ID);

@ -0,0 +1,23 @@
create table tb_menu
(
MENU_ID varchar(20) not null comment '메뉴 ID'
primary key,
MENU_NM varchar(100) not null comment '메뉴 이름',
MENU_DC varchar(200) null comment '메뉴 설명',
UPPER_MENU_ID varchar(20) null comment '상위 메뉴 ID',
MENU_LEVEL int not null comment '메뉴 레벨',
SORT_ORDR int not null comment '정렬 순서',
MENU_URL varchar(200) null comment '메뉴 URL',
URL_PATTERN varchar(2000) null comment 'URL 패턴',
MENU_ICON varchar(100) null comment '메뉴 아이콘',
USE_YN varchar(1) not null comment '사용 여부',
VIEW_YN varchar(1) default 'Y' not null comment '화면 표시 여부',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
MDFCN_DTTM datetime null comment '수정 일시',
MDFR varchar(20) null comment '수정자',
constraint fk_menu_upper
foreign key (UPPER_MENU_ID) references tb_menu (MENU_ID)
)
comment '메뉴';

@ -0,0 +1,15 @@
create table tb_role
(
ROLE_ID varchar(20) not null comment '역할 ID'
primary key,
ROLE_NM varchar(100) not null comment '역할 이름',
ROLE_DC varchar(200) null comment '역할 설명',
SORT_ORDR int default 0 null comment '정렬 순서',
USE_YN varchar(1) not null comment '사용 여부',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
MDFCN_DTTM datetime null comment '수정 일시',
MDFR varchar(20) null comment '수정자'
)
comment '역할';

@ -0,0 +1,14 @@
create table tb_role_menu
(
ROLE_ID varchar(20) not null comment '역할 ID',
MENU_ID varchar(20) not null comment '메뉴 ID',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
primary key (ROLE_ID, MENU_ID),
constraint fk_role_menu_menu
foreign key (MENU_ID) references tb_menu (MENU_ID),
constraint fk_role_menu_role
foreign key (ROLE_ID) references tb_role (ROLE_ID)
)
comment '역할-메뉴 매핑';

@ -0,0 +1,44 @@
create table tb_user
(
USER_ID BIGINT auto_increment not null comment '사용자 ID'
primary key,
USER_ACNT varchar(20) not null comment '사용자 계정',
USER_NM varchar(50) not null comment '사용자 이름',
PASSWD varchar(200) not null comment '비밀번호',
PASSWD_HINT varchar(100) null comment '비밀번호 힌트',
PASSWD_NSR varchar(100) null comment '비밀번호 힌트 답',
EMP_NO varchar(20) null comment '사원 번호',
GENDER varchar(1) null comment '성별',
ZIP varchar(6) null comment '우편번호',
ADDR varchar(150) null comment '주소',
DADDR varchar(150) null comment '상세주소',
AREA_NO varchar(10) null comment '지역 번호',
EML_ADDR varchar(50) null comment '이메일 주소',
ORG_CD varchar(20) null comment '조직 CD',
USER_GROUP_ID varchar(20) null comment '그룹 ID',
NSTT_CD varchar(8) not null comment '소속기관 코드',
POS_NM varchar(60) null comment '직위 이름',
CRTFC_DN varchar(20) null comment '인증 DN값',
USER_STATUS_CD varchar(20) not null comment '사용자 상태',
FXNO varchar(20) null comment '팩스번호',
TELNO varchar(20) null comment '전화번호',
MBL_TELNO varchar(20) null comment '휴대 전화번호',
BRDT varchar(20) null comment '생년월일',
DEPT_CD varchar(7) null comment '부서 코드',
USE_YN varchar(1) not null comment '사용 여부',
RSDNT_NO varchar(200) null comment '주민등록 번호',
PASSWD_INIT_YN varchar(1) default 'N' null comment '비밀번호 초기화 여부',
LOCK_YN varchar(1) null comment '잠김 여부',
LOCK_CNT int not null comment '잠김 횟수',
LOCK_DTTM datetime null comment '잠김 일시',
REG_DTTM datetime null comment '등록 일시',
RGTR varchar(20) null comment '등록자',
MDFCN_DTTM datetime null comment '수정 일시',
MDFR varchar(20) null comment '수정자',
constraint tb_user_unique
unique (USER_ACNT),
constraint fk_user_group
foreign key (USER_GROUP_ID) references tb_group (GROUP_ID)
)
comment '사용자';

@ -0,0 +1,19 @@
create table tb_user_session
(
SESSION_ID varchar(100) not null comment '세션 ID'
primary key,
USER_ID varchar(20) not null comment '사용자 ID',
USER_ACNT varchar(20) null comment '사용자 계정',
LOGIN_DTTM datetime default current_timestamp() not null comment '로그인 시간',
LAST_ACCESS_DTTM datetime default current_timestamp() not null comment '마지막 접속 시간',
IP_ADDR varchar(50) null comment 'IP 주소',
USER_AGENT varchar(500) null comment '사용자 에이전트'
)
comment '사용자 세션 정보';
create index IDX_TB_USER_SESSION_LOGIN_DTTM
on tb_user_session (LOGIN_DTTM);
create index IDX_TB_USER_SESSION_USER_ID
on tb_user_session (USER_ID);

@ -0,0 +1,320 @@
# 통합 데이터베이스 컬럼 단어사전
## 한글 → 영문 약어 변환표
| 한글 | 영문 약어 | 전체 영문 | 의미 |
|------|-----------|-----------|------|
| 접속 | ACCESS | ACCESS | 접속 |
| 계정 | ACNT | ACCOUNT | 계정 |
| 주소 | ADDR | ADDRESS | 주소 |
| 에이전트 | AGENT | AGENT | 에이전트 |
| 전체 | ALL | ALL | 전체, 모든 |
| 답변 | NSR | ANSWER | 답변 |
| 보관 | ARCHIVE | ARCHIVE | 보관, 아카이브 |
| 지역 | AREA | AREA | 지역 |
| 속성 | ATTRIBUTE | ATTRIBUTE | 속성 |
| 배치 | BATCH | BATCH | 배치 |
| 게시판 | BBS | BBS | 게시판 (Bulletin Board System) |
| 생년월일 | BRDT | BIRTH_DATE | 생년월일 |
| 달력 | CALENDAR | CALENDAR | 달력 |
| 범주 | CATEGORY | CATEGORY | 범주, 카테고리 |
| 코드 | CD | CODE | 코드 |
| 클래스 | CLASS | CLASS | 클래스 |
| 컬럼 | CLMN | COLUMN | 컬럼 |
| 개수 | CNT | COUNT | 개수, 수량 |
| 댓글 | COMMENT | COMMENT | 댓글 |
| 완료 | COMPLETED | COMPLETED | 완료된 |
| 설정 | CONFIG | CONFIG | 설정 |
| 내용 | CONTENT | CONTENT | 내용 |
| 손상 | CORRUPTED | CORRUPTED | 손상된 |
| 크론 | CRON | CRON | 크론 (스케줄링) |
| 인증 | CRTFC | CERTIFICATE | 인증 |
| 상세주소 | DADDR | DETAIL_ADDRESS | 상세주소 |
| 데이터 | DATA | DATA | 데이터 |
| 일 | DAY | DAY | 일 |
| 설명 | DC | DESCRIPTION | 설명 |
| 깊이 | DEPTH | DEPTH | 깊이 |
| 부서 | DEPT | DEPARTMENT | 부서 |
| 상세 | DETAIL | DETAIL | 상세 |
| 디바이스 | DEVICE | DEVICE | 디바이스 |
| 일시 | DTTM | DATE_TIME | 일시 |
| 에디터 | EDITOR | EDITOR | 에디터 |
| 이메일 | EML | EMAIL | 이메일 |
| 사원 | EMP | EMPLOYEE | 사원 |
| 종료 | END | END | 종료 |
| 에러 | ERROR | ERROR | 에러 |
| 엑셀 | EXCEL | EXCEL | 엑셀 |
| 초과 | EXCEEDED | EXCEEDED | 초과된 |
| 실행 | EXECUTION | EXECUTION | 실행 |
| 종료 | EXIT | EXIT | 종료 |
| 표현식 | EXPRESSION | EXPRESSION | 표현식 |
| 확장자 | EXT | EXTENSION | 확장자 |
| 추출 | EXTRACT | EXTRACT | 추출 |
| 실패 | FAILED | FAILED | 실패한 |
| 팩스번호 | FXNO | FAX_NUMBER | 팩스번호 |
| 파일 | FILE | FILE | 파일 |
| 성별 | GENDER | GENDER | 성별 |
| 그룹 | GROUP | GROUP | 그룹 |
| 힌트 | HINT | HINT | 힌트 |
| 아이콘 | ICON | ICON | 아이콘 |
| 식별자 | ID | ID | 식별자 |
| 이미지 | IMAGE | IMAGE | 이미지 |
| 정보 | INFO | INFORMATION | 정보 |
| 초기화 | INIT | INITIALIZE | 초기화 |
| 작업 | JOB | JOB | 작업 |
| 마지막 | LAST | LAST | 마지막 |
| 레벨 | LEVEL | LEVEL | 레벨 |
| 라인 | LINE | LINE | 라인 |
| 잠김 | LOCK | LOCK | 잠김 |
| 로그 | LOG | LOG | 로그 |
| 로그인 | LOGIN | LOGIN | 로그인 |
| 최대 | MAX | MAX | 최대 |
| 휴대 | MBL | MOBILE | 휴대 |
| 수정 | MDFCN | MODIFICATION | 수정 |
| 수정자 | MDFR | MODIFIER | 수정자 |
| 메뉴 | MENU | MENU | 메뉴 |
| 메시지 | MESSAGE | MESSAGE | 메시지 |
| 모듈 | MODULE | MODULE | 모듈 |
| 이름 | NM | NAME | 이름 |
| 다음 | NEXT | NEXT | 다음 |
| 번호 | NO | NUMBER | 번호 |
| 비 | NON | NON | 비, 아닌 |
| 공지 | NOTICE | NOTICE | 공지 |
| 소속기관 | NSTT | INSTITUTE | 소속기관 |
| 알림 | NOTIFICATION | NOTIFICATION | 알림 |
| 순서 | ORDR | ORDER | 순서 |
| 조직 | ORG | ORGANIZATION | 조직 |
| 원본 | ORIGINAL | ORIGINAL | 원본 |
| 매개변수 | PARAM | PARAMETER | 매개변수 |
| 부모 | PARENT | PARENT | 부모 |
| 참여자 | PARTICIPANTS | PARTICIPANTS | 참여자 |
| 비밀번호 | PASSWD | PASSWORD | 비밀번호 |
| 경로 | PATH | PATH | 경로 |
| 패턴 | PATTERN | PATTERN | 패턴 |
| 대기중 | PENDING | PENDING | 대기중 |
| 장소 | PLACE | PLACE | 장소 |
| 직위 | POS | POSITION | 직위 |
| 게시물 | POST | POST | 게시물 |
| 비공개 | PRIVATE | PRIVATE | 비공개 |
| 처리 | PROCESS | PROCESS | 처리 |
| 처리중 | PROCESSING | PROCESSING | 처리중 |
| 원시 | RAW | RAW | 원시 |
| 확인 | READ | READ | 확인 |
| 사유 | REASON | REASON | 사유 |
| 등록 | REG | REGISTRATION | 등록 |
| 재시도 | RETRY | RETRY | 재시도 |
| 등록자 | RGTR | REGISTRANT | 등록자 |
| 역할 | ROLE | ROLE | 역할 |
| 주민등록 | RSDNT | RESIDENT | 주민등록 |
| 샘플 | SAMPLE | SAMPLE | 샘플 |
| 일정 | SCHEDULE | SCHEDULE | 일정 |
| 시퀀스 | SEQ | SEQUENCE | 시퀀스 |
| 서버 | SERVER | SERVER | 서버 |
| 세션 | SESSION | SESSION | 세션 |
| 설정 | SETTING | SETTING | 설정 |
| 크기 | SIZE | SIZE | 크기 |
| 정렬 | SORT | SORT | 정렬 |
| 시작 | START | START | 시작 |
| 상태 | STATUS | STATUS | 상태 |
| 저장 | STORED | STORED | 저장된 |
| 성공 | SUCCESS | SUCCESS | 성공 |
| 대상 | TARGET | TARGET | 대상 |
| 전화번호 | TELNO | TELEPHONE_NUMBER | 전화번호 |
| 제목 | TITLE | TITLE | 제목 |
| 총 | TOTAL | TOTAL | 총 |
| 타입 | TYPE | TYPE | 타입 |
| 유니크키 | UK | UNIQUE_KEY | 유니크 키 |
| 업로드 | UPLOAD | UPLOAD | 업로드 |
| 상위 | UPPER | UPPER | 상위 |
| 사용 | USE | USE | 사용 |
| 사용자 | USER | USER | 사용자 |
| 값 | VALUE | VALUE | 값 |
| 화면 | VIEW | VIEW | 화면, 표시 |
| 대기중 | WAITING | WAITING | 대기중 |
| 작성자 | WRITER | WRITER | 작성자 |
| 예아니오 | YN | YES_NO | 예/아니오 |
| 우편번호 | ZIP | ZIP | 우편번호 |
## 영문 약어 사전
| 영문 약어 | 전체 영문 | 한글 의미 | 설명 |
|-----------|-----------|----------|------|
| ACCESS | ACCESS | 접속 | 접속 |
| ACNT | ACCOUNT | 계정 | 계정 |
| ADDR | ADDRESS | 주소 | 주소 |
| AGENT | AGENT | 에이전트 | 에이전트 |
| ALL | ALL | 전체 | 전체, 모든 |
| ANSWER | ANSWER | 답변 | 답변 |
| ARCHIVE | ARCHIVE | 보관 | 보관, 아카이브 |
| AREA | AREA | 지역 | 지역 |
| ATTRIBUTE | ATTRIBUTE | 속성 | 속성 |
| BATCH | BATCH | 배치 | 배치 |
| BBS | BBS | 게시판 | 게시판 (Bulletin Board System) |
| BRDT | BIRTH_DATE | 생년월일 | 생년월일 |
| CALENDAR | CALENDAR | 달력 | 달력 |
| CATEGORY | CATEGORY | 범주 | 범주, 카테고리 |
| CD | CODE | 코드 | 코드 |
| CLASS | CLASS | 클래스 | 클래스 |
| CLMN | COLUMN | 컬럼 | 컬럼 |
| CNT | COUNT | 개수 | 개수, 수량 |
| COMMENT | COMMENT | 댓글 | 댓글 |
| COMPLETED | COMPLETED | 완료 | 완료된 |
| CONFIG | CONFIG | 설정 | 설정 |
| CONTENT | CONTENT | 내용 | 내용 |
| CORRUPTED | CORRUPTED | 손상 | 손상된 |
| CRON | CRON | 크론 | 크론 (스케줄링) |
| CRTFC | CERTIFICATE | 인증 | 인증 |
| DADDR | DETAIL_ADDRESS | 상세주소 | 상세주소 |
| DATA | DATA | 데이터 | 데이터 |
| DAY | DAY | 일 | 일 |
| DC | DESCRIPTION | 설명 | 설명 |
| DEPTH | DEPTH | 깊이 | 깊이 |
| DEPT | DEPARTMENT | 부서 | 부서 |
| DETAIL | DETAIL | 상세 | 상세 |
| DEVICE | DEVICE | 디바이스 | 디바이스 |
| DN | DN | DN | DN |
| DTTM | DATE_TIME | 일시 | 일시 |
| EDITOR | EDITOR | 에디터 | 에디터 |
| EML | EMAIL | 이메일 | 이메일 |
| EMP | EMPLOYEE | 사원 | 사원 |
| END | END | 종료 | 종료 |
| ERROR | ERROR | 에러 | 에러 |
| EXCEL | EXCEL | 엑셀 | 엑셀 |
| EXCEEDED | EXCEEDED | 초과 | 초과된 |
| EXECUTION | EXECUTION | 실행 | 실행 |
| EXIT | EXIT | 종료 | 종료 |
| EXPRESSION | EXPRESSION | 표현식 | 표현식 |
| EXT | EXTENSION | 확장자 | 확장자 |
| EXTRACT | EXTRACT | 추출 | 추출 |
| FAILED | FAILED | 실패 | 실패한 |
| FILE | FILE | 파일 | 파일 |
| FXNO | FAX_NUMBER | 팩스번호 | 팩스번호 |
| GENDER | GENDER | 성별 | 성별 |
| GROUP | GROUP | 그룹 | 그룹 |
| HINT | HINT | 힌트 | 힌트 |
| ICON | ICON | 아이콘 | 아이콘 |
| ID | ID | 식별자 | 식별자 |
| IMAGE | IMAGE | 이미지 | 이미지 |
| INFO | INFORMATION | 정보 | 정보 |
| INIT | INITIALIZE | 초기화 | 초기화 |
| IP | IP | IP | IP |
| JOB | JOB | 작업 | 작업 |
| LAST | LAST | 마지막 | 마지막 |
| LEVEL | LEVEL | 레벨 | 레벨 |
| LINE | LINE | 라인 | 라인 |
| LOCK | LOCK | 잠김 | 잠김 |
| LOG | LOG | 로그 | 로그 |
| LOGIN | LOGIN | 로그인 | 로그인 |
| MAX | MAX | 최대 | 최대 |
| MBL | MOBILE | 휴대 | 휴대 |
| MDFCN | MODIFICATION | 수정 | 수정 |
| MDFR | MODIFIER | 수정자 | 수정자 |
| MENU | MENU | 메뉴 | 메뉴 |
| MESSAGE | MESSAGE | 메시지 | 메시지 |
| MODULE | MODULE | 모듈 | 모듈 |
| NEXT | NEXT | 다음 | 다음 |
| NM | NAME | 이름 | 이름 |
| NO | NUMBER | 번호 | 번호 |
| NON | NON | 비 | 비, 아닌 |
| NOTICE | NOTICE | 공지 | 공지 |
| NOTIFICATION | NOTIFICATION | 알림 | 알림 |
| NSR | ANSWER | 답 | 답 |
| NSTT | INSTITUTE | 소속기관 | 소속기관 |
| ORDR | ORDER | 순서 | 순서 |
| ORG | ORGANIZATION | 조직 | 조직 |
| ORIGINAL | ORIGINAL | 원본 | 원본 |
| PARAM | PARAMETER | 매개변수 | 매개변수 |
| PARENT | PARENT | 부모 | 부모 |
| PARTICIPANTS | PARTICIPANTS | 참여자 | 참여자 |
| PASSWD | PASSWORD | 비밀번호 | 비밀번호 |
| PATH | PATH | 경로 | 경로 |
| PATTERN | PATTERN | 패턴 | 패턴 |
| PENDING | PENDING | 대기중 | 대기중 |
| PLACE | PLACE | 장소 | 장소 |
| POS | POSITION | 직위 | 직위 |
| POST | POST | 게시물 | 게시물 |
| PRIVATE | PRIVATE | 비공개 | 비공개 |
| PROCESS | PROCESS | 처리 | 처리 |
| PROCESSING | PROCESSING | 처리중 | 처리중 |
| RAW | RAW | 원시 | 원시 |
| READ | READ | 확인 | 확인 |
| REASON | REASON | 사유 | 사유 |
| REG | REGISTRATION | 등록 | 등록 |
| RETRY | RETRY | 재시도 | 재시도 |
| RGTR | REGISTRANT | 등록자 | 등록자 |
| ROLE | ROLE | 역할 | 역할 |
| RSDNT | RESIDENT | 주민등록 | 주민등록 |
| SAMPLE | SAMPLE | 샘플 | 샘플 |
| SCHEDULE | SCHEDULE | 일정 | 일정 |
| SEQ | SEQUENCE | 시퀀스 | 시퀀스 |
| SERVER | SERVER | 서버 | 서버 |
| SESSION | SESSION | 세션 | 세션 |
| SETTING | SETTING | 설정 | 설정 |
| SIZE | SIZE | 크기 | 크기 |
| SORT | SORT | 정렬 | 정렬 |
| START | START | 시작 | 시작 |
| STATUS | STATUS | 상태 | 상태 |
| STORED | STORED | 저장 | 저장된 |
| SUCCESS | SUCCESS | 성공 | 성공 |
| TARGET | TARGET | 대상 | 대상 |
| TELNO | TELEPHONE_NUMBER | 전화번호 | 전화번호 |
| TITLE | TITLE | 제목 | 제목 |
| TOTAL | TOTAL | 총 | 총 |
| TUI | TUI | TUI | TUI (Toast UI) |
| TYPE | TYPE | 타입 | 타입 |
| UK | UNIQUE_KEY | 유니크키 | 유니크 키 |
| UPLOAD | UPLOAD | 업로드 | 업로드 |
| UPPER | UPPER | 상위 | 상위 |
| URL | URL | URL | URL |
| USE | USE | 사용 | 사용 |
| USER | USER | 사용자 | 사용자 |
| VALUE | VALUE | 값 | 값 |
| VIEW | VIEW | 화면 | 화면, 표시 |
| WAITING | WAITING | 대기중 | 대기중 |
| WRITER | WRITER | 작성자 | 작성자 |
| YN | YES_NO | 예아니오 | 예/아니오 |
| ZIP | ZIP | 우편번호 | 우편번호 |
## 일반적인 접미사 패턴
| 접미사 | 의미 | 예시 |
|--------|------|------|
| _ID | 식별자 | USER_ID, MENU_ID |
| _CD | 코드 | TYPE_CD, STATUS_CD |
| _NM | 이름 | USER_NM, FILE_NM |
| _DC | 설명 | MENU_DC, ROLE_DC |
| _DTTM | 일시 | REG_DTTM, MDFCN_DTTM |
| _YN | 예/아니오 | USE_YN, DEL_YN |
| _CNT | 개수 | LOGIN_CNT, RETRY_CNT |
| _NO | 번호 | EMP_NO, TELNO |
| _ORDR | 순서 | SORT_ORDR, MENU_ORDR |
| _ADDR | 주소 | HOME_ADDR, WORK_ADDR |
| _PATH | 경로 | FILE_PATH, MENU_PATH |
| _SIZE | 크기 | FILE_SIZE, IMAGE_SIZE |
| _TYPE | 타입 | FILE_TYPE, USER_TYPE |
| _STATUS | 상태 | PROCESS_STATUS, LOGIN_STATUS |
## 주요 컬럼 네이밍 규칙
1. **기본 구조**: `[접두사_]주요단어[_접미사]`
2. **복합어**: 언더스코어(_)로 구분
3. **일시**: 반드시 `_DTTM` 사용
4. **여부**: 반드시 `_YN` 사용
5. **등록/수정**: `REG_DTTM`, `MDFCN_DTTM`, `RGTR`, `MDFR` 패턴 사용
6. **ID**: 테이블명에서 `tb_` 제거 후 `_ID` 추가
## 테이블 참조 정보
- **tb_common_html_editor_file**: 파일 관리
- **tb_excel_sample**: 엑셀 샘플 데이터
- **tb_group**: 그룹 정보
- **tb_group_role**: 그룹-역할 매핑
- **tb_login_log**: 로그인 로그
- **tb_menu**: 메뉴 정보
- **tb_notification**: 알림 정보
- **tb_notification_target**: 알림 대상자
- **tb_notification_target_setting**: 알림 대상자 설정
- **tb_role**: 역할 정보
- **tb_role_menu**: 역할-메뉴 매핑
- **tb_user**: 사용자 정보
- **tb_user_session**: 사용자 세션 정보

@ -0,0 +1,4 @@
INSERT INTO xitframework.tb_bbs_config (BBS_ID, BBS_NM, BBS_TYPE_CD, BBS_DC, UPLOAD_YN, UPLOAD_FILE_CNT, COMMENT_YN, TUI_EDITOR_YN, NOTICE_YN, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBSC00000001', '공지사항', 'NOTICE', '공지사항 TEST', 'Y', 2, 'N', 'Y', 'Y', 'Y', '2025-05-27 10:37:51', 'ADMIN', '2025-07-18 09:41:29', 'ADMIN');
INSERT INTO xitframework.tb_bbs_config (BBS_ID, BBS_NM, BBS_TYPE_CD, BBS_DC, UPLOAD_YN, UPLOAD_FILE_CNT, COMMENT_YN, TUI_EDITOR_YN, NOTICE_YN, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBSC00000040', 'FAQ', 'FAQ', 'FAQ', 'Y', 10, 'N', 'Y', 'N', 'Y', '2025-06-02 11:30:11', 'ADMIN', '2025-06-05 13:22:52', 'ADMIN');
INSERT INTO xitframework.tb_bbs_config (BBS_ID, BBS_NM, BBS_TYPE_CD, BBS_DC, UPLOAD_YN, UPLOAD_FILE_CNT, COMMENT_YN, TUI_EDITOR_YN, NOTICE_YN, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBSC00000041', '질문과 답변', 'QNA', '질문과 답변', 'Y', 5, 'N', 'Y', 'N', 'Y', '2025-06-02 17:07:37', 'ADMIN', '2025-07-18 15:37:40', 'ADMIN');
INSERT INTO xitframework.tb_bbs_config (BBS_ID, BBS_NM, BBS_TYPE_CD, BBS_DC, UPLOAD_YN, UPLOAD_FILE_CNT, COMMENT_YN, TUI_EDITOR_YN, NOTICE_YN, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBSC00000100', '일반 게시판', 'NORMAL', '일반 게시판-1', 'Y', 2, 'Y', 'Y', 'Y', 'Y', '2025-06-05 15:14:48', 'ADMIN', '2025-07-21 10:35:29', 'ADMIN');

@ -0,0 +1,55 @@
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXEC_STATUS_CD', 'COMPLETED', '완료됨', '배치 작업 실행이 성공적으로 완료된 상태', 2, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXEC_STATUS_CD', 'FAILED', '실패함', '배치 작업 실행 중 오류가 발생한 상태', 3, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXEC_STATUS_CD', 'PARTIALLY_COMPLETED', '부분완료', '배치 작업 실행이 일부 성공하고 일부 실패한 상태', 5, 'N', null, null, null, null, null, '2025-06-20 14:12:15', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXEC_STATUS_CD', 'STARTED', '시작됨', '배치 작업 실행이 시작된 상태', 1, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXEC_STATUS_CD', 'VETOED', '거부됨', '배치 작업 실행이 거부된 상태', 4, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXIT_CD', 'COMPLETED', '성공 종료', '배치 작업이 성공적으로 종료됨', 1, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXIT_CD', 'FAILED', '실패 종료', '배치 작업이 오류로 인해 실패 종료됨', 2, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXIT_CD', 'PARTIALLY_COMPLETED', '부분 완료', '배치 작업이 일부 성공하고 일부 실패하여 부분적으로 완료됨', 4, 'Y', null, null, null, null, null, '2025-06-20 14:32:25', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXIT_CD', 'UNKNOWN', '알 수 없음', '배치 작업의 종료 상태를 알 수 없음', 3, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_JOB_STATUS_CD', 'ACTIVE', '활성', '배치 작업이 활성화된 상태', 1, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_JOB_STATUS_CD', 'DELETED', '삭제됨', '배치 작업이 삭제된 상태', 3, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_JOB_STATUS_CD', 'PAUSED', '일시 중지', '배치 작업이 일시 중지된 상태', 2, 'Y', null, null, null, null, null, '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_ANSWER_STATUS_CD', 'COMPLETED', '답변완료', '게시물 답변 상태 - 답변완료', 2, 'Y', null, null, null, null, null, '2025-06-04 12:27:54', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_ANSWER_STATUS_CD', 'WAITING', '대기중', '게시물 답변 상태 - 대기중', 1, 'Y', null, null, null, null, null, '2025-06-04 12:27:54', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_POST_TYPE_CD', 'A', '답변', '게시물 유형 - 답변', 2, 'Y', null, null, null, null, null, '2025-06-04 12:27:54', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_POST_TYPE_CD', 'Q', '질문', '게시물 유형 - 질문', 1, 'Y', null, null, null, null, null, '2025-06-04 12:27:54', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_TYPE_CD', 'FAQ', 'FAQ', 'FAQ 게시판', 4, 'Y', null, null, null, null, null, '2025-05-26 16:43:38', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_TYPE_CD', 'NORMAL', '일반', '자료실 게시판', 1, 'Y', null, null, null, null, null, '2025-05-26 16:43:33', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_TYPE_CD', 'NOTICE', '공지사항', '공지사항 게시판', 2, 'Y', null, null, null, null, null, '2025-05-26 16:43:35', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_TYPE_CD', 'QNA', '질문과답변', '질문과답변 게시판 (답변 시 이메일 발송)', 3, 'Y', null, null, null, null, null, '2025-05-26 16:43:36', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT001', '경영지원부', '경영지원부', 1, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT002', '인사부', '인사부', 2, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT003', '개발부', '개발부', 3, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT004', '마케팅부', '마케팅부', 4, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT005', '영업부', '영업부', 5, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT006', '연구부', '연구부', 6, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', 'DEPT007', '품질관리부', '품질관리부', 7, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NOTIFICATION_TYPE_CD', 'ANSWER', '답변', '답변 관련 알림', 3, 'Y', null, null, null, null, null, '2025-06-23 17:37:59', 'SYSTEM', '2025-06-23 17:37:59', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NOTIFICATION_TYPE_CD', 'BATCH_FAILURE', '배치실패', '배치 실패 알림', 4, 'Y', null, null, null, null, null, '2025-07-15 13:41:19', 'ADMIN', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NOTIFICATION_TYPE_CD', 'QUESTION', '질문', '질문 관련 알림', 2, 'Y', null, null, null, null, null, '2025-06-23 17:37:59', 'SYSTEM', '2025-06-23 17:37:59', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NOTIFICATION_TYPE_CD', 'SCHEDULE', '일정', '일정 관련 알림', 1, 'Y', null, null, null, null, null, '2025-06-23 17:37:59', 'SYSTEM', '2025-06-23 17:37:59', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NSTT_CD', 'NSTT001', '본사', '본사', 1, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NSTT_CD', 'NSTT002', '지역사무소', '지역사무소', 2, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NSTT_CD', 'NSTT003', '연구소', '연구소', 3, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NSTT_CD', 'NSTT004', '해외지사', '해외지사', 4, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NSTT_CD', 'NSTT005', '협력업체', '협력업체', 5, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', 'ORG001', '본사 조직', '본사 조직', 1, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', 'ORG002', '지역 조직', '지역 조직', 2, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', 'ORG003', '연구 조직', '연구 조직', 3, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', 'ORG004', '해외 조직', '해외 조직', 4, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', 'ORG005', '협력 조직', '협력 조직', 5, 'Y', null, null, null, null, null, '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('SCHEDULE_CATEGORY_CD', 'BUSINESS_TRIP', '출장', '출장 관련 일정', 2, 'Y', 'rgba(111, 66, 193, 0.7)', null, null, null, null, '2025-06-13 10:36:55', 'SYSTEM', '2025-06-13 15:50:23', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('SCHEDULE_CATEGORY_CD', 'IMPORTANT', '중요', '중요 일정', 5, 'Y', 'rgba(255, 159, 67, 0.7)', null, null, null, null, '2025-06-13 10:36:55', 'SYSTEM', '2025-06-13 15:32:58', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('SCHEDULE_CATEGORY_CD', 'MEETING', '회의', '회의 관련 일정', 4, 'Y', 'rgba(46, 139, 87, 0.7)', null, null, null, null, '2025-06-13 10:36:55', 'SYSTEM', '2025-06-13 16:10:03', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('SCHEDULE_CATEGORY_CD', 'VACATION', '휴가', '휴가 관련 일정', 3, 'Y', 'rgba(23, 162, 184, 0.7)', null, null, null, null, '2025-06-13 10:36:55', 'SYSTEM', '2025-06-13 15:50:23', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('SCHEDULE_CATEGORY_CD', 'WORK', '업무', '업무 관련 일정', 1, 'Y', 'rgba(55, 136, 216, 0.7)', null, null, null, null, '2025-06-13 10:36:55', 'SYSTEM', '2025-06-13 15:32:25', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD', 'TTT_TEST_CD1_1_1', '테스트 코드1', '1', 1, 'Y', null, null, null, null, null, '2025-07-09 13:56:19', 'ADMIN', '2025-07-21 10:33:44', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD', 'TTT_TEST_CD2_1_1', '테스트 코드2', '2', 2, 'Y', null, null, null, null, null, '2025-07-09 13:56:19', 'ADMIN', '2025-07-21 10:33:44', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD', 'TTT_TEST_CD3_1_1', '테스트 코드3', '3', 3, 'Y', null, null, null, null, null, '2025-07-09 13:56:19', 'ADMIN', '2025-07-21 10:33:49', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD2', 'TTT_TEST_CD1_1_1', '테스트 코드1', '1', 1, 'Y', null, null, null, null, null, '2025-07-11 16:21:46', 'ADMIN', '2025-07-18 09:39:27', 'ADMIN');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD2', 'TTT_TEST_CD2_1_1', '테스트 코드2', '', 2, 'Y', null, null, null, null, null, '2025-07-11 16:21:46', 'ADMIN', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD2', 'TTT_TEST_CD3_1_1', '테스트 코드3', '', 3, 'Y', null, null, null, null, null, '2025-07-11 16:21:46', 'ADMIN', null, null);
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER_STATUS_CD', 'ACTIVE', '활성', '활성 상태의 사용자', 1, 'Y', null, null, null, null, null, '2025-05-13 14:24:50', 'SYSTEM', '2025-05-13 14:24:50', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER_STATUS_CD', 'DELETED', '삭제', '삭제된 상태의 사용자', 4, 'Y', null, null, null, null, null, '2025-05-13 14:24:50', 'SYSTEM', '2025-05-13 14:24:50', 'SYSTEM');
INSERT INTO xitframework.tb_code_detail (CODE_GROUP_ID, CODE_ID, CODE_NM, CODE_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER_STATUS_CD', 'INACTIVE', '비활성', '비활성 상태의 사용자', 2, 'Y', null, null, null, null, null, '2025-05-13 14:24:50', 'SYSTEM', '2025-05-13 14:24:50', 'SYSTEM');

@ -0,0 +1,14 @@
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXEC_STATUS_CD', '배치 작업 실행 상태', '배치 작업 실행의 상태 코드', 'Y', '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_EXIT_CD', '배치 작업 실행 종료 코드', '배치 작업 실행의 종료 코드', 'Y', '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BATCH_JOB_STATUS_CD', '배치 작업 정보 상태', '배치 작업 정보의 상태 코드', 'Y', '2025-06-18 13:24:51', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_ANSWER_STATUS_CD', '게시물 답변 상태', '답변 상태 (WAITING: 대기중, COMPLETED: 답변완료)', 'Y', '2025-06-04 12:27:54', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_POST_TYPE_CD', '게시물 유형', '게시물 유형 (Q: 질문, A: 답변)', 'Y', '2025-06-04 12:27:54', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BBS_TYPE_CD', '게시판 종류', '게시판 종류를 관리하는 코드 그룹', 'Y', '2025-05-26 16:43:32', 'SYSTEM', null, null);
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DEPT_CD', '부서 코드', '부서를 나타내는 코드1', 'Y', '2025-05-13 14:44:53', 'SYSTEM', '2025-05-20 15:08:42', 'SYSTEM');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NOTIFICATION_TYPE_CD', '알림 유형', '공통 알림 시스템의 알림 유형 코드', 'Y', '2025-06-23 17:37:59', 'SYSTEM', '2025-06-23 17:37:59', 'SYSTEM');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('NSTT_CD', '소속기관 코드', '소속기관을 나타내는 코드', 'Y', '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', '조직 코드', '조직을 나타내는 코드', 'Y', '2025-05-13 14:44:53', 'SYSTEM', '2025-05-13 14:44:53', 'SYSTEM');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('SCHEDULE_CATEGORY_CD', '일정 범주', '일정 관리 시스템의 일정 범주 코드', 'Y', '2025-06-13 10:36:55', 'SYSTEM', '2025-06-13 10:36:55', 'SYSTEM');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD', '테스트 코드', '2', 'Y', '2025-07-09 13:55:39', 'ADMIN', '2025-07-21 10:33:36', 'ADMIN');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('TEST_CD2', '테스트 코드', '1', 'Y', '2025-07-11 16:21:03', 'ADMIN', '2025-07-18 09:39:20', 'ADMIN');
INSERT INTO xitframework.tb_code_group (CODE_GROUP_ID, CODE_GROUP_NM, CODE_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER_STATUS_CD', '사용자 상태', '사용자의 상태 코드', 'Y', '2025-05-13 14:24:50', 'SYSTEM', '2025-05-13 14:24:50', 'SYSTEM');

@ -0,0 +1,6 @@
INSERT INTO xitframework.tb_group (GROUP_ID, GROUP_NM, GROUP_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('GROUP_ADMIN', '관리자 그룹', '시스템 관리 권한을 가진 사용자 그룹', 5, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-05-15 10:09:55', 'SYSTEM');
INSERT INTO xitframework.tb_group (GROUP_ID, GROUP_NM, GROUP_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('GROUP_APPROVER', '업무 승인 담당자 그룹', '업무 승인 권한을 가진 사용자 그룹', 4, 'Y', '2025-05-16 12:44:14', 'SYSTEM', '2025-05-16 12:44:14', 'SYSTEM');
INSERT INTO xitframework.tb_group (GROUP_ID, GROUP_NM, GROUP_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('GROUP_SYSTEM', '시스템 관리자 그룹', '모든 시스템 권한을 가진 사용자 그룹(XIT)', 999, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-07-18 09:40:01', 'SYSTEM');
INSERT INTO xitframework.tb_group (GROUP_ID, GROUP_NM, GROUP_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('GROUP_USER', '일반 사용자 그룹', '일반 사용자 권한을 가진 사용자 그룹', 2, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-06-04 16:34:22', 'SYSTEM');
INSERT INTO xitframework.tb_group (GROUP_ID, GROUP_NM, GROUP_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('GROUP_VISITOR', '방문자 그룹', '방문자 권한을 가진 사용자 그룹', 1, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-05-15 10:09:55', 'SYSTEM');
INSERT INTO xitframework.tb_group (GROUP_ID, GROUP_NM, GROUP_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('GROUP_WORKER', '업무 담당자 그룹', '업무 처리 권한을 가진 사용자 그룹', 3, 'Y', '2025-05-16 12:44:12', 'SYSTEM', '2025-07-18 15:37:11', 'SYSTEM');

@ -0,0 +1,8 @@
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_ADMIN', 'ROLE_ADMIN', '2025-05-16 12:41:59', 'SYSTEM');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_APPROVER', 'ROLE_APPROVER', '2025-05-30 14:45:09', 'ADMIN');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_APPROVER', 'ROLE_WORKER', '2025-07-09 14:04:57', 'ADMIN');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_SYSTEM', 'ROLE_ADMIN', '2025-07-09 14:03:39', 'ADMIN');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_SYSTEM', 'ROLE_SYSTEM', '2025-05-16 12:42:00', 'SYSTEM');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_USER', 'ROLE_USER', '2025-05-16 12:41:56', 'SYSTEM');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_USER', 'ROLE_WORKER', '2025-07-18 15:36:42', 'ADMIN');
INSERT INTO xitframework.tb_group_role (GROUP_ID, ROLE_ID, REG_DTTM, RGTR) VALUES ('GROUP_WORKER', 'ROLE_WORKER', '2025-05-16 12:44:20', 'SYSTEM');

@ -0,0 +1,30 @@
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000001', '', '시스템 홈 메뉴', 'ROOT', 1, 1, '/main.do', '/main.do', 'home', 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-30 14:40:46', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000002', '게시판 관리', '공통 관리 메뉴', 'ROOT', 1, 500, '', '', 'edit-3', 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-06-04 14:01:04', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000003', '시스템 관리', '시스템 관리 메뉴', 'ROOT', 1, 900, '', '', 'settings', 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-30 14:41:20', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000005', '코드 통합 관리', '코드 마스터와 코드 상세를 한 화면에서 관리', 'MENU00000003', 2, 1, '/system/code/integrated/manage.do', '/system/code/**/*.do,/system/code/**/*.ajax', null, 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-20 15:07:54', 'SYSTEM');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000006', '사용자 관리', '사용자 관리 메뉴', 'MENU00000003', 2, 2, '/system/user/list.do', '/system/user/**/*.do,/system/user/**/*.ajax', null, 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-16 12:49:48', 'SYSTEM');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000007', '그룹 관리', '그룹 관리 메뉴', 'MENU00000003', 2, 3, '/system/group/list.do', '/system/group/**/*.do,/system/group/**/*.ajax', null, 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-16 12:49:48', 'SYSTEM');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000008', '역할 관리', '역할 관리 메뉴', 'MENU00000003', 2, 4, '/system/role/list.do', '/system/role/**/*.do,/system/role/**/*.ajax', null, 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-16 12:49:48', 'SYSTEM');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000009', '메뉴 관리', '메뉴 관리 메뉴', 'MENU00000003', 2, 5, '/system/menu/list.do', '/system/menu/**/*.do,/system/menu/**/*.ajax', null, 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-16 12:49:48', 'SYSTEM');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000010', '권한 관리', '권한 관리 메뉴', 'MENU00000003', 2, 6, '/system/auth/main.do', '/system/auth/**/*.do,/system/auth/**.ajax,/system/auth/**/*.ajax', null, 'Y', 'Y', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-16 12:49:48', 'SYSTEM');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000011', '공통', '공통', 'ROOT', 1, 100, '', '/common/**/*.do,/common/**/*.ajax', 'package', 'Y', 'N', '2025-05-15 14:03:38', 'SYSTEM', '2025-05-30 14:42:10', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000052', '마이페이지', '마이페이지', 'ROOT', 1, 90, '/mypage.do', '/mypage.do,/mypage/**/*.do,/mypage/**/*.ajax', 'shield', 'Y', 'N', '2025-05-21 09:33:38', 'SYSTEM', '2025-07-08 10:05:50', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000053', '로그인 로그', '', 'MENU00000003', 2, 6, '/system/loginLog/list.do', '/system/loginLog/**/*.do,/system/loginLog/**/*.ajax', '', 'Y', 'Y', '2025-05-22 15:06:30', 'ADMIN', '2025-07-08 14:12:03', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000054', '게시판 설정', '게시판 설정, 게시판 타입별 생성 수정 삭제', 'MENU00000002', 2, 1, '/bbs/manage/config/list.do', '/bbs/manage/config/**/*.do,/bbs/manage/config/**/*.ajax', '', 'Y', 'Y', '2025-05-26 15:04:09', 'ADMIN', '2025-06-05 11:58:22', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000092', 'CRUD 예제', '등록, 수정, 삭제, 조회 예제', 'ROOT', 1, 110, '', '', 'list', 'Y', 'Y', '2025-05-30 18:27:36', 'ADMIN', '2025-06-05 12:00:09', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000093', '공지사항 예제', '공지사항 - 별도 소스 개발', 'MENU00000092', 2, 1, '/template/noticeSample/list.do', '/template/noticeSample/**.do,/template/noticeSample/**/*.ajax', '', 'Y', 'Y', '2025-05-30 18:28:18', 'ADMIN', '2025-06-04 13:42:44', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000094', '공지사항 관리', '게시판 설정의 공지사항 관리', 'MENU00000002', 2, 2, '/bbs/manage/post/BBSC00000001/list.do', '/bbs/manage/post/BBSC00000001/**/*.do,/bbs/manage/post/BBSC00000001/**/*.ajax', '', 'Y', 'Y', '2025-06-02 10:57:48', 'ADMIN', '2025-06-05 12:25:16', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000096', '공지사항', '공지사', 'ROOT', 1, 2, '/bbs/user/post/BBSC00000001/list.do', '/bbs/user/post/BBSC00000001/**/*.do,/bbs/user/post/BBSC00000001/**/*.ajax', 'edit', 'Y', 'Y', '2025-06-02 14:21:23', 'ADMIN', '2025-06-05 12:26:35', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000097', 'FAQ 관리', 'FAQ 관리', 'MENU00000002', 2, 4, '/bbs/manage/post/BBSC00000040/list.do', '/bbs/manage/post/BBSC00000040/**/*.do,/bbs/manage/post/BBSC00000040/**/*.ajax', '', 'Y', 'Y', '2025-06-02 15:51:09', 'ADMIN', '2025-06-05 15:15:56', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000098', 'FAQ', '', 'ROOT', 1, 3, '/bbs/user/post/BBSC00000040/listFaqData.do', '/bbs/user/post/BBSC00000040/**/*.do,/bbs/user/post/BBSC00000040/**/*.ajax', 'info', 'Y', 'Y', '2025-06-02 15:58:24', 'ADMIN', '2025-06-05 12:27:01', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000099', '질문과 답변', '질문과 답변 QNA', 'MENU00000002', 2, 5, '/bbs/manage/qna/BBSC00000041/list.do', '/bbs/manage/qna/BBSC00000041/**/*.do,/bbs/manage/qna/BBSC00000041/**/*.ajax', '', 'Y', 'Y', '2025-06-02 17:08:43', 'ADMIN', '2025-06-05 15:16:00', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000100', '질문과 답변', '질문과 답변', 'ROOT', 1, 5, '/bbs/user/qna/BBSC00000041/list.do', '/bbs/user/qna/BBSC00000041/**/*.do,/bbs/user/qna/BBSC00000041/**/*.ajax', 'help-circle', 'Y', 'Y', '2025-06-02 17:09:22', 'ADMIN', '2025-06-05 12:27:26', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000107', '일반 게시판 관리', '일반 게시판', 'MENU00000002', 2, 3, '/bbs/manage/post/BBSC00000100/list.do', '/bbs/manage/post/BBSC00000100/**/*.do,/bbs/manage/post/BBSC00000100/**/*.ajax', '', 'Y', 'Y', '2025-06-05 15:15:34', 'ADMIN', '2025-07-11 16:18:42', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000108', '일반 게시판', '', 'ROOT', 1, 6, '/bbs/user/post/BBSC00000100/list.do', '/bbs/user/post/BBSC00000100/**/*.do,/bbs/user/post/BBSC00000100/**/*.ajax', 'book', 'Y', 'Y', '2025-06-05 15:16:44', 'ADMIN', '2025-06-05 15:18:28', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000109', 'dataTable 테스트', '1111111111111111111', 'ROOT', 1, 1, '/bbs/user/post/BBSC00000100/dataTableList.do', '/bbs/user/post/BBSC00000100/**/*.do,/bbs/user/post/BBSC00000100/**/*.ajax', 'clipboard', 'Y', 'Y', '2025-06-05 17:18:03', 'USER00000051', '2025-07-18 15:37:29', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000110', '이미지 편집', '이미지 편집 테스트 진행', 'MENU00000092', 2, 2, '/template/imageModifySample/imageModify.do', '/template/imageModifySample/**/*.do,/template/imageModifySample/**/*.ajax', '', 'Y', 'Y', '2025-06-09 15:33:07', 'ADMIN', '2025-06-09 15:33:07', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000111', '일정관리 예제', '', 'MENU00000092', 2, 3, '/template/calendarSample/calendarSample.do', '/template/calendarSample/**/*.do,/template/calendarSample/**/*.ajax', '', 'Y', 'Y', '2025-06-12 09:10:51', 'ADMIN', '2025-07-08 14:11:59', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000112', '배치 관리', '배치관리 프로그램', 'MENU00000003', 2, 10, '/batch/list.do', '/batch/**/*.do,/batch/**/*.ajax', '', 'Y', 'Y', '2025-06-18 10:00:31', 'ADMIN', '2025-06-18 10:00:31', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000113', '쿼리 실행기', '쿼리 실행기', 'MENU00000003', 2, 99, '/db/exec/query.do', '/db/exec/**/*.do,/db/exec/**/*.ajax', '', 'Y', 'Y', '2025-07-07 17:14:18', 'ADMIN', '2025-07-07 17:14:18', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('MENU00000114', '엑셀 예제', '엑셀 다운로드, 업로드', 'MENU00000092', 2, 4, '/template/excelSample/list.do', '/template/excelSample/**/*.do,/template/excelSample/**/*.ajax', '', 'Y', 'Y', '2025-07-10 09:47:22', 'ADMIN', '2025-07-10 09:47:22', 'ADMIN');
INSERT INTO xitframework.tb_menu (MENU_ID, MENU_NM, MENU_DC, UPPER_MENU_ID, MENU_LEVEL, SORT_ORDR, MENU_URL, URL_PATTERN, MENU_ICON, USE_YN, VIEW_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROOT', 'ROOT', '최상위 메뉴', null, 0, 0, null, null, null, 'Y', 'N', '2025-05-16 12:49:48', 'SYSTEM', '2025-05-16 12:49:48', 'SYSTEM');

@ -0,0 +1,6 @@
INSERT INTO xitframework.tb_role (ROLE_ID, ROLE_NM, ROLE_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROLE_ADMIN', '관리자', '시스템 관리 권한을 가진 관리자 역할', 5, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-05-15 10:09:55', 'SYSTEM');
INSERT INTO xitframework.tb_role (ROLE_ID, ROLE_NM, ROLE_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROLE_APPROVER', '업무 승인 담당자', '업무 승인 권한을 가진 담당자 역할', 4, 'Y', '2025-05-16 12:43:54', 'SYSTEM', '2025-05-16 12:43:54', 'SYSTEM');
INSERT INTO xitframework.tb_role (ROLE_ID, ROLE_NM, ROLE_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROLE_SYSTEM', '시스템 관리자', '모든 시스템 권한을 가진 시스템 관리자 역할(XIT)', 6, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-07-18 09:40:18', 'SYSTEM');
INSERT INTO xitframework.tb_role (ROLE_ID, ROLE_NM, ROLE_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROLE_USER', '일반 유저', '기본적인 시스템 사용 권한을 가진 일반 사용자 역할1', 2, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-05-21 16:15:28', 'SYSTEM');
INSERT INTO xitframework.tb_role (ROLE_ID, ROLE_NM, ROLE_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROLE_VISITOR', '방문자', '시스템에 접근 가능한 기본 권한을 가진 방문자 역할', 1, 'Y', '2025-05-15 10:09:55', 'SYSTEM', '2025-07-18 15:37:21', 'SYSTEM');
INSERT INTO xitframework.tb_role (ROLE_ID, ROLE_NM, ROLE_DC, SORT_ORDR, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ROLE_WORKER', '업무 담당자', '업무 처리 권한을 가진 담당자 역할', 3, 'Y', '2025-05-16 12:43:53', 'SYSTEM', '2025-05-16 12:43:53', 'SYSTEM');

@ -0,0 +1,91 @@
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000001', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000002', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000003', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000005', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000006', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000011', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000052', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000053', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000054', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000092', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000093', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000094', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000096', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000097', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000098', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000099', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000100', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000107', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000108', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000109', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000110', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000111', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'MENU00000114', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_ADMIN', 'ROOT', '2025-07-10 09:47:43', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000001', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000002', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000003', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000011', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000052', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000053', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000054', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000094', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000096', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000097', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000098', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000099', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000100', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000107', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'MENU00000108', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_APPROVER', 'ROOT', '2025-07-09 14:04:25', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000001', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000002', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000003', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000005', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000006', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000007', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000008', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000009', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000010', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000011', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000052', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000053', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000054', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000092', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000093', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000094', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000096', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000097', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000098', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000099', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000100', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000107', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000108', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000109', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000110', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000111', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000112', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000113', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'MENU00000114', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_SYSTEM', 'ROOT', '2025-07-17 14:39:05', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000001', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000011', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000052', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000096', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000098', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000100', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000108', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'MENU00000109', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_USER', 'ROOT', '2025-07-08 11:05:31', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000001', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000011', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000052', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000092', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000093', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000096', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000098', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000100', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000108', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000110', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'MENU00000111', '2025-07-08 11:05:36', 'ADMIN');
INSERT INTO xitframework.tb_role_menu (ROLE_ID, MENU_ID, REG_DTTM, RGTR) VALUES ('ROLE_WORKER', 'ROOT', '2025-07-08 11:05:36', 'ADMIN');

@ -0,0 +1,5 @@
INSERT INTO xitframework.tb_user (USER_ID, USER_ACNT, USER_NM, PASSWD, PASSWD_HINT, PASSWD_NSR, EMP_NO, GENDER, ZIP, ADDR, DADDR, AREA_NO, EML_ADDR, ORG_CD, USER_GROUP_ID, NSTT_CD, POS_NM, CRTFC_DN, USER_STATUS_CD, FXNO, TELNO, MBL_TELNO, BRDT, DEPT_CD, USE_YN, RSDNT_NO, PASSWD_INIT_YN, LOCK_YN, LOCK_CNT, LOCK_DTTM, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ADMIN', 'admin', '시스템 관리자', 'Z6XYLOi+w3ukRN5nn08WSYg04spdJAvpCU4EOOd925g=', '관리자 계정', '관리자', '43534534', '', '14507', '경기도 부천시 원미구 계남로 81', '', null, 'admin@example.com', 'ORG001', 'GROUP_SYSTEM', 'NSTT001', '시스템 관리자', null, 'ACTIVE', null, '02-1234-5678', '010-1234-5678', '19800101', 'DEPT001', 'Y', null, 'N', 'N', 0, null, '2025-05-15 10:09:55', 'SYSTEM', '2025-07-21 10:35:19', 'ADMIN');
INSERT INTO xitframework.tb_user (USER_ID, USER_ACNT, USER_NM, PASSWD, PASSWD_HINT, PASSWD_NSR, EMP_NO, GENDER, ZIP, ADDR, DADDR, AREA_NO, EML_ADDR, ORG_CD, USER_GROUP_ID, NSTT_CD, POS_NM, CRTFC_DN, USER_STATUS_CD, FXNO, TELNO, MBL_TELNO, BRDT, DEPT_CD, USE_YN, RSDNT_NO, PASSWD_INIT_YN, LOCK_YN, LOCK_CNT, LOCK_DTTM, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER00000004', 'user', '일반유저', 'Z6XYLOi+w3ukRN5nn08WSYg04spdJAvpCU4EOOd925g=', '', '', '1234', null, '', '', '', null, '', '', 'GROUP_USER', 'NSTT002', '', null, 'DELETED', null, '', '', null, '', 'Y', null, 'N', 'N', 0, null, '2025-05-15 17:38:25', '', '2025-07-18 09:39:37', 'ADMIN');
INSERT INTO xitframework.tb_user (USER_ID, USER_ACNT, USER_NM, PASSWD, PASSWD_HINT, PASSWD_NSR, EMP_NO, GENDER, ZIP, ADDR, DADDR, AREA_NO, EML_ADDR, ORG_CD, USER_GROUP_ID, NSTT_CD, POS_NM, CRTFC_DN, USER_STATUS_CD, FXNO, TELNO, MBL_TELNO, BRDT, DEPT_CD, USE_YN, RSDNT_NO, PASSWD_INIT_YN, LOCK_YN, LOCK_CNT, LOCK_DTTM, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER00000005', 'worker', '업무담당자', 'Z6XYLOi+w3ukRN5nn08WSYg04spdJAvpCU4EOOd925g=', '', '', '', null, '14507', '경기도 부천시 원미구 계남로 81', '111', null, '', 'ORG002', 'GROUP_SYSTEM', 'NSTT001', '', null, 'INACTIVE', null, '', '010-4179-2158', null, '', 'Y', null, 'N', 'N', 0, null, '2025-05-19 13:59:31', 'SYSTEM', '2025-05-21 12:48:46', 'SYSTEM');
INSERT INTO xitframework.tb_user (USER_ID, USER_ACNT, USER_NM, PASSWD, PASSWD_HINT, PASSWD_NSR, EMP_NO, GENDER, ZIP, ADDR, DADDR, AREA_NO, EML_ADDR, ORG_CD, USER_GROUP_ID, NSTT_CD, POS_NM, CRTFC_DN, USER_STATUS_CD, FXNO, TELNO, MBL_TELNO, BRDT, DEPT_CD, USE_YN, RSDNT_NO, PASSWD_INIT_YN, LOCK_YN, LOCK_CNT, LOCK_DTTM, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER00000006', 'system', '시스템 관리자', '7nB/w2O5OvlSbFgYgAA6MRLI1GEeFFSNXXMwCcu2jl8=', null, null, '23r23r32', null, '14507', '경기도 부천시 원미구 계남로 81', '34t34t', null, '', 'ORG001', 'GROUP_APPROVER', 'NSTT001', '', null, 'ACTIVE', null, '', '', null, 'DEPT002', 'Y', null, 'Y', 'N', 0, '2025-07-08 14:07:23', '2025-05-21 12:49:17', 'SYSTEM', '2025-07-17 14:40:23', 'ADMIN');
INSERT INTO xitframework.tb_user (USER_ID, USER_ACNT, USER_NM, PASSWD, PASSWD_HINT, PASSWD_NSR, EMP_NO, GENDER, ZIP, ADDR, DADDR, AREA_NO, EML_ADDR, ORG_CD, USER_GROUP_ID, NSTT_CD, POS_NM, CRTFC_DN, USER_STATUS_CD, FXNO, TELNO, MBL_TELNO, BRDT, DEPT_CD, USE_YN, RSDNT_NO, PASSWD_INIT_YN, LOCK_YN, LOCK_CNT, LOCK_DTTM, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER00000051', 'admin1', '관리자', 'Z6XYLOi+w3ukRN5nn08WSYg04spdJAvpCU4EOOd925g=', null, null, '', null, '14507', '경기도 부천시 원미구 계남로 81', 'ㄷㄹㅈㄷㄹㅈㄹㅈㄹㅈㄷㄹㅈㄷ', null, '', '', 'GROUP_ADMIN', 'NSTT001', '', null, 'ACTIVE', null, '', '', null, '', 'Y', null, 'N', 'N', 0, null, '2025-05-28 10:31:16', 'ADMIN', '2025-07-07 14:51:39', 'USER00000051');

@ -0,0 +1,180 @@
/**
* XIT Framework Gradle
*
* :
* - Spring Boot: 2.7.18 ( )
* - Java: 1.8 ( )
* - : 4.3.0
*/
plugins {
// -
id 'org.springframework.boot' version '2.7.18'
// -
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
// -
id 'java'
// WAR -
id 'war'
}
//
group = 'go.kr.project'
version = '0.0.1-SNAPSHOT'
// ( )
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// Java
//tasks.withType(JavaCompile) {
// options.encoding = 'UTF-8'
// options.compilerArgs += [
// '-Xlint:deprecation',
// '-Xlint:unchecked'
// ]
//}
//
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
//
repositories {
mavenCentral() //
maven { url 'https://maven.egovframe.go.kr/maven/' } //
}
//
ext {
//
tomcatVersion = '9.0.78' // 9 (Servlet 3.1 )
//
tilesVersion = '3.0.8' //
mybatisVersion = '2.3.1' //
commonsTextVersion = '1.10.0' //
egovFrameVersion = '4.3.0' //
}
dependencies {
// ===== =====
// - MVC, REST,
implementation 'org.springframework.boot:spring-boot-starter-web'
// - Bean Validation API
implementation 'org.springframework.boot:spring-boot-starter-validation'
// AOP -
implementation 'org.springframework.boot:spring-boot-starter-aop'
// JDBC - JDBC (HikariCP)
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
//implementation 'org.springframework.boot:spring-boot-starter-data-jpa' /* 실제 사용 X, intellij plugin 사용을 위해 설정 */
// ===== =====
// -
implementation("org.egovframe.rte:org.egovframe.rte.fdl.cmmn:${egovFrameVersion}") {
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl' /* 보안이슈 대응 후 쿼리로그 문제 발생, SLF4J 충돌 발생 */
}
// MVC - MVC
implementation("org.egovframe.rte:org.egovframe.rte.ptl.mvc:${egovFrameVersion}") {
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl' /* 보안이슈 대응 후 쿼리로그 문제 발생, SLF4J 충돌 발생 */
}
// ===== =====
// Logback -
implementation 'ch.qos.logback:logback-classic'
implementation 'ch.qos.logback:logback-core'
// SLF4J -
implementation 'org.slf4j:slf4j-api'
// ===== 릿 & JSP =====
// JSTL - JSP
implementation 'javax.servlet:jstl'
// ===== =====
// 9 (Servlet 3.1 )
implementation "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}"
implementation "org.apache.tomcat.embed:tomcat-embed-el:${tomcatVersion}"
implementation "org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
// ===== =====
// - 릿 (JSP )
implementation "org.apache.tiles:tiles-jsp:${tilesVersion}"
implementation "org.apache.tiles:tiles-core:${tilesVersion}"
implementation "org.apache.tiles:tiles-api:${tilesVersion}"
implementation "org.apache.tiles:tiles-servlet:${tilesVersion}"
implementation "org.apache.tiles:tiles-el:${tilesVersion}"
// ===== =====
// MyBatis - SQL
implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisVersion}"
// MariaDB JDBC - MariaDB
implementation 'org.mariadb.jdbc:mariadb-java-client'
// HikariCP - JDBC (spring-boot-starter-jdbc )
// ===== =====
// Apache Commons Text -
implementation "org.apache.commons:commons-text:${commonsTextVersion}"
// ===== EXCEL =====
implementation 'org.apache.poi:poi:5.3.0'
implementation 'org.apache.poi:poi-ooxml:5.3.0'
// ===== Swagger UI =====
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.7.0'
// ===== sqlPaser =====
implementation 'com.github.jsqlparser:jsqlparser:4.5'
// ===== =====
// Lombok - (Getter, Setter, Builder )
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// - ,
developmentOnly 'org.springframework.boot:spring-boot-devtools'
// ===== =====
// - JUnit, Mockito
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// ===== =====
// 릿 API -
providedCompile "org.apache.tomcat:tomcat-servlet-api:${tomcatVersion}"
// -
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
// ===== =====
// JUnit (JUnit 5 )
tasks.named('test') {
useJUnitPlatform()
}
// ===== WAR =====
// WAR
war {
archiveFileName = 'IBMS-NEW.war'
}
// ===== bootWar =====
// bootWar
bootWar {
archiveFileName = 'IBMS-NEW-BOOT.war'
}
// war ,
// ./build/exploded/xit-framework/
tasks.register('exploded', Copy) {
dependsOn 'war'
from zipTree(tasks.war.archiveFile)
into layout.buildDirectory.dir("exploded/${project.name}")
doFirst {
layout.buildDirectory.dir("exploded/${project.name}").get().asFile.deleteDir()
}
}

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
gradlew vendored

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat vendored

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1,66 @@
package egovframework.config;
import egovframework.exception.EgovAopExceptionTransfer;
import egovframework.exception.EgovDefaultExcepHndlr;
import egovframework.exception.EgovDefaultOthersExcepHndlr;
import org.egovframe.rte.fdl.cmmn.aspect.ExceptionTransfer;
import org.egovframe.rte.fdl.cmmn.exception.handler.ExceptionHandler;
import org.egovframe.rte.fdl.cmmn.exception.manager.DefaultExceptionHandleManager;
import org.egovframe.rte.fdl.cmmn.exception.manager.ExceptionHandlerService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.util.AntPathMatcher;
@Configuration
@EnableAspectJAutoProxy
public class EgovConfigAspect {
@Bean
public EgovDefaultExcepHndlr egovHandler() {
return new EgovDefaultExcepHndlr();
}
@Bean
public EgovDefaultOthersExcepHndlr otherHandler() {
return new EgovDefaultOthersExcepHndlr();
}
@Bean
public DefaultExceptionHandleManager defaultExceptionHandleManager(AntPathMatcher antPathMatcher, EgovDefaultExcepHndlr egovHandler) {
DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager();
defaultExceptionHandleManager.setReqExpMatcher(antPathMatcher);
defaultExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
defaultExceptionHandleManager.setHandlers(new ExceptionHandler[]{egovHandler});
return defaultExceptionHandleManager;
}
@Bean
public DefaultExceptionHandleManager otherExceptionHandleManager(AntPathMatcher antPathMatcher, EgovDefaultOthersExcepHndlr othersExcepHndlr) {
DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager();
defaultExceptionHandleManager.setReqExpMatcher(antPathMatcher);
defaultExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
defaultExceptionHandleManager.setHandlers(new ExceptionHandler[]{othersExcepHndlr});
return defaultExceptionHandleManager;
}
@Bean
public ExceptionTransfer exceptionTransfer(
@Qualifier("defaultExceptionHandleManager") DefaultExceptionHandleManager defaultExceptionHandleManager,
@Qualifier("otherExceptionHandleManager") DefaultExceptionHandleManager otherExceptionHandleManager) {
ExceptionTransfer exceptionTransfer = new ExceptionTransfer();
exceptionTransfer.setExceptionHandlerService(new ExceptionHandlerService[] {
defaultExceptionHandleManager, otherExceptionHandleManager
});
return exceptionTransfer;
}
@Bean
public EgovAopExceptionTransfer aopExceptionTransfer(ExceptionTransfer exceptionTransfer) {
EgovAopExceptionTransfer egovAopExceptionTransfer = new EgovAopExceptionTransfer();
egovAopExceptionTransfer.setExceptionTransfer(exceptionTransfer);
return egovAopExceptionTransfer;
}
}

@ -0,0 +1,60 @@
package egovframework.config;
import org.egovframe.rte.fdl.cmmn.trace.LeaveaTrace;
import org.egovframe.rte.fdl.cmmn.trace.handler.DefaultTraceHandler;
import org.egovframe.rte.fdl.cmmn.trace.handler.TraceHandler;
import org.egovframe.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager;
import org.egovframe.rte.fdl.cmmn.trace.manager.TraceHandlerService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.util.AntPathMatcher;
@Configuration
public class EgovConfigCommon {
@Bean
public AntPathMatcher antPathMatcher() {
return new AntPathMatcher();
}
@Bean
public DefaultTraceHandler defaultTraceHandler() {
return new DefaultTraceHandler();
}
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();
reloadableResourceBundleMessageSource.setBasenames(
"classpath:/egovframework/message/message-common",
"classpath:/org/egovframe/rte/fdl/idgnr/messages/idgnr",
"classpath:/org/egovframe/rte/fdl/property/messages/properties");
reloadableResourceBundleMessageSource.setDefaultEncoding("UTF-8");
reloadableResourceBundleMessageSource.setCacheSeconds(60);
return reloadableResourceBundleMessageSource;
}
@Bean
public MessageSourceAccessor messageSourceAccessor() {
return new MessageSourceAccessor(this.messageSource());
}
@Bean
public DefaultTraceHandleManager traceHandlerService() {
DefaultTraceHandleManager defaultTraceHandleManager = new DefaultTraceHandleManager();
defaultTraceHandleManager.setReqExpMatcher(antPathMatcher());
defaultTraceHandleManager.setPatterns(new String[]{"*"});
defaultTraceHandleManager.setHandlers(new TraceHandler[]{defaultTraceHandler()});
return defaultTraceHandleManager;
}
@Bean
public LeaveaTrace leaveaTrace() {
LeaveaTrace leaveaTrace = new LeaveaTrace();
leaveaTrace.setTraceHandlerServices(new TraceHandlerService[]{traceHandlerService()});
return leaveaTrace;
}
}

@ -0,0 +1,42 @@
package egovframework.config;
import egovframework.configProperties.InterceptorProperties;
import egovframework.interceptor.AuthInterceptor;
import go.kr.project.login.service.LoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*
*/
@Configuration
@RequiredArgsConstructor
public class EgovConfigInterceptor implements WebMvcConfigurer {
private final LoginService loginService;
private final InterceptorProperties interceptorProperties;
/**
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor())
.addPathPatterns("/**") // 모든 경로에 적용
.excludePathPatterns(interceptorProperties.getInterceptorExclude()); // 접근 제어 예외 URL 패턴 제외
}
/**
*
* @return AuthInterceptor
*/
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor(loginService);
}
}

@ -0,0 +1,95 @@
package egovframework.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
/**
* @ClassName : EgovConfigAppTransaction.java
* @Description : Transaction
*
* @author :
* @since : 2021. 7. 20
* @version : 1.0
*
* <pre>
* << (Modification Information) >>
*
*
* ------------- ------------ ---------------------
* 2021. 7. 20
* </pre>
*
*/
@Slf4j
@Configuration
public class EgovConfigTransaction {
@Autowired
DataSource dataSource;
@PostConstruct
public void init() {
log.info("Datasource type: {}", dataSource.getClass().getName());
}
@Bean
public DataSourceTransactionManager txManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
// -------------------------------------------------------------
// TransactionAdvice 설정
// -------------------------------------------------------------
@Bean
public TransactionInterceptor txAdvice(DataSourceTransactionManager txManager) {
TransactionInterceptor txAdvice = new TransactionInterceptor();
txAdvice.setTransactionManager(txManager);
txAdvice.setTransactionAttributeSource(getNameMatchTransactionAttributeSource());
return txAdvice;
}
private NameMatchTransactionAttributeSource getNameMatchTransactionAttributeSource() {
NameMatchTransactionAttributeSource txAttributeSource = new NameMatchTransactionAttributeSource();
txAttributeSource.setNameMap(getRuleBasedTxAttributeMap());
return txAttributeSource;
}
private HashMap<String, TransactionAttribute> getRuleBasedTxAttributeMap() {
HashMap<String, TransactionAttribute> txMethods = new HashMap<String, TransactionAttribute>();
RuleBasedTransactionAttribute txAttribute = new RuleBasedTransactionAttribute();
txAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txAttribute.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
txMethods.put("*", txAttribute);
return txMethods;
}
// -------------------------------------------------------------
// TransactionAdvisor 설정
// -------------------------------------------------------------
@Bean
public Advisor txAdvisor(DataSourceTransactionManager txManager) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(
"execution(* go.kr.project..impl.*Impl.*(..)) or execution(* egovframework.com..*Impl.*(..))");
return new DefaultPointcutAdvisor(pointcut, txAdvice(txManager));
}
}

@ -0,0 +1,20 @@
package egovframework.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class EgovConfigValidation {
@Bean
public Validator getValidator(@Qualifier("messageSource") MessageSource messageSource) {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(messageSource);
return localValidatorFactoryBean;
}
}

@ -0,0 +1,98 @@
package egovframework.config;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import java.util.List;
@Setter
@Configuration
@RequiredArgsConstructor
@Import({
EgovConfigAspect.class,
EgovConfigCommon.class,
EgovConfigTransaction.class,
EgovConfigValidation.class,
EgovConfigInterceptor.class,
EgovErrorConfig.class
})
public class EgovConfigWeb implements WebMvcConfigurer, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/plugins/**")
.addResourceLocations("/resources/plugins/")
.setCacheControl(CacheControl.noCache().mustRevalidate());
registry.addResourceHandler("/css/**")
.addResourceLocations("/resources/css/")
.setCacheControl(CacheControl.noCache().mustRevalidate());
registry.addResourceHandler("/img/**")
.addResourceLocations("/resources/img/")
.setCacheControl(CacheControl.noCache().mustRevalidate());
registry.addResourceHandler("/js/**")
.addResourceLocations("/resources/js/")
.setCacheControl(CacheControl.noCache().mustRevalidate());
registry.addResourceHandler("/xit/**")
.addResourceLocations("/resources/xit/")
.setCacheControl(CacheControl.noCache().mustRevalidate());
registry.addResourceHandler("/font/**")
.addResourceLocations("/resources/font/")
.setCacheControl(CacheControl.noCache().mustRevalidate());
}
/**
* Exception handling is now done using @ControllerAdvice in EgovExceptionAdvice class.
* The SimpleMappingExceptionResolver approach has been replaced with a more modern approach.
*
* @see egovframework.exception.EgovExceptionAdvice
*/
@Override
public void configureHandlerExceptionResolvers(@Nullable List< HandlerExceptionResolver> resolvers) {
// Exception handling is now done using @ControllerAdvice
}
@Bean
public UrlBasedViewResolver viewResolver() {
UrlBasedViewResolver tilesViewResolver = new UrlBasedViewResolver();
tilesViewResolver.setViewClass(TilesView.class);
tilesViewResolver.setOrder(1);
return tilesViewResolver;
}
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tilesConfigurer = new TilesConfigurer();
tilesConfigurer.setDefinitions(
"/WEB-INF/tiles/tiles.xml"
);
tilesConfigurer.setCheckRefresh(true);
return tilesConfigurer;
}
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(2);
return resolver;
}
}

@ -0,0 +1,46 @@
package egovframework.config;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
/**
*
* 404, 500 HTTP .
*/
@Configuration
public class EgovErrorConfig {
/**
* ErrorPageRegistrar .
*
* @return ErrorPageRegistrar
*/
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return new ErrorPageRegistrar() {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
// 404 에러 페이지 설정
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
// 500 에러 페이지 설정
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
// 기타 서버 에러 페이지 설정
ErrorPage error502Page = new ErrorPage(HttpStatus.BAD_GATEWAY, "/error/500");
ErrorPage error503Page = new ErrorPage(HttpStatus.SERVICE_UNAVAILABLE, "/error/500");
ErrorPage error504Page = new ErrorPage(HttpStatus.GATEWAY_TIMEOUT, "/error/500");
// 예외 처리를 위한 에러 페이지 설정
ErrorPage runtimeExceptionPage = new ErrorPage(RuntimeException.class, "/error/500");
// 에러 페이지 등록
registry.addErrorPages(error404Page, error500Page, error502Page, error503Page, error504Page, runtimeExceptionPage);
}
};
}
}

@ -0,0 +1,34 @@
package egovframework.config;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import org.apache.commons.text.StringEscapeUtils;
public class HtmlCharacterEscapes extends CharacterEscapes {
private static final long serialVersionUID = -6353236148390563705L;
private final int[] asciiEscapes;
public HtmlCharacterEscapes() {
this.asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
this.asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
this.asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
this.asciiEscapes['\"'] = CharacterEscapes.ESCAPE_CUSTOM;
this.asciiEscapes['('] = CharacterEscapes.ESCAPE_CUSTOM;
this.asciiEscapes[')'] = CharacterEscapes.ESCAPE_CUSTOM;
this.asciiEscapes['#'] = CharacterEscapes.ESCAPE_CUSTOM;
this.asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
}
@Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
@Override
public SerializableString getEscapeSequence(int ch) {
return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));
}
}

@ -0,0 +1,27 @@
package egovframework.config;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* MyBatis Configuration
*
* This class configures MyBatis by adding custom interceptors.
*/
@Configuration
@Profile({"local", "dev"}) // local과 dev 프로파일에서만 활성화
public class MyBatisConfig {
/**
* Customizes the MyBatis configuration by adding the query interceptor.
*
* @param interceptor The MyBatis query interceptor
* @return A ConfigurationCustomizer that adds the interceptor to MyBatis
*/
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer(MyBatisQueryInterceptor interceptor) {
return configuration -> configuration.addInterceptor(interceptor);
}
}

@ -0,0 +1,449 @@
package egovframework.config;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* MyBatis Query Interceptor
*
* This interceptor logs detailed information about SQL queries executed by MyBatis,
* including the original SQL, actual SQL with parameters, and parameter values.
*/
@Component
@Profile({"local", "dev"}) // local과 dev 프로파일에서만 활성화
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class MyBatisQueryInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 실제 실행될 쿼리 추출 (MyBatis 내부에서 처리된 결과)
String actualSql = getActualSql(boundSql, parameter);
// 파라미터 정보 추출
Map<String, Object> paramMap = extractDetailedParameters(parameter, parameterMappings);
logDetailedQueryInfo(ms.getId(), sql, actualSql, paramMap);
// 원래 쿼리 실행
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
// 쿼리 실행 시간 로깅
log.debug("Query execution time: {} ms", (endTime - startTime));
return result;
}
private String getActualSql(BoundSql boundSql, Object parameter) {
String sql = boundSql.getSql();
if (parameter == null) {
return sql;
}
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings.isEmpty()) {
return sql;
}
// SQL 복사본 생성
String actualSql = sql;
try {
// 파라미터 값 추출
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
Object value = null;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameter instanceof Map) {
value = ((Map<?, ?>) parameter).get(propertyName);
} else if (parameter instanceof String) {
// String 타입 파라미터 처리
value = parameter;
} else if (parameter instanceof Number) {
// Number 타입 파라미터 처리 (Integer, Long, Double 등)
value = parameter;
} else if (parameter instanceof Boolean) {
// Boolean 타입 파라미터 처리
value = parameter;
} else {
value = getParameterValue(parameter, propertyName);
}
String valueStr = value != null ? value.toString() : "null";
// SQL 인젝션 방지를 위한 문자열 이스케이프 처리
valueStr = valueStr.replace("'", "''");
// 다양한 데이터 타입에 대한 처리
if (value instanceof String) {
// 문자열 타입은 따옴표로 감싸기
actualSql = actualSql.replaceFirst("\\?", "'" + valueStr + "'");
} else if (value instanceof Number) {
// 숫자 타입 (Integer, Long, Double 등)은 그대로 사용
actualSql = actualSql.replaceFirst("\\?", valueStr);
} else if (value instanceof Boolean) {
// Boolean 타입은 그대로 사용
actualSql = actualSql.replaceFirst("\\?", valueStr);
} else if (value instanceof java.util.Date) {
// 표준 타임스탬프 포맷 사용
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
actualSql = actualSql.replaceFirst("\\?", "'" + sdf.format(value) + "'");
} else if (value instanceof java.time.LocalDateTime) {
// Java 8 LocalDateTime 처리
actualSql = actualSql.replaceFirst("\\?", "'" + value.toString().replace('T', ' ') + "'");
} else {
// 기타 타입은 null이 아닌 경우 따옴표로 감싸기
actualSql = actualSql.replaceFirst("\\?", value != null ? "'" + valueStr + "'" : valueStr);
}
}
} catch (Exception e) {
log.warn("Failed to get actual SQL with parameters", e);
return sql;
}
return actualSql;
}
private Object getParameterValue(Object parameter, String propertyName) {
try {
// 현재 클래스부터 상위 클래스까지 순회하면서 필드 찾기
Class<?> currentClass = parameter.getClass();
while (currentClass != null) {
try {
Field field = currentClass.getDeclaredField(propertyName);
field.setAccessible(true);
return field.get(parameter);
} catch (NoSuchFieldException e) {
// 현재 클래스에서 필드를 찾지 못하면 상위 클래스로 이동
currentClass = currentClass.getSuperclass();
}
}
throw new NoSuchFieldException(propertyName);
} catch (Exception e) {
log.warn("필드 값 추출 실패: {} ({})", propertyName, e.getMessage());
return null;
}
}
private Map<String, Object> extractDetailedParameters(Object parameter, List<ParameterMapping> parameterMappings) {
Map<String, Object> paramMap = new HashMap<>();
if (parameter == null) {
return paramMap;
}
if (parameter instanceof Map) {
paramMap.putAll((Map<String, Object>) parameter);
} else {
// 객체의 필드 정보도 추출
paramMap.put("param", parameter);
extractObjectFields(parameter, paramMap);
}
return paramMap;
}
private void extractObjectFields(Object obj, Map<String, Object> paramMap) {
try {
Class<?> currentClass = obj.getClass();
while (currentClass != null) {
Field[] fields = currentClass.getDeclaredFields();
for (Field field : fields) {
try {
// String 타입의 객체는 건너뛰기
if (obj instanceof String) {
continue;
}
// 시스템 클래스의 필드는 건너뛰기
if (field.getDeclaringClass().getName().startsWith("java.")) {
continue;
}
field.setAccessible(true);
Object value = field.get(obj);
paramMap.put(field.getName(), value);
} catch (IllegalAccessException | SecurityException e) {
// 개별 필드 접근 실패는 무시하고 계속 진행
log.debug("필드 접근 실패: {} ({})", field.getName(), e.getMessage());
}
}
currentClass = currentClass.getSuperclass();
}
} catch (Exception e) {
log.warn("객체 필드 추출 실패", e);
}
}
private void logDetailedQueryInfo(String mapperMethod, String originalSql, String actualSql, Map<String, Object> paramMap) {
StringBuilder logMessage = new StringBuilder();
logMessage.append("\n");
logMessage.append("┌─────────────── MyBatis Query Details ───────────────\n");
logMessage.append("│ Mapper Method: ").append(mapperMethod).append("\n");
logMessage.append("│ Parameters: ").append(paramMap).append("\n");
// 원본 SQL 포맷팅, prd, 운영
//logMessage.append("│ Original SQL:\n");
//formatSqlInLog(logMessage, originalSql);
// 실제 실행 SQL 포맷팅, local, dev 로컬 개발
logMessage.append("│ Actual SQL:\n");
formatSqlInLog(logMessage, actualSql);
logMessage.append("└──────────────────────────────────────────────────────");
log.info(logMessage.toString());
}
private void formatSqlInLog(StringBuilder logMessage, String sql) {
// SQL 키워드 하이라이트 및 들여쓰기
String formattedSql = formatSql(sql);
String[] lines = formattedSql.split("\n");
for (String line : lines) {
//logMessage.append("│ ").append(line).append("\n");
logMessage.append(line).append("\n");
}
}
private String formatSql(String sql) {
try {
// SQL 파서를 사용한 포맷팅
Statement statement = CCJSqlParserUtil.parse(sql);
return formatStatement(statement, 0);
} catch (JSQLParserException e) {
log.debug("SQL 파싱 실패. 기본 포맷팅으로 대체합니다.", e);
log.info("SQL 파싱 실패. 기본 포맷팅으로 대체합니다.");
// 파싱 실패 시 기본 포맷팅 사용
return sql.replaceAll("\\s+", " ").trim();
}
}
private String formatStatement(Statement statement, int indent) {
StringBuilder result = new StringBuilder();
String indentation = String.join("", java.util.Collections.nCopies(indent, " "));
if (statement instanceof Select) {
formatSelect((Select) statement, result, indent);
} else if (statement instanceof Insert) {
formatInsert((Insert) statement, result, indent);
} else if (statement instanceof Update) {
formatUpdate((Update) statement, result, indent);
} else if (statement instanceof Delete) {
formatDelete((Delete) statement, result, indent);
}
return result.toString();
}
private void formatSelect(Select select, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
// SELECT 절
result.append(indentation).append("SELECT\n");
formatSelectItems(plainSelect.getSelectItems(), result, indent + 2);
// FROM 절
if (plainSelect.getFromItem() != null) {
result.append(indentation).append("FROM\n");
result.append(indentation).append(" ").append(plainSelect.getFromItem()).append("\n");
}
// JOIN 절
if (plainSelect.getJoins() != null) {
for (Join join : plainSelect.getJoins()) {
result.append(indentation).append(join.isLeft() ? "LEFT JOIN " : "JOIN ")
.append(join.getRightItem()).append("\n");
if (join.getOnExpression() != null) {
result.append(indentation).append(" ON ").append(join.getOnExpression()).append("\n");
}
}
}
// WHERE 절
if (plainSelect.getWhere() != null) {
result.append(indentation).append("WHERE\n");
result.append(indentation).append(" ").append(plainSelect.getWhere()).append("\n");
}
// GROUP BY 절
if (plainSelect.getGroupBy() != null) {
result.append(indentation).append("GROUP BY\n");
result.append(indentation).append(" ")
.append(plainSelect.getGroupBy().getGroupByExpressions().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
// HAVING 절
if (plainSelect.getHaving() != null) {
result.append(indentation).append("HAVING\n");
result.append(indentation).append(" ").append(plainSelect.getHaving()).append("\n");
}
// ORDER BY 절
if (plainSelect.getOrderByElements() != null) {
result.append(indentation).append("ORDER BY\n");
result.append(indentation).append(" ")
.append(plainSelect.getOrderByElements().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
}
private void formatSelectItems(List<SelectItem> items, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
for (int i = 0; i < items.size(); i++) {
result.append(indentation).append(items.get(i));
if (i < items.size() - 1) {
result.append(",");
}
result.append("\n");
}
}
private void formatInsert(Insert insert, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
result.append(indentation).append("INSERT INTO ").append(insert.getTable()).append("\n");
// 컬럼 목록
if (insert.getColumns() != null) {
result.append(indentation).append("(")
.append(insert.getColumns().stream()
.map(Column::getColumnName)
.collect(Collectors.joining(", ")))
.append(")\n");
}
result.append(indentation).append("VALUES\n");
// VALUES 절 포맷팅
if (insert.getItemsList() != null) {
result.append(indentation).append(" ").append(insert.getItemsList()).append("\n");
}
}
private void formatUpdate(Update update, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
// UPDATE 절
result.append(indentation).append("UPDATE\n");
result.append(indentation).append(" ").append(update.getTable()).append("\n");
// SET 절
result.append(indentation).append("SET\n");
List<UpdateSet> updateSets = update.getUpdateSets();
for (int i = 0; i < updateSets.size(); i++) {
UpdateSet updateSet = updateSets.get(i);
result.append(indentation).append(" ")
.append(updateSet.getColumns().get(0))
.append(" = ")
.append(updateSet.getExpressions().get(0));
if (i < updateSets.size() - 1) {
result.append(",");
}
result.append("\n");
}
// WHERE 절
if (update.getWhere() != null) {
result.append(indentation).append("WHERE\n");
result.append(indentation).append(" ").append(update.getWhere()).append("\n");
}
// ORDER BY 절 (일부 데이터베이스에서 지원)
if (update.getOrderByElements() != null) {
result.append(indentation).append("ORDER BY\n");
result.append(indentation).append(" ")
.append(update.getOrderByElements().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
// LIMIT 절 (일부 데이터베이스에서 지원)
if (update.getLimit() != null) {
result.append(indentation).append("LIMIT ")
.append(update.getLimit()).append("\n");
}
}
private void formatDelete(Delete delete, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
// DELETE 절
result.append(indentation).append("DELETE FROM\n");
result.append(indentation).append(" ").append(delete.getTable()).append("\n");
// WHERE 절
if (delete.getWhere() != null) {
result.append(indentation).append("WHERE\n");
result.append(indentation).append(" ").append(delete.getWhere()).append("\n");
}
// ORDER BY 절 (일부 데이터베이스에서 지원)
if (delete.getOrderByElements() != null) {
result.append(indentation).append("ORDER BY\n");
result.append(indentation).append(" ")
.append(delete.getOrderByElements().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
// LIMIT 절 (일부 데이터베이스에서 지원)
if (delete.getLimit() != null) {
result.append(indentation).append("LIMIT ")
.append(delete.getLimit()).append("\n");
}
}
}

@ -0,0 +1,73 @@
package egovframework.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.SpringDocUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Swagger UI
* OpenAPI 3.0 API .
* .
* @Controller .
*/
@Configuration
public class SwaggerConfig {
static {
// @Controller 어노테이션이 있는 클래스도 스캔하도록 설정
SpringDocUtils.getConfig().addAnnotationsToIgnore(RequestMapping.class);
}
/**
* API GroupedOpenApi .
*
* @return GroupedOpenApi
*/
@Bean
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("All")
.packagesToScan("go.kr.project")
.pathsToMatch("/**")
.build();
}
/**
* API GroupedOpenApi .
*
* @return GroupedOpenApi
*/
@Bean
public GroupedOpenApi loginApi() {
return GroupedOpenApi.builder()
.group("Login")
.packagesToScan("go.kr.project.login")
.pathsToMatch("/login/**")
.build();
}
/**
* OpenAPI .
* API (, , ) .
*
* @return OpenAPI
*/
@Bean
public OpenAPI openAPI() {
Info info = new Info()
.title("XIT Framework API")
.description("XIT Framework API 문서 - 세션 기반 인증 사용")
.version("v1.0.0")
.license(new License().name("Apache 2.0").url("http://springdoc.org"));
return new OpenAPI()
.components(new Components())
.info(info);
}
}

@ -0,0 +1,47 @@
package egovframework.configProperties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* packageName : egovframework.config
* fileName : FileUploadProperties
* author :
* date : 25. 5. 23.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 23.
*/
@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "file.upload")
public class FileUploadProperties {
/** 파일 저장 기본 경로 */
private String path;
/** 최대 파일 크기 (단일 파일) - 기본값 10MB */
private long maxSize;
/** 최대 총 파일 크기 - 기본값 50MB */
private long maxTotalSize;
/** 허용된 파일 확장자 */
private String allowedExtensions;
/** 최대 파일 개수 - 기본값 10개 */
private int maxFiles;
/** 실제 파일 삭제 여부 - 기본값 true */
private boolean realFileDelete;
/** 하위 디렉토리 설정 */
private Map<String, String> subDirs;
}

@ -0,0 +1,31 @@
package egovframework.configProperties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* packageName : egovframework.config
* fileName : InterceptorProperties
* author :
* date : 25. 5. 19.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 19.
*/
@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "interceptor")
public class InterceptorProperties {
private List<String> interceptorExclude;
private List<String> refererExclude;
}

@ -0,0 +1,35 @@
package egovframework.configProperties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* packageName : egovframework.config
* fileName : LoginProperties
* author :
* date : 25. 5. 19.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 19.
* 25. 5. 22.
*/
@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "login")
public class LoginProperties {
private String url; // 로그인 페이지 URL
private Lock lock; // 로그인 잠금 설정
private boolean allowMultipleLogin = true; // 동시 접속 가능 여부 (기본값: true)
@Setter
@Getter
public static class Lock {
private int count; // 비밀번호 잠김 최종 카운트
}
}

@ -0,0 +1,113 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* fileName : BatchConstants
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
public class BatchConstants {
/**
* (TB_BATCH_JOB_INFO.STATUS)
*/
public static final String JOB_INFO_STATUS_ACTIVE = "ACTIVE";
public static final String JOB_INFO_STATUS_PAUSED = "PAUSED";
public static final String JOB_INFO_STATUS_DELETED = "DELETED";
/**
* (TB_BATCH_JOB_EXECUTION.STATUS)
* - ,,,
*/
public static final String JOB_EXECUTION_STATUS_STARTED = "STARTED";
public static final String JOB_EXECUTION_STATUS_COMPLETED = "COMPLETED";
public static final String JOB_EXECUTION_STATUS_FAILED = "FAILED";
public static final String JOB_EXECUTION_STATUS_VETOED = "VETOED";
/**
* (TB_BATCH_JOB_EXECUTION.EXIT_CODE)
*/
public static final String JOB_EXECUTION_EXIT_COMPLETED = "COMPLETED";
public static final String JOB_EXECUTION_EXIT_FAILED = "FAILED";
public static final String JOB_EXECUTION_EXIT_UNKNOWN = "UNKNOWN";
public static final String JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED";
/**
*
*/
public static final String LOG_LEVEL_INFO = "INFO";
public static final String LOG_LEVEL_WARN = "WARN";
public static final String LOG_LEVEL_ERROR = "ERROR";
/**
*
*/
public static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";
/**
* ()
*/
public static final long TIME_ITEM_PROCESS_DELAY = 100; // 0.1초
public static final long TIME_JOB_DELAY_TEST = 3000; // 3초 (테스트용)
public static final long TIME_JOB_DELAY_1_MIN = 60000; // 1분
public static final long TIME_JOB_DELAY_3_MIN = 180000; // 3분
public static final long TIME_JOB_DELAY_5_MIN = 300000; // 5분
/**
* JobDataMap
*/
public static final String JOB_DATA_EXECUTION_ID = "executionId";
/**
*
*/
public static final String JOB_NAME_SAMPLE = "sampleJob";
public static final String JOB_GROUP_SAMPLE = "sampleGroup";
public static final String JOB_TRIGGER_SUFFIX = "Trigger";
/**
* Cron
*/
public static final String CRON_EVERY_MINUTE = "0 * * * * ?";
public static final String CRON_EVERY_ONE_SECOND = "1 0 * * * ?";
/**
*
*/
public static final String JOB_DESC_SAMPLE = "샘플 배치 작업 (매 분마다 실행)";
/**
* 릿
*/
// 기본 배치 작업 로그 메시지
public static final String LOG_MSG_BATCH_JOB_START = "===== 배치 작업 시작: %s.%s - %s =====";
public static final String LOG_MSG_BATCH_JOB_COMPLETE = "===== 배치 작업 완료: %s.%s - %s =====";
public static final String LOG_MSG_BATCH_JOB_PARTIAL_COMPLETE = "===== 배치 작업 부분 완료: %s.%s - 총 %d개 파일 중 처리: %d개, 성공: %d개, 실패: %d개 (%s) =====";
public static final String LOG_MSG_BATCH_JOB_FAILED = "===== 배치 작업 실패: %s.%s - 총 %d개 파일 중 처리: %d개, 성공: %d개, 실패: %d개 (%s) =====";
public static final String LOG_MSG_BATCH_JOB_ERROR = "===== 배치 작업 실행 중 오류 발생: %s.%s - %s =====";
/**
*
*/
public static final String LOG_MSG_BATCH_INIT_SERVER_BOOT_DETECTED = "서버 부팅 완료 감지 - 배치 작업 초기화를 5초 후에 시작합니다.";
public static final String LOG_MSG_BATCH_INIT_START = "배치 작업 초기화 시작";
public static final String LOG_MSG_BATCH_INIT_COMPLETE = "배치 작업 초기화 완료";
public static final String LOG_MSG_BATCH_INIT_INTERRUPT_ERROR = "배치 작업 초기화 대기 중 인터럽트 발생: {}";
public static final String LOG_MSG_BATCH_INIT_ERROR = "배치 작업 초기화 중 오류 발생: %s";
public static final String LOG_MSG_BATCH_INIT_NO_JOBS = "등록된 배치 작업이 없습니다.";
public static final String LOG_MSG_BATCH_INIT_JOBS_COUNT = "총 {}개의 배치 작업 정보를 조회했습니다.";
public static final String LOG_MSG_BATCH_INIT_SKIP_DELETED = "삭제된 작업은 스케줄러에 등록하지 않습니다: {}.{}";
public static final String LOG_MSG_BATCH_INIT_SCHEDULE_SUCCESS = "배치 작업을 스케줄러에 등록했습니다: {}.{}";
public static final String LOG_MSG_BATCH_INIT_SCHEDULE_FAILED = "배치 작업 스케줄링에 실패했습니다: {}.{}";
public static final String LOG_MSG_BATCH_INIT_CLASS_NOT_FOUND = "배치 작업 클래스를 찾을 수 없습니다: {}";
public static final String LOG_MSG_BATCH_INIT_SCHEDULE_ERROR = "배치 작업 스케줄링 중 오류 발생: {}.{} - {}";
public static final String LOG_MSG_BATCH_INIT_DB_ERROR = "배치 작업 초기화 중 오류 발생: {}";
public static final String LOG_MSG_BATCH_INIT_REGISTER_COMPLETE = "배치 작업 정보 등록 완료: %s";
public static final String LOG_MSG_BATCH_INIT_REGISTER_ERROR = "샘플 배치 작업 등록 중 오류 발생: %s";
}

@ -0,0 +1,65 @@
package egovframework.constant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* packageName : go.kr.project.common.constant
* fileName : FileContentTypeConstants
* author :
* date : 2025-05-17
* description : MIME
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-17
*/
public class FileContentTypeConstants {
/**
* MIME ( )
*/
public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
/**
* MIME
*/
private static final Map<String, String> CONTENT_TYPE_MAP;
static {
Map<String, String> map = new HashMap<>();
// 이미지 파일
map.put("jpg", "image/jpeg");
map.put("jpeg", "image/jpeg");
map.put("png", "image/png");
map.put("gif", "image/gif");
// 문서 파일
map.put("pdf", "application/pdf");
map.put("doc", "application/msword");
map.put("docx", "application/msword");
map.put("xls", "application/vnd.ms-excel");
map.put("xlsx", "application/vnd.ms-excel");
map.put("ppt", "application/vnd.ms-powerpoint");
map.put("pptx", "application/vnd.ms-powerpoint");
map.put("txt", "text/plain");
CONTENT_TYPE_MAP = Collections.unmodifiableMap(map);
}
/**
* MIME .
*
* @param fileExt ()
* @return MIME
*/
public static String getContentType(String fileExt) {
if (fileExt == null || fileExt.isEmpty()) {
return DEFAULT_CONTENT_TYPE;
}
return CONTENT_TYPE_MAP.getOrDefault(fileExt.toLowerCase(), DEFAULT_CONTENT_TYPE);
}
}

@ -0,0 +1,205 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* fileName : MessageConstants
* author :
* date : 2025-05-22
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-22
*/
public class MessageConstants {
/**
*
*/
public static class Login {
// 성공 메시지
public static final String SUCCESS = "로그인에 성공하였습니다.";
// 오류 메시지
public static final String ERROR_INVALID_CREDENTIALS = "아이디 또는 비밀번호가 일치하지 않습니다.";
public static final String ERROR_INACTIVE_ACCOUNT = "비활성화 계정입니다. 관리자에 문의하세요.";
public static final String ERROR_PROCESS = "로그인 처리 중 오류가 발생했습니다.";
}
/**
*
*/
public static class LoginFailure {
public static final String ABNORMAL_PATTERN_PREFIX = "비정상적인 로그인 패턴: ";
public static final String USER_NOT_FOUND = "사용자 계정 없음";
public static final String ACCOUNT_LOCKED = "계정 잠김";
public static final String PASSWORD_MISMATCH = "비밀번호 불일치";
public static final String ACCOUNT_INACTIVE = "계정 비활성화";
public static final String MESSAGE_ACCOUNT_LOCKED = "사용자 계정이 잠겼습니다.";
}
/**
*
*/
public static class Logout {
// 성공 메시지
public static final String SUCCESS = "로그아웃 되었습니다.";
// 오류 메시지
public static final String ERROR_PROCESS = "로그아웃 처리 중 오류가 발생했습니다.";
}
/**
*
*/
public static class Account {
// 계정 잠금 관련 메시지
public static final String UNLOCK_SUCCESS = "계정 잠금이 해제되었습니다.";
public static final String UNLOCK_FAIL = "계정 잠금 해제에 실패했습니다.";
public static final String UNLOCK_ERROR = "계정 잠금 해제 중 오류가 발생했습니다.";
}
/**
*
*/
public static class LoginLog {
// 목록 조회 관련 메시지
public static final String LIST_SUCCESS = "로그인 로그 목록을 조회하였습니다.";
public static final String LIST_ERROR = "로그인 로그 목록 조회 중 오류가 발생했습니다.";
// 상세 조회 관련 메시지
public static final String DETAIL_SUCCESS = "로그인 로그 상세 정보를 조회하였습니다.";
public static final String DETAIL_ERROR = "로그인 로그 상세 조회 중 오류가 발생했습니다.";
public static final String NOT_EXIST = "존재하지 않는 로그입니다.";
}
/**
*
*/
public static class Stats {
// 디바이스 유형별 통계
public static final String DEVICE_SUCCESS = "디바이스 유형별 로그인 통계를 조회하였습니다.";
public static final String DEVICE_ERROR = "디바이스 유형별 로그인 통계 조회 중 오류가 발생했습니다.";
// 성공/실패 비율 통계
public static final String SUCCESS_FAIL_SUCCESS = "로그인 성공/실패 비율 통계를 조회하였습니다.";
public static final String SUCCESS_FAIL_ERROR = "로그인 성공/실패 비율 통계 조회 중 오류가 발생했습니다.";
// 시간대별 통계
public static final String HOURLY_SUCCESS = "시간대별 로그인 시도 통계를 조회하였습니다.";
public static final String HOURLY_ERROR = "시간대별 로그인 시도 통계 조회 중 오류가 발생했습니다.";
}
/**
*
*/
public static class Auth {
// 메뉴-역할 관련 메시지
public static final String MENU_ROLE_ASSIGN_SUCCESS = "메뉴가 역할에 성공적으로 할당되었습니다.";
public static final String MENU_ROLE_REMOVE_SUCCESS = "메뉴가 역할에서 성공적으로 제거되었습니다.";
// 역할-그룹 관련 메시지
public static final String ROLE_GROUP_ASSIGN_SUCCESS = "역할이 그룹에 성공적으로 할당되었습니다.";
public static final String ROLE_GROUP_REMOVE_SUCCESS = "역할이 그룹에서 성공적으로 제거되었습니다.";
}
/**
*
*/
public static class Code {
// 코드 상세 관련 메시지
public static final String DETAIL_SAVE_SUCCESS = "코드 상세 정보가 성공적으로 저장되었습니다.";
// 그룹코드 관련 메시지
public static final String GROUP_SAVE_SUCCESS = "그룹코드 정보가 성공적으로 저장되었습니다.";
}
/**
*
*/
public static class Group {
// 그룹 저장 관련 메시지
public static final String SAVE_SUCCESS = "그룹이 성공적으로 저장되었습니다.";
}
/**
*
*/
public static class Menu {
// 성공 메시지
public static final String REGISTER_SUCCESS = "메뉴가 성공적으로 등록되었습니다.";
public static final String EDIT_SUCCESS = "메뉴 정보가 성공적으로 수정되었습니다.";
public static final String DELETE_SUCCESS = "메뉴가 성공적으로 삭제되었습니다.";
// 오류 메시지
public static final String ROOT_REGISTER_ERROR = "ROOT 메뉴 ID로 새 메뉴를 등록할 수 없습니다.";
public static final String UPPER_MENU_REQUIRED = "상위 메뉴를 선택해주세요.";
public static final String REGISTER_ERROR = "메뉴 등록에 실패했습니다.";
public static final String ROOT_EDIT_ERROR = "ROOT 메뉴는 수정할 수 없습니다.";
public static final String EDIT_ERROR = "메뉴 정보 수정에 실패했습니다.";
public static final String ROOT_DELETE_ERROR = "ROOT 메뉴는 삭제할 수 없습니다.";
public static final String CHILD_EXISTS_ERROR = "하위 메뉴가 존재하여 삭제할 수 없습니다.";
public static final String DELETE_ERROR = "메뉴 삭제에 실패했습니다.";
}
/**
*
*/
public static class Role {
// 성공 메시지
public static final String SAVE_SUCCESS = "역할이 성공적으로 저장되었습니다.";
// 오류 메시지
public static final String SAVE_ERROR = "역할 저장에 실패했습니다.";
}
/**
*
*/
public static class User {
// 성공 메시지
public static final String REGISTER_SUCCESS = "사용자가 성공적으로 등록되었습니다.";
public static final String EDIT_SUCCESS = "사용자 정보가 성공적으로 수정되었습니다.";
public static final String PASSWORD_RESET_SUCCESS = "비밀번호가 성공적으로 초기화되었습니다.";
public static final String UNLOCK_SUCCESS = "사용자 잠금이 성공적으로 해제되었습니다.";
// 오류 메시지
public static final String REGISTER_ERROR = "사용자 등록에 실패했습니다.";
public static final String EDIT_ERROR = "사용자 정보 수정에 실패했습니다.";
public static final String NOT_FOUND = "사용자 정보를 찾을 수 없습니다.";
public static final String PASSWORD_RESET_ERROR = "비밀번호 초기화에 실패했습니다.";
public static final String UNLOCK_ERROR = "사용자 잠금 해제에 실패했습니다.";
}
/**
*
*/
public static class UserGroup {
// 성공 메시지
public static final String GROUP_LIST_SUCCESS = "그룹 목록을 성공적으로 조회했습니다.";
public static final String ROLE_LIST_SUCCESS = "역할 목록을 성공적으로 조회했습니다.";
public static final String MENU_LIST_SUCCESS = "메뉴 목록을 성공적으로 조회했습니다.";
// 오류 메시지
public static final String GROUP_LIST_ERROR = "그룹 목록 조회 중 오류가 발생했습니다: ";
public static final String ROLE_LIST_ERROR = "역할 목록 조회 중 오류가 발생했습니다: ";
public static final String MENU_LIST_ERROR = "메뉴 목록 조회 중 오류가 발생했습니다: ";
}
/**
*
*/
public static class Common {
// 성공 메시지
public static final String SAVE_SUCCESS = "저장되었습니다.";
public static final String UPDATE_SUCCESS = "수정되었습니다.";
public static final String DELETE_SUCCESS = "삭제되었습니다.";
// 오류 메시지
public static final String SAVE_ERROR = "저장 중 오류가 발생했습니다.";
public static final String UPDATE_ERROR = "수정 중 오류가 발생했습니다.";
public static final String DELETE_ERROR = "삭제 중 오류가 발생했습니다.";
public static final String SYSTEM_ERROR = "시스템 오류가 발생했습니다.";
}
}

@ -0,0 +1,39 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* fileName : SessionConstants
* author :
* date : 2025-05-22
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-22
*/
public class SessionConstants {
/**
*
* SessionVO
*/
public static final String SESSION_KEY = "sessionVO";
/**
* ID
* ID
*/
public static final String VISITOR_ROLE_ID = "ROLE_VISITOR";
/**
*
*
*/
public static final String SESSION_EXPIRED = "SESSION_EXPIRED";
/**
*
* Referer
*/
public static final String INVALID_ACCESS = "INVALID_ACCESS";
}

@ -0,0 +1,23 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* fileName : TilesConstants
* author :
* date : 2025-05-22
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-22
*/
public class TilesConstants {
public static final String LOGIN = ".login";
public static final String BASE = ".base";
public static final String INNER = ".inner";
public static final String POPUP = ".popup";
}

@ -0,0 +1,23 @@
package egovframework.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
*
*/
@Getter
@ResponseStatus(HttpStatus.FORBIDDEN)
public class AccessDeniedException extends RuntimeException {
private final String userAcnt;
private final String requestURI;
public AccessDeniedException(String userAcnt, String requestURI) {
super("접근 권한이 없습니다.");
this.userAcnt = userAcnt;
this.requestURI = requestURI;
}
}

@ -0,0 +1,21 @@
package egovframework.exception;
import lombok.Setter;
import org.aspectj.lang.JoinPoint;
import org.egovframe.rte.fdl.cmmn.aspect.ExceptionTransfer;
@Setter
//@Aspect
public class EgovAopExceptionTransfer {
private ExceptionTransfer exceptionTransfer;
//@Pointcut("execution(* go.kr.project..impl.*Impl.*(..)) or execution(* egovframework.com..*Impl.*(..))")
private void exceptionTransferService() {}
//@AfterThrowing(pointcut="exceptionTransferService()", throwing="ex")
public void doAfterThrowingExceptionTransferService(JoinPoint thisJoinPoint, Exception ex) throws Exception {
exceptionTransfer.transfer(thisJoinPoint, ex);
}
}

@ -0,0 +1,14 @@
package egovframework.exception;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.exception.handler.ExceptionHandler;
@Slf4j
public class EgovDefaultExcepHndlr implements ExceptionHandler {
@Override
public void occur(Exception ex, String packageName) {
log.debug("##### EgovServiceExceptionHandler Run...");
}
}

@ -0,0 +1,14 @@
package egovframework.exception;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.exception.handler.ExceptionHandler;
@Slf4j
public class EgovDefaultOthersExcepHndlr implements ExceptionHandler {
@Override
public void occur(Exception exception, String packageName) {
log.debug("##### EgovSampleOthersExcepHndlr Run...");
}
}

@ -0,0 +1,147 @@
package egovframework.exception;
import egovframework.configProperties.LoginProperties;
import egovframework.util.ApiResponseEntity;
import egovframework.util.HttpServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.TransactionException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @ControllerAdvice
* SimpleMappingExceptionResolver
*
* AJAX (*.ajax) JSON ,
* egovError.jsp .
*/
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class EgovExceptionAdvice {
private final LoginProperties loginProperties;
/**
* DataAccessException
* .
*/
@ExceptionHandler(DataAccessException.class)
public Object handleDataAccessException(DataAccessException e, HttpServletRequest request) {
log.error("DataAccessException 발생: ", e);
return getModelAndView(e, request, HttpStatus.BAD_REQUEST);
}
/**
* TransactionException
* .
*/
@ExceptionHandler(TransactionException.class)
public Object handleTransactionException(TransactionException e, HttpServletRequest request) {
log.error("TransactionException 발생: ", e);
return getModelAndView(e, request, HttpStatus.BAD_REQUEST);
}
/**
* AccessDeniedException
* .
*/
@ExceptionHandler(AccessDeniedException.class)
public Object handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
log.warn("접근 권한 예외 발생 - 사용자: {}, URI: {}", e.getUserAcnt(), e.getRequestURI());
return getModelAndView(e, request, HttpStatus.FORBIDDEN);
}
/**
*
* HttpSessionRequiredException .
* AuthInterceptor AJAX .
*/
/**
* RuntimeException
* .
*/
@ExceptionHandler(RuntimeException.class)
public Object handleRuntimeException(RuntimeException e, HttpServletRequest request) {
log.error("RuntimeException 발생: ", e);
return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* Exception
* .
*/
@ExceptionHandler(Exception.class)
public Object handleException(Exception e, HttpServletRequest request) {
log.error("Exception 발생: ", e);
return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* IOException
* IO .
*/
@ExceptionHandler(IOException.class)
public Object handleIOException(Exception e, HttpServletRequest request) {
log.error("IOException 발생: ", e);
return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* Throwable
* Throwable .
*/
@ExceptionHandler(Throwable.class)
public Object handleThrowable(Throwable e, HttpServletRequest request) {
log.error("Throwable 발생: ", e);
return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* MessageException
* .
*/
@ExceptionHandler(MessageException.class)
public Object handleMessageException(MessageException e, HttpServletRequest request) {
log.error("MessageException 발생: ", e);
return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
*
* AJAX ResponseEntity<ApiResponse<Void>> ,
* egovError ModelAndView .
* AJAX URL (*.ajax) HTTP .
*
* @param e
* @param request HTTP
* @param status HTTP
* @return AJAX ResponseEntity, ModelAndView
*/
private Object getModelAndView(Throwable e, HttpServletRequest request, HttpStatus status) {
// AJAX 요청인 경우 ApiResponse를 사용한 JSON 응답 반환
if (HttpServletUtil.isAjaxRequest(request) || HttpServletUtil.isRealAjaxRequest(request)) {
ApiResponseEntity<Void> responseBody = ApiResponseEntity.<Void>builder()
.result(false)
.message(e.getMessage())
.errorCode(e.getClass().getSimpleName())
.build();
return new ResponseEntity<>(responseBody, status);
}
// 일반 요청인 경우 egovError 페이지로 리다이렉트
ModelAndView mav = new ModelAndView("error/egovError");
mav.addObject("exceptionName", e.getClass().getSimpleName());
mav.addObject("exception", e);
return mav;
}
}

@ -0,0 +1,65 @@
package egovframework.exception;
import lombok.Getter;
/**
*
*
* .
* BasicException "MESSAGE" ,
* .
*/
@Getter
public class MessageException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
*
*/
private final String errorCode;
/**
*
*
* @param message
*/
public MessageException(String message) {
super(message);
this.errorCode = "MESSAGE_EXCEPTION";
}
/**
*
*
* @param message
* @param errorCode
*/
public MessageException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
/**
*
*
* @param message
* @param cause
*/
public MessageException(String message, Throwable cause) {
super(message, cause);
this.errorCode = "MESSAGE_EXCEPTION";
}
/**
*
*
* @param message
* @param errorCode
* @param cause
*/
public MessageException(String message, String errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
}

@ -0,0 +1,54 @@
package egovframework.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* packageName : egovframework.filter
* fileName : SessionRefreshFilter
* author :
* date : 25. 5. 21.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 21.
*/
@Slf4j
@Component
public class SessionRefreshFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 배치 작업 경로는 세션 리프레시에서 제외
String requestURI = httpRequest.getRequestURI();
if (requestURI != null && requestURI.startsWith("/batch/")) {
chain.doFilter(request, response);
return;
}
try {
// 세션이 있으면 접근하여 갱신 (세션 타임아웃 연장)
httpRequest.getSession(false);
// 디버깅 용도 (필요시 주석 해제)
// if (httpRequest.getSession().getAttribute("sessionVO") != null) {
// log.debug("세션 리프레시 - 세션 정보 존재");
// } else {
// log.debug("세션 리프레시 - 세션 정보 없음");
// }
} catch (Exception e) {
// 세션 관련 오류 발생 시 로그만 남기고 계속 진행
log.debug("세션 리프레시 중 오류 발생: {}", e.getMessage());
}
chain.doFilter(request, response);
}
}

@ -0,0 +1,38 @@
package egovframework.filter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* XSS
* XSS
*/
public class XssFilter extends OncePerRequestFilter {
private final XssUtil xssUtil;
public XssFilter(XssUtil xssUtil) {
this.xssUtil = xssUtil;
}
@Override
protected void doFilterInternal(@Nullable HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// XSS 방지를 위한 헤더 설정
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("X-Content-Type-Options", "nosniff");
// 요청을 래핑하여 XSS 필터링 적용
XssRequestWrapper xssRequestWrapper = new XssRequestWrapper(request, xssUtil);
// 필터 체인 실행
filterChain.doFilter(xssRequestWrapper, response);
}
}

@ -0,0 +1,32 @@
package egovframework.filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* XSS
*/
@Configuration
public class XssFilterConfig {
private final XssUtil xssUtil;
public XssFilterConfig(XssUtil xssUtil) {
this.xssUtil = xssUtil;
}
/**
* XSS
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new XssFilter(xssUtil));
registrationBean.addUrlPatterns("/*"); // 모든 URL에 적용
registrationBean.setName("xssFilter");
registrationBean.setOrder(1); // 필터 순서 (낮은 숫자가 먼저 실행)
return registrationBean;
}
}

@ -0,0 +1,81 @@
package egovframework.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.HashMap;
import java.util.Map;
/**
* XSS HttpServletRequest
*/
public class XssRequestWrapper extends HttpServletRequestWrapper {
private final XssUtil xssUtil;
public XssRequestWrapper(HttpServletRequest request, XssUtil xssUtil) {
super(request);
this.xssUtil = xssUtil;
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return xssFilter(name, value);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return null;
}
String[] filteredValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
filteredValues[i] = xssFilter(name, values[i]);
}
return filteredValues;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> paramMap = super.getParameterMap();
Map<String, String[]> filteredParamMap = new HashMap<>();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
String[] values = entry.getValue();
String[] filteredValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
filteredValues[i] = xssFilter(entry.getKey(), values[i]);
}
filteredParamMap.put(entry.getKey(), filteredValues);
}
return filteredParamMap;
}
/**
* XSS
* @param name
* @param value
* @return
*/
private String xssFilter(String name, String value) {
if (value == null) {
return null;
}
// HTML 에디터 내용인 경우 cleanHtml 메서드 사용
//if (name != null && (name.contains("content") || name.contains("html") || name.contains("editor"))) {
if (name != null && (name.contains("html") || name.contains("editor"))) {
return xssUtil.cleanHtml(value);
}
// 파일명인 경우 sanitizeFilename 메서드 사용
if (name != null && (name.contains("filename") || name.contains("file"))) {
return xssUtil.sanitizeFilename(value);
}
// 일반 텍스트인 경우 escape 메서드 사용
return xssUtil.escape(value);
}
}

@ -0,0 +1,105 @@
package egovframework.filter;
import org.springframework.stereotype.Component;
import java.util.regex.Pattern;
/**
* XSS(Cross-Site Scripting)
*/
@Component
public class XssUtil {
/**
* HTML XSS
* @param value
* @return
*/
public String escape(String value) {
if (value == null) {
return null;
}
return value.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("\"", "&quot;")
.replaceAll("'", "&#x27;")
.replaceAll("/", "&#x2F;")
.replaceAll("\\(", "&#40;")
.replaceAll("\\)", "&#41;");
}
/**
*
* @param value
* @return
*/
public String unescape(String value) {
if (value == null) {
return null;
}
return value.replaceAll("&amp;", "&")
.replaceAll("&lt;", "<")
.replaceAll("&gt;", ">")
.replaceAll("&quot;", "\"")
.replaceAll("&#x27;", "'")
.replaceAll("&#x2F;", "/")
.replaceAll("&#40;", "(")
.replaceAll("&#41;", ")");
}
/**
* HTML
* @param value HTML
* @return HTML
*/
public String cleanHtml(String value) {
if (value == null) {
return null;
}
// 허용할 HTML 태그 패턴
String allowedTags = "<p>|<br>|<b>|<i>|<strong>|<em>|<u>|<ul>|<ol>|<li>|<h1>|<h2>|<h3>|<h4>|<h5>|<h6>|<table>|<tr>|<td>|<th>|<tbody>|<thead>|<img>|<a>|<div>|<span>";
// 스크립트 태그 및 이벤트 핸들러 패턴
String scriptPattern = "<script[^>]*>.*?</script>";
String eventPattern = " on\\w+=\".*?\"";
String stylePattern = " style=\".*?\"";
String inlineFramePattern = "<iframe[^>]*>.*?</iframe>";
String embedPattern = "<embed[^>]*>.*?</embed>";
String objectPattern = "<object[^>]*>.*?</object>";
// 스크립트 및 이벤트 핸들러 제거
value = Pattern.compile(scriptPattern, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
value = Pattern.compile(eventPattern, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
value = Pattern.compile(inlineFramePattern, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
value = Pattern.compile(embedPattern, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
value = Pattern.compile(objectPattern, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
// style 속성은 제한적으로 허용 (위험한 표현식 제거)
value = Pattern.compile("expression\\s*\\(.*?\\)", Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
value = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
value = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
// href 속성에서 javascript: 제거
value = Pattern.compile("href\\s*=\\s*['\"]\\s*javascript:.*?['\"]", Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("href=\"#\"");
return value;
}
/**
* XSS
* @param filename
* @return
*/
public String sanitizeFilename(String filename) {
if (filename == null) {
return null;
}
// 파일명에서 위험한 문자 제거
return filename.replaceAll("[\\\\/:*?\"<>|]", "_");
}
}

@ -0,0 +1,404 @@
package egovframework.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import egovframework.configProperties.InterceptorProperties;
import egovframework.configProperties.LoginProperties;
import egovframework.exception.AccessDeniedException;
import egovframework.filter.XssUtil;
import egovframework.util.ApiResponseEntity;
import egovframework.util.HttpServletUtil;
import egovframework.constant.SessionConstants;
import go.kr.project.login.mapper.LoginMapper;
import go.kr.project.login.model.SessionVO;
import go.kr.project.login.model.UserSessionVO;
import go.kr.project.login.service.LoginService;
import go.kr.project.system.menu.model.MenuVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* packageName : egovframework.interceptor
* fileName : AuthInterceptor
* author :
* date : 2025-05-13
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-13
*/
@Slf4j
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
private final LoginService loginService;
// URL 패턴 매칭을 위한 AntPathMatcher
private final AntPathMatcher pathMatcher = new AntPathMatcher();
// XSS 방지 유틸리티
private final XssUtil xssUtil = new XssUtil();
// JSON 변환을 위한 ObjectMapper
private final ObjectMapper objectMapper = new ObjectMapper();
// Referer 검사에서 제외할 URL 패턴 목록 설정
@Autowired
private InterceptorProperties interceptorProperties;
// 로그인 관련 설정
@Autowired
private LoginProperties loginProperties;
// 로그인 매퍼
@Autowired
private LoginMapper loginMapper;
/**
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Object handler) throws Exception {
String requestURI = request.getRequestURI();
// 접근 제어 예외 URL 패턴은 WebMvcConfigurer에서 처리됨
// 이 메서드가 호출된다는 것은 이미 예외 패턴이 아니라는 의미
// Referer 헤더 검사 - URL을 브라우저 주소창에 직접 입력해서 접근하는 것을 방지
// 첫 페이지 접근이나 로그인 페이지 등 일부 URL은 Referer 검사에서 제외
if (!isRefererCheckExcluded(requestURI)) {
String referer = request.getHeader("Referer");
if (referer == null || referer.isEmpty()) {
// Referer 헤더가 없는 경우 (직접 URL 입력 또는 북마크 등)
log.warn("Referer 헤더 없음: {}", requestURI);
// AJAX 요청인 경우 JSON 응답 반환
if (isAjaxRequest(request)) {
handleRefererMissing(response);
return false;
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트
redirectToLoginPageWithPopupCheck(request, response, "잘못된 접근입니다. 로그인 페이지로 이동합니다.");
return false;
}
}
try {
// 세션 정보 조회
SessionVO sessionVO = loginService.getSessionInfo(request);
// 세션이 있고 로그인 상태인 경우, 동시 접속 허용 불가일 때만 세션 정보를 DB에서 확인
if (sessionVO != null && sessionVO.isLogin() && !loginProperties.isAllowMultipleLogin()) {
HttpSession session = request.getSession(false);
if (session != null) {
String sessionId = session.getId();
String userId = sessionVO.getUser().getUserId();
// DB에서 세션 정보 조회
UserSessionVO userSessionVO = loginMapper.selectUserSessionBySessionId(sessionId);
if (userSessionVO == null) {
// DB에 세션 정보가 없으면 현재 세션 무효화
log.warn("DB에 세션 정보가 없음. 세션 무효화 (동시 접속 제한): {}", sessionId);
session.invalidate();
// AJAX 요청인 경우 JSON 응답 반환
if (isAjaxRequest(request)) {
handleAjaxSessionExpired(response);
return false;
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트
redirectToLoginPageWithPopupCheck(request, response, "세션이 종료되었습니다. 로그인 페이지로 이동합니다.");
return false;
} else {
// DB에 세션 정보가 있으면 마지막 접속 시간 업데이트
userSessionVO.setLastAccessDttm(LocalDateTime.now());
loginMapper.updateUserSession(userSessionVO);
log.debug("세션 정보 업데이트 (동시 접속 제한): {}", sessionId);
}
}
}
// 세션이 없거나 로그인 상태가 아닌 경우
if (sessionVO == null || !sessionVO.isLogin()) {
// 방문자 권한 확인
if (sessionVO != null && sessionVO.isVisitor()) {
// 방문자 권한으로 접근 가능한지 확인
if (hasAccess(sessionVO, requestURI)) {
return true;
}
}
// AJAX 요청인 경우 JSON 응답 반환
if (isAjaxRequest(request)) {
handleAjaxSessionExpired(response);
return false;
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트
redirectToLoginPageWithPopupCheck(request, response, "세션이 종료되었습니다. 로그인 페이지로 이동합니다.");
return false;
}
// 비밀번호 초기화 여부 확인
if ("Y".equals(sessionVO.getUser().getPasswdInitYn())) {
// 비밀번호 변경 페이지 또는 비밀번호 변경 처리 URL인 경우에만 접근 허용
if (requestURI.equals("/mypage/password.do") ||
requestURI.equals("/mypage/password.ajax") ||
requestURI.equals("/mypage/check-password.ajax") ||
requestURI.equals("/mypage/password-reset.do")) {
return true;
} else {
// 비밀번호 변경 페이지로 리다이렉트
if (isAjaxRequest(request)) {
// AJAX 요청인 경우 JSON 응답 반환
sendJsonResponse(response, HttpStatus.UNAUTHORIZED, false, "비밀번호를 변경해야 합니다.", "PASSWORD_RESET_REQUIRED");
return false;
} else {
// 일반 요청인 경우 비밀번호 변경 페이지로 리다이렉트
redirectToPage(request, response, "비밀번호를 변경해야 합니다.", "/mypage/password-reset.do");
return false;
}
}
}
// 로그인 상태인 경우 접근 권한 확인
if (hasAccess(sessionVO, requestURI)) {
return true;
}
// 접근 권한이 없는 경우 예외 발생
log.warn("접근 권한 없음: {} - {}", sessionVO.getUser().getUserAcnt(), requestURI);
throw new AccessDeniedException(sessionVO.getUser().getUserAcnt(), requestURI);
//response.sendError(HttpServletResponse.SC_FORBIDDEN, "접근 권한이 없습니다.");
//return false;
} catch (AccessDeniedException e) {
throw e;
} catch (Exception e) {
log.error("접근 제어 처리 중 오류 발생", e);
// AJAX 요청인 경우 JSON 응답 반환
if (isAjaxRequest(request)) {
try {
handleAjaxSessionExpired(response);
} catch (IOException ex) {
log.error("AJAX 응답 처리 중 오류 발생", ex);
}
return false;
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트
try {
redirectToLoginPageWithPopupCheck(request, response, "세션이 종료되었습니다. 로그인 페이지로 이동합니다.");
} catch (IOException ex) {
log.error("리다이렉트 처리 중 오류 발생", ex);
}
return false;
}
}
/**
*
* @param sessionVO
* @param requestURI URI
* @return
*/
private boolean hasAccess(SessionVO sessionVO, String requestURI) {
// 메뉴 트리를 1차원 리스트로 변환
List<MenuVO> menus = flattenMenuTree(sessionVO.getMenus());
if (menus.isEmpty()) {
return false;
}
// 메뉴의 URL 패턴과 일치하는지 확인
for (MenuVO menu : menus) {
if (menu.getUrlPattern() != null && !menu.getUrlPattern().isEmpty()) {
// XSS 방지를 위한 이스케이프 처리된 URL 패턴을 원래대로 복원하고 쉼표로 구분된 패턴을 분리
String[] patterns = xssUtil.unescape(menu.getUrlPattern()).split(",");
for (String pattern : patterns) {
// 각 패턴과 요청 URI를 비교하여 일치하면 접근 허용
if (pathMatcher.match(pattern.trim(), requestURI)) {
return true;
}
}
}
}
// 일치하는 패턴이 없으면 접근 불가
return false;
}
/**
* 1
*
*
* @param menus
* @return 1
*/
private List<MenuVO> flattenMenuTree(List<MenuVO> menus) {
List<MenuVO> flatList = new ArrayList<>();
if (menus != null) {
for (MenuVO menu : menus) {
// 현재 메뉴를 리스트에 추가
flatList.add(menu);
// 하위 메뉴가 있으면 재귀적으로 처리하여 리스트에 추가
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
flatList.addAll(flattenMenuTree(menu.getChildren()));
}
}
}
return flatList;
}
/**
* AJAX
* JSON
*
* @param response HTTP
* @throws IOException
*/
private void handleAjaxSessionExpired(HttpServletResponse response) throws IOException {
sendJsonResponse(response, HttpStatus.UNAUTHORIZED, false,
"세션이 만료되었습니다. 로그인 페이지로 이동합니다.",
SessionConstants.SESSION_EXPIRED);
}
/**
* Referer AJAX
* URL
*
* @param response HTTP
* @throws IOException
*/
private void handleRefererMissing(HttpServletResponse response) throws IOException {
sendJsonResponse(response, HttpStatus.FORBIDDEN, false,
"잘못된 접근입니다. 로그인 페이지로 이동합니다.",
SessionConstants.INVALID_ACCESS);
}
/**
* JSON
*
* @param response HTTP
* @param status HTTP
* @param result
* @param message
* @param errorCode
* @throws IOException
*/
private void sendJsonResponse(HttpServletResponse response, HttpStatus status, boolean result,
String message, String errorCode) throws IOException {
// 응답 상태 코드 및 컨텐츠 타입 설정
response.setStatus(status.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
// 응답 객체 생성
ApiResponseEntity<Void> responseBody = ApiResponseEntity.<Void>builder()
.result(result)
.message(message)
.errorCode(errorCode)
.build();
// JSON 변환 및 응답 출력
response.getWriter().write(objectMapper.writeValueAsString(responseBody));
}
/**
* Referer URL
* , URL
*
* @param requestURI URI
* @return Referer (true: , false: )
*/
private boolean isRefererCheckExcluded(String requestURI) {
// 설정된 제외 패턴과 요청 URI를 비교
for (String pattern : interceptorProperties.getRefererExclude()) {
if (pathMatcher.match(pattern, requestURI)) {
return true; // 제외 패턴에 해당하면 Referer 검사 제외
}
}
// 제외 패턴에 해당하지 않으면 Referer 검사 필요
return false;
}
/**
* AJAX
*
* @param request HTTP
* @return AJAX
*/
private boolean isAjaxRequest(HttpServletRequest request) {
return HttpServletUtil.isAjaxRequest(request) || HttpServletUtil.isRealAjaxRequest(request);
}
/**
*
*
* @param request HTTP
* @param response HTTP
* @param message
* @param targetUrl URL
* @throws IOException
*/
private void redirectToPage(HttpServletRequest request, HttpServletResponse response,
String message, String targetUrl) throws IOException {
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("<script type='text/javascript'>");
response.getWriter().write("alert('" + message + "');");
response.getWriter().write("location.href = '" + request.getContextPath() + targetUrl + "';");
response.getWriter().write("</script>");
}
/**
*
*
* @param request HTTP
* @param response HTTP
* @param message
* @throws IOException
*/
private void redirectToLoginPageWithPopupCheck(HttpServletRequest request, HttpServletResponse response,
String message) throws IOException {
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("<script type='text/javascript'>");
response.getWriter().write("alert('" + message + "');");
response.getWriter().write("if(window.opener) {");
response.getWriter().write(" var topWindow = window;");
response.getWriter().write(" while(topWindow.opener && !topWindow.opener.closed) {");
response.getWriter().write(" topWindow = topWindow.opener;");
response.getWriter().write(" }");
response.getWriter().write(" topWindow.location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write(" window.close();");
response.getWriter().write("} else {");
response.getWriter().write(" location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write("}");
response.getWriter().write("</script>");
}
}

@ -0,0 +1,50 @@
package egovframework.util;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* packageName : egovframework.common.model
* fileName : ApiResponse
* author :
* date : 25. 5. 8.
* description : AJAX
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 8.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponseEntity<T> {
/**
*
*/
private boolean success;
/**
*
*/
private boolean result;
/**
*
*/
private String message;
/**
* ( )
*/
private T data;
/**
* ( )
*/
private String errorCode;
}

@ -0,0 +1,173 @@
package egovframework.util;
import go.kr.project.common.model.PagingVO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.HashMap;
import java.util.Map;
/**
* packageName : egovframework.common.util
* fileName : ApiResponseUtil
* author :
* date : 25. 5. 8.
* description : AJAX
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 8.
*/
public class ApiResponseUtil {
/**
* ( )
* @param message
* @return ResponseEntity
*/
public static ResponseEntity<ApiResponseEntity<Void>> success(String message) {
ApiResponseEntity<Void> response = ApiResponseEntity.<Void>builder()
.success(true)
.result(true)
.message(message)
.build();
return ResponseEntity.ok(response);
}
/**
* ( )
* @param data
* @param message
* @param <T>
* @return ResponseEntity
*/
public static <T> ResponseEntity<ApiResponseEntity<T>> success(T data, String message) {
ApiResponseEntity<T> response = ApiResponseEntity.<T>builder()
.success(true)
.result(true)
.message(message)
.data(data)
.build();
return ResponseEntity.ok(response);
}
/**
* ( , )
* @param data
* @param <T>
* @return ResponseEntity
*/
public static <T> ResponseEntity<ApiResponseEntity<T>> success(T data) {
return success(data, "요청이 성공적으로 처리되었습니다.");
}
/**
*
* TOAST UI Grid
*
* TOAST UI Grid :
* {
* data: {
* contents: [...], // 실제 데이터 배열
* pagination: {
* page: number,
* perPage: number,
* totalCount: number
* }
* }
* }
*
* @param data
* @param pagingVO
* @param message
* @param <T>
* @return ResponseEntity
*/
public static <T> ResponseEntity<ApiResponseEntity<Map<String, Object>>> successWithGrid(T data, PagingVO pagingVO, String message) {
// TOAST UI Grid 형식에 맞는 데이터 구조 생성
Map<String, Object> gridData = new HashMap<>();
// contents에 실제 데이터 배열 설정
gridData.put("contents", data);
// pagination 정보 설정
if( "Y".equals(pagingVO.getPagingYn()) ){
Map<String, Object> paginationMap = new HashMap<>();
paginationMap.put("page", pagingVO.getPage());
paginationMap.put("perPage", pagingVO.getPerPage());
paginationMap.put("totalCount", pagingVO.getTotalCount());
paginationMap.put("totalPages", pagingVO.getTotalPages());
gridData.put("pagination", paginationMap);
}
// 응답 객체 생성
ApiResponseEntity<Map<String, Object>> response = ApiResponseEntity.<Map<String, Object>>builder()
.success(true)
.result(true)
.message(message)
.data(gridData)
.build();
return ResponseEntity.ok(response);
}
/**
* ( )
* @param data
* @param pagingVO
* @param <T>
* @return ResponseEntity
*/
public static <T> ResponseEntity<ApiResponseEntity<Map<String, Object>>> successWithGrid(T data, PagingVO pagingVO) {
return successWithGrid(data, pagingVO, "요청이 성공적으로 처리되었습니다.");
}
/**
*
* @param message
* @param errorCode
* @param status HTTP
* @return ResponseEntity
*/
public static ResponseEntity<ApiResponseEntity<Void>> error(String message, String errorCode, HttpStatus status) {
ApiResponseEntity<Void> response = ApiResponseEntity.<Void>builder()
.success(false)
.result(false)
.message(message)
.errorCode(errorCode)
.build();
return ResponseEntity.status(status).body(response);
}
/**
* ( HTTP : BAD_REQUEST)
* @param message
* @param errorCode
* @return ResponseEntity
*/
public static ResponseEntity<ApiResponseEntity<Void>> error(String message, String errorCode) {
return error(message, errorCode, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* ( : "UNKNOWN_ERROR")
* @param message
* @param status HTTP
* @return ResponseEntity
*/
public static ResponseEntity<ApiResponseEntity<Void>> error(String message, HttpStatus status) {
return error(message, "UNKNOWN_ERROR", status);
}
/**
* ( HTTP : BAD_REQUEST, : "UNKNOWN_ERROR")
* @param message
* @return ResponseEntity
*/
public static ResponseEntity<ApiResponseEntity<Void>> error(String message) {
return error(message, "MESSAGE", HttpStatus.BAD_REQUEST);
}
}

@ -0,0 +1,107 @@
package egovframework.util;
import lombok.extern.slf4j.Slf4j;
/**
* packageName : egovframework.util
* fileName : BatchSessionUtil
* author :
* date : 2025-01-27
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Slf4j
public class BatchSessionUtil {
/**
* ID
*/
public static final String BATCH_SYSTEM_USER_ID = "BATCH_SYSTEM";
/**
*
*/
public static final String BATCH_SYSTEM_USER_ACCOUNT = "BATCH_SYSTEM";
/**
*
*/
public static final String BATCH_SYSTEM_USER_NAME = "시스템 배치";
/**
* ID .
*
* @return ID
*/
public static String getBatchUserId() {
return BATCH_SYSTEM_USER_ID;
}
/**
* .
*
* @return
*/
public static String getBatchUserAccount() {
return BATCH_SYSTEM_USER_ACCOUNT;
}
/**
* .
*
* @return
*/
public static String getBatchUserName() {
return BATCH_SYSTEM_USER_NAME;
}
/**
* .
*
* @return true ( )
*/
public static boolean isBatchSystemUser() {
return true;
}
/**
* .
*
* @param jobName
* @param jobGroup
*/
public static void logBatchContext(String jobName, String jobGroup) {
log.info("배치 작업 실행 컨텍스트 - 작업: {}.{}, 사용자: {}, 계정: {}, 시스템권한: {}",
jobGroup, jobName, getBatchUserId(), getBatchUserAccount(), isBatchSystemUser());
}
/**
* .
*
* @param sessionAccessor
* @param defaultValue
* @param <T>
* @return
*/
public static <T> T safeSessionAccess(SessionAccessor<T> sessionAccessor, T defaultValue) {
try {
return sessionAccessor.access();
} catch (Exception e) {
log.debug("세션 접근 실패 (배치 컨텍스트), 기본값 사용: {}", e.getMessage());
return defaultValue;
}
}
/**
*
*
* @param <T>
*/
@FunctionalInterface
public interface SessionAccessor<T> {
T access() throws Exception;
}
}

@ -0,0 +1,255 @@
package egovframework.util;
import javax.servlet.http.HttpServletRequest;
/**
* packageName : egovframework.util
* fileName : ClientInfoUtil
* author :
* date : 2025-05-22
* description : (, OS, )
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-22
*/
public class ClientInfoUtil {
/**
* User-Agent .
*
* @param userAgent
* @return
*/
public static String extractDeviceInfo(String userAgent) {
if (userAgent == null || userAgent.isEmpty()) {
return "Unknown";
}
userAgent = userAgent.toLowerCase();
StringBuilder deviceInfo = new StringBuilder();
// OS 정보 추출
String osInfo = extractOsInfo(userAgent);
// 디바이스 유형 및 제조사 정보 추출
String deviceType = extractDeviceType(userAgent);
// 브라우저 정보 추출
String browserInfo = extractBrowserInfo(userAgent);
// 정보 조합
if (!deviceType.isEmpty()) {
deviceInfo.append(deviceType);
}
if (!osInfo.isEmpty()) {
if (deviceInfo.length() > 0) {
deviceInfo.append(" - ");
}
deviceInfo.append(osInfo);
}
// 모바일/태블릿이 아닌 경우에만 브라우저 정보 추가
if (!deviceType.contains("Mobile") && !deviceType.contains("Tablet") && !browserInfo.isEmpty()) {
if (deviceInfo.length() > 0) {
deviceInfo.append(" - ");
}
deviceInfo.append(browserInfo);
}
return deviceInfo.length() > 0 ? deviceInfo.toString() : "Unknown Device";
}
/**
* User-Agent OS .
*
* @param userAgent
* @return OS
*/
public static String extractOsInfo(String userAgent) {
// Windows 버전 확인
if (userAgent.contains("windows")) {
if (userAgent.contains("windows nt 10")) {
return "Windows 10";
} else if (userAgent.contains("windows nt 6.3")) {
return "Windows 8.1";
} else if (userAgent.contains("windows nt 6.2")) {
return "Windows 8";
} else if (userAgent.contains("windows nt 6.1")) {
return "Windows 7";
} else if (userAgent.contains("windows nt 6.0")) {
return "Windows Vista";
} else if (userAgent.contains("windows nt 5.1") || userAgent.contains("windows xp")) {
return "Windows XP";
} else {
return "Windows";
}
}
// Mac OS 버전 확인
if (userAgent.contains("macintosh") || userAgent.contains("mac os x")) {
if (userAgent.contains("mac os x 10_15")) {
return "macOS Catalina";
} else if (userAgent.contains("mac os x 10_14")) {
return "macOS Mojave";
} else if (userAgent.contains("mac os x 10_13")) {
return "macOS High Sierra";
} else if (userAgent.contains("mac os x 10_12")) {
return "macOS Sierra";
} else {
return "Mac OS";
}
}
// 기타 OS 확인
if (userAgent.contains("android")) {
// Android 버전 추출
int startIndex = userAgent.indexOf("android ");
if (startIndex != -1) {
int endIndex = userAgent.indexOf(";", startIndex);
if (endIndex != -1) {
return "Android " + userAgent.substring(startIndex + 8, endIndex).trim();
}
}
return "Android";
} else if (userAgent.contains("ios")) {
return "iOS";
} else if (userAgent.contains("iphone os") || userAgent.contains("cpu os")) {
// iOS 버전 추출
int startIndex = userAgent.contains("iphone os ") ? userAgent.indexOf("iphone os ") + 10 : userAgent.indexOf("cpu os ") + 7;
if (startIndex != -1) {
int endIndex = userAgent.indexOf(" ", startIndex);
if (endIndex != -1) {
String version = userAgent.substring(startIndex, endIndex).trim().replace("_", ".");
return "iOS " + version;
}
}
return "iOS";
} else if (userAgent.contains("linux")) {
return "Linux";
} else if (userAgent.contains("unix")) {
return "Unix";
}
return "";
}
/**
* User-Agent .
*
* @param userAgent
* @return
*/
public static String extractDeviceType(String userAgent) {
// 모바일 디바이스 확인
if (userAgent.contains("mobile")) {
if (userAgent.contains("iphone")) {
return "iPhone";
} else if (userAgent.contains("android")) {
// 안드로이드 제조사 확인
if (userAgent.contains("samsung")) {
return "Samsung Mobile";
} else if (userAgent.contains("lg")) {
return "LG Mobile";
} else if (userAgent.contains("huawei")) {
return "Huawei Mobile";
} else if (userAgent.contains("xiaomi")) {
return "Xiaomi Mobile";
} else if (userAgent.contains("oppo")) {
return "OPPO Mobile";
} else if (userAgent.contains("vivo")) {
return "Vivo Mobile";
} else if (userAgent.contains("oneplus")) {
return "OnePlus Mobile";
} else if (userAgent.contains("motorola")) {
return "Motorola Mobile";
} else if (userAgent.contains("nokia")) {
return "Nokia Mobile";
} else {
return "Android Mobile";
}
} else if (userAgent.contains("windows phone")) {
return "Windows Phone";
} else {
return "Mobile Device";
}
}
// 태블릿 확인
if (userAgent.contains("ipad")) {
return "iPad";
} else if (userAgent.contains("android") && !userAgent.contains("mobile")) {
// 안드로이드 태블릿 제조사 확인
if (userAgent.contains("samsung")) {
return "Samsung Tablet";
} else if (userAgent.contains("huawei")) {
return "Huawei Tablet";
} else if (userAgent.contains("lenovo")) {
return "Lenovo Tablet";
} else if (userAgent.contains("asus")) {
return "Asus Tablet";
} else if (userAgent.contains("amazon") || userAgent.contains("kindle")) {
return "Amazon Tablet";
} else {
return "Android Tablet";
}
} else if (userAgent.contains("tablet")) {
return "Tablet";
}
return "";
}
/**
* User-Agent .
*
* @param userAgent
* @return
*/
public static String extractBrowserInfo(String userAgent) {
if (userAgent.contains("edge/") || userAgent.contains("edg/")) {
return "Edge Browser";
} else if (userAgent.contains("chrome/")) {
return "Chrome Browser";
} else if (userAgent.contains("safari/") && !userAgent.contains("chrome/")) {
return "Safari Browser";
} else if (userAgent.contains("firefox/")) {
return "Firefox Browser";
} else if (userAgent.contains("opera/") || userAgent.contains("opr/")) {
return "Opera Browser";
} else if (userAgent.contains("msie") || userAgent.contains("trident/")) {
return "Internet Explorer";
}
return "";
}
/**
* IP .
*
* @param request HTTP
* @return IP
*/
public static String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}

@ -0,0 +1,431 @@
package egovframework.util;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
*
*/
@Component
public class CollectionUtil {
/**
* null
* @param collection
* @return null true, false
*/
public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty();
}
/**
* null
* @param collection
* @return null true, false
*/
public static boolean isNotEmpty(Collection<?> collection) {
return !isEmpty(collection);
}
/**
* null
* @param map
* @return null true, false
*/
public static boolean isEmpty(Map<?, ?> map) {
return map == null || map.isEmpty();
}
/**
* null
* @param map
* @return null true, false
*/
public static boolean isNotEmpty(Map<?, ?> map) {
return !isEmpty(map);
}
/**
* null
* @param array
* @return null true, false
*/
public static <T> boolean isEmpty(T[] array) {
return array == null || array.length == 0;
}
/**
* null
* @param array
* @return null true, false
*/
public static <T> boolean isNotEmpty(T[] array) {
return !isEmpty(array);
}
/**
* (null )
* @param collection
* @return , null 0
*/
public static int size(Collection<?> collection) {
return collection == null ? 0 : collection.size();
}
/**
* (null )
* @param map
* @return , null 0
*/
public static int size(Map<?, ?> map) {
return map == null ? 0 : map.size();
}
/**
* (null )
* @param array
* @return , null 0
*/
public static <T> int size(T[] array) {
return array == null ? 0 : array.length;
}
/**
* null ,
* @param collection
* @return null ,
*/
public static <T> Collection<T> emptyIfNull(Collection<T> collection) {
return collection == null ? Collections.emptyList() : collection;
}
/**
* null ,
* @param list
* @return null ,
*/
public static <T> List<T> emptyIfNull(List<T> list) {
return list == null ? Collections.emptyList() : list;
}
/**
* null ,
* @param map
* @return null ,
*/
public static <K, V> Map<K, V> emptyIfNull(Map<K, V> map) {
return map == null ? Collections.emptyMap() : map;
}
/**
* (null )
* @param array
* @return , null
*/
public static <T> List<T> toList(T[] array) {
if (array == null) {
return Collections.emptyList();
}
return Arrays.asList(array);
}
/**
* (null )
* @param collection
* @return , null
*/
public static <T> List<T> toList(Collection<T> collection) {
if (collection == null) {
return Collections.emptyList();
}
return new ArrayList<>(collection);
}
/**
* Set (null )
* @param collection
* @return Set, null Set
*/
public static <T> Set<T> toSet(Collection<T> collection) {
if (collection == null) {
return Collections.emptySet();
}
return new HashSet<>(collection);
}
/**
* Set (null )
* @param array
* @return Set, null Set
*/
public static <T> Set<T> toSet(T[] array) {
if (array == null) {
return Collections.emptySet();
}
Set<T> set = new HashSet<>();
Collections.addAll(set, array);
return set;
}
/**
*
* @param collection
* @param predicate
* @return
*/
public static <T> List<T> filter(Collection<T> collection, Predicate<T> predicate) {
if (isEmpty(collection)) {
return Collections.emptyList();
}
return collection.stream()
.filter(predicate)
.collect(Collectors.toList());
}
/**
*
* @param collection
* @param mapper
* @return
*/
public static <T, R> List<R> map(Collection<T> collection, Function<T, R> mapper) {
if (isEmpty(collection)) {
return Collections.emptyList();
}
return collection.stream()
.map(mapper)
.collect(Collectors.toList());
}
/**
*
* @param collection
* @param comparator
* @return
*/
public static <T> List<T> sort(Collection<T> collection, Comparator<? super T> comparator) {
if (isEmpty(collection)) {
return Collections.emptyList();
}
List<T> list = new ArrayList<>(collection);
list.sort(comparator);
return list;
}
/**
* (Comparable )
* @param collection
* @return
*/
public static <T extends Comparable<? super T>> List<T> sort(Collection<T> collection) {
if (isEmpty(collection)) {
return Collections.emptyList();
}
List<T> list = new ArrayList<>(collection);
Collections.sort(list);
return list;
}
/**
*
* @param collection
* @return
*/
public static <T> List<T> distinct(Collection<T> collection) {
if (isEmpty(collection)) {
return Collections.emptyList();
}
return collection.stream()
.distinct()
.collect(Collectors.toList());
}
/**
*
* @param collection
* @param keyMapper
* @return
*/
public static <T, K> Map<K, List<T>> groupBy(Collection<T> collection, Function<T, K> keyMapper) {
if (isEmpty(collection)) {
return Collections.emptyMap();
}
return collection.stream()
.collect(Collectors.groupingBy(keyMapper));
}
/**
*
* @param collection
* @param keyMapper
* @return
*/
public static <T, K> Map<K, T> toMap(Collection<T> collection, Function<T, K> keyMapper) {
if (isEmpty(collection)) {
return Collections.emptyMap();
}
return collection.stream()
.collect(Collectors.toMap(keyMapper, Function.identity(), (a, b) -> a));
}
/**
*
* @param collection
* @param keyMapper
* @param valueMapper
* @return
*/
public static <T, K, V> Map<K, V> toMap(Collection<T> collection, Function<T, K> keyMapper, Function<T, V> valueMapper) {
if (isEmpty(collection)) {
return Collections.emptyMap();
}
return collection.stream()
.collect(Collectors.toMap(keyMapper, valueMapper, (a, b) -> a));
}
/**
*
* @param collection1
* @param collection2
* @return
*/
public static <T> List<T> union(Collection<T> collection1, Collection<T> collection2) {
Set<T> set = new HashSet<>();
if (isNotEmpty(collection1)) {
set.addAll(collection1);
}
if (isNotEmpty(collection2)) {
set.addAll(collection2);
}
return new ArrayList<>(set);
}
/**
*
* @param collection1
* @param collection2
* @return
*/
public static <T> List<T> intersection(Collection<T> collection1, Collection<T> collection2) {
if (isEmpty(collection1) || isEmpty(collection2)) {
return Collections.emptyList();
}
List<T> result = new ArrayList<>();
Set<T> set = new HashSet<>(collection2);
for (T item : collection1) {
if (set.contains(item)) {
result.add(item);
}
}
return result;
}
/**
* (collection1 - collection2)
* @param collection1
* @param collection2
* @return
*/
public static <T> List<T> difference(Collection<T> collection1, Collection<T> collection2) {
if (isEmpty(collection1)) {
return Collections.emptyList();
}
if (isEmpty(collection2)) {
return new ArrayList<>(collection1);
}
List<T> result = new ArrayList<>(collection1);
result.removeAll(new HashSet<>(collection2));
return result;
}
/**
*
* @param collection
* @param size
* @return
*/
public static <T> List<List<T>> partition(Collection<T> collection, int size) {
if (isEmpty(collection)) {
return Collections.emptyList();
}
if (size <= 0) {
throw new IllegalArgumentException("Size must be greater than 0");
}
List<List<T>> result = new ArrayList<>();
List<T> list = new ArrayList<>(collection);
int total = list.size();
for (int i = 0; i < total; i += size) {
result.add(list.subList(i, Math.min(total, i + size)));
}
return result;
}
/**
* (null )
* @param collection
* @return , null
*/
public static <T> T getFirst(Collection<T> collection) {
if (isEmpty(collection)) {
return null;
}
return collection.iterator().next();
}
/**
* (null )
* @param list
* @return , null
*/
public static <T> T getLast(List<T> list) {
if (isEmpty(list)) {
return null;
}
return list.get(list.size() - 1);
}
/**
* (null )
* @param map
* @param key
* @param defaultValue
* @return ,
*/
public static <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) {
if (isEmpty(map)) {
return defaultValue;
}
return map.getOrDefault(key, defaultValue);
}
/**
*
* @param map1
* @param map2
* @return
*/
public static <K, V> Map<K, V> merge(Map<K, V> map1, Map<K, V> map2) {
Map<K, V> result = new HashMap<>();
if (isNotEmpty(map1)) {
result.putAll(map1);
}
if (isNotEmpty(map2)) {
result.putAll(map2);
}
return result;
}
}

@ -0,0 +1,512 @@
package egovframework.util;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import java.util.Locale;
/**
*
*/
@Component
public class DateUtil {
/**
* "yyyyMM"
* @return (: "202405")
*/
public String getCurrentYearMonth() {
LocalDate now = LocalDate.now();
return now.format(DateTimeFormatter.ofPattern("yyyyMM"));
}
/**
*
* @param pattern (: "yyyy-MM-dd", "yyyyMMdd", "yyyy/MM/dd")
* @return
*/
public String getCurrentDate(String pattern) {
LocalDate now = LocalDate.now();
return now.format(DateTimeFormatter.ofPattern(pattern));
}
/**
*
*
* @param pattern (: "yyyy-MM-dd", "yyyyMMdd")
* @param days (: , : )
* @return
*/
public static String getCurrentDateAddDays(String pattern, int days) {
LocalDate now = LocalDate.now();
return now.plusDays(days).format(DateTimeFormatter.ofPattern(pattern));
}
/**
*
* @param pattern (: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
* @return
*/
public static String getCurrentDateTime(String pattern) {
LocalDateTime now = LocalDateTime.now();
return now.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 30
* @param pattern (: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
* @return 30
*/
public static String getCurrentDateTimeRoundedToHalfHour(String pattern) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime adjusted;
int minute = now.getMinute();
if (minute < 30) {
// 0~29분이면 같은 시간의 30분으로 조정
adjusted = now.withMinute(30).withSecond(0).withNano(0);
} else {
// 30~59분이면 다음 시간의 00분으로 조정
adjusted = now.plusHours(1).withMinute(0).withSecond(0).withNano(0);
}
return adjusted.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 30 1
* @param pattern (: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
* @return 30 1
*/
public static String getCurrentDateTimeRoundedToHalfHourPlusOneHour(String pattern) {
// 먼저 30분 단위로 조정된 시간을 구한 후
LocalDateTime adjusted = LocalDateTime.parse(
getCurrentDateTimeRoundedToHalfHour("yyyy-MM-dd'T'HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
);
// 1시간 추가
adjusted = adjusted.plusHours(1);
return adjusted.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 30
* @param dateTime LocalDateTime
* @return 30 LocalDateTime
*/
public static LocalDateTime roundToHalfHour(LocalDateTime dateTime) {
if (dateTime == null) {
return null;
}
int minute = dateTime.getMinute();
if (minute < 30) {
// 0~29분이면 같은 시간의 30분으로 조정
return dateTime.withMinute(30).withSecond(0).withNano(0);
} else {
// 30~59분이면 다음 시간의 00분으로 조정
return dateTime.plusHours(1).withMinute(0).withSecond(0).withNano(0);
}
}
/**
* LocalDate
* @param dateStr
* @param pattern (: "yyyy-MM-dd", "yyyyMMdd")
* @return LocalDate , null
*/
public static LocalDate parseLocalDate(String dateStr, String pattern) {
if (dateStr == null || pattern == null) {
return null;
}
try {
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
} catch (Exception e) {
return null;
}
}
/**
* LocalDateTime
* @param dateTimeStr
* @param pattern (: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
* @return LocalDateTime , null
*/
public static LocalDateTime parseLocalDateTime(String dateTimeStr, String pattern) {
if (dateTimeStr == null || pattern == null) {
return null;
}
try {
return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern));
} catch (Exception e) {
return null;
}
}
/**
* LocalDate
* @param date LocalDate
* @param pattern (: "yyyy-MM-dd", "yyyyMMdd")
* @return , null
*/
public static String formatLocalDate(LocalDate date, String pattern) {
if (date == null || pattern == null) {
return null;
}
return date.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* LocalDateTime
* @param dateTime LocalDateTime
* @param pattern (: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
* @return , null
*/
public static String formatLocalDateTime(Object dateTime, String pattern) {
if (!(dateTime instanceof LocalDateTime) || pattern == null) {
return "";
}
return ((LocalDateTime) dateTime).format(DateTimeFormatter.ofPattern(pattern));
}
/**
* Date LocalDate
* @param date Date
* @return LocalDate , null
*/
public static LocalDate toLocalDate(Date date) {
if (date == null) {
return null;
}
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
/**
* Date LocalDateTime
* @param date Date
* @return LocalDateTime , null
*/
public static LocalDateTime toLocalDateTime(Date date) {
if (date == null) {
return null;
}
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
/**
* LocalDate Date
* @param localDate LocalDate
* @return Date , null
*/
public static Date toDate(LocalDate localDate) {
if (localDate == null) {
return null;
}
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
}
/**
* LocalDateTime Date
* @param localDateTime LocalDateTime
* @return Date , null
*/
public static Date toDate(LocalDateTime localDateTime) {
if (localDateTime == null) {
return null;
}
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
*
* @param date
* @param days (: , : )
* @return
*/
public static LocalDate addDays(LocalDate date, int days) {
if (date == null) {
return null;
}
return date.plusDays(days);
}
/**
*
* @param date
* @param months (: , : )
* @return
*/
public static LocalDate addMonths(LocalDate date, int months) {
if (date == null) {
return null;
}
return date.plusMonths(months);
}
/**
*
* @param date
* @param years (: , : )
* @return
*/
public static LocalDate addYears(LocalDate date, int years) {
if (date == null) {
return null;
}
return date.plusYears(years);
}
/**
*
* @param startDate
* @param endDate
* @return (endDate - startDate)
*/
public static long daysBetween(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null) {
return 0;
}
return ChronoUnit.DAYS.between(startDate, endDate);
}
/**
*
* @param startDate
* @param endDate
* @return (endDate - startDate)
*/
public static long monthsBetween(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null) {
return 0;
}
return ChronoUnit.MONTHS.between(startDate, endDate);
}
/**
*
* @param startDate
* @param endDate
* @return (endDate - startDate)
*/
public static long yearsBetween(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null) {
return 0;
}
return ChronoUnit.YEARS.between(startDate, endDate);
}
/**
*
* @param startDate
* @param endDate
* @return (, , )
*/
public static Period periodBetween(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null) {
return Period.ZERO;
}
return Period.between(startDate, endDate);
}
/**
* ()
* @param date
* @return (: "월요일", "화요일")
*/
public static String getDayOfWeekName(LocalDate date) {
if (date == null) {
return null;
}
return date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREAN);
}
/**
* ()
* @param date
* @return (: "Monday", "Tuesday")
*/
public static String getDayOfWeekNameInEnglish(LocalDate date) {
if (date == null) {
return null;
}
return date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH);
}
/**
* ( )
* @param date
* @return (: "월", "화")
*/
public static String getDayOfWeekShortName(LocalDate date) {
if (date == null) {
return null;
}
return date.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.KOREAN);
}
/**
*
* @param date
* @return true, false
*/
public static boolean isWeekend(LocalDate date) {
if (date == null) {
return false;
}
DayOfWeek dayOfWeek = date.getDayOfWeek();
return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
}
/**
*
* @param date
* @return true, false
*/
public static boolean isWeekday(LocalDate date) {
return !isWeekend(date);
}
/**
*
* @param date
* @return true, false
*/
public static boolean isToday(LocalDate date) {
if (date == null) {
return false;
}
return date.equals(LocalDate.now());
}
/**
*
* @param date
* @return
*/
public static LocalDate getFirstDayOfMonth(LocalDate date) {
if (date == null) {
return null;
}
return date.with(TemporalAdjusters.firstDayOfMonth());
}
/**
*
* @param date
* @return
*/
public static LocalDate getLastDayOfMonth(LocalDate date) {
if (date == null) {
return null;
}
return date.with(TemporalAdjusters.lastDayOfMonth());
}
/**
*
* @param date
* @return
*/
public static LocalDate getFirstDayOfYear(LocalDate date) {
if (date == null) {
return null;
}
return date.with(TemporalAdjusters.firstDayOfYear());
}
/**
*
* @param date
* @return
*/
public static LocalDate getLastDayOfYear(LocalDate date) {
if (date == null) {
return null;
}
return date.with(TemporalAdjusters.lastDayOfYear());
}
/**
*
* @param date
* @param dayOfWeek (: DayOfWeek.MONDAY)
* @return
*/
public static LocalDate getNextDayOfWeek(LocalDate date, DayOfWeek dayOfWeek) {
if (date == null || dayOfWeek == null) {
return null;
}
return date.with(TemporalAdjusters.next(dayOfWeek));
}
/**
*
* @param date
* @param dayOfWeek (: DayOfWeek.MONDAY)
* @return
*/
public static LocalDate getPreviousDayOfWeek(LocalDate date, DayOfWeek dayOfWeek) {
if (date == null || dayOfWeek == null) {
return null;
}
return date.with(TemporalAdjusters.previous(dayOfWeek));
}
/**
* LocalDateTime
* @param date
* @param hour
* @param minute
* @param second
* @return LocalDateTime
*/
public static LocalDateTime combineDateTime(LocalDate date, int hour, int minute, int second) {
if (date == null) {
return null;
}
return LocalDateTime.of(date, LocalTime.of(hour, minute, second));
}
/**
* "HH:mm:ss"
* @param request HttpServletRequest
* @param pattern (: "HH:mm:ss")
* @return (HH:mm:ss )
*/
public static String getSessionExpiryTime(HttpServletRequest request, String pattern) {
if (request == null) {
return "00:00:00";
}
HttpSession session = request.getSession(false);
if (session == null) {
return "00:00:00";
}
// 세션 최대 유효 시간(초)
int maxInactiveInterval = session.getMaxInactiveInterval();
// 시, 분, 초 계산
int hours = maxInactiveInterval / 3600;
int minutes = (maxInactiveInterval % 3600) / 60;
int seconds = maxInactiveInterval % 60;
// HH:mm:ss 형식으로 반환
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
}

@ -0,0 +1,161 @@
/**
* Class Name : EgovFileScrty.java
* Description : Base64/ / Business Interface class
* Modification Information
*
*
* ------- -------- ---------------------------
* 2009.02.04
*
* @author
* @since 2009. 02. 04
* @version 1.0
* @see
*
* Copyright (C) 2009 by MOPAS All right reserved.
*/
package egovframework.util;
//import org.apache.tomcat.util.codec.binary.Base64;
import org.apache.commons.codec.binary.Base64;
import java.security.MessageDigest;
/**
* @Class Name : EgovFileScrty.java
* @Description :
* @Modification Information
*
*
* ---------- ------- -------------------
* 2019.11.29 encryptPassword(String data) : KISA ( )
* 2022.11.16
*
* @author
* @since 2009.08.26
* @version 1.0
*/
public class EgovFileScrty {
/**
*
*
* @param byte[] data
* @return String result
* @exception Exception
*/
public static String encodeBinary(byte[] data) throws Exception {
if (data == null) {
return "";
}
return new String(Base64.encodeBase64(data));
}
/**
*
*
* @param String data
* @return String result
* @exception Exception
*/
@Deprecated
public static String encode(String data) throws Exception {
return encodeBinary(data.getBytes());
}
/**
*
*
* @param String data
* @return String result
* @exception Exception
*/
public static byte[] decodeBinary(String data) throws Exception {
return Base64.decodeBase64(data.getBytes());
}
/**
*
*
* @param String data
* @return String result
* @exception Exception
*/
@Deprecated
public static String decode(String data) throws Exception {
return new String(decodeBinary(data));
}
/**
* ( SHA-256 )
*
* @param password
* @param id salt ID
* @return
* @throws Exception
*/
public static String encryptPassword(String password, String id) throws Exception {
if (password == null) return "";
if (id == null) return ""; // KISA 보안약점 조치 (2018-12-11, 신용호)
byte[] hashValue; // 해쉬값
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(id.getBytes());
hashValue = md.digest(password.getBytes());
return new String(Base64.encodeBase64(hashValue));
}
/**
* ( SHA-256 )
* @param data
* @param salt Salt
* @return
* @throws Exception
*/
public static String encryptPassword(String data, byte[] salt) throws Exception {
if (data == null) {
return "";
}
byte[] hashValue; // 해쉬값
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(salt);
hashValue = md.digest(data.getBytes());
return new String(Base64.encodeBase64(hashValue));
}
/**
* (salt ).
*
* @param data
* @param encoded (Base64 )
* @return
* @throws Exception
*/
public static boolean checkPassword(String data, String encoded, byte[] salt) throws Exception {
byte[] hashValue; // 해쉬값
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(salt);
hashValue = md.digest(data.getBytes());
return MessageDigest.isEqual(hashValue, Base64.decodeBase64(encoded.getBytes()));
}
}

@ -0,0 +1,640 @@
package egovframework.util;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import egovframework.configProperties.FileUploadProperties;
import go.kr.project.common.model.FileVO;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
/**
* /
*/
@Component
public class FileUtil {
/** 파일 업로드 설정 */
private final FileUploadProperties fileUploadProperties;
/** 날짜 유틸리티 */
private final DateUtil dateUtil;
/** 파일 저장 기본 경로 */
private String uploadPath;
/** 최대 파일 크기 (단일 파일) - 기본값 10MB */
private long maxFileSize;
/** 최대 총 파일 크기 - 기본값 50MB */
private long maxTotalSize;
/** 허용된 파일 확장자 */
private String allowedExtensions;
/** 허용된 파일 확장자 목록 */
private List<String> allowedExtensionList;
/** 최대 파일 개수 - 기본값 10개 */
private int maxFiles;
/** 실제 파일 삭제 여부 - 기본값 true */
private boolean realFileDelete;
/** 하위 디렉토리 설정 */
private Map<String, String> subDirs;
/**
*
* @param fileUploadProperties
* @param dateUtil
*/
public FileUtil(FileUploadProperties fileUploadProperties, DateUtil dateUtil) {
this.fileUploadProperties = fileUploadProperties;
this.dateUtil = dateUtil;
}
/**
*
* FileUploadProperties
*/
@PostConstruct
public void init() {
// 설정 값 초기화
this.uploadPath = fileUploadProperties.getPath();
this.maxFileSize = fileUploadProperties.getMaxSize();
this.maxTotalSize = fileUploadProperties.getMaxTotalSize();
this.allowedExtensions = fileUploadProperties.getAllowedExtensions();
this.maxFiles = fileUploadProperties.getMaxFiles();
this.realFileDelete = fileUploadProperties.isRealFileDelete();
this.subDirs = fileUploadProperties.getSubDirs();
// 허용된 파일 확장자 목록 초기화
allowedExtensionList = Arrays.stream(allowedExtensions.split(","))
.map(String::trim)
.map(String::toLowerCase)
.collect(Collectors.toList());
}
/**
* <h3> </h3>
* : return "/" .
* @see #cleanPath(String, boolean)
*/
public static String cleanPath(String filePath) throws IllegalArgumentException {
// Java 는 파일 경로 구분자가 "\" 든 "/" 든 똑같이 대응하도록 되어 있다.
// 그러므로 useOsFileSeparatorOnResult=false 로 세팅한다.
return cleanPath(filePath, false);
}
/**
* <h3> </h3>
* @param filePath
* @param useOsFileSeparatorOnResult return OS true .
* true window "C:\sdf\some.txt" , false "C:/sdf/some.txt"
* @return
* @throws IllegalArgumentException , WEB Root .
*/
public static String cleanPath(String filePath, boolean useOsFileSeparatorOnResult) throws IllegalArgumentException {
if(filePath == null || filePath.trim().isEmpty()) {
return filePath;
}
filePath = filePath.trim();
/*
, "/" .
.
String OS [useOsFileSeparatorOnResult=true] .
*/
String sanitizedPath = filePath.replaceAll("\\\\+", "/");
// "../" 경로 제거
sanitizedPath = sanitizedPath.replaceAll("\\.\\.", "");
// "/./" 경로 제거
sanitizedPath = sanitizedPath.replaceAll("/\\./", "/");
// "&" 제거
sanitizedPath = sanitizedPath.replaceAll("&", "");
// Replace multiple consecutive slashes with a single slash
sanitizedPath = sanitizedPath.replaceAll("/{2,}", "/");
// 루트 경로 사용 검사 ("/" or "C:/"). 발견되면 예외를 던진다.
checkRootPathUsage(filePath, sanitizedPath);
// URL 경로 체크(= Web root 사용여부 검사). 발견되면 예외를 던진다.
checkUrlLikePathUsage(filePath, sanitizedPath);
// 최종적으로 운영체제에 맞게 문자열을 반환할지 분기처리하여 return 한다.
return useOsFileSeparatorOnResult ?
changeFileSeparatorDependOnOs(sanitizedPath) : sanitizedPath;
}
/**
* .
*/
private static void checkRootPathUsage(String filePath, String sanitizedPath) {
if (sanitizedPath.equals("/") || sanitizedPath.matches("[A-Za-z]:/$")) {
throw new IllegalArgumentException("Invalid path: " + filePath);
}
}
/**
* URL Path . Web Root .
*/
private static void checkUrlLikePathUsage(String filePath, String sanitizedPath) throws IllegalArgumentException {
Path path = Paths.get(sanitizedPath);
// Check if the path is a URL-like path (potentially a web root)
URI uri = path.toUri();
if (uri.getScheme() != null && uri.getHost() != null) {
throw new IllegalArgumentException("Path resembles a URL-like path. Error Occurred Path => " + filePath);
}
}
/**
* .
*/
public static String changeFileSeparatorDependOnOs(String path) {
String osName = System.getProperty("os.name").toLowerCase();
return osName.toLowerCase().contains("win") ? path.replace("/", File.separator) : path;
}
/**
* File File .
*/
public static File cleanPath(File file) {
String path = file.getPath();
return new File(cleanPath(path));
}
/**
* Path File .
*/
public static Path cleanPath(Path file) {
String path = file.toFile().getPath();
return Paths.get(cleanPath(path));
}
/**
*
* (Path Traversal)
* @param path
* @param isFileName (true: , false: )
* @throws IOException
*/
private void validatePath(String path, boolean isFileName) throws IOException {
if (path == null) {
throw new IOException("경로가 null입니다.");
}
// 경로 정규화 시도
try {
Path normalizedPath = Paths.get(cleanPath(path)).normalize();
String normalizedPathStr = normalizedPath.toString();
// 정규화 후에도 '..'가 남아있는지 확인
if (normalizedPathStr.contains("..")) {
String type = isFileName ? "파일명" : "디렉토리 경로";
throw new IOException("잘못된 " + type + "입니다: " + path);
}
// 추가 경로 검증 로직
if (isFileName) {
// 파일명에는 경로 구분자가 없어야 함
if (normalizedPathStr.contains("/") || normalizedPathStr.contains("\\")) {
throw new IOException("파일명에 경로 구분자가 포함되어 있습니다: " + path);
}
} else {
// 디렉토리 경로 검증 로직 (필요에 따라 추가)
}
// 허용된 문자만 포함되어 있는지 검증 (정규식 사용)
//if (!normalizedPathStr.matches("[a-zA-Z0-9_\\-\\.가-힣]+")) {
// String type = isFileName ? "파일명" : "디렉토리 경로";
// throw new IOException("허용되지 않은 문자가 포함된 " + type + "입니다: " + path);
//}
} catch (InvalidPathException e) {
throw new IOException("유효하지 않은 경로입니다: " + path, e);
}
}
/**
*
* @param fileExt
* @return
*/
private boolean isAllowedExtension(String fileExt) {
return allowedExtensionList.contains(fileExt.toLowerCase());
}
/**
*
* @param key (: bbs-notice, html-editor)
* @return
* @throws IOException
*/
public String getSubDir(String key) throws IOException {
if (!subDirs.containsKey(key)) {
throw new IOException("설정된 하위 디렉토리가 없습니다: " + key);
}
return subDirs.get(key);
}
/**
*
* @param fileName
* @param userAgent User-Agent
* @return
* @throws IOException
*/
private String getEncodedFilename(String fileName, String userAgent) throws IOException {
if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Chrome")) {
// IE, Chrome
return URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
} else if (userAgent.contains("Firefox")) {
// Firefox
return "\"" + new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1) + "\"";
} else {
// 기타 브라우저
return URLEncoder.encode(fileName, "UTF-8");
}
}
/**
* FileVO
* @param originalFilename
* @param storedFilename
* @param subDir
* @param fileSize
* @param fileExt
* @param contentType
* @return FileVO
*/
private FileVO createFileVO(String originalFilename, String storedFilename, String subDir,
long fileSize, String fileExt, String contentType) {
FileVO fileVO = new FileVO();
fileVO.setOriginalFileNm(originalFilename);
fileVO.setStoredFileNm(storedFilename);
fileVO.setFilePath(subDir);
fileVO.setFileSize(fileSize);
fileVO.setFileExt(fileExt);
fileVO.setContentType(contentType);
return fileVO;
}
/**
*
* @param files
* @param subDir (notice, biz, qna )
* @return
* @throws IOException
*/
public List<FileVO> uploadFiles(List<MultipartFile> files, String subDir) throws IOException {
// 파일 유효성 검증
validateFiles(files);
// 디렉토리 경로 검증
validatePath(subDir, false);
// 년월 정보 추출
String yearMonth = dateUtil.getCurrentYearMonth();
// 년월 디렉토리를 포함한 경로 생성
String yearMonthPath = subDir + File.separator + yearMonth;
// 디렉토리 경로 검증
validatePath(yearMonthPath, false);
// 디렉토리 생성
String uploadDir = uploadPath + File.separator + yearMonthPath;
createDirectoryIfNotExists(uploadDir);
// 파일 업로드 처리
return processFileUploads(files, yearMonthPath, uploadDir);
}
/**
*
* @param files
* @throws IOException
*/
private void validateFiles(List<MultipartFile> files) throws IOException {
// 파일 개수 검증
int validFileCount = (int) files.stream()
.filter(file -> !file.isEmpty())
.count();
if (validFileCount > maxFiles) {
throw new IOException("파일 개수가 제한을 초과했습니다. 최대 " + maxFiles + "개까지 가능합니다.");
}
}
/**
*
* @param files
* @param subDir
* @param uploadDir
* @return
* @throws IOException
*/
private List<FileVO> processFileUploads(List<MultipartFile> files, String subDir, String uploadDir) throws IOException {
List<FileVO> uploadedFiles = new ArrayList<>();
long totalSize = 0;
for (MultipartFile file : files) {
if (file.isEmpty()) continue;
// 파일 크기 검증
validateFileSize(file, totalSize);
totalSize += file.getSize();
// 파일 저장 및 정보 생성
FileVO fileVO = saveFile(file, subDir, uploadDir);
uploadedFiles.add(fileVO);
}
return uploadedFiles;
}
/**
*
* @param file
* @param currentTotalSize
* @throws IOException
*/
private void validateFileSize(MultipartFile file, long currentTotalSize) throws IOException {
// 단일 파일 크기 검증
if (file.getSize() > maxFileSize * 1024 * 1024) {
throw new IOException("파일 크기가 제한을 초과했습니다. 최대 " + maxFileSize + "MB까지 가능합니다.");
}
// 총 파일 크기 검증
if (currentTotalSize + file.getSize() > maxTotalSize * 1024 * 1024) {
throw new IOException("총 파일 크기가 제한을 초과했습니다. 최대 " + maxTotalSize + "MB까지 가능합니다.");
}
}
/**
*
* @param file
* @param subDir
* @param uploadDir
* @return
* @throws IOException
*/
private FileVO saveFile(MultipartFile file, String subDir, String uploadDir) throws IOException {
// 원본 파일명 및 확장자 추출
String originalFilename = file.getOriginalFilename();
String fileExt = getFileExtension(originalFilename);
// 확장자 검증
if (!isAllowedExtension(fileExt)) {
throw new IOException("허용되지 않은 파일 형식입니다: " + fileExt);
}
// UUID를 이용한 저장 파일명 생성
String storedFilename = UUID.randomUUID() + "." + fileExt;
// 파일명 검증
validatePath(storedFilename, true);
// 파일 저장 경로 생성 및 검증
Path filePath = createAndValidateFilePath(uploadDir, storedFilename);
// 파일 저장
Files.write(filePath, file.getBytes());
// 파일 정보 생성 및 반환
return createFileVO(originalFilename, storedFilename, subDir,
file.getSize(), fileExt, file.getContentType());
}
/**
*
* @param uploadDir
* @param storedFilename
* @return
* @throws IOException
*/
private Path createAndValidateFilePath(String uploadDir, String storedFilename) throws IOException {
Path filePath = Paths.get(uploadDir).normalize().resolve(storedFilename).normalize();
// 생성된 경로가 업로드 디렉토리 내에 있는지 확인
File targetFile = filePath.toFile();
String canonicalUploadDir = new File(uploadDir).getCanonicalPath();
String canonicalTargetPath = targetFile.getCanonicalPath();
if (!canonicalTargetPath.startsWith(canonicalUploadDir)) {
throw new IOException("잘못된 파일 경로입니다.");
}
return filePath;
}
/**
*
* @param fileVO
* @param request HTTP
* @param response HTTP
* @throws IOException
*/
public void downloadFile(FileVO fileVO, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 파일 정보 검증 및 파일 객체 생성
File file = validateAndGetFile(fileVO);
// 파일 확장자 검증
validateFileExtension(fileVO.getOriginalFileNm());
// 브라우저별 인코딩된 파일명 생성
String userAgent = request.getHeader("User-Agent");
String encodedFilename = getEncodedFilename(fileVO.getOriginalFileNm(), userAgent);
// 응답 헤더 설정
setResponseHeaders(response, fileVO, file, encodedFilename);
// 파일 전송
sendFile(file, response);
}
/**
*
* @param fileVO
* @return
* @throws IOException
*/
private File validateAndGetFile(FileVO fileVO) throws IOException {
// 파일 경로 생성
String filePath = uploadPath + File.separator + fileVO.getFilePath() + File.separator + fileVO.getStoredFileNm();
File file = new File(filePath);
// 파일 존재 여부 확인
if (!file.exists()) {
throw new IOException("파일을 찾을 수 없습니다: " + filePath);
}
// 보안 검사: 경로 검증 (경로 탐색 공격 방지)
String canonicalPath = file.getCanonicalPath();
if (!canonicalPath.startsWith(new File(uploadPath).getCanonicalPath())) {
throw new IOException("잘못된 파일 경로입니다.");
}
return file;
}
/**
*
* @param filename
* @throws IOException
*/
private void validateFileExtension(String filename) throws IOException {
String fileExt = getFileExtension(filename);
if (!isAllowedExtension(fileExt)) {
throw new IOException("허용되지 않은 파일 형식입니다: " + fileExt);
}
}
/**
*
* @param response HTTP
* @param fileVO
* @param file
* @param encodedFilename
*/
private void setResponseHeaders(HttpServletResponse response, FileVO fileVO, File file, String encodedFilename) {
// 컨텐츠 타입 및 길이 설정
response.setContentType(fileVO.getContentType());
response.setContentLength((int) file.length());
// 다운로드 관련 헤더 설정
response.setHeader("Content-Disposition", "attachment; filename=" + encodedFilename);
response.setHeader("Content-Transfer-Encoding", "binary");
// 캐시 관련 헤더 설정
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// 보안 관련 헤더 설정
response.setHeader("X-Content-Type-Options", "nosniff");
}
/**
*
* @param file
* @param response HTTP
* @throws IOException
*/
private void sendFile(File file, HttpServletResponse response) throws IOException {
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
}
/**
*
* @param fileVO
* @return
*/
public boolean deleteFile(FileVO fileVO) {
// real-file-delete 설정이 false인 경우 실제 파일 삭제하지 않고 true 반환
if (!realFileDelete) {
return true;
}
try {
// 파일 경로 검증 및 파일 객체 생성
File file = getValidatedFileForDelete(fileVO);
// 파일 존재 여부 확인 및 삭제
return file.exists() && file.delete();
} catch (IOException e) {
// 로그 기록 등의 처리를 추가할 수 있음
return false;
}
}
/**
*
* @param fileVO
* @return
* @throws IOException
*/
private File getValidatedFileForDelete(FileVO fileVO) throws IOException {
// 파일 경로 및 파일명 검증
String subDir = fileVO.getFilePath();
String storedFilename = fileVO.getStoredFileNm();
// 경로 검증
validatePath(subDir, false);
validatePath(storedFilename, true);
// 파일 경로 생성
String filePath = uploadPath + File.separator + subDir + File.separator + storedFilename;
File file = new File(filePath);
// 보안 검사: 경로 검증 (경로 탐색 공격 방지)
String canonicalPath = file.getCanonicalPath();
if (!canonicalPath.startsWith(new File(uploadPath).getCanonicalPath())) {
throw new IOException("잘못된 파일 경로입니다.");
}
return file;
}
/**
*
* @param directory
*/
@CanIgnoreReturnValue
private void createDirectoryIfNotExists(String directory) {
File dir = new File(directory);
if (!dir.exists()) {
dir.mkdirs();
}
}
/**
*
* @param filename
* @return ( )
*/
private String getFileExtension(String filename) {
if (filename == null || filename.isEmpty()) {
return "";
}
int lastDotIndex = filename.lastIndexOf(".");
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
return "";
}
return filename.substring(lastDotIndex + 1).toLowerCase();
}
}

@ -0,0 +1,58 @@
package egovframework.util;
import javax.servlet.http.HttpServletRequest;
/**
* packageName : egovframework.util
* fileName : HttpServletUtil
* author :
* date : 25. 5. 8.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 8.
*/
public class HttpServletUtil {
/**
* AJAX
* *.ajax URL AJAX .
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return requestURI != null && requestURI.endsWith(".ajax");
}
/**
* AJAX
* HTTP AJAX .
*
* AJAX :
* 1. 'X-Requested-With: XMLHttpRequest'
* 2. 'Accept' 'application/json'
* 3. 'Content-Type' 'application/json'
*/
public static boolean isRealAjaxRequest(HttpServletRequest request) {
// 'X-Requested-With: XMLHttpRequest' 헤더 확인 (가장 일반적인 AJAX 요청 지표)
String requestedWith = request.getHeader("X-Requested-With");
if ("XMLHttpRequest".equals(requestedWith)) {
return true;
}
// 'Accept' 헤더가 'application/json'을 포함하는지 확인
String accept = request.getHeader("Accept");
if (accept != null && accept.contains("application/json")) {
return true;
}
// 'Content-Type' 헤더가 'application/json'인지 확인
String contentType = request.getHeader("Content-Type");
if (contentType != null && contentType.contains("application/json")) {
return true;
}
return false;
}
}

@ -0,0 +1,170 @@
package egovframework.util;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
/**
* packageName : go.kr.project.batch.util
* fileName : ImageValidationUtil
* author :
* date : 2025-01-27
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Slf4j
public class ImageValidationUtil {
/**
*
*/
private static final List<String> SUPPORTED_IMAGE_EXTENSIONS = Arrays.asList(
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "webp"
);
/**
* MIME
*/
private static final List<String> IMAGE_MIME_TYPES = Arrays.asList(
"image/jpeg", "image/jpg", "image/png", "image/gif",
"image/bmp", "image/tiff", "image/webp"
);
/**
* .
*
* @param fileName
* @return
*/
public static boolean isImageByExtension(String fileName) {
if (fileName == null || fileName.trim().isEmpty()) {
return false;
}
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
return false;
}
String extension = fileName.substring(lastDotIndex + 1).toLowerCase();
return SUPPORTED_IMAGE_EXTENSIONS.contains(extension);
}
/**
* MIME .
*
* @param filePath
* @return
*/
public static boolean isImageByMimeType(Path filePath) {
try {
String mimeType = Files.probeContentType(filePath);
if (mimeType == null) {
return false;
}
return IMAGE_MIME_TYPES.contains(mimeType.toLowerCase());
} catch (IOException e) {
log.warn("MIME 타입 확인 중 오류 발생: {}", filePath, e);
return false;
}
}
/**
* .
*
* @param filePath
* @return (true: , false: )
*/
public static boolean isImageCorrupted(Path filePath) {
try {
File imageFile = filePath.toFile();
// 파일이 존재하지 않거나 크기가 0인 경우
if (!imageFile.exists() || imageFile.length() == 0) {
log.warn("이미지 파일이 존재하지 않거나 크기가 0입니다: {}", filePath);
return true;
}
// ImageIO를 사용하여 이미지 읽기 시도
BufferedImage image = ImageIO.read(imageFile);
// 이미지를 읽을 수 없는 경우 손상된 것으로 판단
if (image == null) {
log.warn("이미지 파일을 읽을 수 없습니다: {}", filePath);
return true;
}
// 이미지 크기가 유효하지 않은 경우
if (image.getWidth() <= 0 || image.getHeight() <= 0) {
log.warn("이미지 크기가 유효하지 않습니다: {} ({}x{})",
filePath, image.getWidth(), image.getHeight());
return true;
}
log.debug("이미지 파일 검증 성공: {} ({}x{})",
filePath, image.getWidth(), image.getHeight());
return false;
} catch (IOException e) {
log.warn("이미지 파일 검증 중 오류 발생: {}", filePath, e);
return true;
} catch (Exception e) {
log.error("이미지 파일 검증 중 예상치 못한 오류 발생: {}", filePath, e);
return true;
}
}
/**
* .
* ( + MIME )
*
* @param filePath
* @return
*/
public static boolean isImageFile(Path filePath) {
String fileName = filePath.getFileName().toString();
// 1차: 확장자 검사
boolean isImageByExt = isImageByExtension(fileName);
// 2차: MIME 타입 검사 (확장자가 이미지인 경우에만)
boolean isImageByMime = isImageByExt && isImageByMimeType(filePath);
log.debug("이미지 파일 검사 결과: {} - 확장자: {}, MIME: {}",
fileName, isImageByExt, isImageByMime);
return isImageByMime;
}
/**
* .
*
* @param filePath
* @return
*/
public static String getImageInfo(Path filePath) {
try {
BufferedImage image = ImageIO.read(filePath.toFile());
if (image == null) {
return "이미지 정보를 읽을 수 없음";
}
return String.format("크기: %dx%d, 타입: %d",
image.getWidth(), image.getHeight(), image.getType());
} catch (IOException e) {
log.warn("이미지 정보 조회 중 오류 발생: {}", filePath, e);
return "이미지 정보 조회 실패: " + e.getMessage();
}
}
}

@ -0,0 +1,352 @@
package egovframework.util;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
/**
*
*/
@Component
public class NumberUtil {
/**
* (int)
* @param str
* @param defaultValue
* @return
*/
public static int toInt(String str, int defaultValue) {
if (StringUtil.isEmpty(str)) {
return defaultValue;
}
try {
return Integer.parseInt(str.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* (long)
* @param str
* @param defaultValue
* @return
*/
public static long toLong(String str, long defaultValue) {
if (StringUtil.isEmpty(str)) {
return defaultValue;
}
try {
return Long.parseLong(str.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* (double)
* @param str
* @param defaultValue
* @return
*/
public static double toDouble(String str, double defaultValue) {
if (StringUtil.isEmpty(str)) {
return defaultValue;
}
try {
return Double.parseDouble(str.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* BigDecimal
* @param str
* @param defaultValue
* @return BigDecimal
*/
public static BigDecimal toBigDecimal(String str, BigDecimal defaultValue) {
if (StringUtil.isEmpty(str)) {
return defaultValue;
}
try {
return new BigDecimal(str.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
*
* @param number
* @return
*/
public static String formatWithComma(long number) {
return NumberFormat.getNumberInstance(Locale.KOREA).format(number);
}
/**
*
* @param number
* @return
*/
public static String formatWithComma(double number) {
return NumberFormat.getNumberInstance(Locale.KOREA).format(number);
}
/**
*
* @param number
* @return
*/
public static String formatWithComma(BigDecimal number) {
if (number == null) {
return "";
}
return NumberFormat.getNumberInstance(Locale.KOREA).format(number);
}
/**
*
* @param number
* @return (: "₩1,000")
*/
public static String formatKRW(long number) {
return NumberFormat.getCurrencyInstance(Locale.KOREA).format(number);
}
/**
*
* @param number
* @return (: "₩1,000.50")
*/
public static String formatKRW(double number) {
return NumberFormat.getCurrencyInstance(Locale.KOREA).format(number);
}
/**
*
* @param number
* @return (: "₩1,000.50")
*/
public static String formatKRW(BigDecimal number) {
if (number == null) {
return "";
}
return NumberFormat.getCurrencyInstance(Locale.KOREA).format(number);
}
/**
*
* @param number
* @return (: "$1,000")
*/
public static String formatUSD(long number) {
return NumberFormat.getCurrencyInstance(Locale.US).format(number);
}
/**
*
* @param number
* @return (: "$1,000.50")
*/
public static String formatUSD(double number) {
return NumberFormat.getCurrencyInstance(Locale.US).format(number);
}
/**
*
* @param number
* @return (: "$1,000.50")
*/
public static String formatUSD(BigDecimal number) {
if (number == null) {
return "";
}
return NumberFormat.getCurrencyInstance(Locale.US).format(number);
}
/**
*
* @param number
* @param scale
* @return
*/
public static double round(double number, int scale) {
return BigDecimal.valueOf(number)
.setScale(scale, RoundingMode.HALF_UP)
.doubleValue();
}
/**
* BigDecimal
* @param number
* @param scale
* @return
*/
public static BigDecimal round(BigDecimal number, int scale) {
if (number == null) {
return null;
}
return number.setScale(scale, RoundingMode.HALF_UP);
}
/**
*
* @param number
* @param scale
* @return
*/
public static double floor(double number, int scale) {
return BigDecimal.valueOf(number)
.setScale(scale, RoundingMode.FLOOR)
.doubleValue();
}
/**
* BigDecimal
* @param number
* @param scale
* @return
*/
public static BigDecimal floor(BigDecimal number, int scale) {
if (number == null) {
return null;
}
return number.setScale(scale, RoundingMode.FLOOR);
}
/**
*
* @param number
* @param scale
* @return
*/
public static double ceil(double number, int scale) {
return BigDecimal.valueOf(number)
.setScale(scale, RoundingMode.CEILING)
.doubleValue();
}
/**
* BigDecimal
* @param number
* @param scale
* @return
*/
public static BigDecimal ceil(BigDecimal number, int scale) {
if (number == null) {
return null;
}
return number.setScale(scale, RoundingMode.CEILING);
}
/**
*
* @param number
* @param pattern (: "#,###.##", "0.00")
* @return
*/
public static String format(double number, String pattern) {
DecimalFormat df = new DecimalFormat(pattern);
return df.format(number);
}
/**
*
* @param number
* @param pattern (: "#,###.##", "0.00")
* @return
*/
public static String format(BigDecimal number, String pattern) {
if (number == null) {
return "";
}
DecimalFormat df = new DecimalFormat(pattern);
return df.format(number);
}
/**
*
* @param a
* @param b
* @return
*/
public static int min(int a, int b) {
return Math.min(a, b);
}
/**
*
* @param a
* @param b
* @return
*/
public static int max(int a, int b) {
return Math.max(a, b);
}
/**
*
* @param number
* @param min
* @param max
* @return true, false
*/
public static boolean isBetween(int number, int min, int max) {
return number >= min && number <= max;
}
/**
*
* @param number
* @param min
* @param max
* @return true, false
*/
public static boolean isBetween(double number, double min, double max) {
return number >= min && number <= max;
}
/**
*
* @param number
* @return true, false
*/
public static boolean isPositive(int number) {
return number > 0;
}
/**
*
* @param number
* @return true, false
*/
public static boolean isNegative(int number) {
return number < 0;
}
/**
* 0
* @param number
* @return 0 true, false
*/
public static boolean isZero(int number) {
return number == 0;
}
/**
* 0 ( )
* @param number
* @return 0 true, false
*/
public static boolean isZero(double number) {
return Math.abs(number) < 0.000001;
}
}

@ -0,0 +1,25 @@
package egovframework.util;
import org.springframework.util.AntPathMatcher;
/**
* Utility class for URL pattern matching
*/
public class PathMatcherUtil {
private static final AntPathMatcher pathMatcher = new AntPathMatcher();
/**
* Check if URL pattern matches the request URI
*
* @param pattern URL pattern
* @param uri Request URI
* @return true if matches, false otherwise
*/
public static boolean match(String pattern, String uri) {
if (pattern == null || uri == null) {
return false;
}
return pathMatcher.match(pattern.trim(), uri);
}
}

@ -0,0 +1,67 @@
package egovframework.util;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* packageName : egovframework.util
* fileName : PathUtil
* author :
* date : 2023-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2023-06-10
*/
public class PathUtil {
/**
* contextPath .
*
* @return contextPath (: "/app")
*/
public static String getContextPath() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getContextPath();
} catch (Exception e) {
// RequestContextHolder가 사용 불가능한 경우 (예: 배치 작업 등)
return "";
}
}
/**
* URL contextPath .
* contextPath URL URL .
*
* @param url contextPath URL
* @return contextPath URL
*/
public static String addContextPath(String url) {
if (url == null || url.isEmpty()) {
return "";
}
// 외부 URL인 경우 (http:// 또는 https://로 시작하는 경우)
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
String contextPath = getContextPath();
// URL이 이미 contextPath로 시작하는 경우
if (!contextPath.isEmpty() && url.startsWith(contextPath)) {
return url;
}
// URL이 /로 시작하지 않는 경우 /를 추가
if (!url.startsWith("/")) {
url = "/" + url;
}
return contextPath + url;
}
}

@ -0,0 +1,235 @@
package egovframework.util;
import egovframework.constant.SessionConstants;
import go.kr.project.login.model.LoginUserVO;
import go.kr.project.login.model.SessionVO;
import go.kr.project.system.group.model.GroupVO;
import go.kr.project.system.menu.model.MenuVO;
import go.kr.project.system.role.model.RoleVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;
/**
* packageName : egovframework.util
* fileName : SessionUtil
* author :
* date : 2025-05-15
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-15
*/
@Slf4j
public class SessionUtil {
// 세션 키는 SessionConstants 클래스에서 관리
/**
* HttpServletRequest .
* HTTP null .
*
* @return HttpServletRequest , null
*/
public static HttpServletRequest getRequest() {
try {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest();
} catch (IllegalStateException e) {
// 배치 작업 등에서 HTTP 요청 컨텍스트가 없는 경우
log.debug("HTTP 요청 컨텍스트가 없습니다. (배치 작업 또는 비웹 컨텍스트)");
return null;
}
}
/**
* HttpSession .
* HTTP null .
*
* @return HttpSession , null
*/
public static HttpSession getSession() {
HttpServletRequest request = getRequest();
return request != null ? request.getSession() : null;
}
/**
* SessionVO .
* HTTP null .
*
* @return SessionVO , null
*/
public static SessionVO getSessionVO() {
HttpSession session = getSession();
if (session != null) {
return (SessionVO) session.getAttribute(SessionConstants.SESSION_KEY);
}
return null;
}
/**
* .
* HTTP null .
*
* @return , null
*/
public static LoginUserVO getLoginUser() {
SessionVO sessionVO = getSessionVO();
if (sessionVO != null && sessionVO.isLogin()) {
return sessionVO.getUser();
}
return null;
}
/**
* ID .
* HTTP null .
*
* @return ID, null
*/
public static String getUserId() {
LoginUserVO user = getLoginUser();
if (user != null) {
return user.getUserId();
}
return null;
}
/**
* .
* HTTP null .
*
* @return , null
*/
public static String getUserName() {
LoginUserVO user = getLoginUser();
if (user != null) {
return user.getUserNm();
}
return null;
}
/**
* .
* HTTP null .
*
* @return , null
*/
public static String getUserAccount() {
LoginUserVO user = getLoginUser();
if (user != null) {
return user.getUserAcnt();
}
return null;
}
/**
* .
* HTTP null .
*
* @return , null
*/
public static GroupVO getUserGroup() {
SessionVO sessionVO = getSessionVO();
if (sessionVO != null && sessionVO.isLogin()) {
return sessionVO.getGroup();
}
return null;
}
/**
* .
* HTTP null .
*
* @return , null
*/
public static List<RoleVO> getUserRoles() {
SessionVO sessionVO = getSessionVO();
if (sessionVO != null) {
return sessionVO.getRoles();
}
return null;
}
/**
* .
* HTTP null .
*
* @return , null
*/
public static List<MenuVO> getUserMenus() {
SessionVO sessionVO = getSessionVO();
if (sessionVO != null) {
return sessionVO.getMenus();
}
return null;
}
/**
* .
* HTTP false .
*
* @return
*/
public static boolean isLogin() {
SessionVO sessionVO = getSessionVO();
return sessionVO != null && sessionVO.isLogin();
}
/**
* .
* HTTP false .
*
* @return
*/
public static boolean isVisitor() {
SessionVO sessionVO = getSessionVO();
return sessionVO != null && sessionVO.isVisitor();
}
/**
* .
* HTTP false .
*
* @return
*/
public static boolean isSystem() {
SessionVO sessionVO = getSessionVO();
return sessionVO != null && sessionVO.isSystem();
}
/**
* .
* .
*
* @return ( true)
*/
public static boolean isBatchSystem() {
// 배치 작업은 시스템 권한으로 실행
return true;
}
/**
* ID .
* .
*
* @return ID
*/
public static String getBatchUserId() {
return "BATCH_SYSTEM";
}
/**
* .
*
* @return
*/
public static String getBatchUserAccount() {
return "BATCH_SYSTEM";
}
}

@ -0,0 +1,218 @@
package egovframework.util;
import org.springframework.stereotype.Component;
/**
*
*/
@Component
public class StringUtil {
/**
* null
* @param str
* @return null true, false
*/
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
/**
* null
* @param str
* @return null true, false
*/
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* null
* @param str
* @return null true, false
*/
public static boolean isBlank(String str) {
if (isEmpty(str)) {
return true;
}
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* null
* @param str
* @return null true, false
*/
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
/**
*
* @param str
* @return , null null
*/
public static String trim(String str) {
return str == null ? null : str.trim();
}
/**
* null ,
* @param str
* @return null ,
*/
public static String nullToEmpty(String str) {
return str == null ? "" : str;
}
/**
* null ,
* @param str
* @param defaultValue
* @return null ,
*/
public static String defaultIfEmpty(String str, String defaultValue) {
return isEmpty(str) ? defaultValue : str;
}
/**
* null ,
* @param str
* @param defaultValue
* @return null ,
*/
public static String defaultIfBlank(String str, String defaultValue) {
return isBlank(str) ? defaultValue : str;
}
/**
*
* @param str
* @param len
* @return , null null
*/
public static String left(String str, int len) {
if (str == null) {
return null;
}
if (len < 0) {
return "";
}
if (str.length() <= len) {
return str;
}
return str.substring(0, len);
}
/**
*
* @param str
* @param len
* @return , null null
*/
public static String right(String str, int len) {
if (str == null) {
return null;
}
if (len < 0) {
return "";
}
if (str.length() <= len) {
return str;
}
return str.substring(str.length() - len);
}
/**
*
* @param str
* @param searchStr
* @param replaceStr
* @return , null null
*/
public static String replace(String str, String searchStr, String replaceStr) {
if (isEmpty(str) || isEmpty(searchStr) || replaceStr == null) {
return str;
}
return str.replace(searchStr, replaceStr);
}
/**
* HTML
* @param str
* @return , null null
*/
public static String escapeHtml(String str) {
if (str == null) {
return null;
}
return str.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
/**
* HTML <br>
* @param str
* @return , null null
*/
public static String nl2br(String str) {
if (str == null) {
return null;
}
return str.replace("\r\n", "<br>")
.replace("\n", "<br>")
.replace("\r", "<br>");
}
/**
*
* @param str
* @param prefix
* @return true, false
*/
public static boolean startsWith(String str, String prefix) {
return str != null && prefix != null && str.startsWith(prefix);
}
/**
*
* @param str
* @param suffix
* @return true, false
*/
public static boolean endsWith(String str, String suffix) {
return str != null && suffix != null && str.endsWith(suffix);
}
/**
*
* @param str
* @param maxLength
* @param suffix (: "...")
* @return , null null
*/
public static String abbreviate(String str, int maxLength, String suffix) {
if (str == null) {
return null;
}
if (str.length() <= maxLength) {
return str;
}
if (suffix == null) {
suffix = "";
}
int suffixLength = suffix.length();
if (maxLength <= suffixLength) {
return suffix;
}
return str.substring(0, maxLength - suffixLength) + suffix;
}
}

@ -0,0 +1,340 @@
package egovframework.util;
import org.springframework.stereotype.Component;
import java.util.regex.Pattern;
/**
*
*/
@Component
public class ValidationUtil {
/** 이메일 주소 정규식 패턴 */
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$");
/** 한국 휴대폰 번호 정규식 패턴 (010-XXXX-XXXX 또는 010XXXXXXXX 형식) */
private static final Pattern MOBILE_PHONE_PATTERN =
Pattern.compile("^01(?:0|1|[6-9])(?:-?\\d{3,4})?(?:-?\\d{4})$");
/** 한국 일반 전화번호 정규식 패턴 (지역번호-국번-번호 형식) */
private static final Pattern PHONE_PATTERN =
Pattern.compile("^(?:(?:\\d{2,3})|(?:\\d{2,3}-))(?:\\d{3,4}-\\d{4})$");
/** 한국 우편번호 정규식 패턴 (5자리) */
private static final Pattern ZIPCODE_PATTERN =
Pattern.compile("^\\d{5}$");
/** 한국 주민등록번호 정규식 패턴 (XXXXXX-XXXXXXX 형식) */
private static final Pattern RESIDENT_REGISTRATION_NUMBER_PATTERN =
Pattern.compile("^\\d{6}-?[1-4]\\d{6}$");
/** 한국 사업자등록번호 정규식 패턴 (XXX-XX-XXXXX 형식) */
private static final Pattern BUSINESS_REGISTRATION_NUMBER_PATTERN =
Pattern.compile("^\\d{3}-?\\d{2}-?\\d{5}$");
/** IP 주소 정규식 패턴 (IPv4) */
private static final Pattern IPV4_PATTERN =
Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
/** URL 정규식 패턴 */
private static final Pattern URL_PATTERN =
Pattern.compile("^(https?|ftp)://[^\\s/$.?#].[^\\s]*$");
/** 날짜 정규식 패턴 (YYYY-MM-DD 형식) */
private static final Pattern DATE_PATTERN =
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$");
/** 시간 정규식 패턴 (HH:MM:SS 형식) */
private static final Pattern TIME_PATTERN =
Pattern.compile("^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$");
/** 한글 정규식 패턴 */
private static final Pattern KOREAN_PATTERN =
Pattern.compile("^[가-힣]+$");
/** 영문자 정규식 패턴 */
private static final Pattern ENGLISH_PATTERN =
Pattern.compile("^[a-zA-Z]+$");
/** 영문자 및 숫자 정규식 패턴 */
private static final Pattern ALPHANUMERIC_PATTERN =
Pattern.compile("^[a-zA-Z0-9]+$");
/** 숫자 정규식 패턴 */
private static final Pattern NUMERIC_PATTERN =
Pattern.compile("^[0-9]+$");
/**
*
* @param email
* @return true, false
*/
public static boolean isValidEmail(String email) {
if (StringUtil.isEmpty(email)) {
return false;
}
return EMAIL_PATTERN.matcher(email).matches();
}
/**
*
* @param mobilePhone
* @return true, false
*/
public static boolean isValidMobilePhone(String mobilePhone) {
if (StringUtil.isEmpty(mobilePhone)) {
return false;
}
return MOBILE_PHONE_PATTERN.matcher(mobilePhone).matches();
}
/**
*
* @param phone
* @return true, false
*/
public static boolean isValidPhone(String phone) {
if (StringUtil.isEmpty(phone)) {
return false;
}
return PHONE_PATTERN.matcher(phone).matches();
}
/**
*
* @param zipcode
* @return true, false
*/
public static boolean isValidZipcode(String zipcode) {
if (StringUtil.isEmpty(zipcode)) {
return false;
}
return ZIPCODE_PATTERN.matcher(zipcode).matches();
}
/**
* ( , )
* @param rrn
* @return true, false
*/
public static boolean isValidResidentRegistrationNumber(String rrn) {
if (StringUtil.isEmpty(rrn)) {
return false;
}
return RESIDENT_REGISTRATION_NUMBER_PATTERN.matcher(rrn).matches();
}
/**
* ( , )
* @param brn
* @return true, false
*/
public static boolean isValidBusinessRegistrationNumber(String brn) {
if (StringUtil.isEmpty(brn)) {
return false;
}
return BUSINESS_REGISTRATION_NUMBER_PATTERN.matcher(brn).matches();
}
/**
* IP (IPv4)
* @param ipAddress IP
* @return true, false
*/
public static boolean isValidIpAddress(String ipAddress) {
if (StringUtil.isEmpty(ipAddress)) {
return false;
}
return IPV4_PATTERN.matcher(ipAddress).matches();
}
/**
* URL
* @param url URL
* @return true, false
*/
public static boolean isValidUrl(String url) {
if (StringUtil.isEmpty(url)) {
return false;
}
return URL_PATTERN.matcher(url).matches();
}
/**
* (YYYY-MM-DD )
* @param date
* @return true, false
*/
public static boolean isValidDate(String date) {
if (StringUtil.isEmpty(date)) {
return false;
}
return DATE_PATTERN.matcher(date).matches();
}
/**
* (HH:MM:SS HH:MM )
* @param time
* @return true, false
*/
public static boolean isValidTime(String time) {
if (StringUtil.isEmpty(time)) {
return false;
}
return TIME_PATTERN.matcher(time).matches();
}
/**
*
* @param text
* @return true, false
*/
public static boolean isKorean(String text) {
if (StringUtil.isEmpty(text)) {
return false;
}
return KOREAN_PATTERN.matcher(text).matches();
}
/**
*
* @param text
* @return true, false
*/
public static boolean isEnglish(String text) {
if (StringUtil.isEmpty(text)) {
return false;
}
return ENGLISH_PATTERN.matcher(text).matches();
}
/**
*
* @param text
* @return true, false
*/
public static boolean isAlphanumeric(String text) {
if (StringUtil.isEmpty(text)) {
return false;
}
return ALPHANUMERIC_PATTERN.matcher(text).matches();
}
/**
*
* @param text
* @return true, false
*/
public static boolean isNumeric(String text) {
if (StringUtil.isEmpty(text)) {
return false;
}
return NUMERIC_PATTERN.matcher(text).matches();
}
/**
* ( , , 8 )
* @param password
* @return true, false
*/
public static boolean isValidPassword(String password) {
if (StringUtil.isEmpty(password) || password.length() < 8) {
return false;
}
boolean hasUpperCase = false;
boolean hasLowerCase = false;
boolean hasDigit = false;
boolean hasSpecialChar = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) {
hasUpperCase = true;
} else if (Character.isLowerCase(c)) {
hasLowerCase = true;
} else if (Character.isDigit(c)) {
hasDigit = true;
} else if (!Character.isLetterOrDigit(c)) {
hasSpecialChar = true;
}
}
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar;
}
/**
*
* @param text
* @param minLength
* @param maxLength
* @return true, false
*/
public static boolean isValidLength(String text, int minLength, int maxLength) {
if (text == null) {
return minLength <= 0;
}
int length = text.length();
return length >= minLength && length <= maxLength;
}
/**
* ( )
* @param rrn (XXXXXX-XXXXXXX )
* @return true, false
*/
public static boolean isValidResidentRegistrationNumberWithChecksum(String rrn) {
if (!isValidResidentRegistrationNumber(rrn)) {
return false;
}
// 하이픈 제거
rrn = rrn.replace("-", "");
// 가중치 배열
int[] weights = {2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5};
// 합계 계산
int sum = 0;
for (int i = 0; i < 12; i++) {
sum += (rrn.charAt(i) - '0') * weights[i];
}
// 체크섬 계산
int checksum = (11 - (sum % 11)) % 10;
// 마지막 자리와 체크섬 비교
return checksum == (rrn.charAt(12) - '0');
}
/**
* ( )
* @param brn (XXX-XX-XXXXX )
* @return true, false
*/
public static boolean isValidBusinessRegistrationNumberWithChecksum(String brn) {
if (!isValidBusinessRegistrationNumber(brn)) {
return false;
}
// 하이픈 제거
brn = brn.replace("-", "");
// 가중치 배열
int[] weights = {1, 3, 7, 1, 3, 7, 1, 3, 5};
// 합계 계산
int sum = 0;
for (int i = 0; i < 9; i++) {
sum += (brn.charAt(i) - '0') * weights[i];
}
// 체크섬 계산
sum += ((brn.charAt(8) - '0') * 5) / 10;
int checksum = (10 - (sum % 10)) % 10;
// 마지막 자리와 체크섬 비교
return checksum == (brn.charAt(9) - '0');
}
}

@ -0,0 +1,98 @@
package egovframework.util.excel;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionMode;
import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.security.GeneralSecurityException;
import java.util.List;
import static egovframework.util.excel.SuperClassReflectionUtils.getAllFieldsWithExcelColumn;
public abstract class BaseSxssfExcelFile implements ExcelFile {
protected static final int ROW_ACCESS_WINDOW_SIZE = 10000;
protected static final int ROW_START_INDEX = 0;
protected static final int COLUMN_START_INDEX = 0;
protected SXSSFWorkbook workbook;
protected Sheet sheet;
public BaseSxssfExcelFile() {
this.workbook = new SXSSFWorkbook(ROW_ACCESS_WINDOW_SIZE);
}
protected void renderHeaders(ExcelMetadata excelMetadata) {
sheet = workbook.createSheet(excelMetadata.getSheetName());
Row row = sheet.createRow(ROW_START_INDEX);
int columnIndex = COLUMN_START_INDEX;
CellStyle style = createCellStyle(workbook, true);
for (String fieldName : excelMetadata.getDataFieldNames()) {
createCell(row, columnIndex++, excelMetadata.getHeaderName(fieldName), style);
}
}
protected void renderDataLines(ExcelSheetData data) {
CellStyle style = createCellStyle(workbook, false);
int rowIndex = ROW_START_INDEX + 1;
List<Field> fields = getAllFieldsWithExcelColumn(data.getType());
for (Object record : data.getDataList()) {
Row row = sheet.createRow(rowIndex++);
int columnIndex = COLUMN_START_INDEX;
try {
for (Field field : fields) {
field.setAccessible(true);
createCell(row, columnIndex++, field.get(record), style);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Error accessing data field rendering data lines.", e);
}
}
}
@Override
public void write(OutputStream stream) {
try {
workbook.write(stream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void writeWithEncryption(OutputStream stream, String password) {
try {
if (password == null) {
write(stream);
} else {
POIFSFileSystem fileSystem = new POIFSFileSystem();
OutputStream encryptorStream = getEncryptorStream(fileSystem, password);
workbook.write(encryptorStream);
encryptorStream.close(); // this is necessary before writing out the FileSystem
fileSystem.writeFilesystem(stream); // write the encrypted file to the response stream
fileSystem.close();
}
workbook.close();
stream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private OutputStream getEncryptorStream(POIFSFileSystem fileSystem, String password) {
try {
Encryptor encryptor = new EncryptionInfo(EncryptionMode.agile).getEncryptor();
encryptor.confirmPassword(password);
return encryptor.getDataStream(fileSystem);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException("Failed to obtain encrypted data stream from POIFSFileSystem.");
}
}
}

@ -0,0 +1,12 @@
package egovframework.util.excel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {
String headerName() default "";
}

@ -0,0 +1,55 @@
package egovframework.util.excel;
import org.apache.poi.ss.usermodel.*;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public interface ExcelFile { // (1)
void write(OutputStream stream) throws IOException;
void writeWithEncryption(OutputStream stream, String password) throws IOException;
default <T> void createCell(Row row, int column, T value, CellStyle style) {
if (value == null) {
return; // avoid NPE
}
Cell cell = row.createCell(column);
if (value instanceof Integer) {
cell.setCellValue((Integer)value);
} else if (value instanceof Long) {
cell.setCellValue((Long)value);
} else if (value instanceof Double) {
cell.setCellValue((Double)value);
} else if (value instanceof Float) {
cell.setCellValue((Float)value);
} else if (value instanceof Boolean) {
cell.setCellValue((Boolean)value);
} else if (value instanceof LocalDateTime) {
LocalDateTime dateTime = (LocalDateTime)value;
cell.setCellValue(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
} else if (value instanceof LocalDate) {
LocalDate date = (LocalDate)value;
cell.setCellValue(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
} else if (value instanceof LocalTime) {
LocalTime time = (LocalTime)value;
cell.setCellValue(time.format(DateTimeFormatter.ofPattern("HH:mm:ss")));
} else {
cell.setCellValue(value.toString());
}
cell.setCellStyle(style);
}
default CellStyle createCellStyle(Workbook wb, boolean isBold) {
CellStyle style = wb.createCellStyle();
Font font = wb.createFont();
font.setBold(isBold);
style.setFont(font);
return style;
}
}

@ -0,0 +1,115 @@
package egovframework.util.excel;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@Slf4j
public class ExcelHandler<T> {
public List<T> handleExcelUpload(List<MultipartFile> mFiles, Class<T> clazz) {
List<T> dataList = new ArrayList<>();
mFiles.forEach(file -> {
try (InputStream inputStream = file.getInputStream()) {
dataList.addAll(parseExcel(inputStream, clazz));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
return dataList;
}
private List<T> parseExcel(InputStream inputStream, Class<T> clazz) throws IOException {
Workbook workbook = new XSSFWorkbook(inputStream);
Sheet sheet = workbook.getSheetAt(0); // 첫 번째 시트를 가져옴
// 헤더 정보 추출
Row headerRow = sheet.getRow(0);
List<String> headers = StreamSupport.stream(headerRow.spliterator(), false)
.map(Cell::getStringCellValue)
.collect(Collectors.toList());
List<T> dataList = StreamSupport.stream(sheet.spliterator(), false)
.skip(1) // 첫 번째 행은 헤더이므로 건너뜁니다.
.filter(this::isRowNotEmpty) // 빈 행이 아닌 경우에만 처리합니다.
.map(row -> mapRowToDto(row, clazz, headers))
.collect(Collectors.toList());
workbook.close();
return dataList;
}
private boolean isRowNotEmpty(Row row) {
Iterator<Cell> cellIterator = row.cellIterator();
while (cellIterator.hasNext()) {
Cell cell = cellIterator.next();
if (cell.getCellType() != CellType.BLANK) {
return true; // 빈 셀이 아닌 경우에만 true를 반환합니다.
}
}
return false; // 모든 셀이 비어 있으면 false를 반환합니다.
}
private T mapRowToDto(Row row, Class<T> clazz, List<String> excelHeaderList) {
T dataDto;
try {
dataDto = clazz.getDeclaredConstructor().newInstance();
Iterator<Cell> cellIterator = row.cellIterator();
while (cellIterator.hasNext()) {
Cell cell = cellIterator.next();
String excelHeaderName = excelHeaderList.get(cell.getColumnIndex());
//각 필드를 순회하며 커스텀 어노테이션인 ExcelHeader값에 맞게 값을 넣어줌
Field[] dtoFields = clazz.getDeclaredFields();
for (Field field : dtoFields) {
if (field.isAnnotationPresent(ExcelColumn.class)) {
ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
if (Objects.requireNonNull(annotation).headerName().equals(excelHeaderName)) {
field.setAccessible(true);
setFieldValue(field, dataDto, cell);
break;
}
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return dataDto;
}
private void setFieldValue(Field field, T dataDto, Cell cell) throws IllegalAccessException {
Class<?> fieldType = field.getType();
field.setAccessible(true);
if (fieldType == int.class || fieldType == Integer.class) {
field.set(dataDto, (int)cell.getNumericCellValue());
} else if (fieldType == long.class || fieldType == Long.class) {
field.set(dataDto, (long)cell.getNumericCellValue());
} else if (fieldType == double.class || fieldType == Double.class) {
field.set(dataDto, cell.getNumericCellValue());
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
field.set(dataDto, cell.getBooleanCellValue());
} else {
// if (fieldType == String.class) {
DataFormatter formatter = new DataFormatter();
field.set(dataDto, formatter.formatCellValue(cell));
}
// 다른 타입에 따른 맵핑을 추가할 수 있습니다.
}
}

@ -0,0 +1,23 @@
package egovframework.util.excel;
import lombok.Getter;
import java.util.List;
import java.util.Map;
@Getter
public class ExcelMetadata {
private final Map<String, String> excelHeaderNames;
private final List<String> dataFieldNames;
private final String sheetName;
public ExcelMetadata(Map<String, String> excelHeaderNames, List<String> dataFieldNames, String sheetName) {
this.excelHeaderNames = excelHeaderNames;
this.dataFieldNames = dataFieldNames;
this.sheetName = sheetName;
}
public String getHeaderName(String fieldName) {
return excelHeaderNames.getOrDefault(fieldName, "");
}
}

@ -0,0 +1,44 @@
package egovframework.util.excel;
import java.lang.reflect.Field;
import java.util.*;
import static egovframework.util.excel.SuperClassReflectionUtils.getAllFields;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;
public class ExcelMetadataFactory { // (2)
private ExcelMetadataFactory() {
}
private static class SingletonHolder {
private static final ExcelMetadataFactory INSTANCE = new ExcelMetadataFactory();
}
public static ExcelMetadataFactory getInstance() {
return SingletonHolder.INSTANCE;
}
public ExcelMetadata createMetadata(Class<?> clazz) {
Map<String, String> headerNamesMap = new LinkedHashMap<>();
List<String> dataFieldNamesList = new ArrayList<>();
for (Field field : getAllFields(clazz)) {
if (field.isAnnotationPresent(ExcelColumn.class)) {
ExcelColumn columnAnnotation = field.getAnnotation(ExcelColumn.class);
headerNamesMap.put(field.getName(), Objects.requireNonNull(columnAnnotation).headerName());
dataFieldNamesList.add(field.getName());
}
}
if (headerNamesMap.isEmpty()) {
throw new RuntimeException(String.format("Class %s has not @ExcelColumn at all", clazz));
}
return new ExcelMetadata(headerNamesMap, dataFieldNamesList, getSheetName(clazz));
}
private String getSheetName(Class<?> clazz) {
ExcelSheet annotation = getAnnotation(clazz, ExcelSheet.class);
if (annotation != null) {
return annotation.name();
}
return "Sheet1";
}
}

@ -0,0 +1,12 @@
package egovframework.util.excel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelSheet {
String name() default "";
}

@ -0,0 +1,17 @@
package egovframework.util.excel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class ExcelSheetData { // (1)
private final List<?> dataList;
private final Class<?> type;
public static ExcelSheetData of(List<?> dataList, Class<?> type) {
return new ExcelSheetData(dataList, type);
}
}

@ -0,0 +1,35 @@
package egovframework.util.excel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ExcelSheetDataGroup { // (2)
private final List<ExcelSheetData> dataList;
private ExcelSheetDataGroup(List<ExcelSheetData> data) {
validateEmpty(data);
this.dataList = new ArrayList<>(data);
}
public List<ExcelSheetData> getExcelSheetData() {
return Collections.unmodifiableList(dataList);
}
public static ExcelSheetDataGroup of(ExcelSheetData... data) {
List<ExcelSheetData> list;
if (data == null) {
list = Collections.emptyList();
} else {
list = Arrays.asList(data);
}
return new ExcelSheetDataGroup(list);
}
private void validateEmpty(List<ExcelSheetData> data) {
if (data.isEmpty()) {
throw new IllegalArgumentException("lists must not be empty");
}
}
}

@ -0,0 +1,52 @@
package egovframework.util.excel;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class OutputExcelFile {
public void serveFile(HttpServletRequest request, HttpServletResponse response, Path filePath) throws IOException {
String fileName = filePath.getFileName().toString();
try {
String browser = request.getHeader("User-Agent");
String encodedFileName;
if (browser.contains("MSIE") || browser.contains("Trident")) {
// IE 브라우저 대응
encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
} else {
// IE 외 브라우저 대응 (크롬, 파이어폭스 등)
encodedFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}
// Content-Disposition 설정
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
try (InputStream is = Files.newInputStream(filePath);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
}
public void generateExcelFile(ExcelSheetData data, Path filePath) throws IOException {
try (OutputStream os = Files.newOutputStream(filePath)) {
new SxssfExcelFile(data, os, null); // 엑셀 파일 생성 및 저장
}
}
}

@ -0,0 +1,66 @@
package egovframework.util.excel;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class SuperClassReflectionUtils {
private SuperClassReflectionUtils() {
}
public static List<Field> getAllFields(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
for (Class<?> clazzInClasses : getAllClassesIncludingSuperClasses(clazz, true)) {
fields.addAll(Arrays.asList(clazzInClasses.getDeclaredFields()));
}
return fields;
}
public static List<Field> getAllFieldsWithExcelColumn(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
for (Field field : getAllFields(clazz)) {
if (field.isAnnotationPresent(ExcelColumn.class)) {
fields.add(field);
}
}
return fields;
}
public static Annotation getAnnotation(Class<?> clazz, Class<? extends Annotation> targetAnnotation) {
for (Class<?> clazzInClasses : getAllClassesIncludingSuperClasses(clazz, false)) {
if (clazzInClasses.isAnnotationPresent(targetAnnotation)) {
return clazzInClasses.getAnnotation(targetAnnotation);
}
}
return null;
}
public static Field getField(Class<?> clazz, String name) throws Exception {
for (Class<?> clazzInClasses : getAllClassesIncludingSuperClasses(clazz, false)) {
for (Field field : clazzInClasses.getDeclaredFields()) {
if (field.getName().equals(name)) {
return clazzInClasses.getDeclaredField(name);
}
}
}
throw new NoSuchFieldException();
}
private static List<Class<?>> getAllClassesIncludingSuperClasses(Class<?> clazz, boolean fromSuper) {
List<Class<?>> classes = new ArrayList<>();
while (clazz != null) {
classes.add(clazz);
clazz = clazz.getSuperclass();
}
if (fromSuper) {
Collections.reverse(classes);
}
return classes;
}
}

@ -0,0 +1,67 @@
package egovframework.util.excel;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class SxssfExcelFile extends BaseSxssfExcelFile {
public SxssfExcelFile(ExcelSheetData data, HttpServletRequest request, HttpServletResponse response,
String fileName) {
this(data, request, response, fileName, null);
}
public SxssfExcelFile(ExcelSheetData data, OutputStream outputStream, @Nullable String password) {
ExcelMetadata metadata = ExcelMetadataFactory.getInstance().createMetadata(data.getType());
exportExcelFile(data, metadata, outputStream, password);
}
private void exportExcelFile(ExcelSheetData data, ExcelMetadata metadata, OutputStream stream,
@Nullable String password) {
renderHeaders(metadata);
renderDataLines(data);
writeWithEncryption(stream, password); // if password is null, encryption will not be applied.
}
public SxssfExcelFile(ExcelSheetData data, HttpServletRequest request, HttpServletResponse response,
String fileName, @Nullable String password) {
try {
setFileName(request, response, fileName);
ExcelMetadata metadata = ExcelMetadataFactory.getInstance().createMetadata(data.getType());
exportExcelFile(data, metadata, response.getOutputStream(), password);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void exportExcelFile(ExcelSheetData data, ExcelMetadata metadata, ServletOutputStream stream,
String password) {
renderHeaders(metadata);
renderDataLines(data);
writeWithEncryption(stream, password); // if password is null, encryption will not be applied.
}
private void setFileName(HttpServletRequest request, HttpServletResponse response, String fileName) {
try {
String browser = request.getHeader("User-Agent");
String encodedFileName;
if (browser.contains("MSIE") || browser.contains("Trident")) {
// IE 브라우저 대응
encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
} else {
// IE 외 브라우저 대응 (크롬, 파이어폭스 등)
encodedFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}
// Content-Disposition 설정
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
}
}

@ -0,0 +1,29 @@
package egovframework.util.excel;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SxssfMultiSheetExcelFile extends BaseSxssfExcelFile { // (4)
public SxssfMultiSheetExcelFile(ExcelSheetDataGroup dataGroup, HttpServletResponse response) throws IOException {
this(dataGroup, response, null);
}
public SxssfMultiSheetExcelFile(ExcelSheetDataGroup dataGroup, HttpServletResponse response,
@Nullable String password) throws IOException {
exportExcelFile(dataGroup, response.getOutputStream(), password);
}
private void exportExcelFile(ExcelSheetDataGroup dataGroup, ServletOutputStream stream, String password) throws
IOException {
for (ExcelSheetData data : dataGroup.getExcelSheetData()) {
ExcelMetadata metadata = ExcelMetadataFactory.getInstance().createMetadata(data.getType());
renderHeaders(metadata);
renderDataLines(data);
}
writeWithEncryption(stream, password); // if password is null, encryption will not be applied.
}
}

@ -0,0 +1,25 @@
package go.kr.project;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
@Slf4j
@ServletComponentScan
@SpringBootApplication
@ComponentScan(basePackages = {"go.kr.project", "egovframework"})
public class IbmsNewApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(IbmsNewApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(IbmsNewApplication.class, args);
}
}

@ -0,0 +1,12 @@
package go.kr.project;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(IbmsNewApplication.class);
}
}

@ -0,0 +1,58 @@
package go.kr.project.common.controller;
import egovframework.util.ApiResponseUtil;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.system.code.model.CodeDetailVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* packageName : go.kr.project.common.controller
* fileName : CommonCodeController
* author :
* date : 2025-05-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-10
*/
@RequestMapping("/common/code")
@Controller
@RequiredArgsConstructor
@Slf4j
@Tag(name = "공통 코드", description = "공통 코드 관련 API")
public class CommonCodeController {
private final CommonCodeService commonCodeService;
/**
* AJAX
*
* @param cdGroupId ID
* @return ResponseEntity
* @throws Exception
*/
@Operation(summary = "코드 상세 목록 조회", description = "특정 코드 그룹에 속한 코드 상세 목록을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "코드 상세 목록 조회 성공"),
@ApiResponse(responseCode = "400", description = "코드 상세 목록 조회 실패"),
@ApiResponse(description = "오류로 인한 실패")
})
@GetMapping("/detail/listByGroupId.ajax")
public ResponseEntity<?> getCodeDetailListByGroupIdAjax(@RequestParam String cdGroupId) {
List<CodeDetailVO> codeDetailList = commonCodeService.selectCodeDetailListByGroupId(cdGroupId);
return ApiResponseUtil.success(codeDetailList);
}
}

@ -0,0 +1,20 @@
package go.kr.project.common.controller;
import go.kr.project.common.service.CommonHeaderService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/common/header")
@Controller
@RequiredArgsConstructor
@Slf4j
@Tag(name = "공통 일정", description = "공통 일정 관련 API")
public class CommonHeaderController {
private final CommonHeaderService commonHeaderService;
}

@ -0,0 +1,52 @@
package go.kr.project.common.controller;
import egovframework.configProperties.LoginProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* packageName : egovframework.config
* fileName : ConfigController
* author :
* date : 25. 5. 19.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 19.
*/
@RequestMapping("/common/config")
@Controller
@RequiredArgsConstructor
@Slf4j
@Tag(name = "시스템 설정", description = "시스템 설정 관련 API")
public class ConfigController {
private final LoginProperties loginProperties;
/**
* URL
*
* @return
*/
@Operation(summary = "로그인 URL 설정값 자바스크립트 반환", description = "로그인 URL 설정값을 자바스크립트 코드로 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "자바스크립트 반환 성공")
})
@GetMapping(value = "/login-url.do", produces = "application/javascript")
public ResponseEntity<String> getLoginUrlJs() {
String js = "var loginUrl = '" + loginProperties.getUrl() + "';";
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/javascript"))
.body(js);
}
}

@ -0,0 +1,121 @@
package go.kr.project.common.controller;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.common.model.HtmlEditorFileVO;
import go.kr.project.common.service.HtmlEditorService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Objects;
/**
* packageName : go.kr.project.common.controller
* fileName : HtmlEditorController
* author :
* date : 2025-05-23
* description : HTML
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-23
*/
@RequestMapping("/common/htmlEditor")
@Controller
@RequiredArgsConstructor
@Slf4j
@Tag(name = "HTML 에디터", description = "HTML 에디터 관련 API")
public class HtmlEditorController {
private final HtmlEditorService htmlEditorService;
/**
* HTML
*
* @param image
* @param moduleId ID ()
* @param request HTTP
* @return URL ResponseEntity
*/
@Operation(summary = "에디터 이미지 업로드", description = "HTML 에디터에서 이미지를 업로드합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "이미지 업로드 성공"),
@ApiResponse(responseCode = "400", description = "이미지 업로드 실패"),
@ApiResponse(description = "오류로 인한 실패")
})
@PostMapping("/uploadImage.ajax")
public ResponseEntity<?> uploadImage(
@RequestParam("image") MultipartFile image,
@RequestParam(value = "moduleId", required = false) String moduleId,
HttpServletRequest request) {
try {
// 이미지 파일 검증
if (image.isEmpty()) {
return ApiResponseUtil.error("이미지 파일이 없습니다.");
}
// 이미지 파일 확장자 검증
String originalFilename = image.getOriginalFilename();
String fileExt = Objects.requireNonNull(originalFilename).substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
if (!fileExt.matches("jpg|jpeg|png|gif")) {
return ApiResponseUtil.error("허용되지 않은 이미지 형식입니다. (jpg, jpeg, png, gif만 가능)");
}
// 파일 업로드 처리
HtmlEditorFileVO uploadedFile = htmlEditorService.uploadHtmlEditorFile(
image, moduleId, "image", SessionUtil.getUserId());
// 이미지 URL 생성
String imageUrl = request.getContextPath() + "/common/htmlEditor/download.do?fileId=" + uploadedFile.getFileId();
// 응답 데이터 생성
HashMap<String, Object> data = new HashMap<>();
data.put("url", imageUrl);
data.put("alt", uploadedFile.getOriginalFileNm());
data.put("fileId", uploadedFile.getFileId());
return ApiResponseUtil.success(data);
} catch (Exception e) {
log.error("이미지 업로드 중 오류 발생", e);
return ApiResponseUtil.error("이미지 업로드 중 오류가 발생했습니다: " + e.getMessage());
}
}
/**
* .
*
* @param fileId ID
* @param request HTTP
* @param response HTTP
*/
@Operation(summary = "파일 다운로드", description = "HTML 에디터에서 사용된 파일을 다운로드합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "파일 다운로드 성공"),
@ApiResponse(responseCode = "400", description = "파일 다운로드 실패"),
@ApiResponse(description = "오류로 인한 실패")
})
@GetMapping("/download.do")
public void downloadFile(
@RequestParam String fileId,
HttpServletRequest request,
HttpServletResponse response) {
// 파일 다운로드 처리 - 서비스 계층으로 위임
htmlEditorService.downloadHtmlEditorFile(fileId, request, response);
}
}

@ -0,0 +1,40 @@
package go.kr.project.common.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* packageName : go.kr.project.common.controller
* fileName : SearchAddressController
* author :
* date : 25. 5. 13.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 13.
*/
@RequestMapping("/common/address")
@Controller
@RequiredArgsConstructor
@Slf4j
@Tag(name = "주소 검색", description = "주소 검색 관련 API")
public class SearchAddressController {
@Operation(summary = "주소 검색 팝업", description = "주소 검색 관련 요청을 처리 페이지를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "주소 검색 팝업 조회 성공")
})
@RequestMapping(value = "/search.do", method={RequestMethod.GET, RequestMethod.POST})
public String addressSearchPage() {
return "common/address/searchAddress";
}
}

@ -0,0 +1,59 @@
package go.kr.project.common.error;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* packageName : go.kr.project.common.error
* fileName : ErrorController
* author :
* date : 25. 6. 5.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 6. 5.
*/
@Controller
@Tag(name = "에러 페이지", description = "에러 페이지 관련 API")
public class ErrorController {
@Operation(summary = "404 에러 페이지", description = "404 에러 페이지를 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "404 에러 페이지 반환 성공")
})
@GetMapping("/error/404")
public String handleError404() {
return "error/404";
}
@Operation(summary = "500 에러 페이지", description = "500 에러 페이지를 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "500 에러 페이지 반환 성공")
})
@GetMapping("/error/500")
public String handleError500() {
return "error/500"; //
}
@Operation(summary = "일반 에러 페이지", description = "일반 에러 페이지를 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "일반 에러 페이지 반환 성공")
})
@GetMapping("/error/error")
public String handleError() {
return "error/error";
}
@Operation(summary = "eGov 에러 페이지", description = "eGov 에러 페이지를 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "eGov 에러 페이지 반환 성공")
})
@GetMapping("/error/egovError")
public String handleEgovError() {
return "error/egovError";
}
}

@ -0,0 +1,29 @@
package go.kr.project.common.mapper;
import go.kr.project.system.code.model.CodeDetailVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.mapper
* fileName : CommonCodeMapper
* author :
* date : 2025-05-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-10
*/
@Mapper
public interface CommonCodeMapper {
/**
* .
*
* @param cdGroupId ID
* @return
*/
List<CodeDetailVO> selectCodeDetailListByGroupId(String cdGroupId);
}

@ -0,0 +1,19 @@
package go.kr.project.common.mapper;
import org.apache.ibatis.annotations.Mapper;
/**
* packageName : go.kr.project.common.mapper
* fileName : CommonCodeMapper
* author :
* date : 2025-05-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-10
*/
@Mapper
public interface CommonHeaderMapper {
}

@ -0,0 +1,92 @@
package go.kr.project.common.mapper;
import go.kr.project.common.model.HtmlEditorFileVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.mapper
* fileName : HtmlEditorMapper
* author :
* date : 2025-05-23
* description : HTML Mapper
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-23
*/
@Mapper
public interface HtmlEditorMapper {
/**
* .
*
* @param fileId ID
* @return
*/
HtmlEditorFileVO selectHtmlEditorFile(String fileId);
/**
* .
*
* @param moduleId ID
* @return
*/
List<HtmlEditorFileVO> selectHtmlEditorFileListByModuleId(String moduleId);
/**
* .
*
* @param vo VO
* @return
*/
List<HtmlEditorFileVO> selectHtmlEditorFileList(HtmlEditorFileVO vo);
/**
* .
*
* @param vo VO
* @return
*/
int selectHtmlEditorFileListTotalCount(HtmlEditorFileVO vo);
/**
* ID .
*
* @return HEDF00000001 ID
*/
String generateFileId();
/**
* .
*
* @param vo VO
* @return
*/
int insertHtmlEditorFile(HtmlEditorFileVO vo);
/**
* .
*
* @param vo VO
* @return
*/
int updateHtmlEditorFile(HtmlEditorFileVO vo);
/**
* .
*
* @param fileId ID
* @return
*/
int deleteHtmlEditorFile(String fileId);
/**
* .
*
* @param moduleId ID
* @return
*/
int deleteHtmlEditorFileByModuleId(String moduleId);
}

@ -0,0 +1,50 @@
package go.kr.project.common.model;
import lombok.Data;
import java.io.Serializable;
/**
* packageName : go.kr.project.common
* fileName : DefaultVO
* author :
* date : 25. 5. 8.
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 8.
*/
@Data
public class DefaultVO implements Serializable {
/** 저장구분 : I(insert), U(update), D(delete) */
private String crudType;
/** checkbox */
private Integer chk;
/* 페이징여부 */
private String pagingYn;
/** 검색조건 */
private String searchCondition = "";
/** 검색Keyword */
private String searchKeyword = "";
/** TotalCount */
private Integer totalCount;
/** 사용유무 */
private String searchUseYn;
/** 정렬컬럼 */
private String sortColumn;
/** 정렬구분 */
private Boolean sortAscending;
}

@ -0,0 +1,43 @@
package go.kr.project.common.model;
import lombok.Data;
/**
* VO
*/
@Data
public class FileVO {
/** 파일 ID */
private String fileId;
/** 참조 ID (공지사항 ID 등) */
private String refId;
/** 원본 파일명 */
private String originalFileNm;
/** 저장 파일명 */
private String storedFileNm;
/** 파일 경로 */
private String filePath;
/** 파일 크기 */
private long fileSize;
/** 파일 확장자 */
private String fileExt;
/** 등록 일시 */
private String regDttm;
/** 등록자 */
private String rgtr;
/** 파일 데이터 (MultipartFile에서 변환) */
private byte[] fileData;
/** 파일 컨텐츠 타입 */
private String contentType;
}

@ -0,0 +1,63 @@
package go.kr.project.common.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.common.model
* fileName : HtmlEditorFileVO
* author :
* date : 2025-05-23
* description : HTML VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-23
*/
@EqualsAndHashCode(callSuper=true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class HtmlEditorFileVO extends PagingVO {
private String fileId;
private String moduleId;
private String originalFileNm;
private String storedFileNm;
private String filePath;
private Long fileSize;
private String fileExt;
private String fileType;
private String useYn;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime regDttm;
private String rgtr;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime mdfcnDttm;
private String mdfr;
// 파일 크기 표시용 (KB, MB 등)
private String fileSizeStr;
// 다운로드 URL
private String downloadUrl;
}

@ -0,0 +1,89 @@
package go.kr.project.common.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* packageName : go.kr.project.common
* fileName : PagingVO
* author :
* date : 25. 5. 8.
* description : VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 25. 5. 8.
*/
@Data
@EqualsAndHashCode(callSuper=true)
public class PagingVO extends DefaultVO {
/** 현재 페이지 번호 */
private Integer page;
/** 페이지당 항목 수 */
private Integer perPage;
/** 시작 인덱스 */
private Integer startIndex;
/** 끝 인덱스 */
private Integer endIndex;
/** 전체 페이지 수 */
private Integer totalPages;
/** 기본 페이지당 항목 수 */
private static final int DEFAULT_PER_PAGE = 10;
/**
* .
*/
public void calculateIndex() {
if (this.page == null) {
this.page = 1;
}
if (this.perPage == null) {
this.perPage = DEFAULT_PER_PAGE;
}
this.startIndex = (this.page - 1) * this.perPage;
this.endIndex = this.startIndex + this.perPage;
}
/**
* .
*/
public void calculateTotalPages() {
if (this.perPage == null) {
this.perPage = DEFAULT_PER_PAGE;
}
if (this.getTotalCount() > 0) {
this.totalPages = (int) Math.ceil((double) this.getTotalCount() / this.perPage);
} else {
this.totalPages = 0;
}
}
/**
* .
* "Y" .
*
* @param pagingYn ("Y" "N")
*/
@Override
public void setPagingYn(String pagingYn) {
super.setPagingYn(pagingYn);
if ("Y".equals(pagingYn)) {
// 페이지 인덱스 계산
calculateIndex();
// 전체 페이지 수 계산
calculateTotalPages();
}
}
}

@ -0,0 +1,27 @@
package go.kr.project.common.service;
import go.kr.project.system.code.model.CodeDetailVO;
import java.util.List;
/**
* packageName : go.kr.project.common.service
* fileName : CommonCodeService
* author :
* date : 2025-05-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-05-10
*/
public interface CommonCodeService {
/**
* .
*
* @param cdGroupId ID
* @return
*/
List<CodeDetailVO> selectCodeDetailListByGroupId(String cdGroupId);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save