Ver código fonte

优化日期生成机制

车车 3 meses atrás
pai
commit
b4af1d01c5

+ 195 - 0
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/websocket/message/WebSocketMessSend.java

@@ -0,0 +1,195 @@
+package cn.iocoder.yudao.module.infra.websocket.message;
+
+
+import jakarta.websocket.*;
+import jakarta.websocket.server.PathParam;
+import jakarta.websocket.server.ServerEndpoint;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+@Component
+@ServerEndpoint("/websocket/messSend/{code}")
+@Slf4j
+public class WebSocketMessSend {
+    private Session session;
+    private String code;
+    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+    private static int onlineCount = 0;
+    private static CopyOnWriteArraySet<WebSocketMessSend> webSocketSet = new CopyOnWriteArraySet<>();
+    //concurrent包的线程安全set,用来存放每个客户端对应的MyWebSocket对象
+    private static ConcurrentHashMap<String, WebSocketMessSend> webSocketMap2 = new ConcurrentHashMap();
+
+    // 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】
+    private final static List<Session> SESSIONS = Collections.synchronizedList(new ArrayList<>());
+    private Thread heartbeatThread;
+
+    /**
+     * 建立连接
+     *
+     * @param session
+     * @param code
+     */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("code") String code) {
+        this.session = session;
+        this.code = code;
+        if (StringUtils.isBlank(code) || code.equals("null")) {
+            log.error(code + "连接失败!");
+            return;
+        }
+        webSocketSet.add(this);
+        SESSIONS.add(session);
+        if (webSocketMap2.containsKey(code)) {
+            webSocketMap2.remove(code);
+            webSocketMap2.put(code, this);
+        } else {
+            webSocketMap2.put(code, this);
+            addOnlineCount();
+        }
+        // 心跳机制
+        heartbeatThread = new Thread(() -> {
+            try {
+                sendHeartbeats(session, code);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        });
+        heartbeatThread.start();
+        log.info("[连接ID:{}] 建立连接, 当前连接数:{}", this.code, webSocketMap2.size());
+        log.info("线程序号{}, 当前存活线程数{}", heartbeatThread.getId(), Thread.activeCount());
+
+    }
+
+    private void sendHeartbeats(Session session, String code) throws IOException {
+        int heartbeatInterval = 60000; // 60 seconds
+        while (true) {
+            try {
+                Thread.sleep(heartbeatInterval);
+                session.getBasicRemote().sendText("heartbeat");
+            } catch (InterruptedException | IOException e) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * 断开连接
+     */
+    @OnClose
+    public void onClose() {
+        webSocketSet.remove(this);
+        if (webSocketMap2.containsKey(code)) {
+            webSocketMap2.remove(code);
+            subOnlineCount();
+        }
+        // 关闭当前线程
+        if (heartbeatThread != null && heartbeatThread.isAlive()) {
+            log.info("关闭线程序号{}, 当前存活线程数{}", heartbeatThread.getId(), Thread.activeCount());
+            heartbeatThread.interrupt(); // Interrupt the heartbeat thread
+            try {
+                heartbeatThread.join(); // Wait for the thread to finish
+            } catch (InterruptedException e) {
+                log.error("Error joining heartbeat thread for connection ID: {}", code, e);
+                log.info("关闭线程序号{}, 当前存活线程数{}", Thread.currentThread().getId(), Thread.activeCount());
+                Thread.currentThread().interrupt(); // Restore the interruption status
+            }
+        }
+        log.info("[连接ID:{}] 断开连接, 当前连接数:{}", code, webSocketMap2.size());
+    }
+
+    /**
+     * 发送错误
+     *
+     * @param session
+     * @param error
+     */
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.info("[连接ID:{}] 错误原因:{}", this.code, error.getMessage());
+        error.printStackTrace();
+        // 发生错误时,关闭连接
+        // conn.close(500, "连接出错");
+    }
+
+    /**
+     * 收到消息
+     *
+     * @param message
+     */
+    @OnMessage
+    public void onMessage(String message) {
+        // log.info("【websocket消息】收到客户端发来的消息:{}", message);
+        log.info("[连接ID:{}] 收到消息:{}", this.code, message);
+    }
+
+    /**
+     * 发送消息
+     *
+     * @param message
+     * @param code
+     */
+    public static void sendMessage(String code, String message) {
+        WebSocketMessSend webSocketIots = webSocketMap2.get(code);
+        log.info("【websocket消息】推送消息, webSocketServer={}", webSocketIots);
+        if (webSocketIots != null) {
+            log.info("【websocket消息】推送消息, message={}", message);
+            try {
+                webSocketIots.session.getBasicRemote().sendText(message);
+            } catch (Exception e) {
+                e.printStackTrace();
+                log.error("[连接ID:{}] 发送消息失败, 消息:{}", code, message, e);
+            }
+        }
+    }
+
+    /**
+     * 群发消息
+     *
+     * @param message
+     */
+    public static void sendMassMessage(String message) {
+        try {
+            for (Session session : SESSIONS) {
+                if (session.isOpen()) {
+                    session.getBasicRemote().sendText(message);
+                    log.info("[连接ID:{}] 发送消息:{}", session.getRequestParameterMap().get("code"), message);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取当前连接数
+     *
+     * @return
+     */
+    public static synchronized int getOnlineCount() {
+        return onlineCount;
+    }
+
+    /**
+     * 当前连接数加一
+     */
+    public static synchronized void addOnlineCount() {
+        WebSocketMessSend.onlineCount++;
+    }
+
+    /**
+     * 当前连接数减一
+     */
+    public static synchronized void subOnlineCount() {
+        WebSocketMessSend.onlineCount--;
+    }
+
+}
+

+ 1 - 1
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/notifyconfig/vo/NotifyConfigPageReqVO.java

@@ -25,7 +25,7 @@ public class NotifyConfigPageReqVO extends PageParam {
     @Schema(description = "通知时间分类(0-提前 1-立即 2-延迟)", example = "1")
     private Integer notifyTimeType;
 
-    @Schema(description = "钟(立即提醒填写0)")
+    @Schema(description = "钟(立即提醒填写0)")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private Integer[] notifyTime;
 

+ 2 - 2
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/notifyconfig/vo/NotifyConfigRespVO.java

@@ -36,8 +36,8 @@ public class NotifyConfigRespVO {
     @ExcelProperty("通知时间分类(0-提前 1-立即 2-延迟)")
     private Integer notifyTimeType;
 
-    @Schema(description = "钟(立即提醒填写0)", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("钟(立即提醒填写0)")
+    @Schema(description = "钟(立即提醒填写0)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("钟(立即提醒填写0)")
     private Integer notifyTime;
 
     @Schema(description = "通知区域", requiredMode = Schema.RequiredMode.REQUIRED, example = "843")

+ 2 - 2
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/notifyconfig/vo/NotifyConfigSaveReqVO.java

@@ -30,8 +30,8 @@ public class NotifyConfigSaveReqVO {
     @NotNull(message = "通知时间分类(0-提前 1-立即 2-延迟)不能为空")
     private Integer notifyTimeType;
 
-    @Schema(description = "钟(立即提醒填写0)", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "钟(立即提醒填写0)不能为空")
+    @Schema(description = "钟(立即提醒填写0)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "钟(立即提醒填写0)不能为空")
     private Integer notifyTime;
 
     @Schema(description = "通知区域", requiredMode = Schema.RequiredMode.REQUIRED, example = "843")

+ 3 - 1
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/controller/admin/sop/vo/SopSaveReqVO.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.iscs.controller.admin.sop.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 import java.util.List;
@@ -54,6 +53,9 @@ public class SopSaveReqVO {
     @Schema(description = "启用执行计划(0不启用 1启用)")
     private Integer enableExecutionPlan;
 
+    @Schema(description = "取消计划时是否删除未执行的作业(0不删除 1删除)")
+    private Integer deleteNotStartJob;
+
     @Schema(description = "备注", example = "你说的对")
     private String remark;
 

+ 7 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/holiday/HolidayService.java

@@ -65,4 +65,11 @@ public interface HolidayService extends IService<HolidayDO> {
 
     List<HolidayRespVO> getHolidayList(HolidayPageReqVO pageReqVO);
 
+    /**
+     * 输入日期,返回里面是工作日的日期
+     * @param days
+     * @return
+     */
+    List<String> getWorkDaysFromDays(List<String> days);
+
 }

+ 15 - 0
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/holiday/HolidayServiceImpl.java

@@ -145,4 +145,19 @@ public class HolidayServiceImpl extends ServiceImpl<HolidayMapper, HolidayDO> im
         return holidayRespVOS;
     }
 
+    @Override
+    public List<String> getWorkDaysFromDays(List<String> days) {
+        List<String> workDays = new ArrayList<>();
+        if (days.isEmpty()) {
+            return workDays;
+        }
+        List<HolidayDO> holidayDOS = list(Wrappers.<HolidayDO>lambdaQuery()
+                .in(HolidayDO::getTheDay, days)
+                .eq(HolidayDO::getHolidayType, "HOLIDAY"));
+        if (!holidayDOS.isEmpty()) {
+            days.removeIf(item -> holidayDOS.stream().map(HolidayDO::getTheDay).toList().contains(item));
+        }
+        return days;
+    }
+
 }

+ 19 - 44
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/sop/SopExecutionPlanServiceImpl.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iscs.controller.admin.sop.vo.*;
 import cn.iocoder.yudao.module.iscs.dal.dataobject.sop.SopDO;
 import cn.iocoder.yudao.module.iscs.dal.dataobject.sop.SopExecutionPlanDO;
 import cn.iocoder.yudao.module.iscs.dal.mysql.sop.SopExecutionPlanMapper;
+import cn.iocoder.yudao.module.iscs.service.holiday.HolidayService;
 import cn.iocoder.yudao.module.iscs.utils.SuperDateUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -33,6 +34,8 @@ public class SopExecutionPlanServiceImpl extends ServiceImpl<SopExecutionPlanMap
     private SopExecutionPlanMapper sopExecutionPlanMapper;
     @Resource
     private SopService sopService;
+    @Resource
+    private HolidayService holidayService;
 
     @Override
     public Long createSopExecutionPlan(SopExecutionPlanSaveReqVO createReqVO) {
@@ -151,9 +154,10 @@ public class SopExecutionPlanServiceImpl extends ServiceImpl<SopExecutionPlanMap
     public SopExecutionPlanRespVO getSopExecutionPlan(Long id) {
         SopExecutionPlanDO sopExecutionPlanDO = sopExecutionPlanMapper.selectById(id);
         SopExecutionPlanRespVO bean = BeanUtils.toBean(sopExecutionPlanDO, SopExecutionPlanRespVO.class);
-        // sop数据
+        // 1.sop数据
         SopRespVO sopDetailById = sopService.getSopDetailById(bean.getSopId());
         ArrayList<SopPreviewDataVO> sopRespVOS = new ArrayList<>();
+        // 2.准备参数,获取执行日期
         Integer count = 10;
         String endDateStr = "9999-12-31";
         if (StringUtils.isNotBlank(bean.getEndType()) && (bean.getEndType().equals("0") || bean.getEndType().equals("1"))) {
@@ -162,86 +166,60 @@ public class SopExecutionPlanServiceImpl extends ServiceImpl<SopExecutionPlanMap
             endDateStr = bean.getEndValue();
         }
         if (StringUtils.isNotBlank(bean.getEndType()) && bean.getEndType().equals("2")) {
-            // 如果是时间截至和永不截至
+            // 如果是次数截至
             count = Integer.valueOf(bean.getEndValue());
             endDateStr = "9999-12-31";
         }
+        // 计算的有效的时间
+        List<String> days = new ArrayList<>();
         // 年
         if (bean.getFrequencyUnit().equals(0)) {
             // 处理年的数据
             String yyyymmdd = SuperDateUtils.getYYYYMMDD();
-            List<String> adjacentDatesYearDay = SuperDateUtils.getAdjacentDatesYearDay(
+            days = SuperDateUtils.getAdjacentDatesYearDay(
                     yyyymmdd,
                     endDateStr,
                     bean.getFrequency(),
                     Integer.parseInt(bean.getTimePoint()),
                     count);
-            for (String s : adjacentDatesYearDay) {
-                SopPreviewDataVO sopPreviewDataVO = new SopPreviewDataVO();
-                sopPreviewDataVO.setPlanDay(s);
-                sopPreviewDataVO.setStartTime(bean.getStartTime());
-                sopPreviewDataVO.setDuration(bean.getDuration());
-                sopPreviewDataVO.setWorkstationName(sopDetailById.getWorkstationName());
-                sopPreviewDataVO.setMachineryName(sopDetailById.getMachineryName());
-                sopPreviewDataVO.setSopTypeName(sopDetailById.getSopTypeName());
-                sopRespVOS.add(sopPreviewDataVO);
-            }
-            bean.setPreviewDataList(sopRespVOS);
         }
         // 月
         if (bean.getFrequencyUnit().equals(1)) {
             // 处理月的数据
             String yyyymmdd = SuperDateUtils.getYYYYMMDD();
-            List<String> adjacentDatesMonthDay = SuperDateUtils.getAdjacentDatesMonthDay(
+            days = SuperDateUtils.getAdjacentDatesMonthDay(
                     yyyymmdd,
                     endDateStr,
                     bean.getFrequency(),
                     Integer.parseInt(bean.getTimePoint()),
                     count);
-            for (String s : adjacentDatesMonthDay) {
-                SopPreviewDataVO sopPreviewDataVO = new SopPreviewDataVO();
-                sopPreviewDataVO.setPlanDay(s);
-                sopPreviewDataVO.setStartTime(bean.getStartTime());
-                sopPreviewDataVO.setDuration(bean.getDuration());
-                sopPreviewDataVO.setWorkstationName(sopDetailById.getWorkstationName());
-                sopPreviewDataVO.setMachineryName(sopDetailById.getMachineryName());
-                sopPreviewDataVO.setSopTypeName(sopDetailById.getSopTypeName());
-                sopRespVOS.add(sopPreviewDataVO);
-            }
-            bean.setPreviewDataList(sopRespVOS);
         }
         // 周
         if (bean.getFrequencyUnit().equals(2)) {
             // 处理周的数据
             String yyyymmdd = SuperDateUtils.getYYYYMMDD();
-            List<String> adjacentDatesByWeekday = SuperDateUtils.getAdjacentDatesByWeekday(
+            days = SuperDateUtils.getAdjacentDatesByWeekday(
                     yyyymmdd,
                     endDateStr,
                     bean.getFrequency(),
                     bean.getTimePoint(),
                     count);
-            for (String s : adjacentDatesByWeekday) {
-                SopPreviewDataVO sopPreviewDataVO = new SopPreviewDataVO();
-                sopPreviewDataVO.setPlanDay(s);
-                sopPreviewDataVO.setStartTime(bean.getStartTime());
-                sopPreviewDataVO.setDuration(bean.getDuration());
-                sopPreviewDataVO.setWorkstationName(sopDetailById.getWorkstationName());
-                sopPreviewDataVO.setMachineryName(sopDetailById.getMachineryName());
-                sopPreviewDataVO.setSopTypeName(sopDetailById.getSopTypeName());
-                sopRespVOS.add(sopPreviewDataVO);
-            }
-            bean.setPreviewDataList(sopRespVOS);
         }
         // 天
         if (bean.getFrequencyUnit().equals(3)) {
             // 处理天的数据
             String yyyymmdd = SuperDateUtils.getYYYYMMDD();
-            List<String> adjacentDatesByDay = SuperDateUtils.getAdjacentDatesByDay(
+            days = SuperDateUtils.getAdjacentDatesByDay(
                     yyyymmdd,
                     endDateStr,
                     bean.getFrequency(),
                     count);
-            for (String s : adjacentDatesByDay) {
+        }
+        // 3.去除日历里面安排的休息日
+        List<String> workDaysFromDays = holidayService.getWorkDaysFromDays(days);
+        // 4.获取有效天数的模拟数据
+        if (!workDaysFromDays.isEmpty()) {
+            for (String s : workDaysFromDays) {
                 SopPreviewDataVO sopPreviewDataVO = new SopPreviewDataVO();
                 sopPreviewDataVO.setPlanDay(s);
                 sopPreviewDataVO.setStartTime(bean.getStartTime());
@@ -251,14 +229,11 @@ public class SopExecutionPlanServiceImpl extends ServiceImpl<SopExecutionPlanMap
                 sopPreviewDataVO.setSopTypeName(sopDetailById.getSopTypeName());
                 sopRespVOS.add(sopPreviewDataVO);
             }
-            bean.setPreviewDataList(sopRespVOS);
         }
+        bean.setPreviewDataList(sopRespVOS);
         return bean;
     }
 
-
-
-
     @Override
     public SopExecutionPlanRespVO selectSopExecutionPlanBySopId(Long sopId) {
         SopExecutionPlanDO sopExecutionPlanDO = getOne(Wrappers.<SopExecutionPlanDO>lambdaQuery()

+ 3 - 1
yudao-module-iscs/src/main/java/cn/iocoder/yudao/module/iscs/service/sop/SopServiceImpl.java

@@ -382,7 +382,9 @@ public class SopServiceImpl extends ServiceImpl<SopMapper, SopDO> implements Sop
                 .eq(SopDO::getId, updateReqVO.getId())
                 .set(SopDO::getEnableExecutionPlan, updateReqVO.getEnableExecutionPlan()));
         // 如果是关闭,删掉之前未准备的
-        if (updateReqVO.getEnableExecutionPlan().equals(0)) {
+        if (updateReqVO.getEnableExecutionPlan().equals(0)
+                && updateReqVO.getDeleteNotStartJob() != null
+                && updateReqVO.getDeleteNotStartJob().equals(1)) {
             List<JobTicketDO> jobTicketDOS = jobTicketService.list(Wrappers.<JobTicketDO>lambdaQuery()
                     .eq(JobTicketDO::getSopId, updateReqVO.getId())
                     .eq(JobTicketDO::getTicketStatus, "0")

+ 19 - 4
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyMessageServiceImpl.java

@@ -1,24 +1,27 @@
 package cn.iocoder.yudao.module.system.service.notify;
 
+import cn.hutool.core.date.DateUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.websocket.message.WebSocketMessSend;
 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyMessageDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyMessageMapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 站内信 Service 实现类
  *
  * @author xrcoder
  */
+@Slf4j
 @Service
 @Validated
 public class NotifyMessageServiceImpl implements NotifyMessageService {
@@ -34,6 +37,18 @@ public class NotifyMessageServiceImpl implements NotifyMessageService {
                 .setTemplateType(template.getType()).setTemplateNickname(template.getNickname())
                 .setTemplateContent(templateContent).setTemplateParams(templateParams).setReadStatus(false);
         notifyMessageMapper.insert(message);
+        // 发送socket
+        Map<String, Object> map = new HashMap<>();
+        map.put("userId", userId);
+        map.put("time", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
+        map.put("templateContent", templateContent);
+        try {
+            ObjectMapper objectMapper = new ObjectMapper();
+            String jsonStr = objectMapper.writeValueAsString(map); // 转换为紧凑格式的 JSON
+            WebSocketMessSend.sendMessage(String.valueOf(userId), jsonStr);
+        } catch (Exception e) {
+            log.error("实时站内信解析异常{}", e);
+        }
         return message.getId();
     }