From dd8ea77f4ba15e90a254fce02b788c4426d67896 Mon Sep 17 00:00:00 2001 From: leebj Date: Wed, 17 Jul 2024 14:06:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A7=84=ED=96=89=EB=8F=84(=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=8A=A4)=20=EC=B2=98=EB=A6=AC=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 ++ .../java/cokr/xit/fims/base/SseEntity.java | 25 +++ .../xit/fims/base/service/bean/SseBean.java | 189 ++++++++++++++++++ .../xit/fims/base/web/MainController.java | 12 ++ .../java/cokr/xit/fims/cmmn/CmmnQuery.java | 11 + .../resources/js/fims/cmmn/fims-cmmnUtil.js | 31 +++ 6 files changed, 280 insertions(+) create mode 100644 src/main/java/cokr/xit/fims/base/SseEntity.java create mode 100644 src/main/java/cokr/xit/fims/base/service/bean/SseBean.java 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