浏览代码

完成心跳检测功能

xusl 1 年之前
父节点
当前提交
95e8fdaeab
共有 40 个文件被更改,包括 764 次插入103 次删除
  1. 4 0
      backend/pom.xml
  2. 7 2
      backend/src/main/java/com/jiayue/ssi/backenum/ApproveOperaterEnum.java
  3. 3 0
      backend/src/main/java/com/jiayue/ssi/config/MybatisPlusConfig.java
  4. 37 0
      backend/src/main/java/com/jiayue/ssi/config/StartDoSomethingConfig.java
  5. 1 1
      backend/src/main/java/com/jiayue/ssi/config/WebConfig.java
  6. 2 1
      backend/src/main/java/com/jiayue/ssi/config/WebSecurityConfig.java
  7. 25 0
      backend/src/main/java/com/jiayue/ssi/config/WebSocketConfig.java
  8. 31 0
      backend/src/main/java/com/jiayue/ssi/config/WebSocketConfigurator.java
  9. 10 0
      backend/src/main/java/com/jiayue/ssi/constant/CacheConstants.java
  10. 5 1
      backend/src/main/java/com/jiayue/ssi/controller/ElectricFieldController.java
  11. 1 2
      backend/src/main/java/com/jiayue/ssi/controller/IpBlacklistController.java
  12. 38 0
      backend/src/main/java/com/jiayue/ssi/controller/SysApproveController.java
  13. 11 0
      backend/src/main/java/com/jiayue/ssi/controller/SysParameterController.java
  14. 7 1
      backend/src/main/java/com/jiayue/ssi/controller/SysPolicyController.java
  15. 30 14
      backend/src/main/java/com/jiayue/ssi/controller/SysUserController.java
  16. 3 1
      backend/src/main/java/com/jiayue/ssi/dto/ActiveUserDto.java
  17. 4 0
      backend/src/main/java/com/jiayue/ssi/entity/ElectricField.java
  18. 4 0
      backend/src/main/java/com/jiayue/ssi/entity/SysApprove.java
  19. 4 0
      backend/src/main/java/com/jiayue/ssi/entity/SysBlacklist.java
  20. 4 0
      backend/src/main/java/com/jiayue/ssi/entity/SysParameter.java
  21. 4 0
      backend/src/main/java/com/jiayue/ssi/entity/SysPolicy.java
  22. 12 12
      backend/src/main/java/com/jiayue/ssi/filter/JwtAuthenticationTokenFilter.java
  23. 38 6
      backend/src/main/java/com/jiayue/ssi/filter/VerifySmFilter.java
  24. 43 9
      backend/src/main/java/com/jiayue/ssi/handler/CustomAuthenticationSuccessHandler.java
  25. 1 1
      backend/src/main/java/com/jiayue/ssi/handler/CustomLogoutSuccessHandler.java
  26. 12 5
      backend/src/main/java/com/jiayue/ssi/job/AutoScanAccount.java
  27. 12 7
      backend/src/main/java/com/jiayue/ssi/job/AutoScanHeartUser.java
  28. 131 0
      backend/src/main/java/com/jiayue/ssi/service/WebSocketServer.java
  29. 15 0
      backend/src/main/java/com/jiayue/ssi/service/impl/SysApproveServiceImpl.java
  30. 1 1
      backend/src/main/resources/application.yml
  31. 98 18
      ui/src/layout/components/AppMain.vue
  32. 11 7
      ui/src/layout/components/Navbar.vue
  33. 99 0
      ui/src/layout/components/Sidebar/index.vue
  34. 1 1
      ui/src/views/bizManager/electricField/index.vue
  35. 5 3
      ui/src/views/examineManager/sysApprove/index.vue
  36. 0 1
      ui/src/views/login/index.vue
  37. 1 1
      ui/src/views/sysManager/ipBlacklist/index.vue
  38. 16 3
      ui/src/views/sysManager/sysParameter/index.vue
  39. 32 4
      ui/src/views/sysManager/userManager/index.vue
  40. 1 1
      ui/vue.config.js

+ 4 - 0
backend/pom.xml

@@ -218,6 +218,10 @@
             <artifactId>fastjson2</artifactId>
             <version>2.0.23</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <finalName>ssi</finalName>

+ 7 - 2
backend/src/main/java/com/jiayue/ssi/backenum/ApproveOperaterEnum.java

@@ -25,9 +25,14 @@ public enum ApproveOperaterEnum {
      */
     DELETE(2, "删除"),
     /**
-     * 删除
+     * 授权
+     */
+    APPROVE(3, "授权"),
+    /**
+     * 解锁
      */
-    APPROVE(3, "授权");
+    RELOCK(4, "解锁");
+
     private Integer code;
     private String message;
 }

+ 3 - 0
backend/src/main/java/com/jiayue/ssi/config/MybatisPlusConfig.java

@@ -2,6 +2,7 @@ package com.jiayue.ssi.config;
 
 import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 
 import org.springframework.context.annotation.Bean;
@@ -27,6 +28,8 @@ public class MybatisPlusConfig {
 //        paginationInnerInterceptor.setMaxLimit(500L);
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
         interceptor.addInnerInterceptor(paginationInnerInterceptor);
+        //注册乐观锁插件
+        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
         return interceptor;
     }
 }

+ 37 - 0
backend/src/main/java/com/jiayue/ssi/config/StartDoSomethingConfig.java

@@ -0,0 +1,37 @@
+package com.jiayue.ssi.config;
+
+import com.jiayue.ssi.entity.SysUser;
+import com.jiayue.ssi.service.SysUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+
+/**
+ * 程序启动做些事情
+ *
+ * @author xsl
+ * @version 3.0
+ */
+@Configuration
+@Slf4j
+public class StartDoSomethingConfig {
+
+    @Autowired
+    SysUserService sysUserService;
+
+    /**
+     * 将所有用户在线状态设置为“离线”
+     */
+    @Bean
+    public void setOffLine() {
+        List<SysUser> userList = sysUserService.list();
+        for (SysUser sysUser:userList){
+            sysUser.setOnlineStatus("1");
+        }
+        sysUserService.saveOrUpdateBatch(userList);
+    }
+}

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/config/WebConfig.java

@@ -36,7 +36,7 @@ public class WebConfig implements WebMvcConfigurer {
     public void addInterceptors(InterceptorRegistry registry){
         // 刷新token拦截器注册
         registry.addInterceptor(new TokenStatusInterceptor()).addPathPatterns("/**")
-                .excludePathPatterns("/sysParameterController/getUseSendMail","/refreshToken","/error","/getMailCode","/getVerifyCode","/sysUserController/establishHeart","/login","/index.html","/user/login","/css/**","/images/**","/js/**","/fonts/**");
+                .excludePathPatterns("/sysParameterController/getUseSendMail","/refreshToken","/error","/getMailCode","/getVerifyCode","/login","/index.html","/user/login","/css/**","/images/**","/js/**","/fonts/**");
     }
     /**
      * 初始加载

+ 2 - 1
backend/src/main/java/com/jiayue/ssi/config/WebSecurityConfig.java

@@ -107,7 +107,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .and().authorizeRequests()
 //                .antMatchers("/user/login","/captchaImage").permitAll()
                 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
-                .antMatchers("/getVerifyCode/**","/getMailCode/**","/sysUserController/establishHeart/**").permitAll()
+                .antMatchers("/getVerifyCode/**","/getMailCode/**","/websocket/testsocket","/favicon.ico").permitAll()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and().headers().cacheControl();
@@ -116,6 +116,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .failureHandler(customAuthenticationFailureHandler);
         httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(customLogoutSuccessHandler);
         httpSecurity.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler);
+
     }
 
     //针对静态资源放行

+ 25 - 0
backend/src/main/java/com/jiayue/ssi/config/WebSocketConfig.java

@@ -0,0 +1,25 @@
+package com.jiayue.ssi.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+*
+*
+* @author xsl
+* @since 2023/08/21
+*/
+@Configuration
+public class WebSocketConfig {
+    /**
+     * ServerEndpointExporter类的作用是,会扫描所有的服务器端点,
+     * 把带有@ServerEndpoint 注解的所有类都添加进来
+     *
+     */
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter(){
+        return new ServerEndpointExporter();
+    }
+
+}

+ 31 - 0
backend/src/main/java/com/jiayue/ssi/config/WebSocketConfigurator.java

@@ -0,0 +1,31 @@
+package com.jiayue.ssi.config;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.Enumeration;
+import java.util.Map;
+
+/**
+* 用于将客户端的ip传递给websocket中的session,相当于是一个中介
+*
+* @author xsl
+* @since 2023/08/21
+*/
+public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
+    @Override
+    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
+
+        Map<String, Object> attributes = sec.getUserProperties();
+        HttpSession session = (HttpSession) request.getHttpSession();
+        if (session != null) {
+            attributes.put("ActiveUserDto", session.getAttribute("ActiveUserDto"));
+            Enumeration<String> names = session.getAttributeNames();
+            while (names.hasMoreElements()) {
+                String name = names.nextElement();
+                attributes.put(name, session.getAttribute(name));
+            }
+        }
+    }
+}

+ 10 - 0
backend/src/main/java/com/jiayue/ssi/constant/CacheConstants.java

@@ -2,6 +2,8 @@ package com.jiayue.ssi.constant;
 
 import com.jiayue.ssi.dto.ActiveUserDto;
 import com.jiayue.ssi.entity.SysBlacklist;
+
+import javax.servlet.http.HttpServletRequest;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -89,4 +91,12 @@ public class CacheConstants {
      * 用于用户心跳
      */
     public static Map<String, ActiveUserDto> ACTIVE_USER_MAP = new ConcurrentHashMap<String,ActiveUserDto>();
+    /**
+     * 用于被踢用户的request存储
+     */
+    public static Map<String, HttpServletRequest> KICKED_USER_MAP = new ConcurrentHashMap<String, HttpServletRequest>();
+    /**
+     * 用于被踢用户的sessionId
+     */
+    public static Map<String, String> SESSIONID_USER_MAP = new ConcurrentHashMap<String, String>();
 }

+ 5 - 1
backend/src/main/java/com/jiayue/ssi/controller/ElectricFieldController.java

@@ -107,11 +107,15 @@ public class ElectricFieldController {
                 return ResponseVO.fail("场站位置长度不能超过100个字符!");
             }
 
+            ElectricField oldElectricField = electricFieldService.getById(electricField.getId());
+            if (electricField.getVersion().intValue() != oldElectricField.getVersion().intValue()){
+                return ResponseVO.fail("此数据被操作过,自动刷新列表后请重试操作!");
+            }
+
             boolean bo = electricFieldService.saveOrUpdate(electricField);
             if (bo) {
                 return ResponseVO.success("场站信息保存成功");
             } else {
-                log.error("场站信息保存失败");
                 return ResponseVO.fail("场站信息保存失败");
             }
         } catch (Exception e) {

+ 1 - 2
backend/src/main/java/com/jiayue/ssi/controller/IpBlacklistController.java

@@ -137,7 +137,7 @@ public class IpBlacklistController {
             // id获取ip信息
             SysBlacklist sysBlacklist = sysBlacklistService.getById(id);
             if (sysBlacklist == null) {
-                return ResponseVO.fail("不能执行删除ip操作!");
+                return ResponseVO.fail("此数据被操作过,自动刷新列表后请重试操作!");
             }
 
             boolean bo = sysBlacklistService.removeById(Integer.parseInt(id));
@@ -145,7 +145,6 @@ public class IpBlacklistController {
                 CacheConstants.blacklistMap.remove(sysBlacklist.getIp());
                 return ResponseVO.success("删除ip成功");
             } else {
-                log.error("删除用户信息失败");
                 return ResponseVO.fail("删除ip失败");
             }
         } catch (Exception e) {

+ 38 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysApproveController.java

@@ -14,6 +14,7 @@ import com.jiayue.ssi.constant.ApproveConstants;
 import com.jiayue.ssi.constant.CustomException;
 import com.jiayue.ssi.constant.SecretKeyConstants;
 import com.jiayue.ssi.entity.SysApprove;
+import com.jiayue.ssi.entity.SysParameter;
 import com.jiayue.ssi.entity.SysUser;
 import com.jiayue.ssi.service.SysApproveService;
 import com.jiayue.ssi.service.SysUserService;
@@ -223,6 +224,39 @@ public class SysApproveController {
                                 }
                                 record.setParameterContent(parameterContent.toString());
                             }
+                            else if (record.getOperation().equals(String.valueOf(ApproveOperaterEnum.RELOCK.getCode()))){
+                                // 敏感数据先解密
+                                String idcard = SM2CryptUtils.decrypt(record.getIdcard(), SecretKeyConstants.SERVER_PRIVATE_KEY);
+                                String nickname = SM2CryptUtils.decrypt(record.getNickname(), SecretKeyConstants.SERVER_PRIVATE_KEY);
+                                String mailbox = SM2CryptUtils.decrypt(record.getMailbox(), SecretKeyConstants.SERVER_PRIVATE_KEY);
+                                String phonenumber = SM2CryptUtils.decrypt(record.getPhonenumber(), SecretKeyConstants.SERVER_PRIVATE_KEY);
+                                // 脱密处理
+                                idcard = DesensitizedUtil.idCardNum(idcard,5,2);
+                                nickname = DesensitizedUtil.chineseName(nickname);
+                                mailbox = DesensitizedUtil.email(mailbox);
+                                phonenumber = DesensitizedUtil.mobilePhone(phonenumber);
+                                // 封装新增操作
+                                StringBuffer parameterContent = new StringBuffer("");
+                                parameterContent.append("账号:"+record.getUsername());
+                                parameterContent.append(",身份证号码:"+idcard);
+                                if ("0".equals(record.getUsertype())){
+                                    parameterContent.append(",用户类型:管理员");
+                                }
+                                else {
+                                    parameterContent.append(",用户类型:业务用户");
+                                }
+
+                                parameterContent.append(",姓名:"+nickname);
+                                parameterContent.append(",邮箱:"+mailbox);
+                                parameterContent.append(",手机号码:"+phonenumber);
+                                if (record.getExpDate()==null){
+
+                                }
+                                else {
+                                    parameterContent.append(",账号有效期:"+DateFormatUtils.format(record.getExpDate(),"yyyy-MM-dd"));
+                                }
+                                record.setParameterContent(parameterContent.toString());
+                            }
                         }
                     }
             );
@@ -241,6 +275,10 @@ public class SysApproveController {
     @PreventReplay
     public ResponseVO submitApprove(@RequestBody SysApprove sysApprove) throws CustomException {
         try {
+            SysApprove oldSysApprove = sysApproveService.getById(sysApprove.getId());
+            if (sysApprove.getVersion().intValue() != oldSysApprove.getVersion().intValue()){
+                return ResponseVO.fail("此数据被操作过,自动刷新列表后请重试操作!");
+            }
             sysApproveService.submitApprove(sysApprove);
             return ResponseVO.success("审核提交成功");
         } catch (Exception e) {

+ 11 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysParameterController.java

@@ -11,6 +11,7 @@ import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.constant.CustomException;
 import com.jiayue.ssi.entity.SysParameter;
+import com.jiayue.ssi.entity.SysPolicy;
 import com.jiayue.ssi.service.SysParameterService;
 import com.jiayue.ssi.util.ResponseVO;
 import org.apache.commons.lang3.StringUtils;
@@ -129,6 +130,12 @@ public class SysParameterController {
             } else if (sysParameter.getSysDescribe().length() > 200) {
                 return ResponseVO.fail("参数描述长度不能超过200个字符!");
             }
+
+            SysParameter oldSysParameter = sysParameterService.getById(sysParameter.getId());
+            if (sysParameter.getVersion().intValue() != oldSysParameter.getVersion().intValue()){
+                return ResponseVO.fail("此数据被操作过,自动刷新列表后请重试操作!");
+            }
+
             boolean bo = sysParameterService.updateById(sysParameter);
             if (bo) {
                 if ("useSendMail".equals(sysParameter.getSysKey())) {
@@ -157,6 +164,10 @@ public class SysParameterController {
                 return ResponseVO.fail("id不能为空!");
             }
             SysParameter sysParameter = sysParameterService.getById(Integer.parseInt(id));
+            if (sysParameter == null) {
+                return ResponseVO.fail("此数据被操作过,自动刷新列表后请重试操作!");
+            }
+
             if ("useSendMail".equals(sysParameter.getSysKey())) {
                 CacheConstants.use_send_mail = true;
             }

+ 7 - 1
backend/src/main/java/com/jiayue/ssi/controller/SysPolicyController.java

@@ -7,6 +7,7 @@ import com.jiayue.ssi.annotation.PreventReplay;
 import com.jiayue.ssi.backenum.AuditType;
 import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.constant.CustomException;
+import com.jiayue.ssi.entity.ElectricField;
 import com.jiayue.ssi.entity.SysPolicy;
 import com.jiayue.ssi.service.SysPolicyService;
 import com.jiayue.ssi.util.IdUtils;
@@ -140,11 +141,16 @@ public class SysPolicyController {
             } else if (!NumberUtil.isInteger(sysPolicy.getExcLevelSameUser())) {
                 return ResponseVO.fail("同一用户多点登录异常级别不是整型!");
             }
+
+            SysPolicy oldSysPolicy = sysPolicyService.getById(sysPolicy.getId());
+            if (sysPolicy.getVersion().intValue() != oldSysPolicy.getVersion().intValue()){
+                return ResponseVO.fail("此数据被操作过,自动刷新列表后请重试操作!");
+            }
+
             boolean bo = sysPolicyService.saveOrUpdate(sysPolicy);
             if (bo) {
                 return ResponseVO.success("策略配置保存成功");
             } else {
-                log.error("添加用户信息失败");
                 return ResponseVO.fail("策略配置保存失败");
             }
         } catch (Exception e) {

+ 30 - 14
backend/src/main/java/com/jiayue/ssi/controller/SysUserController.java

@@ -594,7 +594,7 @@ public class SysUserController {
             wrapper.eq("entity_name", "SysUser");
             List<SysApprove> list = sysApproveService.list(wrapper);
             if (list.size() > 0) {
-                return ResponseVO.fail("此记录存在未审批的操作,不能进行修改!");
+                return ResponseVO.fail("此记录存在未审批的操作,不能进行解锁!");
             }
             // id获取用户
             SysUser sysUser = sysUserService.getById(id);
@@ -605,13 +605,37 @@ public class SysUserController {
                 return ResponseVO.fail("只能对【锁定】状态的进行解锁!");
             }
 
-            boolean bo = sysUserService.relockUserById(Integer.parseInt(id));
+            // 根据id改变状态为注销,提交到审核
+            SysApprove sysApprove = new SysApprove();
+            sysApprove.setUsername(sysUser.getUsername());
+            sysApprove.setNickname(sysUser.getNickname());
+            sysApprove.setIdcard(sysUser.getIdcard());
+            sysApprove.setMailbox(sysUser.getMailbox());
+            sysApprove.setPhonenumber(sysUser.getPhonenumber());
+            sysApprove.setUsertype(sysUser.getUsertype());
+            sysApprove.setStatus(sysUser.getStatus());
+            sysApprove.setMasterId(String.valueOf(sysUser.getId()));
+            sysApprove.setParameterContent(JSONUtil.parse(sysUser).toString());
+            // 用户新增插入审批表
+            sysApprove.setModuleName(ApproveConstants.MODULE_NAME_USER);
+            sysApprove.setOperation(String.valueOf(ApproveOperaterEnum.RELOCK.getCode()));
+            sysApprove.setApproveStatus(String.valueOf(ApproveStatusEnum.DSP.getCode()));
+            sysApprove.setEntityName("SysUser");
+            boolean bo = sysUserService.addUserByApprove(Long.parseLong(id),sysApprove);
             if (bo) {
-                return ResponseVO.success("解锁成功");
+                return ResponseVO.success("解锁用户成功,等待审核管理员进行审核!");
             } else {
-                log.error("解锁失败");
-                return ResponseVO.fail("解锁失败");
+                log.error("解锁用户失败");
+                return ResponseVO.fail("解锁用户失败");
             }
+
+//            boolean bo = sysUserService.relockUserById(Integer.parseInt(id));
+//            if (bo) {
+//                return ResponseVO.success("解锁成功");
+//            } else {
+//                log.error("解锁失败");
+//                return ResponseVO.fail("解锁失败");
+//            }
         } catch (Exception e) {
             throw new CustomException("解锁异常", e);
         }
@@ -824,19 +848,11 @@ public class SysUserController {
     }
 
     /**
-     * 建立心跳
+     * 验证心跳是否存活
      */
     @GetMapping(value = "/establishHeart")
     public ResponseVO establishHeart(HttpServletRequest request, HttpServletResponse response) throws CustomException {
         try {
-            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-            SysUser sysUser = (SysUser)authentication.getPrincipal();
-            ActiveUserDto activeUserDto = new ActiveUserDto();
-            activeUserDto.setAuthentication(authentication);
-            activeUserDto.setHttpServletRequest(request);
-            CacheConstants.ACTIVE_USER_MAP.put(sysUser.getUsername(),activeUserDto);
-            LocalCache.set(CacheConstants.HEART_KEY+sysUser.getUsername(),"在线",8000);
-            System.out.println("存心跳:"+DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
             return ResponseVO.success();
         } catch (Exception e) {
             throw new CustomException("建立心跳异常", e);

+ 3 - 1
backend/src/main/java/com/jiayue/ssi/dto/ActiveUserDto.java

@@ -1,5 +1,6 @@
 package com.jiayue.ssi.dto;
 
+import com.jiayue.ssi.entity.SysUser;
 import lombok.Data;
 import org.springframework.security.core.Authentication;
 
@@ -14,5 +15,6 @@ import javax.servlet.http.HttpServletRequest;
 @Data
 public class ActiveUserDto {
     private HttpServletRequest httpServletRequest;
-    private Authentication authentication;
+    private SysUser sysUser;
+    private Long loginTime;
 }

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/entity/ElectricField.java

@@ -4,6 +4,7 @@ package com.jiayue.ssi.entity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.Version;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -55,4 +56,7 @@ public class ElectricField extends BaseEntity {
      * 场站位置
      */
     private String location;
+    //乐观锁
+    @Version
+    private Integer version;
 }

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/entity/SysApprove.java

@@ -87,4 +87,8 @@ public class SysApprove extends BaseEntity{
     @TableField(value = "exp_date",fill = FieldFill.INSERT_UPDATE)
     @JsonFormat(pattern = "yyyy-MM-dd",timezone="GMT+8")
     private Date expDate;
+
+    //乐观锁
+    @Version
+    private Integer version;
 }

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/entity/SysBlacklist.java

@@ -2,6 +2,7 @@ package com.jiayue.ssi.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.Version;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
@@ -33,4 +34,7 @@ public class SysBlacklist {
     private Date ipTime;
     /** 添加者 */
     private String addBy;
+    //乐观锁
+    @Version
+    private Integer version;
 }

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/entity/SysParameter.java

@@ -3,6 +3,7 @@ package com.jiayue.ssi.entity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.Version;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -34,4 +35,7 @@ public class SysParameter extends BaseEntity {
      * 参数描述
      */
     private String sysDescribe;
+    //乐观锁
+    @Version
+    private Integer version;
 }

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/entity/SysPolicy.java

@@ -86,4 +86,8 @@ public class SysPolicy extends BaseEntity{
      */
     @TableField(value = "auditable_event",fill = FieldFill.INSERT_UPDATE)
     private String auditableEvent;
+
+    //乐观锁
+    @Version
+    private Integer version;
 }

+ 12 - 12
backend/src/main/java/com/jiayue/ssi/filter/JwtAuthenticationTokenFilter.java

@@ -77,19 +77,23 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                     Claims claims = jwtTokenUtil.getClaimsFromToken(token);
                     if (claims == null) {
                         // 无效token
-                        ResponseInfo.doResponse(response, "令牌无效,请重新登录!", 406);
+                        ResponseInfo.doResponse(response, "令牌无效,请重新登录1!", 406);
                         return;
                     }
 
                     username = claims.getSubject();
                     if (username != null) {
                         if (CacheConstants.LOGIN_TOKEN_MAP.get(username) == null) {
-                            ResponseInfo.doResponse(response, "令牌无效,请重新登录!", 406);
+                            String killIp = IPUtils.getIpAddr(request);
+                            CacheConstants.IP_USER_MAP.remove(killIp);
+                            ResponseInfo.doResponse(response, "令牌无效,请重新登录2!", 406);
                             return;
                         } else {
                             String cacheToken = CacheConstants.LOGIN_TOKEN_MAP.get(username);
                             //内存token和当前token一致  说明是当前登陆用户访问
                             if (!token.equals(cacheToken)) {
+                                String killIp = IPUtils.getIpAddr(request);
+                                CacheConstants.IP_USER_MAP.remove(killIp);
                                 ResponseInfo.doResponse(response, "账号已在另一台机器登录,请重新登录!", 406);
                                 return;
                             }
@@ -123,7 +127,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                             }
                         }
                         if ("1".equals(user.getStatus()) && user.getLockTime() == 0) {
-                            ResponseInfo.doResponse(response, "账号已被锁定,请联系管理员!", 406);
+                            ResponseInfo.doResponse(response, "账号长期未使用被系统锁定,请联系管理员解锁!", 406);
                             return;
                         }
                         if (jwtTokenUtil.validateToken(token, userDetails)) {
@@ -164,7 +168,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                         // 将token存储内存中,便于重复登录比对
                         CacheConstants.LOGIN_TOKEN_MAP.remove(claims.getSubject());
                         LoginConstants.sessionMap.remove(claims.getSubject());
-                        ResponseInfo.doResponse(response, "令牌无效,请重新登录!", 406);
+                        ResponseInfo.doResponse(response, "令牌无效,请重新登录3!", 406);
                         return;
                     }
                 }
@@ -200,9 +204,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                         // 保存数据库
                         OperateLogFactory.recordOper(operLog);
 
-                        LocalCache.remove(CacheConstants.REACTIVE_KEY + token);
-
-                        SysUser sysUser = (SysUser) SecurityContextHolder.getContext().getAuthentication();
+                        SysUser sysUser = (SysUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                         // 设置用户离线状态
                         sysUser.setOnlineStatus("1");
                         sysUserService.updateUser(sysUser);
@@ -216,14 +218,12 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                                 countMap.remove();
                             }
                         }
-
+                        LocalCache.remove(CacheConstants.REACTIVE_KEY + token);
                         // 超出配置设定值则退出
                         ResponseInfo.doResponse(response, "超出非活动时长退出!", 406);
                         return;
                     } else {
-                        if (!request.getRequestURI().equals("/sysUserController/establishHeart")) {
-                            LocalCache.set(CacheConstants.REACTIVE_KEY + token, System.currentTimeMillis(), 1000 * 60 * 60);
-                        }
+                        LocalCache.set(CacheConstants.REACTIVE_KEY + token, System.currentTimeMillis(), 1000 * 60 * 60);
                     }
                 }
             } else {
@@ -271,7 +271,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
                         }
 
                         if ("1".equals(user.getStatus()) && user.getLockTime() == 0) {
-                            ResponseInfo.doResponse(response, "账号已被锁定,请联系管理员!", 406);
+                            ResponseInfo.doResponse(response, "账号长期未使用被系统锁定,请联系管理员解锁!", 406);
                             return;
                         }
 

+ 38 - 6
backend/src/main/java/com/jiayue/ssi/filter/VerifySmFilter.java

@@ -5,15 +5,20 @@ import cn.hutool.json.JSONUtil;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.constant.Constants;
 import com.jiayue.ssi.constant.SecretKeyConstants;
+import com.jiayue.ssi.dto.ActiveUserDto;
+import com.jiayue.ssi.entity.SysUser;
 import com.jiayue.ssi.factory.LoginFactory;
+import com.jiayue.ssi.service.SysLogininforService;
+import com.jiayue.ssi.service.impl.UserServiceImpl;
 import com.jiayue.ssi.servlet.ParameterRequestWrapper;
-import com.jiayue.ssi.util.IPUtils;
-import com.jiayue.ssi.util.ResponseInfo;
-import com.jiayue.ssi.util.SM2CryptUtils;
+import com.jiayue.ssi.util.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.core.annotation.Order;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import javax.servlet.FilterChain;
@@ -54,12 +59,38 @@ public class VerifySmFilter extends OncePerRequestFilter {
             if (!("POST".equalsIgnoreCase(request.getMethod())
                     && defaultFilterProcessUrl.equals(request.getServletPath())) && !("/getVerifyCode".equals(request.getServletPath()))
                     && !("/sysParameterController/getUseSendMail".equals(request.getServletPath())) && !("/getMailCode".equals(request.getServletPath()))) {
-                // 验证token
-                String tokenStr = request.getHeader("Authorization");
+                String tokenStr = "";
+                String tokenSign = "";
+                if ("/websocket/testsocket".equals(request.getServletPath())){
+                    // 心跳
+                    tokenStr = request.getParameter("accessToken");
+                    tokenSign = request.getParameter("tokenSign");
+                }
+                else{
+                    // 验证token
+                    tokenStr = request.getHeader("Authorization");
+                    tokenSign = request.getHeader("JySign");
+                }
+
                 if (StringUtils.isNotEmpty(tokenStr)) {
                     // 解密token
                     String decryptTokenStr = SM2CryptUtils.decrypt(tokenStr, SecretKeyConstants.SERVER_PRIVATE_KEY);
-                    String tokenSign = request.getHeader("JySign");
+
+                    if ("/websocket/testsocket".equals(request.getServletPath())){
+                        ActiveUserDto activeUserDto = new ActiveUserDto();
+                        String username = new JwtTokenUtil().getUsernameFromToken(decryptTokenStr);
+                        UserDetails userDetails = SpringUtils.getBean(UserServiceImpl.class).loadUserByUsername(username);
+                        SysUser sysUser = (SysUser)userDetails;
+                        activeUserDto.setSysUser(sysUser);
+                        activeUserDto.setHttpServletRequest(request);
+                        request.getSession().setAttribute("ActiveUserDto",activeUserDto);
+                        // 心跳
+                        tokenStr = request.getParameter("accessToken");
+                        tokenSign = request.getParameter("tokenSign");
+                    }
+
+
+
                     // 验证签名
                     boolean verifySign =
                             SM2CryptUtils.verifySign(SecretKeyConstants.CLIENT_PUBLIC_KEY, decryptTokenStr, tokenSign);
@@ -200,6 +231,7 @@ public class VerifySmFilter extends OncePerRequestFilter {
             ParameterRequestWrapper pr = new ParameterRequestWrapper(initWrapper, stringToMap, decryptStr);
             filterChain.doFilter(pr, response);
         } catch (Exception e) {
+            e.printStackTrace();
 //            log.error(IPUtils.getIpAddr(request) + "访问系统失败", e);
             ResponseInfo.doResponse(response, "访问失败,联系管理员!", 401);
             return;

+ 43 - 9
backend/src/main/java/com/jiayue/ssi/handler/CustomAuthenticationSuccessHandler.java

@@ -3,7 +3,6 @@ package com.jiayue.ssi.handler;
 import java.io.IOException;
 import java.util.Date;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 
 import javax.servlet.ServletException;
@@ -16,11 +15,13 @@ import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.constant.Constants;
 import com.jiayue.ssi.constant.LoginConstants;
 import com.jiayue.ssi.constant.SecretKeyConstants;
+import com.jiayue.ssi.dto.ActiveUserDto;
 import com.jiayue.ssi.dto.UserVisitInfoDto;
-import com.jiayue.ssi.entity.SysAlarm;
+import com.jiayue.ssi.entity.SysLogininfor;
 import com.jiayue.ssi.entity.SysPolicy;
 import com.jiayue.ssi.factory.LoginFactory;
 import com.jiayue.ssi.service.SysAlarmService;
+import com.jiayue.ssi.service.SysLogininforService;
 import com.jiayue.ssi.service.SysPolicyService;
 import com.jiayue.ssi.service.SysUserService;
 import com.jiayue.ssi.util.*;
@@ -100,10 +101,12 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
         if (CacheConstants.LOGIN_TOKEN_MAP.get(username)!=null){
             // 将之前用户登录过的ip删除掉
             Iterator<Map.Entry<String, String>> countMap = CacheConstants.IP_USER_MAP.entrySet().iterator();
+            String kickedIp = "";
             while (countMap.hasNext()) {
                 Map.Entry<String, String> entry = countMap.next();
                 String cacheusername = entry.getValue();
                 if (cacheusername.equals(username)){
+                    kickedIp = entry.getKey();
                     countMap.remove();
                 }
             }
@@ -137,20 +140,51 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
 //                }
             }
             else if ("1".equals(noticeWay)){
-                // 告警
-                SysAlarm sysAlarm = new SysAlarm();
-                sysAlarm.setAlarmContent("账号【"+sysUser.getUsername()+"】多点登录");
-                sysAlarm.setReadSign("0");
-                // 低于策略设定值则告警
-                sysAlarmService.save(sysAlarm);
+                log.info("发送邮箱通知系统管理员后台输出======> "+"账号【"+sysUser.getUsername()+"】多点登录","本次登录时间:"+DateUtils.getTime()+",登录IP:"+IPUtils.getIpAddr(request));
+            }
+
+            // 封装被下线的对象退出信息
+            SysLogininfor logininfor = new SysLogininfor();
+            logininfor.setUserName(sysUser.getUsername());
+            logininfor.setIpaddr(kickedIp);
+            String address = AddressUtils.getRealAddressByIP(kickedIp);
+            logininfor.setLoginLocation(address);
+            HttpServletRequest kickedRequest = CacheConstants.KICKED_USER_MAP.get(sysUser.getUsername());
+            // 获取被踢用户的request
+            final UserAgent kickedUserAgent = UserAgent.parseUserAgentString(kickedRequest.getHeader("User-Agent"));
+            logininfor.setBrowser(kickedUserAgent.getBrowser().getName());
+            logininfor.setOs(kickedUserAgent.getOperatingSystem().getName());
+            logininfor.setMsg("退出成功");
+            logininfor.setLoginTime(new Date());
+            logininfor.setCreateBy(sysUser.getUsername());
+            // 日志状态
+            logininfor.setStatus(Constants.SUCCESS);
+            // 插入数据
+            SpringUtils.getBean(SysLogininforService.class).insertLogininfor(logininfor);
+
+            Iterator<Map.Entry<String, String>> socketSessionMap = CacheConstants.SESSIONID_USER_MAP.entrySet().iterator();
+            while (socketSessionMap.hasNext()) {
+                Map.Entry<String, String> entry2 = socketSessionMap.next();
+                String cacheusername = entry2.getValue();
+                if (cacheusername.equals(sysUser.getUsername())) {
+                    socketSessionMap.remove();
+                }
             }
+            System.out.println(CacheConstants.SESSIONID_USER_MAP.toString());
+//            System.out.println("被踢用户的sessionid:"+kickedRequest.getSession().getId());
+//            CacheConstants.SESSIONID_USER_MAP.put(kickedRequest.getSession().getId(),sysUser.getUsername());
         }
         CacheConstants.LOGIN_TOKEN_MAP.put(username,token);
         // 登录成功后将用户本次操作时间存入缓存,为了判断非活动退出时应用,60分钟自动失效
         LocalCache.set(CacheConstants.REACTIVE_KEY + token,vtime,1000*60*60);
         // 将登录成功的用户ip加入缓存中
         CacheConstants.IP_USER_MAP.put(ip,username);
-
+        ActiveUserDto activeUserDto = new ActiveUserDto();
+        activeUserDto.setSysUser(sysUser);
+        activeUserDto.setHttpServletRequest(request);
+        activeUserDto.setLoginTime(System.currentTimeMillis());
+        CacheConstants.ACTIVE_USER_MAP.put(username,activeUserDto);
+        CacheConstants.KICKED_USER_MAP.put(username,request);
         String obj = JSONUtil.toJsonStr(ResponseVO.success(token));
         // token加密处理
         String encrypt = SM2CryptUtils.encrypt(obj, SecretKeyConstants.CLIENT_PUBLIC_KEY);

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/handler/CustomLogoutSuccessHandler.java

@@ -52,7 +52,7 @@ public class CustomLogoutSuccessHandler implements org.springframework.security.
 //        CacheConstants.LOGIN_TOKEN_MAP.remove(sysUser.getUsername());
 //        LoginConstants.sessionMap.remove(sysUser.getUsername());
 
-        CacheConstants.IP_USER_MAP.remove(IPUtils.getIpAddr(request));
+//        CacheConstants.IP_USER_MAP.remove(IPUtils.getIpAddr(request));
 
         String obj = JSONUtil.toJsonStr(ResponseVO.success("退出成功"));
         // token加密处理

+ 12 - 5
backend/src/main/java/com/jiayue/ssi/job/AutoScanAccount.java

@@ -1,8 +1,11 @@
 package com.jiayue.ssi.job;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.jiayue.ssi.entity.SysPolicy;
 import com.jiayue.ssi.entity.SysUser;
+import com.jiayue.ssi.mapper.SysUserMapper;
 import com.jiayue.ssi.service.SysPolicyService;
 import com.jiayue.ssi.service.SysUserService;
 import lombok.extern.slf4j.Slf4j;
@@ -28,11 +31,13 @@ public class AutoScanAccount {
     SysUserService sysUserService;
     @Autowired
     SysPolicyService sysPolicyService;
+    @Autowired
+    SysUserMapper sysUserMapper;
 
     /**
      * 每10分钟执行一次扫描
      */
-    @Scheduled(cron = "0 0/10 * * * ?")
+    @Scheduled(cron = "0 0/1 * * * ?")
     public void scanNotUsedAccount() throws Exception{
         SysPolicy sysPolicy = sysPolicyService.getOne(new QueryWrapper<>());
         int autoScanAccount = sysPolicy.getScanAccount();
@@ -51,10 +56,12 @@ public class AutoScanAccount {
                 calendar.add(Calendar.MONTH, autoScanAccount);
                 if (calendar.getTimeInMillis()<System.currentTimeMillis()){
                     // 上次登录后N月没有使用,锁定账号
-//                    sysUser.setLockTime(System.currentTimeMillis());
-                    sysUser.setStatus("1");
-                    Boolean bo = sysUserService.updateUser(sysUser);
-                    if (!bo){
+                    // 设置用户离线状态
+                    LambdaUpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<SysUser>().lambda();
+                    updateWrapper.eq(SysUser::getId, sysUser.getId()).set(SysUser::getStatus, "1");
+                    int i = sysUserMapper.update(new SysUser(), updateWrapper);
+
+                    if (i<=0){
                         log.info(sysUser.getUsername()+",超过"+autoScanAccount+"个月未使用,锁定账号失败");
                     }
                     else{

+ 12 - 7
backend/src/main/java/com/jiayue/ssi/job/AutoScanHeartUser.java

@@ -1,11 +1,14 @@
 package com.jiayue.ssi.job;
 
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.constant.Constants;
 import com.jiayue.ssi.constant.LoginConstants;
 import com.jiayue.ssi.dto.ActiveUserDto;
 import com.jiayue.ssi.entity.SysLogininfor;
 import com.jiayue.ssi.entity.SysUser;
+import com.jiayue.ssi.mapper.SysUserMapper;
 import com.jiayue.ssi.service.SysLogininforService;
 import com.jiayue.ssi.service.SysUserService;
 import com.jiayue.ssi.util.*;
@@ -34,7 +37,8 @@ import java.util.Map;
 public class AutoScanHeartUser {
     @Autowired
     SysUserService sysUserService;
-
+    @Autowired
+    SysUserMapper sysUserMapper;
     /**
      * 每2秒扫描一次
      */
@@ -45,13 +49,14 @@ public class AutoScanHeartUser {
             Map.Entry<String,ActiveUserDto> entry = activeUserIterator.next();
             String username = entry.getKey();
             ActiveUserDto activeUserDto = entry.getValue();
-            SysUser sysUser = (SysUser)activeUserDto.getAuthentication().getPrincipal();
+            SysUser sysUser = activeUserDto.getSysUser();
 
-            if (LocalCache.get(CacheConstants.HEART_KEY+username)==null){
-                System.out.println("清理离线:"+ DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
+            if (LocalCache.get(CacheConstants.HEART_KEY+username)==null && (System.currentTimeMillis()- activeUserDto.getLoginTime()>5000)){
                 // 设置用户离线状态
-                sysUser.setOnlineStatus("1");
-                sysUserService.updateUser(sysUser);
+                LambdaUpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<SysUser>().lambda();
+                updateWrapper.eq(SysUser::getId, sysUser.getId()).set(SysUser::getOnlineStatus, "1");
+                sysUserMapper.update(new SysUser(), updateWrapper);
+
                 // 记录用户退出日志
                 HttpServletRequest request = activeUserDto.getHttpServletRequest();
                 final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
@@ -79,6 +84,7 @@ public class AutoScanHeartUser {
                 // 将token存储内存中,便于重复登录比对
                 CacheConstants.LOGIN_TOKEN_MAP.remove(sysUser.getUsername());
                 LoginConstants.sessionMap.remove(sysUser.getUsername());
+                CacheConstants.KICKED_USER_MAP.remove(sysUser.getUsername());
                 Iterator<Map.Entry<String, String>> ipUserMap = CacheConstants.IP_USER_MAP.entrySet().iterator();
                 while (ipUserMap.hasNext()) {
                     Map.Entry<String, String> entry1 = ipUserMap.next();
@@ -89,7 +95,6 @@ public class AutoScanHeartUser {
                 }
                 activeUserIterator.remove();
             }
-            System.out.println("activeUserMap数量:"+CacheConstants.ACTIVE_USER_MAP.size());
         }
     }
 }

+ 131 - 0
backend/src/main/java/com/jiayue/ssi/service/WebSocketServer.java

@@ -0,0 +1,131 @@
+package com.jiayue.ssi.service;
+
+import com.alibaba.fastjson2.JSON;
+import com.jiayue.ssi.config.WebSocketConfigurator;
+import com.jiayue.ssi.constant.CacheConstants;
+import com.jiayue.ssi.dto.ActiveUserDto;
+import com.jiayue.ssi.entity.SysUser;
+import com.jiayue.ssi.util.LocalCache;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.stereotype.Component;
+import javax.websocket.*;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+*
+*
+* @author xsl
+* @since 2023/08/21
+*/
+@Slf4j
+@Component
+@ServerEndpoint(value = "/websocket/testsocket",configurator = WebSocketConfigurator.class)
+public class WebSocketServer {
+    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+    private static int onlineCount = 0;
+
+    //与某个客户端的连接会话,需要通过它来给客户端发送数据
+    private Session session;
+
+    /**
+     * 连接建立成功调用的方法*/
+    @OnOpen
+    public void onOpen(Session session) {
+        this.session = session;
+        try {
+            Map<String, Object> userProperties = session.getUserProperties();
+            ActiveUserDto activeUserDto = (ActiveUserDto) userProperties.get("ActiveUserDto");
+            SysUser sysUser = activeUserDto.getSysUser();
+            CacheConstants.SESSIONID_USER_MAP.put(session.getId(),sysUser.getUsername());
+            sendMessage("连接成功");
+        } catch (Exception e) {
+            log.error("websocket IO异常");
+        }
+    }
+
+    /**
+     * 连接关闭调用的方法
+     */
+    @OnClose
+    public void onClose(Session session) {
+        try {
+            session.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 收到客户端消息后调用的方法
+     *
+     * @param message 客户端发送过来的消息*/
+    @OnMessage
+    public void onMessage(String message, Session session) throws Exception{
+        Map<String, Object> userProperties = session.getUserProperties();
+        ActiveUserDto activeUserDto = (ActiveUserDto) userProperties.get("ActiveUserDto");
+        SysUser sysUser = activeUserDto.getSysUser();
+        // 如果某个用户被踢掉,则不接收此用户的sessionid的心跳
+        if (CacheConstants.SESSIONID_USER_MAP.get(session.getId())!=null){
+            LocalCache.set(CacheConstants.HEART_KEY+sysUser.getUsername(),"在线",7000);
+            this.session.getBasicRemote().sendText((JSON.toJSONString(session.getId())));
+        }
+        else{
+            onClose(session);
+        }
+    }
+
+    /**
+     *
+     * @param session
+     * @param error
+     */
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.error("发生错误");
+        error.printStackTrace();
+    }
+    /**
+     * 实现服务器主动推送
+     */
+    public void sendMessage(Object obj)  {
+        try {
+            synchronized (this.session) {
+                this.session.getBasicRemote().sendText((JSON.toJSONString(obj)));
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+//    /**
+//     * 群发自定义消息
+//     * */
+//    public static void sendInfo(Object obj) {
+//        for (WebSocketServer item : webSocketSet) {
+//            try {
+//                item.sendMessage(obj);
+//            } catch (Exception e) {
+//                continue;
+//            }
+//        }
+//    }
+
+    public static synchronized int getOnlineCount() {
+        return onlineCount;
+    }
+
+    public static synchronized void addOnlineCount() {
+        WebSocketServer.onlineCount++;
+    }
+
+    public static synchronized void subOnlineCount() {
+        WebSocketServer.onlineCount--;
+    }
+
+
+}

+ 15 - 0
backend/src/main/java/com/jiayue/ssi/service/impl/SysApproveServiceImpl.java

@@ -16,6 +16,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Date;
+
 /**
 * 审核实现类
 *
@@ -97,6 +99,19 @@ public class SysApproveServiceImpl extends ServiceImpl<SysApproveMapper, SysAppr
                 sysUser.setSignstr("0");
                 sysUserMapper.updateById(sysUser);
             }
+            else if (newsysApprove.getOperation().equals(String.valueOf(ApproveOperaterEnum.RELOCK.getCode()))){
+                // 解锁操作
+                SysUser sysUser = new SysUser();
+                sysUser.setId(Long.parseLong(newsysApprove.getMasterId()));
+                if ("0".equals(newsysApprove.getApproveResult())){
+                    // 审核通过,修改姓名、邮箱、手机号、有效日期
+                    sysUser.setStatus("0");
+                    sysUser.setLockTime(0L);
+                    sysUser.setLoginDate(new Date());
+                }
+                sysUser.setSignstr("0");
+                sysUserMapper.updateById(sysUser);
+            }
         }
     }
 }

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

@@ -1,5 +1,5 @@
 server:
-  port: 8888
+  port: 443
   ssl:
     key-store: classpath:9564748_api.jiayuepowertech.com.pfx
     key-store-type: PKCS12

+ 98 - 18
ui/src/layout/components/AppMain.vue

@@ -24,26 +24,106 @@ export default {
     }
   },
 
-  data() {
-    return {
-      timer: null,
-      intervalId:null
-    }
-  },
-  created() {
-    this.intervalId = setInterval(this.myFunction, 2000); // 设置初始定时器
-  },
-  destroyed() {
-    clearInterval(this.intervalId)
+  // data() {
+  //   // return {
+  //   //   timer: null,
+  //   //   intervalId:null
+  //   // }
+  //
+  //   return{
+  //     websock: null, //建立的连接
+  //     lockReconnect: false, //是否真正建立连接
+  //     timeout: 1 * 1000, //20秒一次心跳
+  //     timeoutObj: null, //心跳心跳倒计时
+  //     serverTimeoutObj: null, //心跳倒计时
+  //     timeoutnum: null //断开 重连倒计时
+  //   }
+  //
+  //
+  // },
+  // created() {
+  //   // //页面刚进入时开启长连接
+  //   this.initWebSocket();
+  //
+  // },
+  // destroyed() {
+  //   //页面销毁时关闭长连接
+  //   clearTimeout(this.timeoutObj);
+  //   this.websocketclose();
+  //
+  // },
+
+  mounted() {
+
   },
   methods: {
-    myFunction() {
-      // 在这里执行你的代码逻辑
-      this.$axios.get('/sysUserController/establishHeart').then((res) => {
-      })
-      //clearInterval(intervalId); // 清除之前的定时器
-      //this.intervalId = setInterval(myFunction, 1000); // 设置新的定时器
-    }
+    // initWebSocket() {
+    //   //建立连接
+    //   //初始化weosocket
+    //   let tokenStr = localStorage.getItem("jy")
+    //   let visitPort
+    //   if (process.env.NODE_ENV=='development'){
+    //     visitPort = '8888'
+    //   }
+    //   else {
+    //     visitPort = '443'
+    //   }
+    //   let wsuri = 'wss:'+location.host.split(':')[0]+ ':'+visitPort+'/websocket/testsocket?accessToken='+tokenStr.split("&")[0]+'&'+'tokenSign='+tokenStr.split("&")[1];
+    //   //建立连接
+    //   this.websock = new WebSocket(wsuri);
+    //   //连接成功
+    //   this.websock.onopen = this.websocketonopen;
+    //   //连接错误
+    //   this.websock.onerror = this.websocketonerror;
+    //   //接收信息
+    //   this.websock.onmessage = this.websocketonmessage;
+    //   //连接关闭
+    //   this.websock.onclose = this.websocketclose;
+    // },
+    // start() {
+    //   //开启心跳
+    //   var self = this;
+    //   self.timeoutObj && clearTimeout(self.timeoutObj);
+    //   // self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
+    //   self.timeoutObj = setTimeout(function() {
+    //     self.websock.send("heartbeat");
+    //   }, self.timeout);
+    // },
+    // websocketonopen() {
+    //   //连接成功事件
+    //   this.websocketsend('发送数据');
+    //   //提示成功
+    //   console.log("连接成功", 3);
+    //   //开启心跳
+    //   this.start();
+    // },
+    // websocketonerror(e) {
+    //   //连接失败事件
+    //   //错误
+    //   console.log("WebSocket连接发生错误");
+    //   //重连
+    //   // this.reconnect();
+    // },
+    // websocketclose(e) {
+    //   //连接关闭事件
+    //   //提示关闭
+    //   console.log("连接已关闭");
+    //   //重连
+    //   // this.reconnect();
+    // },
+    // websocketonmessage(event) {
+    //   //接收服务器推送的信息
+    //   //打印收到服务器的内容
+    //   console.log("收到服务器信息",event.data);
+    //   let data=event.data
+    //   //收到服务器信息,心跳重置
+    //   this.start()
+    // },
+    // websocketsend(msg) {
+    //   //向服务器发送信息
+    //   this.websock.send(msg);
+    // }
+
   }
 
 }

+ 11 - 7
ui/src/layout/components/Navbar.vue

@@ -116,7 +116,7 @@ import {debounce} from "lodash";
 export default {
   data() {
     return {
-      interval: null,
+      intervalId: null,
       isMessage: false,
       alarmIconShow: false,
       title: '告警消息',
@@ -139,11 +139,10 @@ export default {
   },
   created() {
     this.getCurrentUser()
+    // this.intervalId = setInterval(this.myFunction, 3000); // 设置初始定时器
   },
-  beforeDestroy() {
-    if (this.interval) {
-      clearInterval(this.interval)
-    }
+  destroyed() {
+    // clearInterval(this.intervalId)
   },
   computed: {
     ...mapGetters([
@@ -169,6 +168,11 @@ export default {
     }
   },
   methods: {
+    // myFunction() {
+    //   // 在这里执行你的代码逻辑
+    //   this.$axios.get('/sysUserController/establishHeart').then((res) => {
+    //   })
+    // },
     async getCurrentUser() {
       var user;
       let sysTime1
@@ -250,10 +254,10 @@ export default {
       await this.$axios.get('/sysUserController/getUserRole', {params: searchParams}).then((res) => {
         let userRole = res.data
         if (userRole.roleId == 1) {
-          this.getAlarmData()
+          // this.getAlarmData()
           // 系统管理员角色显示告警图标
           this.alarmIconShow = true
-          this.getAlarmData()
+          // this.getAlarmData()
 
           // 取消定时刷新告警,无法实现用户非活动状态超时登出
           // // 增加定时每分钟获取告警消息

+ 99 - 0
ui/src/layout/components/Sidebar/index.vue

@@ -52,6 +52,105 @@ export default {
         isCollapse() {
             return !this.sidebar.opened;
         }
+    },
+  data() {
+    // return {
+    //   timer: null,
+    //   intervalId:null
+    // }
+
+    return{
+      websock: null, //建立的连接
+      lockReconnect: false, //是否真正建立连接
+      timeout: 1 * 1000, //20秒一次心跳
+      timeoutObj: null, //心跳心跳倒计时
+      serverTimeoutObj: null, //心跳倒计时
+      timeoutnum: null, //断开 重连倒计时
+      sessionid:null
     }
+
+
+  },
+  created() {
+    // //页面刚进入时开启长连接
+    this.initWebSocket();
+
+  },
+  destroyed() {
+    //页面销毁时关闭长连接
+    clearTimeout(this.timeoutObj);
+    this.websocketclose();
+
+  },
+  methods:{
+    initWebSocket() {
+      //建立连接
+      //初始化weosocket
+      let tokenStr = localStorage.getItem("jy")
+      let visitPort
+      if (process.env.NODE_ENV=='development'){
+        visitPort = '443'
+      }
+      else {
+        visitPort = '443'
+      }
+      let wsuri = 'wss:'+location.host.split(':')[0]+ ':'+visitPort+'/websocket/testsocket?accessToken='+tokenStr.split("&")[0]+'&'+'tokenSign='+tokenStr.split("&")[1];
+      //建立连接
+      this.websock = new WebSocket(wsuri);
+      //连接成功
+      this.websock.onopen = this.websocketonopen;
+      //连接错误
+      this.websock.onerror = this.websocketonerror;
+      //接收信息
+      this.websock.onmessage = this.websocketonmessage;
+      //连接关闭
+      this.websock.onclose = this.websocketclose;
+    },
+    start() {
+      //开启心跳
+      var self = this;
+      self.timeoutObj && clearTimeout(self.timeoutObj);
+      // self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
+      self.timeoutObj = setTimeout(function() {
+        self.websock.send("heartbeat");
+      }, self.timeout);
+    },
+    websocketonopen() {
+      //连接成功事件
+      this.websocketsend(this.sessionid);
+      //提示成功
+      // console.log("连接成功", 3);
+      //开启心跳
+      this.start();
+    },
+    websocketonerror(e) {
+      //连接失败事件
+      //错误
+      console.log("WebSocket连接发生错误");
+      //重连
+      // this.reconnect();
+    },
+    websocketclose(e) {
+      //连接关闭事件
+      //提示关闭
+      // console.log("连接已关闭");
+      //重连
+      // this.reconnect();
+    },
+    websocketonmessage(event) {
+      //接收服务器推送的信息
+      //打印收到服务器的内容
+      // console.log("收到服务器信息",event.data);
+      let data=event.data
+      this.sessionid = data
+      //收到服务器信息,心跳重置
+      this.start()
+    },
+    websocketsend(msg) {
+      //向服务器发送信息
+      this.websock.send(msg);
+    }
+
+  }
 };
 </script>

+ 1 - 1
ui/src/views/bizManager/electricField/index.vue

@@ -237,11 +237,11 @@ export default {
           await this.$axios.post('/electricField/', this.form).then(res => {
             if (res.code == 0) {
               this.$message.success('场站信息保存成功')
-              this.getData()
             }
             if (res.code == 1) {
               this.$message.error(res.data)
             }
+            this.getData()
             this.tableLoading = false
           }).catch((error) => {
             this.tableLoading = false

+ 5 - 3
ui/src/views/examineManager/sysApprove/index.vue

@@ -57,9 +57,9 @@
             <vxe-table-column field="moduleName" title="模块名称"/>
             <vxe-table-column field="operation" title="执行操作" :formatter="operationFormat"/>
             <vxe-table-column field="approveStatus" title="审核状态" :formatter="approveStatusFormat"/>
+            <vxe-table-column field="createTime" title="提交时间"/>
+            <vxe-table-column field="parameterContent" title="内容"/>
             <vxe-table-column field="approveResult" title="审核结果" :formatter="approveResultFormat"/>
-            <vxe-table-column field="parameterContent" title="内容">
-            </vxe-table-column>
             <vxe-table-column title="操作" width="320">
               <template slot-scope="scope" v-if="scope.row.approveStatus == 0">
                 <el-button
@@ -168,9 +168,11 @@ export default {
         } else if (cellValue == "1") {
           belongTo = "修改"
         } else if (cellValue == "2") {
-          belongTo = "删除"
+          belongTo = "注销"
         } else if (cellValue == "3") {
           belongTo = "授权"
+        } else if (cellValue == "4") {
+          belongTo = "解锁"
         }
       }
       return belongTo

+ 0 - 1
ui/src/views/login/index.vue

@@ -247,7 +247,6 @@ export default {
       var searchParams = {
         murmur: this.murmur
       }
-      console.log('获取验证码')
       this.$axios.get('/getVerifyCode',
         {params: searchParams}).then((res) => {
         this.verifyuuid = res.data.uuid

+ 1 - 1
ui/src/views/sysManager/ipBlacklist/index.vue

@@ -391,13 +391,13 @@ export default {
             type: 'success',
             message: '删除成功!'
           });
-          this.getList();
         } else {
           this.$message({
             type: 'error',
             message: res.data
           });
         }
+        this.getList();
       }).catch((error) => {
         this.$message({
           type: 'error',

+ 16 - 3
ui/src/views/sysManager/sysParameter/index.vue

@@ -242,11 +242,11 @@ export default {
             await this.$axios.post('/sysParameterController/updateParameter', row).then((res) => {
               if (res.code == 0) {
                 this.$message.success('修改成功')
-                this.getList();
               }
               if (res.code == 1) {
                 this.$message.error(res.data)
               }
+              this.getList();
               this.saveLoding = false
               this.btnLonding = false
             }).catch((error) => {
@@ -295,8 +295,21 @@ export default {
             lk: lk
           }
           await this.$axios.post('/sysParameterController/deleteParameter',param).then(response => {
-            this.$XModal.message({status: 'warning', message: response.message})
-            this.$refs.xTable.remove(row)
+            if (response.code == 0) {
+              this.$message({
+                type: 'success',
+                message: '删除成功!'
+              });
+            } else {
+              this.$message({
+                type: 'error',
+                message: res.data
+              });
+            }
+            this.getList();
+            //
+            // this.$XModal.message({status: 'warning', message: response.message})
+            // this.$refs.xTable.remove(row)
           })
         }
       })

+ 32 - 4
ui/src/views/sysManager/userManager/index.vue

@@ -127,7 +127,7 @@
             <vxe-table-column title="操作" width="320">
               <template slot-scope="scope">
                 <el-button
-                  v-if="scope.row.id !== 1 && scope.row.id !== 2"
+                  v-if="scope.row.id !== 1 && scope.row.id !== 2 && scope.row.id !== 24"
                   size="mini"
                   type="text"
                   icon="el-icon-edit"
@@ -136,7 +136,7 @@
                 >初始/重置密码
                 </el-button>
                 <el-button
-                  v-if="scope.row.id !== 1 && scope.row.id !== 2"
+                  v-if="scope.row.id !== 1 && scope.row.id !== 2 && scope.row.id !== 24"
                   size="mini"
                   type="text"
                   icon="el-icon-circle-check"
@@ -435,6 +435,13 @@ export default {
         });
         return
       }
+      if (_selectData.id == 24) {
+        this.$message({
+          type: 'warning',
+          message: '内置审核管理员用户不能注销!'
+        });
+        return
+      }
       if (_selectData.status == 2) {
         this.$message({
           type: 'warning',
@@ -829,6 +836,13 @@ export default {
         });
         return
       }
+      if (_selectData.id == 24) {
+        this.$message({
+          type: 'warning',
+          message: '内置审核管理员不能删除!'
+        });
+        return
+      }
       this.$prompt('请输入密码', '鉴别操作', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
@@ -921,6 +935,13 @@ export default {
         });
         return
       }
+      if (_selectData.id == 24) {
+        this.$message({
+          type: 'warning',
+          message: '内置审核管理员不能修改!'
+        });
+        return
+      }
       if (_selectData.status == 2) {
         this.$message({
           type: 'warning',
@@ -1157,14 +1178,21 @@ export default {
       if (row.id == 1) {
         this.$message({
           type: 'warning',
-          message: '内置系统管理员不能修改!'
+          message: '内置系统管理员不能分配角色!'
         });
         return
       }
       if (row.id == 2) {
         this.$message({
           type: 'warning',
-          message: '内置审计管理员不能修改!'
+          message: '内置审计管理员不能分配角色!'
+        });
+        return
+      }
+      if (row.id == 24) {
+        this.$message({
+          type: 'warning',
+          message: '内置审核管理员不能分配角色!'
         });
         return
       }

+ 1 - 1
ui/vue.config.js

@@ -51,7 +51,7 @@ module.exports = {
       [process.env.VUE_APP_BASE_API]: {
         // target: `http://127.0.0.1:${port}/mock`,
         // target: 'http://localhost:8888',
-        target: 'https://localhost:8888',
+        target: 'https://localhost:443',
         changeOrigin: true,
         pathRewrite: {
           ['^' + process.env.VUE_APP_BASE_API]: ''