반응형
SMALL

Tomcat만 주구장창 쓰다보니 Jeus는 잘 모름...

Jeus 를 쓰는 고객사를 방문해서 보다보니 구조가 달라 지원에 어려움을 느꼈다.

정확히 한번 정리할 필요성을 느껴 정리해보는 글...


 

Tomcat과 Jeus는 둘 다 웹 애플리케이션 서버(WAS, Web Application Server)이지만, 기능과 역할에서 차이가 있다.
Tomcat은 경량 WAS이고, Jeus는 엔터프라이즈급 WAS이다.

 

📌 1. 기본 개요

  Tomcat Jeus
제작사 Apache Software Foundation TmaxSoft
라이선스 오픈소스 (무료) 상용 소프트웨어 (유료)
주요 용도 가벼운 웹 애플리케이션 실행 (JSP, Servlet) 기업용 WAS, 대규모 시스템 운영
J2EE 지원 여부 부분 지원 (Servlet, JSP만 가능) 완전한 J2EE 지원 (EJB, JMS, JTA 등 포함)

 

 

📌 2. 기술적인 차이

  Tomcat Jeus
설치 경로 /opt/tomcat/ 또는 /home/tomcat/ /home/jeus/
웹 애플리케이션 배포 경로 /webapps/ /domains/{도메인}/applications/
배포 방식 WAR 파일 배포 또는 폴더 배치 WAR 또는 EAR 배포
서버 실행 ./startup.sh ./startServer.sh {도메인} {서버명}
서버 종료 ./shutdown.sh ./stopServer.sh {도메인} {서버명}
관리 콘솔 제공 X (별도 툴 필요) O (jeusadmin CLI, 웹 콘솔 지원)
클러스터링 지원 부분 지원 (추가 설정 필요) O (기본 기능 포함)
멀티 도메인 지원 X (Tomcat 인스턴스를 따로 띄워야 함) O (도메인 기반 다중 서버 운영 가능)
트랜잭션 처리 X (Servlet/JSP 수준) O (JTA, EJB 지원)

 

📌 3. 실행 구조 차이

✔ Tomcat 실행 구조

/home/tomcat/
 ├── bin/           # 실행 파일 (startup.sh, shutdown.sh)
 ├── conf/          # 설정 파일 (server.xml 등)
 ├── logs/          # 로그 파일
 ├── webapps/       # 배포된 웹 애플리케이션 (WAR, JSP, HTML 파일 위치)
 ├── work/          # 실행 중인 애플리케이션 캐시
 ├── temp/          # 임시 파일

 

📌 웹 애플리케이션 배포 방식

  • /webapps/ROOT/에 index.html, index.jsp 등을 배치하면 자동 실행됨
  • 브라우저에서 http://localhost:8080/로 접속 가능

Jeus  실행 구조

/home/jeus/
 ├── bin/            # 실행 파일 (startServer.sh, stopServer.sh 등)
 ├── config/         # 설정 파일
 ├── domains/        # 도메인별 설정 및 애플리케이션 배포
 │   ├── mydomain/
 │   │   ├── applications/  # 웹 애플리케이션 배포 폴더 (WAR, JSP, HTML 파일 위치)
 │   │   ├── servers/       # 실행 중인 서버 관리
 │   │   ├── config/        # 도메인별 설정 파일
 ├── lib/            # 라이브러리
 ├── logs/           # 로그 파일
 ├── deploy/         # 자동 배포 폴더 (WAR, EAR 파일 배치 가능)

 

📌 웹 애플리케이션 배포 방식

  • /domains/{도메인}/applications/ROOT/에 index.html, index.jsp 등을 배치
  • 브라우저에서 http://localhost:8080/로 접속 가능

📌 4. 주요 차이점 정리

  Tomcat  Jeus
속도 가볍고 빠름 상대적으로 무거움
운영 환경 소규모 프로젝트, 개발용 엔터프라이즈 환경, 대기업, 금융권
설치 및 관리 쉽고 간단함 복잡한 설정 필요
보안 기본적인 보안만 제공 기업용 보안 기능 포함
부하 분산 직접 구현 필요 기본적으로 로드 밸런싱 지원
J2EE 표준 지원 Servlet, JSP만 지원 EJB, JMS, JTA 등 전체 지원
가격 무료 유료 (라이선스 비용 필요)

 


📌 5. 언제 Tomcat을 쓰고, 언제 Jeus를 써야 할까?

✔ Tomcat이 적합한 경우
✅ 빠르게 웹 서비스를 개발하고 배포해야 할 때
✅ JSP, Servlet 정도만 사용하고, 트랜잭션, EJB 등의 기능이 필요 없을 때
✅ 비용 절감이 중요한 프로젝트 (오픈소스, 무료 사용 가능)
✅ 단일 서버에서 운영하는 소규모 웹 서비스

 

✔ Jeus가 적합한 경우
✅ 엔터프라이즈 환경에서 안정적으로 운영해야 할 때
✅ 대규모 트랜잭션 처리, 복잡한 비즈니스 로직이 필요한 경우
✅ 보안 및 고가용성이 중요한 금융, 공공기관, 대기업 환경
✅ 여러 대의 서버를 클러스터링해서 운영해야 할 때

 


✅ Tomcat에서 HTML/JS 파일 실행 방식

Tomcat에서는 webapps/ROOT/ 폴더에 HTML/JS 파일을 넣고, startup.sh를 실행하면 브라우저에서 확인할 수 있다.

cd /home/tomcat/bin ./startup.sh

 

📌 파일 저장 위치

/home/tomcat/webapps/ROOT/index.html /home/tomcat/webapps/ROOT/js/script.js

 

📌 브라우저에서 실행 확인

http://localhost:8080/index.html

 

 

✅ Jeus에서 HTML/JS 파일 실행 방식

Jeus에서는 Tomcat과 폴더 구조와 실행 방식이 다르다.
Jeus는 보통 WAR 파일을 배포하는 방식을 사용하지만, 정적 파일을 직접 배포할 수도 있다.

 

2️⃣ HTML/JS 파일 저장 위치

📌 Jeus에서 정적 파일을 직접 넣으려면 다음 경로를 사용해야 한다.
(Tomcat의 webapps/ROOT 역할)

/home/jeus/domains/mydomain/applications/ROOT/

 

📌 예제 파일 위치

/home/jeus/domains/mydomain/applications/ROOT/index.html
/home/jeus/domains/mydomain/applications/ROOT/js/script.js

 

📌 브라우저에서 실행 확인

http://localhost:8080/index.html

 

 

✅ Jeus 서버 실행 방법

Jeus는 Tomcat의 startup.sh처럼 startServer.sh를 사용한다.

1️⃣ Jeus 실행

cd /home/jeus/bin
./startServer.sh mydomain server1

 

🔹 mydomain → 실행할 도메인 이름
🔹 server1 → 실행할 서버 인스턴스

 

2️⃣ Jeus 중지

cd /home/jeus/bin 
./stopServer.sh mydomain server1
 

✅ 최종 정리

  Tomcat Jeus
정적 파일 위치 /webapps/ROOT/ /domains/mydomain/applications/ROOT/
서버 실행 ./startup.sh ./startServer.sh mydomain server1
서버 중지 ./shutdown.sh ./stopServer.sh mydomain server1
브라우저에서 확인 http://localhost:8080/index.html http://localhost:8080/index.html

 

반응형
LIST
반응형
SMALL

스프레드 연산자(...)는 배열, 객체, 문자열 등의 요소를 개별적으로 펼쳐서 사용할 수 있도록 해주는 JavaScript 연산자.

 

FullCalendar 사용할 때 아래처럼 eventSources에 일정을 배열로 넣어야하는데,

eventSources: [
    {
        id: "대한민국 공휴일",
        googleCalendarId: "ko.south_korea#holiday@group.v.calendar.google.com",
        color: "#FF0000",
        editable: false
    },
    {
        id: "일정2",
        color: "#FF0000",
        editable: false
    },
    ...
]

 

특정 eventList를 Map형태로 뽑아 전체를 넣기 위해 아래같이 사용함.

eventSources: [
    eventList.map(event => ({
        id: event.id,
        groupId: event.id,
        events: [],
        backgroundColor: event.bgColor,
    })), // ❌ 이렇게 하면 안됨
    {
        id: "대한민국 공휴일",
        googleCalendarId: "ko.south_korea#holiday@group.v.calendar.google.com",
        color: "#FF0000",
        editable: false
    }
]

문제는 eventList.map(...)이 이미 배열을 반환하기 때문에 이중배열([ [...mappedEvents], {...} ]) 형태가 되어 추가되지 않음.

 

이럴 때 사용하는게 스프레드 연산자!

   //이렇게 사용해야 한다.  
   eventSources: [
        ...eventList.map(event => ({ // 기본 이벤트 항목 추가
            id: event.id,
            groupId: event.id,
            events: [],
            backgroundColor: event.bgColor,
        })),
        {
            id: "대한민국 공휴일", // 대한민국 공휴일 추가
            googleCalendarId: "ko.south_korea#holiday@group.v.calendar.google.com",
            color: "#FF0000",
            editable: false
        }
    ],

 

 


📌 1. 배열에서의 스프레드 연산자 활용

배열을 복사하거나, 합칠 때 사용됩니다.

✅ 예제 1: 배열 복사 (얕은 복사)

const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // arr1을 복사

console.log(arr2); // [1, 2, 3]


arr2 = [...arr1]를 사용하면 arr1의 개별 요소가 펼쳐져서 새로운 배열이 생성됩니다.

 

✅ 예제 2: 배열 합치기

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const merged = [...arr1, ...arr2]; // 두 배열을 합침

console.log(merged); // [1, 2, 3, 4, 5, 6]


arr1과 arr2의 요소들이 개별적으로 풀려서 하나의 배열로 합쳐짐.

 

✅  예제 3: 배열의 중첩 구조 해결

const arr1 = [1, 2, 3];
const arr2 = [arr1, 4, 5];

console.log(arr2);
// [[1, 2, 3], 4, 5] <-- 배열이 중첩됨

const arr3 = [...arr1, 4, 5]; // 스프레드 연산자로 펼침

console.log(arr3);
// [1, 2, 3, 4, 5] <-- 중첩되지 않고 개별 요소가 추가됨

스프레드 연산자를 사용하면 배열의 중첩 없이 개별 요소로 추가할 수 있음.

 

📌 2. 객체에서의 스프레드 연산자 활용

객체를 복사하거나, 속성을 병합할 때 유용합니다.

 

✅ 예제 4: 객체 복사

 
const obj1 = { name: "Alice", age: 25 };
const obj2 = { ...obj1 }; // obj1을 복사

console.log(obj2); // { name: "Alice", age: 25 }

기존 객체를 새로운 객체로 복사하는 방법.

 

✅ 예제 5: 객체 속성 병합

 
const obj1 = { name: "Alice", age: 25 };
const obj2 = { job: "Developer", city: "Seoul" };

const mergedObj = { ...obj1, ...obj2 };

console.log(mergedObj);
// { name: "Alice", age: 25, job: "Developer", city: "Seoul" }

 

 

반응형
LIST

'[ javascript ]' 카테고리의 다른 글

Node.js 뜯어보기  (0) 2025.03.20
대표적인 es6 문법 정리  (0) 2024.12.30
부동소수점 이슈  (0) 2024.03.29
Node.js & npm 업데이트 하기  (1) 2024.03.22
리액트란?  (0) 2024.03.19
반응형
SMALL

부트스트랩으로 만들어진 템플릿에서 modal을 띄우기 위해 아래처럼 style.display = "block"을  사용했다.

document.getElementById('event-modal').style.display = "block";

 

근데 안나오는거...;; 왜지...

 

 

 

확인해보니 부트스트랩의 modal은 기존 HTML/CSS 모달과는 다르게 Javascript로 동작하기 때문에 사용 방법이 다르다.

HTML의 display: block; 속성만으로는 제대로 표시되지 않는 이유도 부트스트랩 modal이 내부적으로 추가적인 조작을 하기 때문.

var eventModal = new bootstrap.Modal(document.getElementById("event-modal"));
eventModal.show();

이렇게 사용해야함!

 

✅ 부트스트랩 모달의 주요 차이점

부트스트랩 modal 부분을 보면 아래와 같이 class 가 설정되어있다.

<div class="modal fade" id="event-modal" tabindex="-1">
    <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content">
        ...

 

1) 애니메이션 효과 (fade 클래스) 

부트스트랩 모달은 기본적으로 fade 클래스를 사용하여 부드러운 애니메이션 효과가 적용됨. 

 

2) 배경 비활성화 (.modal-backdrop)

모달이 열릴 때 자동으로 <div class="modal-backdrop"> 요소가 생성됨.

이를 통해 배경 클릭 시 닫히는 기능을 제공함.

배경 비활성화 (.modal-backdrop) - 뒤에 흐려짐

 

3) 키보드 ESC 키 닫기 기능

esc 키를 누르면 모달이 닫히도록 기본 설정되어 있음.

keyboard: false 옵션을 사용하면 ESC 키로 닫히는 기능을 비활성화할 수 있음.

 

4) ARIA 접근성 지원
role="dialog"와 aria-labelledby="modal-title" 등의 속성이 자동으로 추가되어 접근성이 향상됨.

 

5) JavaScript API 제공
.show(), .hide(), .toggle() 등의 메서드를 제공하여 동적으로 모달을 조작할 수 있음.

 

<button class="btn btn-primary w-100" id="btn-new-event">Create New Event</button>

 

근데 사실 JavaScript 방식 (onclick 사용) 을 안해도 된다.

 

부트스트랩에서는 data-bs-toggle="modal"과 data-bs-target="#모달ID"를 버튼에 추가하면 자동으로 모달을 띄울 수 있음!

<button class="btn btn-primary w-100" id="btn-new-event" data-bs-toggle="modal" data-bs-target="#event-modal">Create New Event</button>

 

비슷하게 모달 안에 있는 닫힘버튼도 data-bs-dismiss="modal" 설정해서 사용 가능.

 aria-hidden="true"  는 접근성 API 차단 (가상 커서로 탐색 x)

<button type="button" class="btn-close" data-bs-dismiss="modal"aria-hidden="true"></button>

 

 

 

✅ 부트스트랩 모달에 항목 저장해서 리스트에 추가하기

열고 닫는건 했으니 저장처리 해주고 모달을 닫아주면 된다.

저장버튼이 기존에 <button type="submit"> 이었는데 submit은 폼을 제출하면서 페이지를 새로고침하니까 type:"button"으로 변경해줌.

 

 

 

 

반응형
LIST
반응형
SMALL

 

pc 변경한 뒤 gitlab에서 새로 프로젝트를 clone 하는데 아래 오류메시지 발생.

계정이 틀린건 아니다.

Cloning into 'C:\Users\사용자이름\git\폴더이름'...
Permission denied,please try again.
Permission denied, please try again.
git@gitlab.xxxxxx.com: Permission denied (publickey,password).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

 

생각해보니 PC를 변경하면서 gitlab에 등록된 SSH키가 기존과 맞지 않을 거라는게 생각남.

 

확인 방법은 아래와 같다.

 

1. 로컬 SSH 키 확인

git bash에서 확인한다.

ls ~/.ssh/id_rsa.pub

 

파일 있으면 SSH키가 있는 것.

나는 

ls: cannot access '/c/Users/사용자이름/.ssh/id_rsa.pub': No such file or directory

이렇게 떴다. 없다는 소리임.ㅋㅋㅋ

 

 

2. SSH 키 생성 (없다면 새로 생성)

ssh-keygen -t rsa -b 4096 -C "email@example.com"

 

이렇게 해서 생성하면 몇가지 문구가 뜨는데

 

Enter file in which to save the key (/c/Users/사용자 이름/.ssh/id_rsa):

 

질문이 나오면 그냥 Enter하면 된다. 암호 설정하는건데 암호(passphrase)는 비워도 되고 설정해도 됨.

난 그냥 Enter로 비우겠다.

 

Enter passphrase (empty for no passphrase):
Enter same passphrase again:

 

전부 Enter 치면 비워짐.

 

이제 다시 1번 하면 파일이 있다고 나올꺼임.

 

3. GitLab에 SSH 키 등록

파일경로 들어가면 그 파일 있어서 메모장으로 열어서 등록해도 되지만 그냥 git bash로 마무리하자.

clip < ~/.ssh/id_rsa.pub

 

이렇게하면 클립보드에 SSH 키가 복사됨.

 

이제 Gitlab으로 가서

 

Settings → SSH Keys

새 키 추가 (+Add SSH Key) 클릭

복사한 키를 붙여넣고 등록 (Add key)

 

 

 

4. 잘 연결됐는지 확인

gitlab 주소는 git@gitlab.주소.com 이렇게 생김.

git bash에 아래와 같이 입력

 

ssh -T git@gitlab.이름.com

 

Welcome to GitLab, @gitlab 이름!

 

이렇게 나오면 정상연결 된 것.

 

 

gitlab에 있는 프로젝트 clone이 안되면 참고할 것.

 

 

 

▼ 처음 로컬 SSH 키 등록했을 때의 포스팅

 

[git] GitHub SSH 키 재생성하기 / Passphrase for SSH key

문제상황잘 쓰던 fork에서 아래와 같은 창이 계속해서 떴다. Passphrase for SSH key '/c/Users/User/.ssh/id_rsa'해당 경로에 가서 id_rsa.pub 파일을 열면 공개키가 있다.이 키를 복사해서 붙여넣어도 안됨... Pas

heannim-world.tistory.com

 

반응형
LIST

반응형
SMALL

egov 4.2 버전

JDK 1.8

Tomcat 8.0

HSQL 사용


1. 프로젝트 생성

eGovFrame Template Project 생성

 

group_id는 동일하게 넣고

 

프로젝트가 만들어지면 Server를 연결합니다.

 

 

/sht_webapp 으로 실행됨.

 

그럼 이렇게 홈페이지 생성 완료.

 


2. 수정할 부분 확인

여기서 공지사항과 갤러리 부분만 시트로 변경해줄 것이다.

 

기본화면이고

 

등록버튼을 누르면

 

이렇게 등록화면으로 가서 게시글 저장 후

 

다시 조회화면으로 돌아온다.

 

 

기존 로직은 조회페이지 / 상세페이지 / 등록페이지 / 수정페이지 등등 나뉘어져 있으나,

우리는 단순 조회성 table만 제공하므로 페이지 이동은 빼고, 그냥 조회페이지에서 CRUD 전체를 진행하게 한다.

 

+++ 추가로 조회수는 사용하지 않음. 상세페이지를 들어가는게 아니라 count 불가함.

 


3. 제품 넣기

1) 제품 파일 넣기

프로젝트 구조는 아래처럼 되어있다.

 

ibsheet는 javascript 기반의 제품으로 js 부분에 제품을 넣어 사용하는 페이지에 import 해서 사용하면 된다.

js 폴더가 있으니 알기 쉽게 이쪽에 넣어주자.

붙여넣기

이동되면 이런경로로 되어있음.

 

 

2) 공지사항 페이지 확인

 

공지사항 페이지로 들어가서 주소를 보면 

 

http://localhost:8080/sht_webapp/cop/bbs/selectBoardList.do?bbsId=BBSMSTR_AAAAAAAAAAAA&menuNo=43

 

이런식으로 되어있는데, 

 

따라가보니 EgovBBSManageController.java 발견.

 

return 쪽을 보니

 

 

cop/bbs/EgovNoticeList 로 이동시킴을 알 수 있음.

이게 페이지 jsp 이다.

 

화면 여기있구요.

 

내용을 보니 알림마당 안에 있는 [공지사항], [사이트갤러리]가 이쪽을 바라보는 듯 하다.

 

일단 이 페이지에 시트를 띄워본다.

 

div 추가

<script type="text/javascript" src="<c:url value='/js/EgovBBSMng.js' />" ></script>


<%-- 시트 관련 js 추가 --%>
<link type="text/css" rel="stylesheet" href="<c:url value='/js/ibsheet8/css/default/main.css'/>"/>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibsheet.js'/>"></script> <%-- 코어 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/locale/ko.js'/>"></script> <%-- 메세지 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-common.js'/>"></script> <%-- 시트 공통 설정 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-dialog.js'/>"></script> <%-- 시트에서 띄우는 다이얼로그 관련 함수 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-excel.js'/>"></script> <%-- 서버롤 호출하여 다운로드/업로드시 필요 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibleaders.js'/>"></script> <%-- 라이선스  파일 --%>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>


...


    <!-- //검색 필드 박스 끝 -->
    <!-- 
    <div id="page_info"><div id="page_info_align"></div></div>                    
     -->

     <!-- ibsheet 표시 -->
    <div id="search_field">
        <div id="sheetDiv" style="width: 100%; height: 500px;"></div>
    </div>               



    <!-- table add start -->

 

 

js 부분은 아래와 같이 작성

더보기

 

<body onload="initSheet()"> 로 해서 페이지 로딩과 동시에 생성되도록 설정

<script type="text/javascript">

function initSheet(){
	var options = {
		Cfg: {
			SearchMode:2
		},
		
		Def: {
			Row: {
			}
		},
		LeftCols:[
			{Header: "번호", 		Type: "Int", 	Name: "SEQ", 		Width: 50,	 CanEdit:0,Visible:0},
		],
		Cols: [
			{Header: "제목",		Type: "Text",	Name: "nttSj",		Width: 200,		Align: "Left",RelWidth:1,},
			{Header: "작성자",		Type: "Text",	Name: "frstRegisterNm",	Width: 80,	Align: "Center", 	Visible:1, CanEdit:0},
			{Header: "작성일",		Type: "Date",	Name: "frstRegisterPnttm",	Width: 100,		Align: "Center", 
					Format:"yyyy-MM-dd",DataFormat:"yyyy-MM-dd",EditFormat:"yyyyMMdd", CanEdit:0
			},
			{Header: "조회수",		Type: "Int", 		Name: "inqireCo",	Width: 100,	Align: "Center",	Visible:1, CanEdit:0},		
		],
		Events: {}

	};
	
	//시트 초기화
	IBSheet.create({
		id: 'sheet', // 생성할 시트의 id
		el: 'sheetDiv', // 시트를 생성할 Dom 객체 및 id
		options: options, // 생성될 시트의 속성
	});

}


</script>

...

<body onload="initSheet()">

 

 

그럼 아래와 같이 표시된다.

 

 

여기까지 생성 완료


4. 조회

조회를 해보자.

 

EgovBBSManageController.java에서 공지사항 페이지로 오는 selectBoardList.do 쪽을 보면

 

 

Map<String, Object> map = bbsMngService.selectBoardArticles(boardVO, vo.getBbsAttrbCode());

 

bbsMngService는 게시판 관리 기능을 담당하는 서비스 클래스이다.

이 클래스는 게시글 목록을 조회하는 selectBoardArticles 메서드를 제공하며,

해당 메서드는 조회 결과를 Map<String, Object> 형태로 반환한다.

반환된 Map 객체에는 게시글 목록을 담은 resultList와 총 게시글 수를 나타내는 resultCnt가 포함된다.

 

이 데이터를 처리하기 위해 컨트롤러에서는 bbsMngService.selectBoardArticles 메서드를 호출하고,

결과로 얻어진 Map 객체에서 resultList와 resultCnt를 추출한다.

추출된 데이터를 모델에 담아 뷰로 전달하기 위해 model.addAttribute 메서드를 사용하여 각각 resultList와 resultCnt를 화면에 전달한다.

 

즉, 게시글 목록과 총 게시글 수를 화면에서 받을 수 있는 것.

 

 

페이지 네비게이션도 있는 화면이니 SearchMode:4를 사용해서 페이징 모드로 조회를 하자.

기존 로직은 냅두되, selectBoardArticles 로 데이터를 불러오는것만 동일하게 하기.

 

 

1) 컨트롤러에 조회로직 추가

▼ EgovBBSManageController.java 에 ibsheet 전용 paging 조회용 selectLargeSheetData 추가

더보기

 

페이징 조회를 하기 위해서는 ibpage에 담긴 현재페이지 수에 맞게 데이터를 보내줘야한다.

 

    /**
     * IBSheet8 paging 조회
     *
     * @param boardVO
     * @param model
     * @return
     * @throws Exception
     */
	@RequestMapping(value = "/cop/bbs/searchPaging.do")
	@ResponseBody
	public String selectLargeSheetData(@RequestParam Map<String,String> map,
							@ModelAttribute("searchVO") BoardVO searchVO,
							ModelMap model) throws Exception {

		//서버페이징 필수 조건
		int rowidx = Integer.parseInt((String)map.get("ibpage"));  //현재가 몇번째 페이지
		int onepagerow = Integer.parseInt((String)map.get("OnePageRow")); //한번에 몇건씩 가져올 것인지
		String searchCnd = (String)map.get("searchCnd"); //검색조건
		String searchWrd = (String)map.get("searchWrd"); //검색어
		
		/***********************  이 부분이 제일 중요 ***************************************/
		int startrow,endrow;
		startrow = rowidx==1?0:((rowidx-1) * onepagerow);
		endrow = startrow + (onepagerow-1);
		/***********************  이 부분이 제일 중요 ***************************************/

		//정보를 보낸다
		searchVO.setFirstIndex(startrow); //첫인덱스
		searchVO.setLastIndex(endrow); //마지막인덱스
		searchVO.setRecordCountPerPage(onepagerow); //페이지당 레코드 개수
		searchVO.setPageIndex(rowidx); //페이지인덱스
		searchVO.setSearchCnd(searchCnd); //조회조건
		searchVO.setSearchWrd(searchWrd); //검색어
		searchVO.setBbsId(searchVO.getBbsId()); // bbsId

		Map<String, Object> map2 = bbsMngService.selectBoardArticles(searchVO,null);
		int totCnt = Integer.parseInt((String)map2.get("resultCnt"));

	
		Map<String, Object> sheetData = new HashMap<>();
		sheetData.put("Total", totCnt); // 반드시 필요
		sheetData.put("data", map2.get("resultList"));
		System.out.println("데이터 조회 : "+map2.get("resultList"));
		
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.getFactory().configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), true); //한글설정
		String jsonString = objectMapper.writeValueAsString(sheetData);

		return jsonString;
	}

 

import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

 

 이것들이 추가되어야하는데 pom.xml 에서 jackson을 추가해줘야 함....

하는 김에 common-lang도 추가하자.

 

 

 

2) pom.xml 추가

Controller에 쓰는거 미리 추가하자.

		<!-- ibsheet용 jackson 추가 -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.15.2</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.15.2</version>
		</dependency>
		<!-- ibsheet용 commons-lang 추가 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.12.0</version>
		</dependency>

 

pom.xml 에 위 구문 추가해주고 Maven > Update Project 하는거 잊지말기!

 

 

3) js에 조회로직 추가

 

		Events: {
			onRenderFirstFinish: function(param) {
				search();
			},
		}

	};
    
    ....
    
    
    //외부함수로 조회로직 추가
    function search(){
	// 폼 요소 선택
	const form = document.forms["frm"];
	// FormData 생성 후 URLSearchParams로 변환
	const SearchParam = new URLSearchParams(new FormData(form)).toString();

	
	sheet.doSearchPaging({
		url: "searchPaging.do",
		param:SearchParam,
		cPage: 1,
		sync: 0,
		method:"POST",
		pageLengthParam:"OnePageRow",
		callback:function (rtn) {
		    var rtnData = JSON.parse(rtn.data); 

		   
		  }
	});
}

 

기존 조회로직에 맞게 Form 요소를 전체 던져주자.

기본적으로 내장 DB인 HSQL로 돌고있으니 데이터가 없음.

등록버튼 눌러서 게시글 하나 추가 후 페이지 새로고침 해보면 시트에도 잘 조회됨을 볼 수 있다.

조회데이터 : [egovframework.let.cop.bbs.service.BoardVO@3adbf1bd[bbsAttrbCode=,bbsNm=,bbsTyCode=,fileAtchPosblAt=,firstIndex=1,frstRegisterNm=관리자,isExpired=N,lastIndex=1,lastUpdusrNm=,pageIndex=1,pageSize=10,pageUnit=10,parntsReplyLc=,parntsSortOrdr=,plusCount=false,posblAtchFileNumber=0,recordCountPerPage=10,replyPosblAt=,rowNo=0,searchBgnDe=,searchCnd=,searchEndDe=,searchUseYn=,searchWrd=,sortOrdr=0,subPageIndex=,atchFileId=,bbsId=BBSMSTR_AAAAAAAAAAAA,frstRegisterId=USRCNFRM_00000000000,frstRegisterPnttm=2025-2-18,inqireCo=0,lastUpdusrId=,lastUpdusrPnttm=,ntceBgnde=10000101            ,ntceBgndeView=,ntceEndde=99991231            ,ntceEnddeView=,ntcrId=,ntcrNm=,nttCn=,nttId=1,nttNo=0,nttSj=제목입니다,parnts=0,password=,replyAt=N,replyLc=0,sortOrdr=0,useAt=Y]]

서버 콘솔에도 sysout로 데이터를 확인할 수 있음.

 

여기까지 기본조회 완료.

 

[페이지 디자인 및 구현 방법]

시트는 조회성으로 따로 페이지 이동해서 저장하는 등의 기능을 구현하기엔 번거롭다.

따라서 최대한 기존 게시판과 동일하게 만들기 위해 EditDialog를 사용해서 게시글 추가 페이지처럼 사용한다.
목록 상에서는 제목만 보이지만 실제로는 내용 및 첨부파일까지 가져온다.
클릭 이벤트에서 EditDialog를 띄우고 내용을 추가할 수 있으며, 첨부파일도 등록할 수 있다.
Insert, Update 페이지를 각각 만드는 것이 아닌, [저장]버튼 하나로 게시글을 한번에 저장한다.
단, 게시글은 1개씩만 수정될 수 있도록 한다.

 

 

 


추가적으로 날짜형식을 맞춰준다던지, 작성자를 가져온다던지, 게시글 추가, 저장 버튼을 만든다던지 등

jsp 수정이 필요하다.

 

아래 전체 소스 붙여넣기

 

▼ 아래에 EgovNoticeList.jsp 전체 소스

더보기

시트 표시를 위한 js 설정 + 버튼 모양을 위한 .styled-button 스타일 추가 + 기존테이블 주석처리

 

<%--
  Class Name : EgovNoticeList.jsp
  Description : 게시물 목록화면
  Modification Information
 
      수정일         수정자                   수정내용
    -------    --------    ---------------------------
     2009.03.19   이삼섭          최초 생성
     2011.08.31   JJY       경량환경 버전 생성
 
    author   : 공통서비스 개발팀 이삼섭
    since    : 2009.03.19  
--%>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="egovframework.com.cmm.service.EgovProperties" %>    
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<c:set var="ImgUrl" value="/images/egovframework/cop/bbs/"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<meta http-equiv="content-language" content="ko">
<link href="<c:url value='/'/>css/common.css" rel="stylesheet" type="text/css" >
<c:if test="${anonymous == 'true'}"><c:set var="prefix" value="/anonymous"/></c:if>
<script type="text/javascript" src="<c:url value='/js/EgovBBSMng.js' />" ></script>


<%-- 시트 관련 js --%>
<link type="text/css" rel="stylesheet" href="<c:url value='/js/ibsheet8/css/default/main.css'/>"/>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibsheet.js'/>"></script> <%-- 코어 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/locale/ko.js'/>"></script> <%-- 메세지 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-common.js'/>"></script> <%-- 시트 공통 설정 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-dialog.js'/>"></script> <%-- 시트에서 띄우는 다이얼로그 관련 함수 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/plugins/ibsheet-excel.js'/>"></script> <%-- 서버롤 호출하여 다운로드/업로드시 필요 파일 --%>
<script type="text/javascript" src="<c:url value='/js/ibsheet8/ibleaders.js'/>"></script> <%-- 라이선스  파일 --%>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>



<c:choose>
<c:when test="${preview == 'true'}">
<script type="text/javascript">
<!--
    function press(event) {
    }

    function fn_egov_addNotice() {
    }
    
    function fn_egov_select_noticeList(pageNo) {
    }
    
    function fn_egov_inqire_notice(nttId, bbsId) {      
    }
//-->
</script>
</c:when>
<c:otherwise>
<script type="text/javascript">
<!--
    function press(event) {
        if (event.keyCode==13) {
            fn_egov_select_noticeList('1');
        }
    }

    function fn_egov_addNotice() {
        document.frm.action = "<c:url value='/cop/bbs${prefix}/addBoardArticle.do'/>";
        document.frm.submit();
    }
    
    function fn_egov_select_noticeList(pageNo) {
        document.frm.pageIndex.value = pageNo;
        document.frm.action = "<c:url value='/cop/bbs${prefix}/selectBoardList.do'/>";
        document.frm.submit();  
    }
    
    function fn_egov_inqire_notice(nttId, bbsId) {
        document.subForm.nttId.value = nttId;
        document.subForm.bbsId.value = bbsId;
        document.subForm.action = "<c:url value='/cop/bbs${prefix}/selectBoardArticle.do'/>";
        document.subForm.submit();          
    }
//-->

/*********
 시트 초기화
 **********/
function initSheet(){
	var options = {
		Cfg: {
			SearchMode:4,
			ColorState: 7, // 편집할 수 없는 셀의 배경색을 표현하지 않음
			InfoRowConfig: {
				Visible: true,
				Space: "Bottom",
				"Layout": ['Paging2',"<button onclick='addArticle()' class='styled-button'>게시글 추가</button> " + 
				 	"<button onclick='saveArticle()' class='styled-button''>저장</button>"],
			},
			Export:{
				FilePath:'../../files/'
			},
			
			PageLength: 5,  // 한페이지당 표시할 게시글 수
		},
		
		Def: {
			Row: {
				CanFormula: true,
				CalcOrder: "sStatus,isLblistFile"
				
			}
		},
		LeftCols:[
			{Header: "번호", 		Type: "Int", 	Name: "SEQ", 				Width: 50,	 	CanEdit:0,	Visible:0},
			{Header: "번호",		Type: "Int",	Name: "nttIdx",				Width: 50,	 	CanEdit:0, 	Align: "Center", Visible:1},
			{Header: "상태",						Name: "sStatus",			Width: 60,   	Extend: IB_Preset.STATUS, CanEdit:0},
		],
		Cols: [
			{Header: "제목",		Type: "Text",	Name: "nttSj",				Width: 200,		Align: "Left",	RelWidth:1},
			{Header: "첨부파일",	Type: "File",	Name: "lblistFile",			Width: 100,		Align: "Center"},
			{Header: "내용",		Type: "Lines",	Name: "nttCn",				Width: 100,		Align: "Left",	Visible:0},
			{Header: "작성자",		Type: "Text",	Name: "frstRegisterNm",		Width: 80,		Align: "Center", 	Visible:1, DefaultValue:getUsername(), CanEdit:0},
			{Header: "작성일",		Type: "Date",	Name: "frstRegisterPnttm",	Width: 100,		Align: "Center", 
					Format:"yyyy-MM-dd",DataFormat:"yyyy-MM-dd",EditFormat:"yyyyMMdd", DefaultValue:today(), CanEdit:0
			},
		//	{Header: "조회수",		Type: "Int", 		Name: "inqireCo",	Width: 100,	Align: "Center",	Visible:0, CanEdit:0}, //조회수 사용 안함
			{Header: "첨부파일 포함유무",	Type: "Bool",	Name: "isLblistFile",	Width: 100,		Align: "Center", Visible:0,
				Formula:function(fr){
					if(fr.Row['lblistFile'] == ''){
						return false;
					}else{
						return true;
					}
				}
			},
			{Header: "게시물 삭제",						Name: "sDelete",	Width: 60,	Extend: IB_Preset.DelCheck, Visible:0},
			{Header: "파일 id",		Type: "Text",	Name: "atchFileId",	Width: 100,						Visible:0},
			{"Header": "파일명", 		Type: "Text",	Name: "streFileNm",	Width: 100,	Align: "Center", 	Visible: 0},
			
				
		],
		Events: {
			onRenderFirstFinish: function(param) {
				//데이터 조회
				search();
			},
			
			onBeforeDataLoad:function(evtParam){
				// 조회 결과 데이터
				var DATA = evtParam.data;
				// 조회된 데이터 일부를 수정한다.
			    for(var i = 0; i < DATA.length; i++){
			        var row = DATA[i];
			        //frstRegisterPnttm 값이 비어있지 않는 경우, 날짜가 한자리수 일 때 0을 붙여준다.
			        if(!isNull(row["frstRegisterPnttm"])){
			            row["frstRegisterPnttm"] = formatDate(row["frstRegisterPnttm"]);
			        }

					//nttId 값을 넣어준다.
			        row["nttIdx"] =row["nttId"];
			        
					//삭제된 행의 데이터를 지워준다.
			    	if( row["nttSj"] == "이 글은 작성자에 의해서 삭제되었습니다." ){
			    	   row["Del"] = 1;
			    	   row["nttSjTextStyle"] = 9;
			    	   row["nttSjCanEdit"] = 0;

					   //컬럼 비우기
			    	   row["lblistFileDisabled"] = 1;
			    	   row["frstRegisterNm"] = '';
			    	   row["frstRegisterPnttm"] = '';
			    	   row["atchFileId"] = '';
			    	   row["streFileNm"] = '';
			    	   row["nttCn"] = '';
			    	   
			      	}
			       
				   //파일명을 첨부파일 컬럼에 넣는다.
		            if(row["streFileNm"]) row["lblistFile"] = row["streFileNm"];
			        
			    }
			},
			onSetFile: function (evtParam) {
				if(evtParam.files[0]){
				    console.log("첨부파일명 : ", evtParam.files[0].name);
					//파일명을 저장하는 더미에 이름을 추가한다.
					evtParam.row["streFileNm"] = evtParam.files[0].name;
				}
			},
			onAfterClick: function(param) {
				//게시글은 무조건 한개만 수정되도록 한다.
				
				// 상태값 확인
				var isModified = sheet.getRowsByStatus("Added,Changed,Deleted");
				
				if(!isNull(param.row.Del)){
					return;
				}
				
    		    if(param.col == "nttSj" && isModified.length < 1 ){ //변경된게 없음
    		     	sheet.showEditDialog({row:param.row,excludeHideCol:1});
    		    }else if(param.col == "nttSj" && isModified.length == 1 && param.row.sStatus != "R"){ //변경된게 있고 그걸 클릭한 경우
      		      	sheet.showEditDialog({row:param.row,excludeHideCol:1});
    		    }else if((param.col == "nttSj" || param.col == "lblistFile") && isModified.length == 1 && param.row.sStatus == "R"){ //변경된게 있고 신규를 클릭한 경우
					sheet.setAttribute(param.row, "lblistFile", "Disabled", 1);
    		        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 편집이 가능합니다.</span>",time: 1000});

    		    }
			},
			onDblClick:function(evtParam){
    		    if(evtParam.col == "nttSj"){
					return true;//더블클릭을 막음
    		    }
			},
	   		 onAfterSave : function(evtParam) {
	   			 //저장 후 메시지 출력
				 sheet.showMessageTime({
				        message: "<span style='color:black'>저장되었습니다.</span>",
				        func:function(args){
				        	search(); //재조회
				        }
				 })
			 }
		}
	};
	
	//시트 초기화
	IBSheet.create({
		id: 'sheet', // 생성할 시트의 id
		el: 'sheetDiv', // 시트를 생성할 Dom 객체 및 id
		options: options, // 생성될 시트의 속성
	});

}


/**************
 편집 다이얼로그 수정
 **************/
IBSheet.onBeforeCreate = function (obj) {
	//파일 타입 컬럼은 EditDialog에 뜨지 않게 한다.(EditDialog에서 파일첨부 불가)
	if(obj.id == "editSheet_sheet"){
		var editDialogCols = obj.options["Body"][0];
		for(i=0;i<editDialogCols.length;i++){ 
			if(editDialogCols[i]["TargetType"] == "File"){
				editDialogCols[i]["Visible"] = 0;
				
			}else if(editDialogCols[i]["TargetType"] == "Lines" 
					|| editDialogCols[i]["TargetType"] == "Bool"){
				editDialogCols[i]["Visible"] = 1;
			}
				
		}
	}
    return obj;
}



/*****
 조회
 *****/
function search(){
	// 폼 요소 선택
	const form = document.forms["frm"];
	// FormData 생성 후 URLSearchParams로 변환
	const SearchParam = new URLSearchParams(new FormData(form)).toString();

	
	sheet.doSearchPaging({
		url: "searchPaging.do",
		param:SearchParam,
		cPage: 1,
		sync: 0,
		method:"POST",
		pageLengthParam:"OnePageRow",
		callback:function (rtn) {
		    var rtnData = JSON.parse(rtn.data); 

		   
		  }
	});
}


/********************
 한자릿수 날짜 앞에 0을 넣는다.
 ********************/
function formatDate(dateString) {
    // 문자열을 "-"를 기준으로 분리
    let parts = dateString.split("-");

    // parts[1]이 한 자리 숫자인 경우 (월)
    if (parts[1].length === 1) {
        parts[1] = "0" + parts[1];
    }

    // parts[2]이 한 자리 숫자인 경우 (일)
    if (parts[2].length === 1) {
        parts[2] = "0" + parts[2];
    }

    // "YYYY-MM-DD" 형식으로 다시 합치기
    return parts.join("-");
}


/*************
 공백 여부 판단한다.
 *************/
function isNull(value) {
	
	if(value == null || value == undefined || value == ""){
		return true;	
	}else{
		return false;
	}
	
}

/**************
 오늘 날짜를 가져온다.
 **************/
function today(){
	return IBSheet.dateToString(new Date(),'yyyy-MM-dd');
}


/********************
 상단에서 접속한 아이디를가져온다.
 ********************/
function getUsername() {
    const loginElement = document.getElementById("header_loginname");
    const fullName = loginElement.textContent;
    // "님"을 제외한 아이디 부분만 추출
    const username = fullName.trim().replace("님", "");
    
    return username;
}


/*********
 게시글 추가
 **********/
function addArticle(){

	//로그인한 아이디가 없을 경우 alert 발생
	if(isNull(getUsername())){
		alert("로그인 후 등록 가능합니다.");
		return;
	}

	
	//행추가 시 EditDialog 열림
	if(!sheet.hasChangedData()){
		sheet.addRow({"next":sheet.getFirstRow(),init:{nttIdx : sheet.getTotalRowCount()+1}} );
		sheet.showEditDialog({row:sheet.getFocusedRow(),excludeHideCol:1});
	}else{

        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 등록이 가능합니다.</span>",time: 1000});
	}
}


/**********
 상태값 저장
 **********/
function saveArticle(){
	//로그인한 아이디가 없을 경우 alert 발생
	if(isNull(getUsername())){
		alert("로그인 후 등록 가능합니다.");
		return;
	}
	
	popup(); //alert발생
}

/******************
 상태값에 따른 alert 발생
 ******************/
function popup() {
	
	//상태값에 따라 Text 변경
	var article = sheet.getSaveJson().data;
	var statusNm = "";
	var statusBtn = "저장";

	if(article.length == 0){
        sheet.showMessageTime({message: "<span style='color:black'>변경사항이 없습니다.</span>",time: 1000});	
        return;
	}
	
	if(article[0].sStatus == "I"){
		statusNm = "추가";
	}else if(article[0].sStatus == "U"){
		statusNm = "수정";
	}else if(article[0].sStatus == "D"){
		statusNm = "삭제";
		statusBtn = "삭제";
	}
	
	
	//팝업 띄우기
	if(article.length > 1){ //편집 게시글 한개이상
        sheet.showMessageTime({message: "<span style='color:black'>게시글은 한개씩 편집이 가능합니다.</span>",time: 1000});
	
	}else if(article.length == 1){ //게시글을 한개씩만 저장한다.

		sheet.showMessageTime({
			message: "<span style='color:black'>해당 게시글을 " + statusNm + "하시겠습니까?</span>",
			time: 0,
			buttons: [statusBtn, "취소"],
			func: function (args) {
				//저장 시
				if (args==1) {
					
					var formData = sheet.getSaveJson({saveMode:2, formData: true}); //시트의 데이터와 file를 추출


					// 'frm' 폼 데이터 추가
					const form = document.forms["frm"];
					for (var k = 0; k < form.elements.length; k++) {
						var frmInput = form.elements[k];
						if (frmInput.name) {
							formData.append(frmInput.name, frmInput.value);
						}
					}

					
					//데이터 저장
					$.ajax({
						url: "/sht_webapp/cop/bbs/sheetfileSave.do",
							data: formData,
							method: "POST",
							enctype: 'multipart/form-data',
							contentType: false,
							processData: false,
							cache: false,
							success:function(data){
								var result = data.IO.Result;
								var fileData = data.IO.data;
								//결과를 시트에 반영한다.
								sheet.applySaveResult(result, null, null, fileData);
								
							},
							error: function(jqXHR, textStatus, errorThrown) {
								// 오류 발생 시 처리할 코드
								console.error("Error: " + textStatus, errorThrown);
							}

					});
				} else if (args==2) {
					//취소에 대한 처리
				}
			}
		});
	}
}

</script>
</c:otherwise>
</c:choose>
<title><c:out value="${brdMstrVO.bbsNm}"/> 목록</title>

<style type="text/css">
    h1 {font-size:12px;}
    caption {visibility:hidden; font-size:0; height:0; margin:0; padding:0; line-height:0;}
    
    /* ibsheet InfoRow의 버튼 디자인 */
	.styled-button{
		background-color: #3d5575; /* 버튼 배경색 */
		color: white; /* 텍스트 색상 */
		border: none; /* 테두리 제거 */
		padding: 6px 15px; /* 내부 여백 */
		font-size: 12px; /* 글씨 크기 */
		font-weight: bold; /* 글씨 굵게 */
		border-radius: 5px; /* 둥근 모서리 */
		cursor: pointer; /* 마우스 포인터 */
		transition: background-color 0.3s ease, transform 0.2s ease; /* 호버 효과 애니메이션 */
		float: right; /* 오른쪽 정렬 */
		margin-left: 10px; /* 버튼 간격 */
	}
</style>


</head>
<body onload="initSheet()">
<noscript class="noScriptTitle">자바스크립트를 지원하지 않는 브라우저에서는 일부 기능을 사용하실 수 없습니다.</noscript>
<!-- 전체 레이어 시작 -->
<div id="wrap">
    <!-- header 시작 -->
    <div id="header_mainsize"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncHeader.jsp"/></div>
    <div id="topnavi"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncTopnav.jsp"/></div>        
    <!-- //header 끝 --> 
    <!-- container 시작 -->
    <div id="container">
        <!-- 좌측메뉴 시작 -->
        <div id="leftmenu"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncLeftmenu.jsp"/></div>
        <!-- //좌측메뉴 끝 -->
            <!-- 현재위치 네비게이션 시작 -->
            <div id="content">
                <div id="cur_loc">
                    <div id="cur_loc_align">
                        <ul>
                            <li>HOME</li>
                            <li>&gt;</li>
                            <li>알림마당</li>
                            <li>&gt;</li>
                            <li><strong>${brdMstrVO.bbsNm}</strong></li>
                        </ul>
                    </div>
                </div>
                <!-- 검색 필드 박스 시작 -->
                <div id="search_field">
                    <div id="search_field_loc"><h2><strong><c:out value='${brdMstrVO.bbsNm}'/></strong></h2></div>
					<form name="frm" action ="<c:url value='/cop/bbs${prefix}/selectBoardList.do'/>" method="post">
						<input type="hidden" name="bbsId" value="<c:out value='${boardVO.bbsId}'/>" />
						<input type="hidden" name="nttId"  value="0" />
						<input type="hidden" name="bbsTyCode" value="<c:out value='${brdMstrVO.bbsTyCode}'/>" />
						<input type="hidden" name="bbsAttrbCode" value="<c:out value='${brdMstrVO.bbsAttrbCode}'/>" />
						<input type="hidden" name="authFlag" value="<c:out value='${brdMstrVO.authFlag}'/>" />
						<input name="pageIndex" type="hidden" value="<c:out value='${searchVO.pageIndex}'/>"/>
						<input name="ntcrId" type="hidden" value=""/>
						<input name="ntceBgnde" type="hidden" value=""/>
						<input name="ntceEndde" type="hidden" value=""/>
                        <input type="submit" value="실행" onclick="fn_egov_select_noticeList('1'); return false;" id="invisible" class="invisible" />
                        
                        <fieldset><legend>조건정보 영역</legend>
                        <div class="sf_start">
                            <ul id="search_first_ul">
                                <li>
								    <select name="searchCnd" class="select" title="검색조건 선택">
								           <option value="0" <c:if test="${searchVO.searchCnd == '0'}">selected="selected"</c:if> >제목</option>
								           <option value="1" <c:if test="${searchVO.searchCnd == '1'}">selected="selected"</c:if> >내용</option>             
								           <option value="2" <c:if test="${searchVO.searchCnd == '2'}">selected="selected"</c:if> >작성자</option>            
                                    </select>
                                </li>
                                <li>
                                    <input name="searchWrd" type="text" size="35" value='<c:out value="${searchVO.searchWrd}"/>' maxlength="35" onkeypress="press(event);" title="검색어 입력"> 
                                </li>
                                <li>
                                    <div class="buttons" style="position:absolute;left:870px;top:182px;">
                                        <a href="#LINK" onclick="javascript:fn_egov_select_noticeList('1'); return false;"><img src="<c:url value='/images/img_search.gif' />" alt="search" />조회 </a>
                                        <% if(null != session.getAttribute("LoginVO")){ %>
                                        <c:if test="${brdMstrVO.authFlag == 'Y'}">
                                           <!-- <a href="<c:url value='/cop/bbs${prefix}/addBoardArticle.do'/>?bbsId=<c:out value="${boardVO.bbsId}"/>">등록</a> -->
                                        </c:if>
                                        <%} %>
                                    </div>                              
                                </li>      
                            </ul>
                        </div>
                        </fieldset>
                    </form>
                </div>
                <!-- //검색 필드 박스 끝 -->
                <!-- 
                <div id="page_info"><div id="page_info_align"></div></div>                    
                 -->
                 
					<div id="search_field">
						<div id="sheetDiv" style="width: 100%; height: 500px;"></div>
					</div>
                 
                <!-- table add start -->
                <!--<div class="default_tablestyle">
                    <table summary="번호, 제목, 게시시작일, 게시종료일, 작성자, 작성일, 조회수   입니다" cellpadding="0" cellspacing="0">
                    <caption>게시물 목록</caption>
                    <colgroup>
                    <col width="10%">
                    <col>  
                    <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
	                    <col width="10%">
	                    <col width="10%">
				    </c:if>
				    <c:if test="${anonymous != 'true'}">
                        <col width="10%">
                    </c:if>
                    <col width="15%">
                    <col width="8%">
                    </colgroup>
                    <thead>
                    <tr>
                        <th scope="col" class="f_field" nowrap="nowrap">번호</th>
                        <th scope="col" nowrap="nowrap">제목</th>
                        <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
	                        <th scope="col" nowrap="nowrap">게시시작일</th>
	                        <th scope="col" nowrap="nowrap">게시종료일</th>
	                    </c:if>
	                    <c:if test="${anonymous != 'true'}">
	                        <th scope="col" nowrap="nowrap">작성자</th>
	                    </c:if>
                        <th scope="col" nowrap="nowrap">작성일</th>
                        <th scope="col" nowrap="nowrap">조회수</th>
                    </tr>
                    </thead>
                    <tbody>                 

                    <c:forEach var="result" items="${resultList}" varStatus="status">
                                                   
                      <tr>
				        <td><b><c:out value="${paginationInfo.totalRecordCount+1 - ((searchVO.pageIndex-1) * searchVO.pageSize + status.count)}"/></b></td>            
				        <td align="left">
				            <form name="subForm" method="post" action="<c:url value='/cop/bbs${prefix}/selectBoardArticle.do'/>">
				            <c:if test="${result.replyLc!=0}">
				                <c:forEach begin="0" end="${result.replyLc}" step="1">
				                    &nbsp;
				                </c:forEach>
				                <img src="<c:url value='/images/reply_arrow.gif'/>" alt="reply arrow"/>
				            </c:if>
				            <c:choose>
				                <c:when test="${result.isExpired=='Y' || result.useAt == 'N'}">
				                    <c:out value="${result.nttSj}" />
				                </c:when>
				                <c:otherwise>
				                        <input type="hidden" name="bbsId" value="<c:out value='${result.bbsId}'/>" />
				                        <input type="hidden" name="nttId"  value="<c:out value="${result.nttId}"/>" />
				                        <input type="hidden" name="bbsTyCode" value="<c:out value='${brdMstrVO.bbsTyCode}'/>" />
				                        <input type="hidden" name="bbsAttrbCode" value="<c:out value='${brdMstrVO.bbsAttrbCode}'/>" />
				                        <input type="hidden" name="authFlag" value="<c:out value='${brdMstrVO.authFlag}'/>" />
				                        <input name="pageIndex" type="hidden" value="<c:out value='${searchVO.pageIndex}'/>"/>
				                        

										<input type="hidden" name="parnts" value="<c:out value='${result.parnts}'/>" >
										<input type="hidden" name="sortOrdr" value="<c:out value='${result.sortOrdr}'/>" >
										<input type="hidden" name="replyLc" value="<c:out value='${result.replyLc}'/>" >
				                        
				                        
				                        <span class="link"><input type="submit" style="width:320px;border:solid 0px black;text-align:left;" value="<c:out value="${result.nttSj}"/>" ></span>
				                </c:otherwise>
				            </c:choose>
				            </form>
				        </td>
				        <c:if test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
				            <td ><c:out value="${result.ntceBgnde}"/></td>
				            <td ><c:out value="${result.ntceEndde}"/></td>
				        </c:if>
				        <c:if test="${anonymous != 'true'}">
				            <td ><c:out value="${result.frstRegisterNm}"/></td>
				        </c:if>
				        <td ><c:out value="${result.frstRegisterPnttm}"/></td>
				        <td ><c:out value="${result.inqireCo}"/></td>
				      </tr>
				     </c:forEach>     
				     <c:if test="${fn:length(resultList) == 0}">
				      <tr>
				        <c:choose>
				            <c:when test="${brdMstrVO.bbsAttrbCode == 'BBSA01'}">
				                <td colspan="7" ><spring:message code="common.nodata.msg" /></td>
				            </c:when>
				            <c:otherwise>
				                <c:choose>
				                    <c:when test="${anonymous == 'true'}">
				                        <td colspan="4" ><spring:message code="common.nodata.msg" /></td>
				                    </c:when>
				                    <c:otherwise>
				                        <td colspan="5" ><spring:message code="common.nodata.msg" /></td>
				                    </c:otherwise>
				                </c:choose>     
				            </c:otherwise>
				        </c:choose>       
				          </tr>      
				     </c:if>  
                    </tbody>
                    </table>
                </div>--> 
				
                <!-- 페이지 네비게이션 시작
                <div id="paging_div">
                    <ul class="paging_align">
                        <ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="fn_egov_select_noticeList" />    
                    </ul>
                </div> -->
                <!-- //페이지 네비게이션 끝 -->  
            </div>
            <!-- //content 끝 -->    
        </div>
        <!-- //container 끝 -->
        <!-- footer 시작 -->
        <div id="footer"><jsp:include page="/WEB-INF/jsp/main/inc/EgovIncFooter.jsp"/></div>
        <!-- //footer 끝 -->
    </div>
    <!-- //전체 레이어 끝 -->
 </body>
</html>

 

 

 


 

 

이렇게 나오면 jsp 설정은 끝!

 


4. 저장

화면에 [게시글 추가], [저장] 버튼을 만들고 제목 클릭 시 EditDialog를 띄우게 만들었다.

ibsheet는 상태값이라는게 있어서 추가/수정/삭제 와 같이 상태값이 변화한 항목들을 뽑아 DB에 반영할 수 있다.

기존에는 추가 / 수정 / 삭제가 다 로직이 따로 있는데, 이걸 그냥 하나로 묶어버리려고 함.

 


1) 컨트롤러에 저장로직 추가

▼ EgovBBSManageController.java 에 ibsheet 전용 저장로직인 filesaveIBSheet 추가

더보기

 

상단에 ServletContext 는 수기로 추가해주자.

 

@Autowired
private ServletContext servletContext;

 

저장로직 추가(각 내용 주석 확인)

  /**
  * IBSheet8 저장로직
  *
  * @param boardVO
  * @param model
  * @return
  * @throws Exception
  */
	@RequestMapping(value = "/cop/bbs/sheetfileSave.do", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
	@ResponseBody
	public String filesaveIBSheet(@RequestParam Map<String,String> map,
			HttpServletRequest request, 
			final MultipartHttpServletRequest multiRequest, 
							@ModelAttribute("searchVO") BoardVO boardVO,
						    @ModelAttribute("bdMstr") BoardMaster bdMstr, @ModelAttribute("board") Board board, 
						    BindingResult bindingResult, ModelMap model) throws Exception {
		

		LoginVO user = (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser();
		Map<String, Object> saveResult = new HashMap<>();
		Map<String, Object> IO = new HashMap<>();
		
		try {
			// ---------------------- 시트 데이터 추출 -------------------------
			String formData = map.get("Data");
			String decodedData = HtmlUtils.htmlUnescape(formData); //form 데이터 디코딩
			ObjectMapper objectMapper = new ObjectMapper(); // JSON 문자열을 파싱
			Map<String, Object> jsonMap = objectMapper.readValue(decodedData, Map.class); // JSON을 Map으로 변환
			 
			// 'data' 배열에서 첫 번째 항목을 가져와 Map으로 변환
			Map<String, Object> firstItem = ((List<Map<String, Object>>) jsonMap.get("data")).get(0);
			
			// 각 항목을 Map<String, String>으로 변환
			Map<String, String> resultMap = new HashMap<>();
			for (Map.Entry<String, Object> entry : firstItem.entrySet()) {
			    resultMap.put(entry.getKey(), entry.getValue().toString());
			}
			// 시트데이터 결과 출력
			System.out.println("저장할 데이터 : " + jsonMap);
		 
		 
			// ---------------------- 기본 데이터 세팅 -------------------------
			board.setLastUpdusrId(user.getUniqId());
			
			board.setNtcrNm("");	// dummy 오류 수정 (익명이 아닌 경우 validator 처리를 위해 dummy로 지정됨)
			board.setPassword("");	// dummy 오류 수정 (익명이 아닌 경우 validator 처리를 위해 dummy로 지정됨)
			board.setNttCn(resultMap.get("nttCn"));
			board.setNttSj(resultMap.get("nttSj"));
			board.setNtcrId((String)map.get("ntcrId"));
			board.setNtceBgnde(board.getNtceBgnde());
			board.setNtceEndde(board.getNtceEndde());
			board.setBbsId(board.getBbsId());
		
			//nttid 이 long 타입이기 때문에 형변환
			int nttIdx = Integer.parseInt(resultMap.get("nttIdx"));
			if (nttIdx != 0) {
				long longNumber = (long) nttIdx; 
			    board.setNttId(longNumber);
			} else {
			    // 기본값 설정 (예: 0)
			        board.setNttId(0);
			    }
		 
		 

			// ---------------------- 파일 추출 -------------------------
			/* 시트에 추가된 파일은 저장시 서버로 전송되며, 파일이름과 저장경로를 응답하면 파일 다운로드 가능
			 * 파일이 있는 경우, 신규파일인지, 파일수정인지 여부에 따라 파일명 및 파일경로 업데이트
			 */
			
			//ibsheet의 file 이름은 [행ID + $ + file컬럼명]
			String fileName = resultMap.get("id") + "$lblistFile";
			// 파일을 받기 위한 MultipartFile 객체 생성
			MultipartFile file = multiRequest.getFile(fileName);
			
			FileVO fvo = new FileVO();
			
			// file ID 유무
			String atchFileId_new = ""; //신규 파일이면 ID 생성
			String atchFileId = boardVO.getAtchFileId(); //기존파일 ID
			String atchFileId_old = resultMap.get("atchFileId"); //기존파일 ID(추가x)
			String fileName_old = resultMap.get("streFileNm"); //기존 파일 이름(추가x)
			
			// 현재 프로젝트의 루트 경로 가져오기
			String webappPath = servletContext.getRealPath("/files");
			System.out.println("파일 저장 경로 설정 webappPath : " + webappPath);
			
			// 파일이 존재할 때
			if (file != null && !file.isEmpty()) {
				
			    //1. 기존 파일 ID가 없음(신규 저장)
				if ("".equals(atchFileId)) {
				    // 새로운 첨부 파일 ID 생성 및 파일 저장
				    List<FileVO> result = fileUtil.parseFileInf(
				        Collections.singletonMap("fileName", file), // 파일을 Map 형태로 전달
				        "BBS_", // 파일 prefix
				        0, // 파일 순번 (기본값)
				        atchFileId, // 첨부 파일 ID
				        webappPath // 파일 저장 경로
				    );
					
				    if (!result.isEmpty()) {
				        FileVO fileVO = result.get(0);
				        //파일 정보 추가
				        atchFileId_new = fileMngService.insertFileInf(fileVO);
				        // Board 객체에 새 첨부 파일 ID 설정
				        board.setAtchFileId(atchFileId_new);
				    }
				
				
				//2. 기존 파일이 있는 상태에서 신규파일로 변경
				} else {
				    // 기존 첨부 파일 ID에 파일 추가
				    fvo.setAtchFileId(atchFileId);
				    int cnt = fileMngService.getMaxFileSN(fvo);
				    List<FileVO> _result = fileUtil.parseFileInf(
				        Collections.singletonMap("fileName", file), // 파일을 Map 형태로 전달
				        "BBS_", // 파일 prefix
				        cnt, // 파일 순번 
				        atchFileId, // 첨부 파일 ID
				        webappPath // 파일 저장 경로
				    );
				    //파일 정보 업데이트
				        fileMngService.updateFileInfs(_result);
				    }
			    
			} else { // 파일이 없음
			    System.out.println("파일이 없습니다.");
				if(atchFileId_old != null && (resultMap.get("isLblistFile")).equals("true") ) { //기존에 파일이 있었으면 파일명 유지
				
					 board.setAtchFileId(atchFileId_old); //기존에 있던 파일id 유지
					 board.setStreFileNm(fileName_old); //기존에 있던 파일이름 유지
		        }
			 
			}
		    
			
		    
			// ---------------------- 상태값에 따라 저장 -------------------------
		
			if("Added".equals(resultMap.get("STATUS"))) {
				
				System.out.println("################# 추가 #################");
				board.setNttNo(4L); // 게시물 번호 long
				board.setFrstRegisterId(user.getUniqId()); //여기에 알아서 최초등록자아이디		

				bbsMngService.insertBoardArticle(board);
			
			}else if("Changed".equals(resultMap.get("STATUS"))) {
			
				System.out.println("################# 수정 #################");
				bbsMngService.updateBoardArticle(board);
			
			}else {
			
				System.out.println("################# 삭제 #################");
			    board.setLastUpdusrId(user.getUniqId());
			    bbsMngService.deleteBoardArticle(board);
			}
		 
			
			// ---------------------- 응답 -------------------------
		    IO.put("Result", "0");
		    IO.put("Message", "저장되었습니다.");
		
			//파일이 있는 경우 파일 응답 추가
		    if (file != null && !file.isEmpty()) {
		    	List<Map<String, String>> fileArr = new ArrayList<>();
		    	Map<String, String> fileData1 = new HashMap<>();
				String originalFilename = file.getOriginalFilename(); //파일이름
		
				fileData1.put("file", (String) originalFilename);
		    	fileData1.put("filePath", webappPath + '/');
		    	fileData1.put("id", (String) resultMap.get("id"));
		    	fileArr.add(fileData1);
		
		    	// IO 객체에 파일데이터 추가
		    	IO.put("data", fileArr);
		    }
		
			
			saveResult.put("IO", IO);
		    
			
		}catch(Exception ex){
		     IO.put("Result", "-10");
		     IO.put("Message", "저장 중 오류가 발생했습니다.");
		     saveResult.put("IO", IO);
			
		} finally {}
		
		ObjectMapper objectMapper = new ObjectMapper();
		String jsonString = objectMapper.writeValueAsString(saveResult); 
		
		return jsonString;
	}

 

 

그럼 이렇게 뜨는데

 

원래 게시글 목록에는 첨부파일 이름도 확인할 수가 없다.

내용은 그냥 동일 테이블에서 불러오면 되는데 첨부파일은 vo 부터 추가해줘야함.

 

따라서 공지사항 리스트에서 첨부파일도 받을 수 있도록 Board.java를 수정해줘야함.

 

2) Board.java 수정

▼ Board.java 에 file name을 받을 수 있도록 변수 및 getter Setter 추가

    /**
     * 게시물 첨부파일 명
     */
    private String streFileNm = "";
    
    
    ...
    
    
    /**
	 * streFileNm attribute를 리턴한다.
	 *
	 * @return the streFileNm
	 */
	public String getStreFileNm() {
		return streFileNm;
	}

	/**
	 * streFileNm attribute 값을 설정한다.
	 *
	 * @param streFileNm
	 *            the streFileNm to set
	 */
	public void setStreFileNm(String streFileNm) {
		this.streFileNm = streFileNm;
	}

 

3) EgovBoard_SQL_hsql.xml 수정

여기까지 하면 저장은 잘된다. 문제는 [내용]과 [첨부파일]이 제대로 조회되지 않음.

이유는 데이터를 조회하는 selectBoardArticleList 의 SELECT 문에서 [내용]과 [파일명]을 불러오지 않기 때문.(원래 안불러옴)

 

따라서 EgovBoard_SQL_hsql.xml 에서 공지사항 목록을 조회하는 부분을 약간 수정해서 [내용]과 [파일명] 이 나올 수 있도록 한다.


▼ EgovBoard_SQL_hsql.xml  에 a.NTT_CN, f.STRE_FILE_NM 추가

(상단에 resultMap도 추가할 것)

더보기

(MyBatis 기반임)

 

resultMap에서 NTT_CN, STRE_FILE_NM 컬럼을 추가하고,

SELECT 쪽에서

 

a.NTT_CN, f.STRE_FILE_NM 

 

LEFT OUTER JOIN
LETTNFILEDETAIL f
ON a.ATCH_FILE_ID = f.ATCH_FILE_ID

 

만 추가한 것.

File 쪽은 LETTNFILEDETAIL 테이블에서 관리하기 때문에 JOIN 해줬다.

	<resultMap id="boardList" class="egovframework.let.cop.bbs.service.BoardVO">
		<result property="bbsId" column="BBS_ID" columnIndex="1"/>
		<result property="nttId" column="NTT_ID" columnIndex="2"/>
		<result property="nttSj" column="NTT_SJ" columnIndex="3"/>
		<result property="frstRegisterId" column="FRST_REGISTER_ID" columnIndex="4"/>
		<result property="frstRegisterNm" column="FRST_REGISTER_NM" columnIndex="5"/>
		<result property="frstRegisterPnttm" column="FRST_REGIST_PNTTM" columnIndex="6"/>
		<result property="inqireCo" column="RDCNT" columnIndex="7"/>
		<result property="parnts" column="PARNTSCTT_NO" columnIndex="8"/>
		<result property="replyAt" column="ANSWER_AT" columnIndex="9"/>		
		<result property="replyLc" column="ANSWER_LC" columnIndex="10"/>
		<result property="useAt" column="USE_AT" columnIndex="11"/>
		<result property="atchFileId" column="ATCH_FILE_ID" columnIndex="12"/>
		<result property="ntceBgnde" column="NTCE_BGNDE" columnIndex="13"/>
		<result property="ntceEndde" column="NTCE_ENDDE" columnIndex="14"/>
		<result property="nttCn" column="NTT_CN" columnIndex="15"/>
		<result property="streFileNm" column="STRE_FILE_NM" columnIndex="16"/>
	</resultMap>


....

    <select id="selectBoardArticleList" parameterType="egovframework.let.cop.bbs.service.BoardVO" resultMap="boardList">
		
			SELECT
				a.NTT_ID, a.NTT_SJ, a.FRST_REGISTER_ID, IFNULL(b.USER_NM, a.NTCR_NM) as FRST_REGISTER_NM,
				YEAR(a.FRST_REGIST_PNTTM)||'-'||MONTH(a.FRST_REGIST_PNTTM)||'-'||DAYOFMONTH(a.FRST_REGIST_PNTTM) as FRST_REGIST_PNTTM,
				a.RDCNT, a.PARNTSCTT_NO, a.ANSWER_AT, a.ANSWER_LC, a.USE_AT, a.ATCH_FILE_ID,
				a.BBS_ID, a.NTCE_BGNDE, a.NTCE_ENDDE,
				
				a.NTT_CN,
        		f.STRE_FILE_NM  
			FROM
				LETTNBBS a
			LEFT OUTER JOIN 
				LETTNEMPLYRINFO b
			ON a.FRST_REGISTER_ID = b.ESNTL_ID 

			LEFT OUTER JOIN
				LETTNFILEDETAIL f
			ON a.ATCH_FILE_ID = f.ATCH_FILE_ID


			WHERE
				a.BBS_ID = #{bbsId}
		
			<if test="searchCnd == 0">AND
					a.NTT_SJ LIKE '%' || #{searchWrd} || '%' 		
			</if>
			<if test="searchCnd == 1">AND
					a.NTT_CN LIKE  '%' || #{searchWrd} || '%' 		
			</if>
			<if test="searchCnd == 2">AND
					b.USER_NM LIKE '%' || #{searchWrd} || '%' 		
			</if>
					
			ORDER BY a.SORT_ORDR DESC, NTT_NO ASC
			LIMIT #{recordCountPerPage} OFFSET #{firstIndex}
						
	</select>

 

SELECT 문만 수정해주면 된다.

 

이렇게 되면 조회할 때 게시글 내용과 파일까지 함께 넘어온다.

 


4. 파일 설정

 

아까 게시글 첨부파일 명을 board에서 처리하기 위해 Board.java에서 FIle이름을 받을 수 있는 인자를 추가해줬었다.

    /**
     * 게시물 첨부파일 명
     */
    private String streFileNm = "";
    
    .....
    
    
    	/**
	 * streFileNm attribute를 리턴한다.
	 *
	 * @return the streFileNm
	 */
	public String getStreFileNm() {
		return streFileNm;
	}

	/**
	 * streFileNm attribute 값을 설정한다.
	 *
	 * @param streFileNm
	 *            the streFileNm to set
	 */
	public void setStreFileNm(String streFileNm) {
		this.streFileNm = streFileNm;
	}

 

 

파일명만 문제가 아니라, 해당 파일 경로와 파일명을 페이지로 리턴해줘야하기 때문에

첨부파일 목록에서 파일 저장 경로와 이름을 수정해줘야 한다.

 

1) EgovFileMngUtil.java 수정


첨부파일에 대한 목록 정보를 취득하는  parseFileInf를 아래와 같이 수정한다.

    /**
     * 첨부파일에 대한 목록 정보를 취득한다.
     *
     * @param files
     * @return
     * @throws Exception
     */
    public List<FileVO> parseFileInf ~~~~

 

EgovFileMngUtil.java 에서 아래부분 수정

더보기

일부 파일 경로 및 폴더설정을 따라간다.

    /**
     * 첨부파일에 대한 목록 정보를 취득한다.
     *
     * @param files
     * @return
     * @throws Exception
     */
    public List<FileVO> parseFileInf(Map<String, MultipartFile> files, String KeyStr, int fileKeyParam, String atchFileId, String storePath) throws Exception {
	int fileKey = fileKeyParam;

	String storePathString = "";
	String atchFileIdString = "";

	if ("".equals(storePath) || storePath == null) {
	    storePathString = propertyService.getString("Globals.fileStorePath");
	} else {
	    //storePathString = propertyService.getString(storePath);
		storePathString = storePath; //IBSheet) Cfg.FilePath에 설정한 경로 그대로 설정
	}

	if ("".equals(atchFileId) || atchFileId == null) {
	    atchFileIdString = idgenService.getNextStringId();
	} else {
	    atchFileIdString = atchFileId;
	}

	//File saveFolder = new File(storePathString);
	File saveFolder = new File(EgovWebUtil.filePathBlackList(storePathString)); //IBSheet) 폴더 설정

	if (!saveFolder.exists() || saveFolder.isFile()) {
	    saveFolder.mkdirs();
	}

	Iterator<Entry<String, MultipartFile>> itr = files.entrySet().iterator();
	MultipartFile file;
	String filePath = "";
	List<FileVO> result  = new ArrayList<FileVO>();
	FileVO fvo;

	while (itr.hasNext()) {
	    Entry<String, MultipartFile> entry = itr.next();

	    file = entry.getValue();
	    String orginFileName = file.getOriginalFilename();

	    //--------------------------------------
	    // 원 파일명이 없는 경우 처리
	    // (첨부가 되지 않은 input file type)
	    //--------------------------------------
	    if ("".equals(orginFileName)) {
		continue;
	    }
	    ////------------------------------------

	    int index = orginFileName.lastIndexOf(".");
	    //String fileName = orginFileName.substring(0, index);
	    String fileExt = orginFileName.substring(index + 1);
	    String newName = KeyStr + EgovStringUtil.getTimeStamp() + fileKey;
	    long _size = file.getSize();

	    if (!"".equals(orginFileName)) {
		//filePath = storePathString + File.separator + newName;
		//file.transferTo(new File(filePath));
		filePath = storePathString + File.separator + orginFileName; //IBSheet)기존이름으로 설정
		file.transferTo(new File(EgovWebUtil.filePathBlackList(filePath))); //IBSheet)파일 path 설정
	    }
	    fvo = new FileVO();
	    fvo.setFileExtsn(fileExt);
	    fvo.setFileStreCours(storePathString);
	    fvo.setFileMg(Long.toString(_size));
	    fvo.setOrignlFileNm(orginFileName);
	    //fvo.setStreFileNm(newName);
	    fvo.setStreFileNm(orginFileName); //IBSheet) 파일 이름을 [파일이름.확장자] 형식으로 그대로 전달
	    
	    fvo.setAtchFileId(atchFileIdString);
	    fvo.setFileSn(String.valueOf(fileKey));

	    //writeFile(file, newName, storePathString);
	    result.add(fvo);

	    fileKey++;
	}

	return result;
    }

 

 

이렇게 하면 설정 끝.

 


5. 정리

정리하자면 수정한 파일 목록은 아래와 같다.(제품은 추가한 것)

 

 

동작확인

 

로그인하고 시트 모양 인

 

 

게시글 추가

 

 

첨부파일 등록 후 클릭하면 다운로드 됨

 

 

수정

 

 

삭제

반응형
LIST

'Project > eGov 샘플 프로젝트' 카테고리의 다른 글

부트스트랩(Bootstrap) 모달 띄우기  (0) 2025.02.26

반응형
SMALL

1. let과 const

var 대신 블록 스코프(block scope)를 지원하는 let과 상수 선언용 const를 사용합니다.

let name = "Alice"; // 변경 가능
const age = 25;     // 변경 불가

if (true) {
  let name = "Bob";
  console.log(name); // "Bob"
}
console.log(name);   // "Alice"

 

 

2. 화살표 함수(Arrow Function)

간결한 함수 표현식입니다. this 바인딩이 기존 함수와 다르게 작동합니다.

// 일반 함수
function add(a, b) {
  return a + b;
}

// 화살표 함수
const add = (a, b) => a + b;

console.log(add(2, 3)); // 5

 

 

3. 템플릿 리터럴(Template Literal)

문자열 안에서 변수를 쉽게 포함할 수 있는 백틱(`) 문법.

const name = "Alice";
const message = `Hello, ${name}!`;
console.log(message); // "Hello, Alice!"

 

4. 디스트럭처링 할당(Destructuring Assignment)

객체나 배열의 값을 쉽게 추출하여 변수에 할당합니다.

// 배열
const [a, b] = [1, 2];
console.log(a, b); // 1, 2

// 객체
const user = { name: "Alice", age: 25 };
const { name, age } = user;
console.log(name, age); // "Alice", 25

 

5. 기본 매개변수(Default Parameters)

함수 매개변수에 기본값을 설정합니다.

const greet = (name = "Guest") => `Hello, ${name}!`;
console.log(greet()); // "Hello, Guest!"
console.log(greet("Alice")); // "Hello, Alice!"

 

6. 스프레드 연산자(Spread Operator)

배열이나 객체를 쉽게 복사하거나 병합할 수 있습니다.

// 배열
const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5];
console.log(moreNumbers); // [1, 2, 3, 4, 5]

// 객체
const user = { name: "Alice", age: 25 };
const updatedUser = { ...user, age: 30 }; // age 업데이트
console.log(updatedUser); // { name: "Alice", age: 30 }

 

7. 클래스(Class)

ES6에서는 객체 지향 프로그래밍을 더 쉽게 작성할 수 있는 class 문법을 제공합니다.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, my name is ${this.name}.`;
  }
}

const alice = new Person("Alice", 25);
console.log(alice.greet()); // "Hello, my name is Alice."

 

8. 모듈(Modules)

ES6에서는 import와 export를 사용하여 모듈을 구성합니다.

// module.js
export const greet = (name) => `Hello, ${name}!`;

// main.js
import { greet } from './module.js';
console.log(greet("Alice")); // "Hello, Alice!"

 

9. 프로미스(Promises)

비동기 작업을 처리하는 새로운 방식.

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("Data loaded!"), 1000);
  });
};

fetchData().then((data) => console.log(data)); // 1초 후 "Data loaded!"

 

10. 맵(Map)과 셋(Set)

새로운 자료 구조를 지원합니다.

// Map
const map = new Map();
map.set("key", "value");
console.log(map.get("key")); // "value"

// Set
const set = new Set([1, 2, 3, 3]);
console.log(set); // Set(3) { 1, 2, 3 }

 

11. for...of 루프

이터러블 객체(배열 등)를 순회하기 위한 새로운 루프.

const array = [10, 20, 30];
for (const value of array) {
  console.log(value); // 10, 20, 30
}

 

 

12. 심볼(Symbol)

고유한 식별자를 생성할 때 사용.

const sym = Symbol("unique");
console.log(sym); // Symbol(unique)

 

13. Rest 파라미터(Rest Parameters)

함수 호출 시 전달되는 나머지 매개변수를 배열로 모아 처리할 수 있습니다.

const sum = (...numbers) => {
  return numbers.reduce((total, num) => total + num, 0);
};

console.log(sum(1, 2, 3, 4)); // 10

 

14. Symbol.iterator와 이터러블(Iterables)

객체를 이터러블로 만들어 for...of 문이나 spread 문법에서 사용할 수 있습니다.

const iterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};

for (const value of iterable) {
  console.log(value); // 1, 2, 3
}

 

15. Object.assign

객체를 복사하거나 병합할 때 사용됩니다.

const target = { a: 1 };
const source = { b: 2, c: 3 };

const merged = Object.assign({}, target, source);
console.log(merged); // { a: 1, b: 2, c: 3 }

 

16. Object.entries와 Object.values

객체의 키와 값을 배열 형태로 반환합니다.

const obj = { a: 1, b: 2 };

// Object.entries
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value); // "a 1", "b 2"
}

// Object.values
console.log(Object.values(obj)); // [1, 2]

 

17. includes 메서드

배열이나 문자열에 특정 값이 포함되어 있는지 확인합니다.

const fruits = ["apple", "banana", "cherry"];
console.log(fruits.includes("banana")); // true

const sentence = "Hello, world!";
console.log(sentence.includes("world")); // true

 

18. find와 findIndex

배열에서 조건에 맞는 첫 번째 요소나 그 인덱스를 반환합니다.

const numbers = [10, 20, 30, 40];

// find
const found = numbers.find(num => num > 25);
console.log(found); // 30

// findIndex
const index = numbers.findIndex(num => num > 25);
console.log(index); // 2

 

19. Promise.all과 Promise.race

복수의 프로미스를 동시에 처리합니다.

const p1 = new Promise(resolve => setTimeout(() => resolve("P1"), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve("P2"), 2000));

// Promise.all
Promise.all([p1, p2]).then(results => console.log(results)); // ["P1", "P2"]

// Promise.race
Promise.race([p1, p2]).then(result => console.log(result)); // "P1"

 

 

20. Default Import와 Named Import

ES6 모듈에서 다양한 방식으로 코드를 가져옵니다.

// module.js
export const greet = name => `Hello, ${name}!`;
export default name => `Hi, ${name}!`;

// main.js
import defaultGreet, { greet } from "./module.js";

console.log(defaultGreet("Alice")); // "Hi, Alice!"
console.log(greet("Bob"));         // "Hello, Bob!"

 

21. 태스크 큐와 마이크로태스크 큐(Task Queue & Microtask Queue)

Promise와 setTimeout의 실행 순서를 이해할 수 있는 개념입니다.

console.log("Start");

setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));

console.log("End");

// 출력 순서:
// Start
// End
// Promise
// Timeout

 

22. SetTimeout과 SetInterval에 화살표 함수 사용

화살표 함수는 this를 고정해 주므로 setTimeout 등에서 유용합니다.

class Timer {
  start() {
    this.seconds = 0;
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
}

const timer = new Timer();
timer.start();

 

23. Class와 Static 메서드

클래스에서 인스턴스 생성 없이 호출할 수 있는 메서드를 정의할 수 있습니다.

class MathUtils {
  static add(a, b) {
    return a + b;
  }
}

console.log(MathUtils.add(5, 3)); // 8

 

24. 다중 객체 병합과 스프레드

ES6의 스프레드 연산자를 활용한 다중 병합이 가능합니다.

const defaultConfig = { theme: "light", fontSize: 14 };
const userConfig = { fontSize: 16, language: "en" };
const finalConfig = { ...defaultConfig, ...userConfig };

console.log(finalConfig); // { theme: "light", fontSize: 16, language: "en" }

 

25. 지연 평가(Lazy Evaluation)

generator를 사용해 필요한 데이터만 계산하도록 구현 가능합니다.

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = generateSequence();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2

 

반응형
LIST

'[ javascript ]' 카테고리의 다른 글

Node.js 뜯어보기  (0) 2025.03.20
스프레드 연산자 (...)란?  (0) 2025.02.27
부동소수점 이슈  (0) 2024.03.29
Node.js & npm 업데이트 하기  (1) 2024.03.22
리액트란?  (0) 2024.03.19
반응형
SMALL

+ Recent posts

반응형
LIST