diff --git a/pom.xml b/pom.xml
index 0666096c..b63d3d8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,18 @@
+
+
+ 3rd-party
+ echelon
+ compile
+
+
+ 3rd-party
+ dguard
+ compile
+
+
diff --git a/src/main/java/cokr/xit/fims/base/SseEntity.java b/src/main/java/cokr/xit/fims/base/SseEntity.java
new file mode 100644
index 00000000..ecdb880a
--- /dev/null
+++ b/src/main/java/cokr/xit/fims/base/SseEntity.java
@@ -0,0 +1,25 @@
+package cokr.xit.fims.base;
+
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import cokr.xit.foundation.data.DataObject;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class SseEntity {
+
+ private long createdAt;
+
+ private SseEmitter emitter;
+
+ private DataObject progress;
+
+ public SseEntity(SseEmitter emitter) {
+ this.createdAt = System.currentTimeMillis();
+ this.emitter = emitter;
+ this.progress = new DataObject();
+ }
+
+}
diff --git a/src/main/java/cokr/xit/fims/base/service/bean/SseBean.java b/src/main/java/cokr/xit/fims/base/service/bean/SseBean.java
new file mode 100644
index 00000000..bc026424
--- /dev/null
+++ b/src/main/java/cokr/xit/fims/base/service/bean/SseBean.java
@@ -0,0 +1,189 @@
+package cokr.xit.fims.base.service.bean;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Resource;
+
+import org.apache.catalina.connector.ClientAbortException;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import cokr.xit.fims.base.SseEntity;
+import cokr.xit.foundation.component.AbstractBean;
+
+@Component("sseBean")
+public class SseBean extends AbstractBean {
+ private static final long ONE_HOUR = 60 * 1000;
+ private String cursor;
+ private ConcurrentHashMap sseMap = new ConcurrentHashMap<>();
+ private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+
+ @Resource(name="objectMapper")
+ private ObjectMapper objectMapper;
+
+ /** SSE키를 등록한다.
+ * @param sseKey
+ * @return SseEmitter
+ */
+ public SseEmitter start(String sseKey) {
+ SseEmitter emitter = new SseEmitter(-1L);
+ sseMap.put(sseKey, new SseEntity(emitter));
+ return emitter;
+ }
+
+ /** 작업할 SSE키를 설정한다.
+ * @param sseKey
+ * @return SseBean
+ */
+ public SseBean setCursor(String sseKey) {
+ this.cursor = sseKey;
+ return this;
+ }
+
+ /** 클라이언트에 메시지를 전송한다.
+ * @param msg 메시지
+ * @return SseBean
+ */
+ public void send(Object msg) {
+ if(ifEmpty(this.cursor, ()->"").equals("")) {
+ return;
+ }
+
+ SseEntity se = sseMap.get(this.cursor);
+ if (se == null) {
+ return;
+ }
+ try {
+ se.getEmitter().send(msg);
+ } catch (ClientAbortException ex) {
+ if (se != null && se.getEmitter() != null) {
+ se.getEmitter().complete();
+ se.setEmitter(null);
+ sseMap.remove(this.cursor);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ if (se != null && se.getEmitter() != null) {
+ se.getEmitter().complete();
+ se.setEmitter(null);
+ sseMap.remove(this.cursor);
+ }
+ }
+ }
+
+ /** 클라이언트에 종료 메시지를 전송한다.
+ * @param
+ * @return
+ */
+ public void end() {
+ if(ifEmpty(this.cursor, ()->"").equals("")) {
+ return;
+ }
+
+ SseEntity se = sseMap.get(this.cursor);
+ if (se == null) {
+ return;
+ }
+ String msg = "FIN";
+ try {
+ se.getEmitter().send(msg);
+ } catch (ClientAbortException ex) {
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (se != null && se.getEmitter() != null) {
+ se.getEmitter().complete();
+ se.setEmitter(null);
+ sseMap.remove(this.cursor);
+ }
+ }
+ }
+
+ @Scheduled(cron = "0 0 */1 * * *")
+ public void clearSseEmitter() {
+ long now = System.currentTimeMillis() - ONE_HOUR;
+ Iterator it = sseMap.keys().asIterator();
+ System.out.println("size: "+sseMap.size());
+ while (it.hasNext()) {
+ String key = it.next();
+ System.out.println("key: "+key);
+ SseEntity se = sseMap.get(key);
+ if (now > se.getCreatedAt()) {
+ System.out.println("clear old ssemitter "+key+" : "+format.format(se.getCreatedAt()));
+ if (se.getEmitter() != null) {
+ se.setEmitter(null);
+ }
+ sseMap.remove(key);
+ }
+ }
+ }
+
+ public SseBean setProgress(String key, Object value) {
+ if(ifEmpty(this.cursor, ()->"").equals("")) {
+ return this;
+ }
+
+ SseEntity se = sseMap.get(this.cursor);
+ se.getProgress().set(key, value);
+ return this;
+ }
+
+ public SseBean increaseProgress(String key) {
+ if(ifEmpty(this.cursor, ()->"").equals("")) {
+ return this;
+ }
+
+ SseEntity se = sseMap.get(this.cursor);
+ int i = se.getProgress().number(key).intValue();
+ se.getProgress().set(key, i+1);
+ return this;
+ }
+
+ public SseBean increaseProgress(String key, int num) {
+ if(ifEmpty(this.cursor, ()->"").equals("")) {
+ return this;
+ }
+
+ SseEntity se = sseMap.get(this.cursor);
+ int i = se.getProgress().number(key).intValue();
+ se.getProgress().set(key, i+num);
+ return this;
+ }
+
+ public void sendProgress() {
+ if(ifEmpty(this.cursor, ()->"").equals("")) {
+ return;
+ }
+
+ SseEntity se = sseMap.get(this.cursor);
+ if (se == null) {
+ return;
+ }
+ try {
+ se.getEmitter().send(objectMapper.writeValueAsString(se.getProgress()));
+ } catch (ClientAbortException ex) {
+ if (se != null && se.getEmitter() != null) {
+ se.getEmitter().complete();
+ se.setEmitter(null);
+ sseMap.remove(this.cursor);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ if (se != null && se.getEmitter() != null) {
+ se.getEmitter().complete();
+ se.setEmitter(null);
+ sseMap.remove(this.cursor);
+ }
+ }
+ }
+
+
+
+}
diff --git a/src/main/java/cokr/xit/fims/base/web/MainController.java b/src/main/java/cokr/xit/fims/base/web/MainController.java
index 56ab2375..493faaf8 100644
--- a/src/main/java/cokr/xit/fims/base/web/MainController.java
+++ b/src/main/java/cokr/xit/fims/base/web/MainController.java
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import cokr.xit.base.code.CommonCode;
import cokr.xit.base.code.service.CodeService;
@@ -21,6 +22,7 @@ import cokr.xit.base.user.service.SigunguDepartmentService;
import cokr.xit.base.user.service.UserService;
import cokr.xit.fims.base.FimsUser;
import cokr.xit.fims.base.service.bean.OgdpBean;
+import cokr.xit.fims.base.service.bean.SseBean;
import cokr.xit.fims.cmmn.CmmnUtil;
import cokr.xit.fims.task.TaskRequestMappingHandlerMapping;
import cokr.xit.foundation.data.DataObject;
@@ -38,6 +40,9 @@ public class MainController extends cokr.xit.base.web.MainController {
@Autowired
private TaskRequestMappingHandlerMapping requestHandlers;
+ @Autowired
+ private SseBean sseBean;
+
@Resource(name="ogdpBean")
private OgdpBean ogdpBean;
@@ -199,4 +204,11 @@ public class MainController extends cokr.xit.base.web.MainController {
.addObject("remoteAddr", remoteAddr)
.addObject("net", net);
}
+
+
+ @RequestMapping(name="서버 이벤트 수신 등록", value="/subscribe.do")
+ public SseEmitter subscribe(HttpServletRequest req) {
+ return sseBean.start(req.getParameter("sseKey"));
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/cokr/xit/fims/cmmn/CmmnQuery.java b/src/main/java/cokr/xit/fims/cmmn/CmmnQuery.java
index 761721e4..9a3d9090 100644
--- a/src/main/java/cokr/xit/fims/cmmn/CmmnQuery.java
+++ b/src/main/java/cokr/xit/fims/cmmn/CmmnQuery.java
@@ -6,6 +6,8 @@ public class CmmnQuery extends QueryRequest {
private static final long serialVersionUID = 1L;
+ private String sseKey;
+
private String mainOption;
private String subOption;
@@ -31,6 +33,15 @@ public class CmmnQuery extends QueryRequest {
private String schRgtrCd; // 등록 사용자 코드
private String schRgtrNm; // 등록 사용자 명
+ public String getSseKey() {
+ return ifEmpty(sseKey, () -> null);
+ }
+
+ public T setSseKey(String sseKey) {
+ this.sseKey = sseKey;
+ return self();
+ }
+
public String getMainOption() {
return ifEmpty(mainOption, () -> null);
}
diff --git a/src/main/webapp/resources/js/fims/cmmn/fims-cmmnUtil.js b/src/main/webapp/resources/js/fims/cmmn/fims-cmmnUtil.js
index 4584760b..0115bf6d 100644
--- a/src/main/webapp/resources/js/fims/cmmn/fims-cmmnUtil.js
+++ b/src/main/webapp/resources/js/fims/cmmn/fims-cmmnUtil.js
@@ -381,10 +381,41 @@ const GRID = {
}
};
+/**************************************************************************
+* 엑셀다운로드시 개인정보 포함된 셀 이름 정보 추출
+**************************************************************************/
function getCellDefsForPrivacyCell($td_i){
if($td_i.hasClass("privacy-cell")){
return $td_i.find("span:eq(0)").text();
} else {
return $td_i.text();
}
+}
+
+
+/**************************************************************************
+* 프로그레스바 표시
+**************************************************************************/
+function fnProgress(sseKey, progressContent, progressEvent){
+ var eventSource = new EventSource(SSE_SUBSCRIBE_URL+"?sseKey="+sseKey);
+
+ eventSource.onopen = function(e) {
+ $.blockUI({
+ message : ""+progressContent+"",
+ css: { backgroundColor: "lightgrey", padding : "10px" }
+ });
+ };
+
+ eventSource.onerror = function(e) {
+ $.unblockUI();
+ };
+
+ eventSource.onmessage = function(e) {
+ if (e.data == 'FIN'){
+ eventSource.close();
+ $.unblockUI();
+ } else {
+ progressEvent(e.data);
+ }
+ };
}
\ No newline at end of file