초기 셋팅
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
|
||||
@ -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" "$@"
|
||||
@ -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("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("\"", """)
|
||||
.replaceAll("'", "'")
|
||||
.replaceAll("/", "/")
|
||||
.replaceAll("\\(", "(")
|
||||
.replaceAll("\\)", ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* 이스케이프 처리된 문자열을 원래 형태로 복원
|
||||
* @param value 이스케이프 처리된 문자열
|
||||
* @return 원본 문자열
|
||||
*/
|
||||
public String unescape(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll(""", "\"")
|
||||
.replaceAll("'", "'")
|
||||
.replaceAll("/", "/")
|
||||
.replaceAll("(", "(")
|
||||
.replaceAll(")", ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자열 내의 줄바꿈 문자를 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…
Reference in New Issue