فهرست منبع

Merge branch 'fu-dev' of uskycloud/usky-modules into server-165

fuyuchuan 15 ساعت پیش
والد
کامیت
90f51d49b1
15فایلهای تغییر یافته به همراه291 افزوده شده و 20 حذف شده
  1. 1 1
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmTimeConfServiceImpl.java
  2. 6 0
      service-meeting/service-meeting-biz/pom.xml
  3. 22 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/config/WebSocketConfig.java
  4. 1 1
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/controller/web/SignOnOutRequestVO.java
  5. 3 2
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/domain/MeetingAttendee.java
  6. 2 2
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/repository/MeetingInfoRepository.java
  7. 77 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/MeetingDeviceWebSocketService.java
  8. 4 4
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/impl/MeetingDeviceServiceImpl.java
  9. 0 6
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/impl/MeetingFaceServiceImpl.java
  10. 4 2
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/impl/MeetingInfoServiceImpl.java
  11. 21 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/vo/WebSocketConnectionResponseVO.java
  12. 1 1
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/utils/JwtUtils.java
  13. 72 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketController.java
  14. 76 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketHandler.java
  15. 1 1
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmTimeConfServiceImpl.java

+ 1 - 1
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmTimeConfServiceImpl.java

@@ -66,7 +66,7 @@ public class PmTimeConfServiceImpl extends AbstractCrudService<PmTimeConfMapper,
         // 查询时间配置
         PmTimeConf timeConf = getTimeConf(tenantId);
         if (timeConf == null) {
-            throw new RuntimeException("未找到工作报提交统计告时间配置,请联系管理员");
+            throw new BusinessException("未找到工作报提交统计告时间配置,请联系管理员");
         } else if (countDate.equals(LocalDate.now()) && timeConf.getStartTime().isAfter(LocalTime.now()) || countDate.isAfter(LocalDate.now())) {
             responseVO.setSubmitOnTime(0);
             responseVO.setSubmitLate(0);

+ 6 - 0
service-meeting/service-meeting-biz/pom.xml

@@ -73,6 +73,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+        
+        <!-- WebSocket依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>com.auth0</groupId>

+ 22 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/config/WebSocketConfig.java

@@ -0,0 +1,22 @@
+package com.usky.meeting.config;
+
+import com.usky.meeting.websocket.MeetingDeviceWebSocketHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+    @Bean
+    public MeetingDeviceWebSocketHandler meetingDeviceWebSocketHandler() {
+        return new MeetingDeviceWebSocketHandler();
+    }
+}

+ 1 - 1
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/controller/web/SignOnOutRequestVO.java

@@ -20,7 +20,7 @@ public class SignOnOutRequestVO {
     private Integer mothodType;
 
     /**
-     * 签到签退方式(0.人工 1.人脸)
+     * 签到签退方式(0.人工 1.人脸 3.二维码)
      */
     private Integer signType;
 

+ 3 - 2
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/domain/MeetingAttendee.java

@@ -2,6 +2,7 @@ package com.usky.meeting.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+
 import java.time.LocalDateTime;
 import java.io.Serializable;
 
@@ -65,7 +66,7 @@ public class MeetingAttendee implements Serializable {
     private LocalDateTime signOutDate;
 
     /**
-     * 签到方式(0.人工签到 1.人脸签到)
+     * 签到方式(0.人工签到 1.人脸签到 3.二维码)
      */
     private Integer signType;
 
@@ -80,7 +81,7 @@ public class MeetingAttendee implements Serializable {
     private Integer tenantId;
 
     /**
-     * 签退方式(0.人工签退 1.人脸签退)
+     * 签退方式(0.人工签退 1.人脸签退 3.二维码)
      */
     private Integer signOutType;
 

+ 2 - 2
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/repository/MeetingInfoRepository.java

@@ -369,11 +369,11 @@ public interface MeetingInfoRepository extends JpaRepository<MeetingInfo, Long>,
 //    void autoCloseMeetingAndRoom(@Param("updateDate") String updateDate);
 
     @Query(value = "select u.`user_id` as userId,u.`nick_name` as userName,u.sex AS sex,u.phonenumber AS phone,u.dept_id as dept," +
-            " (CASE a.is_sign WHEN 0 THEN '否' WHEN 1 THEN '是' END) as isSign,(CASE a.sign_type WHEN 0 THEN '人工签到' WHEN 1 THEN '人脸签到' END) as signType," +
+            " (CASE a.is_sign WHEN 0 THEN '否' WHEN 1 THEN '是' END) as isSign,(CASE a.sign_type WHEN 0 THEN '人工签到' WHEN 1 THEN '人脸签到' WHEN 2 THEN '二维码签到' END) as signType," +
             " a.sign_date as `date`,a.meeting_id as meetingId,m.approve_status as approveStatus,m.meeting_status as meetingStatus" +
             " from meeting_attendee as a " +
             " left join sys_user as u on a.user_id = u.user_id " +
-            " left join meeting_info as m on m.meeting_id = a.meeting_id "+
+            " left join meeting_info as m on m.meeting_id = a.meeting_id " +
             " where a.meeting_id = :meetingId and a.tenant_id = :tenantId",nativeQuery = true,countQuery = "select COUNT(DISTINCT a.id ) as subcount from meeting_attendee as a left join sys_user as u on a.user_id = u.user_id left join meeting_info as m on m.meeting_id = a.meeting_id where a.meeting_id = :meetingId")
     List<Map<String,Object>> meetingSignList(@Param("meetingId") Long meetingId,@Param("tenantId") Integer tenantId);
 //

+ 77 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/MeetingDeviceWebSocketService.java

@@ -0,0 +1,77 @@
+package com.usky.meeting.service;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.meeting.domain.MeetingDevice;
+import com.usky.meeting.domain.MeetingDeviceHeartbeat;
+import com.usky.meeting.mapper.MeetingDeviceHeartbeatMapper;
+import com.usky.meeting.mapper.MeetingDeviceMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class MeetingDeviceWebSocketService {
+
+    private static final int HEARTBEAT_TIMEOUT_MINUTES = 5;
+
+    @Autowired
+    private MeetingDeviceMapper meetingDeviceMapper;
+
+    @Autowired
+    private MeetingDeviceHeartbeatMapper heartbeatMapper;
+
+    public Map<String, Object> validateDevice(String deviceCode) {
+        Map<String, Object> result = new HashMap<>();
+
+        MeetingDevice device = validateDeviceExists(deviceCode);
+        boolean isOnline = validateDeviceOnline(deviceCode);
+
+        result.put("deviceCode", deviceCode);
+        result.put("deviceName", device.getDeviceName());
+        result.put("deviceStatus", isOnline ? 1 : 0);
+        result.put("isOnline", isOnline);
+        result.put("validateSuccess", true);
+
+        log.info("设备验证成功, deviceCode: {}, isOnline: {}", deviceCode, isOnline);
+        return result;
+    }
+
+    private MeetingDevice validateDeviceExists(String deviceCode) {
+        MeetingDevice device = meetingDeviceMapper.selectOne(
+                Wrappers.<MeetingDevice>lambdaQuery().eq(MeetingDevice::getDeviceCode, deviceCode)
+        );
+        if (device == null) {
+            log.warn("设备不存在, deviceCode: {}", deviceCode);
+            throw new BusinessException("设备不存在: " + deviceCode);
+        }
+        return device;
+    }
+
+    private boolean validateDeviceOnline(String deviceCode) {
+        MeetingDeviceHeartbeat heartbeat = heartbeatMapper.selectOne(
+                Wrappers.<MeetingDeviceHeartbeat>lambdaQuery()
+                        .eq(MeetingDeviceHeartbeat::getDeviceCode, deviceCode)
+                        .orderByDesc(MeetingDeviceHeartbeat::getCreateTime)
+                        .last("LIMIT 1")
+        );
+        if (heartbeat == null) {
+            log.warn("设备心跳记录不存在, deviceCode: {}", deviceCode);
+            throw new BusinessException("设备离线: " + deviceCode + ", 未找到心跳记录");
+        }
+        LocalDateTime heartbeatTime = heartbeat.getCreateTime();
+        LocalDateTime now = LocalDateTime.now();
+        if (heartbeatTime == null || now.isAfter(heartbeatTime.plusMinutes(HEARTBEAT_TIMEOUT_MINUTES))) {
+            log.warn("设备心跳超时, deviceCode: {}, heartbeatTime: {}", deviceCode, heartbeatTime);
+            throw new BusinessException("设备离线: " + deviceCode + ", 心跳超时");
+        }
+        return true;
+    }
+}

+ 4 - 4
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/impl/MeetingDeviceServiceImpl.java

@@ -135,7 +135,7 @@ public class MeetingDeviceServiceImpl extends AbstractCrudService<MeetingDeviceM
                     .filter(Objects::nonNull)
                     .collect(Collectors.toList());
 
-            // 2. 批量查询心跳信息并构建Map(Java 8 兼容版)
+            // 2. 批量查询心跳信息并构建Map
             Map<String, LocalDateTime> deviceHeartbeatMap;
             if (CollectionUtils.isEmpty(validDeviceCodes)) {
                 deviceHeartbeatMap = Collections.emptyMap();
@@ -146,11 +146,11 @@ public class MeetingDeviceServiceImpl extends AbstractCrudService<MeetingDeviceM
                         .collect(Collectors.toMap(
                                 MeetingDeviceHeartbeat::getDeviceCode,
                                 MeetingDeviceHeartbeat::getCreateTime,
-                                (existing, replacement) -> existing  // 解决重复key问题
+                                (existing, replacement) -> existing
                         ));
             }
 
-            // 3. 设备状态赋值(逻辑不变)
+            // 3. 设备状态赋值
             page.getRecords().forEach(meetingDevice -> {
                 String deviceCode1 = meetingDevice.getDeviceCode();
                 boolean isHeartbeatValid = Objects.nonNull(deviceCode1)
@@ -159,7 +159,7 @@ public class MeetingDeviceServiceImpl extends AbstractCrudService<MeetingDeviceM
                 meetingDevice.setDeviceStatus(isHeartbeatValid ? 1 : 0);
             });
 
-            // 4. 通用映射关联数据(逻辑不变)
+            // 4. 通用映射关联数据
             mapAssociatedData(page.getRecords(),
                     MeetingDevice::getRoomId,
                     buildIdMap(meetingRoomsList, MeetingRoom::getRoomId),

+ 0 - 6
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/impl/MeetingFaceServiceImpl.java

@@ -1,10 +1,8 @@
 package com.usky.meeting.service.impl;
 
-import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.usky.common.security.utils.SecurityUtils;
-import com.usky.meet.utils.JwtUtils;
 import com.usky.meet.utils.TimeUtils;
 import com.usky.meeting.domain.MeetingDevice;
 import com.usky.meeting.domain.MeetingFace;
@@ -12,7 +10,6 @@ import com.usky.meeting.mapper.MeetingDeviceMapper;
 import com.usky.meeting.mapper.MeetingFaceMapper;
 import com.usky.meeting.repository.MeetingRoomRepository;
 import com.usky.meeting.server.FaceContrastServer;
-import com.usky.meeting.service.MeetingDeviceService;
 import com.usky.meeting.service.MeetingFaceService;
 import com.usky.common.mybatis.core.AbstractCrudService;
 import com.usky.meeting.service.vo.FaceResultVO;
@@ -21,10 +18,7 @@ import com.usky.meeting.service.vo.MeetingFaceVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.time.LocalDateTime;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * <p>

+ 4 - 2
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/impl/MeetingInfoServiceImpl.java

@@ -521,15 +521,17 @@ public class MeetingInfoServiceImpl extends AbstractCrudService<MeetingInfoMappe
             if (requestVO.getMothodType().equals(0)) {
                 one.setIsSign(1);
                 one.setSignDate(LocalDateTime.now());
-                // 签到签退方式(0.人工 1.人脸)
+                // 签到签退方式(0.人工 1.人脸 3.二维码)
                 one.setSignType(requestVO.getSignType());
 
-            } else {
+            } else if (requestVO.getMothodType().equals(1)) {
                 one.setIsSignOut(1);
                 one.setSignOutDate(LocalDateTime.now());
                 one.setSignOutType(requestVO.getSignType());
             }
             meetingAttendeeService.updateById(one);
+        } else {
+            throw new BusinessException("您无需参加此会议!");
         }
 
     }

+ 21 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/vo/WebSocketConnectionResponseVO.java

@@ -0,0 +1,21 @@
+package com.usky.meeting.service.vo;
+
+import lombok.Data;
+
+@Data
+public class WebSocketConnectionResponseVO {
+
+    private String sessionId;
+
+    private String deviceCode;
+
+    private String deviceName;
+
+    private Integer deviceStatus;
+
+    private Boolean isOnline;
+
+    private String message;
+
+    private Long timestamp;
+}

+ 1 - 1
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/utils/JwtUtils.java

@@ -1,4 +1,4 @@
-package com.usky.meet.utils;
+package com.usky.meeting.utils;
 
 import com.auth0.jwt.JWT;
 import com.auth0.jwt.JWTCreator;

+ 72 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketController.java

@@ -0,0 +1,72 @@
+package com.usky.meeting.websocket;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.meeting.service.MeetingDeviceWebSocketService;
+import com.usky.meeting.service.vo.WebSocketConnectionResponseVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Controller
+@RequiredArgsConstructor
+@RequestMapping("/meetingDevice/websocket")
+public class MeetingDeviceWebSocketController {
+
+    @Autowired
+    private MeetingDeviceWebSocketService deviceWebSocketService;
+
+    @GetMapping("/connect")
+    @ResponseBody
+    public ApiResult<WebSocketConnectionResponseVO> connect(
+            @RequestParam String deviceCode
+            //,@RequestHeader(value = "X-Real-IP", required = false) String realIp
+    ) {
+        log.info("收到WebSocket连接请求, deviceCode: {}", deviceCode);
+        WebSocketConnectionResponseVO response = new WebSocketConnectionResponseVO();
+        try {
+            Map<String, Object> validateResult = deviceWebSocketService.validateDevice(deviceCode);
+            response.setDeviceCode(deviceCode);
+            response.setDeviceName((String) validateResult.get("deviceName"));
+            response.setDeviceStatus((Integer) validateResult.get("deviceStatus"));
+            response.setIsOnline((Boolean) validateResult.get("isOnline"));
+            response.setMessage("设备验证通过,可建立WebSocket连接");
+            response.setTimestamp(System.currentTimeMillis());
+            log.info("设备验证成功, deviceCode: {}", deviceCode);
+            return ApiResult.success(response);
+        } catch (Exception e) {
+            log.error("设备验证失败, deviceCode: {}", deviceCode, e);
+            response.setDeviceCode(deviceCode);
+            response.setMessage("设备验证失败: " + e.getMessage());
+            response.setTimestamp(System.currentTimeMillis());
+            return ApiResult.error(e.getMessage());
+        }
+    }
+
+    @GetMapping("/status/{sessionId}")
+    @ResponseBody
+    public ApiResult<Map<String, Object>> getConnectionStatus(@PathVariable String sessionId) {
+        Map<String, Object> status = new HashMap<>();
+        boolean isOpen = MeetingDeviceWebSocketHandler.isSessionOpen(sessionId);
+        String deviceCode = MeetingDeviceWebSocketHandler.getDeviceCodeBySession(sessionId);
+        status.put("sessionId", sessionId);
+        status.put("deviceCode", deviceCode);
+        status.put("isOpen", isOpen);
+        status.put("checkTime", LocalDateTime.now().toString());
+        return ApiResult.success(status);
+    }
+
+    @GetMapping("/close/{sessionId}")
+    @ResponseBody
+    public ApiResult<Void> closeConnection(@PathVariable String sessionId) {
+        MeetingDeviceWebSocketHandler.removeSession(sessionId);
+        log.info("WebSocket连接已手动关闭, sessionId: {}", sessionId);
+        return ApiResult.success();
+    }
+}

+ 76 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketHandler.java

@@ -0,0 +1,76 @@
+package com.usky.meeting.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+//@Component
+public class MeetingDeviceWebSocketHandler extends TextWebSocketHandler {
+
+    private static final Map<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
+    private static final Map<String, String> SESSION_DEVICE_MAP = new ConcurrentHashMap<>();
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        String sessionId = session.getId();
+        String deviceCode = (String) session.getAttributes().get("deviceCode");
+        SESSION_MAP.put(sessionId, session);
+        if (deviceCode != null) {
+            SESSION_DEVICE_MAP.put(sessionId, deviceCode);
+        }
+        log.info("WebSocket连接已建立, sessionId: {}, deviceCode: {}", sessionId, deviceCode);
+    }
+
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        String payload = message.getPayload();
+        log.info("收到消息, sessionId: {}, message: {}", session.getId(), payload);
+        String deviceCode = SESSION_DEVICE_MAP.get(session.getId());
+        session.sendMessage(new TextMessage("{\"type\":\"ack\",\"deviceCode\":\"" + deviceCode + "\",\"message\":\"消息已收到\"}"));
+    }
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+        String sessionId = session.getId();
+        SESSION_MAP.remove(sessionId);
+        SESSION_DEVICE_MAP.remove(sessionId);
+        log.info("WebSocket连接已关闭, sessionId: {}, status: {}", sessionId, status);
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        log.error("WebSocket传输错误, sessionId: {}", session.getId(), exception);
+        if (session.isOpen()) {
+            session.close();
+        }
+    }
+
+    public static void sendMessage(String sessionId, String message) throws IOException {
+        WebSocketSession session = SESSION_MAP.get(sessionId);
+        if (session != null && session.isOpen()) {
+            session.sendMessage(new TextMessage(message));
+        }
+    }
+
+    public static boolean isSessionOpen(String sessionId) {
+        WebSocketSession session = SESSION_MAP.get(sessionId);
+        return session != null && session.isOpen();
+    }
+
+    public static void removeSession(String sessionId) {
+        SESSION_MAP.remove(sessionId);
+        SESSION_DEVICE_MAP.remove(sessionId);
+    }
+
+    public static String getDeviceCodeBySession(String sessionId) {
+        return SESSION_DEVICE_MAP.get(sessionId);
+    }
+}

+ 1 - 1
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmTimeConfServiceImpl.java

@@ -66,7 +66,7 @@ public class PmTimeConfServiceImpl extends AbstractCrudService<PmTimeConfMapper,
         // 查询时间配置
         PmTimeConf timeConf = getTimeConf(tenantId);
         if (timeConf == null) {
-            throw new RuntimeException("未找到工作报提交统计告时间配置,请联系管理员");
+            throw new BusinessException("未找到工作报提交统计告时间配置,请联系管理员");
         } else if (countDate.equals(LocalDate.now()) && timeConf.getStartTime().isAfter(LocalTime.now()) || countDate.isAfter(LocalDate.now())) {
             responseVO.setSubmitOnTime(0);
             responseVO.setSubmitLate(0);