Explorar el Código

Merge branch 'master' of http://nokia.tianhaikj.tk:13000/other/hb_nokia_pm_ui

wangrulan hace 1 año
padre
commit
f85ee6a0cc

+ 4 - 0
pom.xml

@@ -131,6 +131,10 @@
 			<artifactId>caffeine</artifactId>
 			<version>2.9.3</version>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-websocket</artifactId>
+		</dependency>
 	</dependencies>
 
 	<build>

+ 2 - 0
src/main/java/com/nokia/hb/HbApplication.java

@@ -2,7 +2,9 @@ package com.nokia.hb;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
+@EnableScheduling
 @SpringBootApplication
 public class HbApplication {
     public static void main(String[] args) {

+ 13 - 3
src/main/java/com/nokia/hb/config/CacheConfig.java

@@ -8,8 +8,13 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import javax.servlet.http.HttpSession;
 import java.util.concurrent.TimeUnit;
 
+/**
+ * 缓存配置
+ *
+ */
 @Slf4j
 @ConfigurationProperties("session")
 @Configuration
@@ -20,12 +25,17 @@ public class CacheConfig {
      */
     private Integer timeout;
 
+    /**
+     * 会话缓存
+     *
+     * @return {@link Cache}<{@link String}, {@link HttpSession}>
+     */
     @Bean
-    public Cache<String, Object> sessionCache() {
+    public Cache<String, HttpSession> sessionCache() {
         return Caffeine.newBuilder()
                 .expireAfterAccess(timeout, TimeUnit.SECONDS)
-                .evictionListener((k, v, c) -> log.debug("sessionCache evictionListener -> c: {}, k: {}, v: {}", c, k, v))
-                .removalListener((k, v, c) -> log.debug("sessionCache removalListener -> c: {}, k: {}, v: {}", c, k, v))
+                .evictionListener((k, v, c) -> log.debug("sessionCache evictionListener: {}, {} -> {}", c, k, v))
+                .removalListener((k, v, c) -> log.debug("sessionCache removalListener: {}, {} -> {}", c, k, v))
                 .build();
     }
 }

+ 1 - 1
src/main/java/com/nokia/hb/config/web/LoginHandlerInterceptor.java

@@ -30,7 +30,7 @@ public class LoginHandlerInterceptor implements HandlerInterceptor {
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         HttpSession session = request.getSession();
         String username = SessionUtil.getUsername(session);
-        log.debug("username: {}", username);
+        log.debug("{}: {}", session.getId(), username);
         if (StringUtils.hasText(username) && sessionService.logged(username)) {
             sessionService.update(session);
             return true;

+ 34 - 0
src/main/java/com/nokia/hb/config/web/WebSocketConfig.java

@@ -0,0 +1,34 @@
+package com.nokia.hb.config.web;
+
+import com.nokia.hb.service.WebSocketService;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+
+/**
+ * 网络套接字配置
+ *
+ */
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig implements WebSocketConfigurer {
+    private final WebSocketService webSocketService;
+
+    public WebSocketConfig(WebSocketService webSocketService) {
+        this.webSocketService = webSocketService;
+    }
+
+    /**
+     * 注册网络套接字处理程序
+     *
+     * @param registry 注册表
+     */
+    @Override
+    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+        registry.addHandler(webSocketService, "/ws")
+                .addInterceptors(new WebSocketHandshakeInterceptor())
+                .setAllowedOriginPatterns("*")
+        ;
+    }
+}

+ 79 - 0
src/main/java/com/nokia/hb/config/web/WebSocketHandshakeInterceptor.java

@@ -0,0 +1,79 @@
+package com.nokia.hb.config.web;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.util.StringUtils;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/**
+ * 网络套接字握手拦截器
+ *
+ * @author x
+ * @date 2023/03/07
+ */
+@Slf4j
+public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
+    /**
+     * Invoked before the handshake is processed.
+     *
+     * @param request    the current request
+     * @param response   the current response
+     * @param wsHandler  the target WebSocket handler
+     * @param attributes the attributes from the HTTP handshake to associate with the WebSocket
+     *                   session; the provided attributes are copied, the original map is not used.
+     * @return whether to proceed with the handshake ({@code true}) or abort ({@code false})
+     */
+    @Override
+    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+                                   Map<String, Object> attributes) throws Exception {
+        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
+        HttpServletRequest httpServletRequest = servletServerHttpRequest.getServletRequest();
+        String username = httpServletRequest.getParameter("username");
+        Cookie[] cookies = httpServletRequest.getCookies();
+        log.debug("username: {}", username);
+        log.debug("cookies: {}", JSON.toJSONString(cookies));
+        if (cookies == null) {
+            log.warn("cookies is null");
+            return false;
+        }
+        String sessionId = null;
+        for (Cookie cookie : cookies) {
+            if ("JSESSIONID".equals(cookie.getName())) {
+                sessionId = cookie.getValue();
+                log.debug("JSESSIONID: {}", sessionId);
+                attributes.put("JSESSIONID", sessionId);
+            }
+        }
+        if (!StringUtils.hasText(sessionId)) {
+            log.warn("JSESSIONID is null");
+            return false;
+        }
+        attributes.put("username", username);
+        return true;
+    }
+
+    /**
+     * Invoked after the handshake is done. The response status and headers indicate
+     * the results of the handshake, i.e. whether it was successful or not.
+     *
+     * @param request   the current request
+     * @param response  the current response
+     * @param wsHandler the target WebSocket handler
+     * @param exception an exception raised during the handshake, or {@code null} if none
+     */
+    @Override
+    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+                               Exception exception) {
+        if (exception != null) {
+            log.error(exception.getMessage(), exception);
+        }
+    }
+}

+ 11 - 0
src/main/java/com/nokia/hb/controller/IndicatorTemplateController.java

@@ -4,7 +4,9 @@ import com.nokia.common.R;
 import com.nokia.hb.pojo.TreeNode;
 import com.nokia.hb.pojo.dto.AddTemplateDto;
 import com.nokia.hb.pojo.dto.DeleteTemplateDto;
+import com.nokia.hb.pojo.dto.GetTemplateDetailDto;
 import com.nokia.hb.pojo.dto.UpdateTemplateDto;
+import com.nokia.hb.pojo.vo.GetTemplateDetailVo;
 import com.nokia.hb.service.IndicatorTemplateService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -60,4 +62,13 @@ public class IndicatorTemplateController {
     public R<Object> updateTemplate(@Valid @RequestBody UpdateTemplateDto dto, HttpSession session) {
         return indicatorTemplateService.updateTemplate(dto, session);
     }
+
+    /**
+     * 指标模板详情
+     */
+    @Operation(summary = "指标模板详情")
+    @PostMapping("/api/getTemplateDetail")
+    public R<GetTemplateDetailVo> getTemplateDetail(@Valid @RequestBody GetTemplateDetailDto dto) {
+        return indicatorTemplateService.getTemplateDetail(dto);
+    }
 }

+ 16 - 0
src/main/java/com/nokia/hb/dao/mapper/IndicatorTemplateItemMapper.java

@@ -2,6 +2,7 @@ package com.nokia.hb.dao.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.nokia.hb.dao.entity.IndicatorTemplateItem;
+import com.nokia.hb.pojo.TreeNode;
 import com.nokia.hb.pojo.bo.IndicatorTemplateItemBo;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -66,4 +67,19 @@ public interface IndicatorTemplateItemMapper extends BaseMapper<IndicatorTemplat
             + " </foreach>"
             + " </script>")
     int deleteBatch(@Param("list") List<Long> list);
+
+    /**
+     * 按模板id列表
+     *
+     * @param id id
+     * @return {@link List}<{@link IndicatorTemplateItemBo}>
+     */
+    @Select("<script>"
+            + " select iti.indicator_id as id, pci.indicator_cn as title"
+            + " from pm_parse.indicator_template_item iti"
+            + " inner join pm_parse.per_cfg_indicator pci on iti.indicator_id = pci.indicator_id"
+            + " where iti.template_id = #{id}"
+            + " order by iti.template_id, iti.indicator_id"
+            + " </script>")
+    List<TreeNode> listByTemplateId(@Param("id") Long id);
 }

+ 17 - 0
src/main/java/com/nokia/hb/pojo/dto/GetTemplateDetailDto.java

@@ -0,0 +1,17 @@
+package com.nokia.hb.pojo.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class GetTemplateDetailDto {
+    @Schema(description = "模板id", required = true)
+    @NotNull(message = "id不能为空")
+    private Long id;
+}

+ 21 - 0
src/main/java/com/nokia/hb/pojo/vo/GetTemplateDetailVo.java

@@ -0,0 +1,21 @@
+package com.nokia.hb.pojo.vo;
+
+import com.nokia.hb.pojo.TreeNode;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class GetTemplateDetailVo {
+    @Schema(description = "模板id")
+    private Long id;
+    @Schema(description = "模板名称")
+    private String templateName;
+    @Schema(description = "指标列表")
+    private List<TreeNode> list;
+}

+ 15 - 1
src/main/java/com/nokia/hb/service/IndicatorTemplateService.java

@@ -1,5 +1,4 @@
 package com.nokia.hb.service;
-
 import com.nokia.common.R;
 import com.nokia.common.exception.BizException;
 import com.nokia.hb.dao.entity.IndicatorTemplate;
@@ -10,7 +9,9 @@ import com.nokia.hb.pojo.TreeNode;
 import com.nokia.hb.pojo.bo.IndicatorTemplateItemBo;
 import com.nokia.hb.pojo.dto.AddTemplateDto;
 import com.nokia.hb.pojo.dto.DeleteTemplateDto;
+import com.nokia.hb.pojo.dto.GetTemplateDetailDto;
 import com.nokia.hb.pojo.dto.UpdateTemplateDto;
+import com.nokia.hb.pojo.vo.GetTemplateDetailVo;
 import com.nokia.hb.utils.SessionUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -113,4 +114,17 @@ public class IndicatorTemplateService {
         });
         indicatorTemplateItemMapper.insertBatch(indicatorTemplateItems);
     }
+
+    public R<GetTemplateDetailVo> getTemplateDetail(GetTemplateDetailDto dto) {
+        IndicatorTemplate indicatorTemplate = indicatorTemplateMapper.selectById(dto.getId());
+        if (indicatorTemplate == null) {
+            throw new BizException("找不到该模板");
+        }
+        List<TreeNode> list = indicatorTemplateItemMapper.listByTemplateId(dto.getId());
+        GetTemplateDetailVo vo = new GetTemplateDetailVo();
+        vo.setId(indicatorTemplate.getId());
+        vo.setTemplateName(indicatorTemplate.getTemplateName());
+        vo.setList(list);
+        return R.ok(vo);
+    }
 }

+ 23 - 4
src/main/java/com/nokia/hb/service/SessionService.java

@@ -5,17 +5,20 @@ import com.nokia.hb.config.CacheConfig;
 import com.nokia.hb.utils.SessionUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import javax.servlet.http.HttpSession;
-import java.time.LocalDateTime;
 
+/**
+ * 会话服务
+ */
 @Slf4j
 @Service
 public class SessionService {
     private final CacheConfig cacheConfig;
-    private final Cache<String, Object> sessionCache;
+    private final Cache<String, HttpSession> sessionCache;
 
-    public SessionService(CacheConfig cacheConfig, Cache<String, Object> sessionCache) {
+    public SessionService(CacheConfig cacheConfig, Cache<String, HttpSession> sessionCache) {
         this.cacheConfig = cacheConfig;
         this.sessionCache = sessionCache;
     }
@@ -28,7 +31,7 @@ public class SessionService {
     public void update(HttpSession session) {
         session.setMaxInactiveInterval(cacheConfig.getTimeout());
         String username = SessionUtil.getUsername(session);
-        log.info("{} last login at {}", username, sessionCache.get(username, k -> LocalDateTime.now()));
+        sessionCache.get(username, k -> session);
     }
 
     /**
@@ -51,4 +54,20 @@ public class SessionService {
         session.invalidate();
         sessionCache.invalidate(username);
     }
+
+    /**
+     * 无效
+     *
+     * @param username 用户名
+     */
+    public void invalidate(String username) {
+        if (!StringUtils.hasText(username)) {
+            return;
+        }
+        HttpSession session = sessionCache.getIfPresent(username);
+        if (session != null) {
+            session.invalidate();
+        }
+        sessionCache.invalidate(username);
+    }
 }

+ 129 - 0
src/main/java/com/nokia/hb/service/WebSocketService.java

@@ -0,0 +1,129 @@
+package com.nokia.hb.service;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.web.socket.*;
+import org.springframework.web.socket.handler.AbstractWebSocketHandler;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 网络套接字服务
+ */
+@EqualsAndHashCode(callSuper = true)
+@Slf4j
+@Service
+@Data
+@ConfigurationProperties("socket")
+public class WebSocketService extends AbstractWebSocketHandler {
+    /**
+     * 会话映射
+     */
+    static final ConcurrentMap<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
+    /**
+     * ping时间map
+     */
+    static final ConcurrentMap<String, LocalDateTime> PING_MAP = new ConcurrentHashMap<>();
+    /**
+     * 心跳超时(s)
+     */
+    private Long heartbeatTimeout;
+    private final SessionService sessionService;
+
+    public WebSocketService(SessionService sessionService) {
+        this.sessionService = sessionService;
+    }
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        SESSION_MAP.put(session.getId(), session);
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("afterConnectionEstablished: {} -> {} -> {}", session.getId(), sessionId, username);
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.error("handleTransportError: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
+                exception.getMessage(), exception);
+    }
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("afterConnectionClosed: {}, {} -> {} -> {}", closeStatus, session.getId(), sessionId, username);
+        sessionService.invalidate(username);
+        SESSION_MAP.remove(session.getId());
+    }
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("handleTextMessage: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
+                message.getPayload());
+    }
+
+    @Override
+    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("handleBinaryMessage: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
+                message.getPayload());
+    }
+
+    @Override
+    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
+        Map<String, Object> attributes = session.getAttributes();
+        String sessionId = (String) attributes.get("JSESSIONID");
+        String username = (String) attributes.get("username");
+        log.info("handlePongMessage: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
+                message.getPayload());
+        PING_MAP.put(session.getId(), LocalDateTime.now());
+    }
+
+    @Scheduled(fixedDelay = 2000)
+    public void ping() {
+        for (Map.Entry<String, WebSocketSession> entry : SESSION_MAP.entrySet()) {
+            String k = entry.getKey();
+            WebSocketSession v = entry.getValue();
+            Map<String, Object> attributes = v.getAttributes();
+            String sessionId = (String) attributes.get("JSESSIONID");
+            String username = (String) attributes.get("username");
+            PING_MAP.putIfAbsent(k, LocalDateTime.now());
+            // 心跳超时断开连接
+            if (LocalDateTime.now().minusSeconds(heartbeatTimeout).compareTo(PING_MAP.get(k)) > 0) {
+                log.warn("超过{}s未收到pong: {} -> {} -> {}", heartbeatTimeout, v.getId(), sessionId, username);
+                try {
+                    v.close();
+                    PING_MAP.remove(k);
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+                continue;
+            }
+            // 发送ping
+            try {
+                log.info("ping {} -> {} -> {}", v.getId(), sessionId, username);
+                v.sendMessage(new PingMessage());
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+    }
+}

+ 2 - 0
src/main/resources/application-dev.yml

@@ -1,3 +1,5 @@
+server:
+  port: 9013
 logging:
   level:
     com.nokia.hb: debug

+ 6 - 1
src/main/resources/application.yml

@@ -2,6 +2,9 @@ server:
   port: 9003
   servlet:
     context-path: /
+    session:
+      cookie:
+        http-only: true
   tomcat:
     uri-encoding: UTF-8
     max-threads: 800
@@ -25,4 +28,6 @@ spring:
     username: pmparse
     password: abc123!
 session:
-  timeout: 300
+  timeout: 28800
+socket:
+  heartbeat-timeout: 5

+ 0 - 1
src/main/resources/templates/login.html

@@ -69,7 +69,6 @@
                 data: dataJson,
                 success: function (r) {
                     if (r?.success) {
-                        console.log('r: ', r);
                         layer.msg('登录成功');
                         window.sessionStorage.setItem('userName',r.data.username)
                         setTimeout(() => window.location.href = '/template', 1500)

+ 28 - 13
src/main/resources/templates/template.html

@@ -166,6 +166,27 @@
 <script src="./js/jquery-3.5.1.min.js"></script>
 <script src="./js/layui/layui.js"></script>
 <script>
+    const host = window.location.host
+    const username = window.sessionStorage.getItem('userName')
+    const socket = new WebSocket(`ws://${host}/ws?username=${username}`);
+    socket.onopen = (event) => {
+        console.log(event)
+    }
+
+    socket.onclose = (event) => {
+        console.log(event)
+        logout()
+    }
+
+    socket.onerror = (event) => {
+        console.log(event)
+        logout()
+    }
+
+    const logout = () => {
+        alert('websocket连接异常,请重新登录')
+        setTimeout(() => window.location.href = '/login', 1000)
+    }
         var dataA;
         var layer;
         var tree;
@@ -233,9 +254,6 @@
             initTreeIndicatorTemplate(tree)
             // console.log(window.sessionStorage.getItem('userName'))
             $("#userName").text(window.sessionStorage.getItem('userName'))
-            if(!window.sessionStorage.getItem('userName')){
-                setTimeout(() => window.location.href = '/login', 1000)
-            }
         })
             function handleChange(){
                 console.log('5555')
@@ -663,19 +681,14 @@
 
         // 退出登录
         function logOutFun() {
-            console.log('tuichu')
             $.ajax({
                 type: "POST",
                 // url: '/api/json/citys.json',
                 url: '/api/logout',
                 success: function (r) {
-                    console.log('r: ', r);
-                    if (r?.success) {
-                        layer.msg('退出登录成功');
-                        setTimeout(() => window.location.href = '/login', 1000)
-                    }else{
-                        alert('退出登录失败');
-                    }
+                    window.sessionStorage.clear()
+                    layer.msg('退出登录成功');
+                    setTimeout(() => window.location.href = '/login', 1000)
                 }
             });
         }
@@ -690,8 +703,10 @@
             },1000)
 
         });
-        window.onbeforeunload=function(e){     
-            logOutFun()
+        window.onbeforeunload=function(e){
+            if (window.sessionStorage.getItem('userName')) {
+                logOutFun()
+            }
         }
         // window.addEventListener('unload',logOutFun());
     </script>