From 7bc2f1c832d5e62386cdc0f95cdbabeb6d4b3582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E5=B1=95=E8=88=AA?= <2426745133@qq.com> Date: Tue, 16 Dec 2025 18:06:07 +0800 Subject: [PATCH] =?UTF-8?q?12-16-=E4=BA=BA=E5=91=98=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=A4=A7=E5=B1=8F-=E4=BF=AE=E5=A4=8D=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/mapper/BusAttendanceMapper.java | 6 +- .../service/IBusAttendanceService.java | 18 +- .../service/IBusProjectTeamMemberService.java | 1 - .../impl/BusAttendanceServiceImpl.java | 254 ++++++++++++++++- .../impl/BusProjectTeamMemberServiceImpl.java | 3 + .../system/service/ISysUserService.java | 4 - .../service/impl/SysUserServiceImpl.java | 265 +----------------- .../service/BigScreenWebSocketServer.java | 8 +- .../service/MessageWebSocketServer.java | 207 ++++++++++++++ 9 files changed, 484 insertions(+), 282 deletions(-) create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/MessageWebSocketServer.java diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java index 6a957402..04930bf7 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/mapper/BusAttendanceMapper.java @@ -1,11 +1,13 @@ package org.dromara.project.mapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Select; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.contractor.domain.SubContractor; import org.dromara.project.domain.BusAttendance; import org.dromara.project.domain.bo.BusAttendanceBo; import org.dromara.project.domain.vo.BusAttendanceVo; -import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; /** * 考勤Mapper接口 @@ -18,4 +20,6 @@ public interface BusAttendanceMapper extends BaseMapperPlus queryPageList(BusAttendanceBo bo, PageQuery pageQuery); + @Select("select * from sub_contractor where id = #{id}") + SubContractor getSubContractor(Long id); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java index 489f7aa3..eb673ff1 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusAttendanceService.java @@ -1,24 +1,21 @@ package org.dromara.project.service; -import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotNull; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.project.domain.BusAttendance; +import org.dromara.project.domain.bo.BusAttendanceBo; import org.dromara.project.domain.dto.attendance.*; import org.dromara.project.domain.vo.BusAttendanceVo; -import org.dromara.project.domain.bo.BusAttendanceBo; -import org.dromara.project.domain.BusAttendance; -import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.mybatis.core.page.PageQuery; - -import com.baomidou.mybatisplus.extension.service.IService; import org.dromara.project.domain.vo.BusMonthAttendanceVo; import org.dromara.project.domain.vo.attendance.*; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 考勤Service接口 @@ -232,5 +229,8 @@ public interface IBusAttendanceService extends IService{ */ Long getAttendanceUserCountByDate(Long projectId,LocalDate startDate, LocalDate endDate); + Map getRyglOnlineUserInfoData(Long projectId); + + void getAttendanceInfo(Long projectId,Long timeType,Map map); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusProjectTeamMemberService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusProjectTeamMemberService.java index aeb99c54..a1051f97 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusProjectTeamMemberService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusProjectTeamMemberService.java @@ -11,7 +11,6 @@ import org.dromara.project.domain.dto.projectteammember.BusProjectTeamMemberExit import org.dromara.project.domain.dto.projectteammember.BusProjectTeamMemberQueryReq; import org.dromara.project.domain.dto.projectteammember.BusProjectTeamMemberUpdateReq; import org.dromara.project.domain.vo.projectteammember.BusProjectTeamMemberVo; -import org.springframework.web.bind.annotation.PathVariable; import java.util.List; diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java index 1e5f4cd1..03af5ec3 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java @@ -27,13 +27,14 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.domain.GeoPoint; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.utils.BigDecimalUtil; import org.dromara.common.utils.IdCardEncryptorUtil; import org.dromara.common.utils.JSTUtil; import org.dromara.contractor.domain.SubConstructionUser; +import org.dromara.contractor.domain.SubContractor; import org.dromara.contractor.service.ISubConstructionUserService; -import org.dromara.contractor.service.ISubUserSalaryDetailService; import org.dromara.project.domain.*; import org.dromara.project.domain.bo.BusAttendanceBo; import org.dromara.project.domain.dto.attendance.*; @@ -53,18 +54,20 @@ import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.service.ISysOssService; import org.dromara.system.service.ISysUserService; import org.dromara.websocket.ChatServerHandler; +import org.dromara.websocket.websocket.domain.vo.RyglWebSocketVo; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.*; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjusters; -import java.time.temporal.ValueRange; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static org.dromara.project.domain.enums.BusAttendanceClockStatusEnum.*; @@ -111,12 +114,14 @@ public class BusAttendanceServiceImpl extends ServiceImpl getRyglOnlineUserInfoData(Long projectId){ + //获取该项目的在线人员 再分辨出种类 + //先获取该项目所有人员 + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(SubConstructionUser::getProjectId, projectId); + //未入场人员也要统计坐标 打卡不统计(存疑) +// lqw.and(lqw1 -> +// lqw1.isNotNull(SubConstructionUser::getTeamId) +// .or() +// .isNotNull(SubConstructionUser::getContractorId)); + List list = constructionUserService.list(lqw); + //再去从聊天服务中获取在线的ID + List onlineUserList = ChatServerHandler.getOnlineUserList(); + //构建将要返回的 数据 + List info = new ArrayList<>(); + long zx = 0; //在线 + long lx = 0; //离线 + AtomicLong fb = new AtomicLong(); //分包 + AtomicLong sg = new AtomicLong(); //施工 + //将属于该项目的在线ID过滤出来 + for (SubConstructionUser constructionUser : list) { + if (onlineUserList.contains(constructionUser.getSysUserId().toString())){ + //如果是此项目的在线人员 没有缓存信息 应该是还没来得及发送坐标信息进行缓存 + zx++; + //此项目 在线的 userId 去获取缓存的坐标信息 GpsEquipmentServiceImpl.setData() + String key = "rydw_userId_:" + constructionUser.getSysUserId(); + SysUserVo cacheUserVo = RedisUtils.getCacheObject(key); + if (cacheUserVo == null){ + continue; + } + //去判断是什么种类的用户 + list.stream().filter(item -> item.getSysUserId().equals(constructionUser.getSysUserId())) + .findFirst().ifPresent(item -> { + if (item.getUserRole().equals("0")){ + cacheUserVo.setJslx("施工"); + sg.getAndIncrement(); + } else if (item.getUserRole().equals("2")) { + cacheUserVo.setJslx("分包"); + fb.getAndIncrement(); + } + }); + info.add(cacheUserVo); + }else { + lx++; + } + } + //将数据返回 + Map map = new HashMap<>(); + map.put("zx", String.valueOf(zx)); + map.put("lx", String.valueOf(lx)); + map.put("fb", String.valueOf(fb.get())); + map.put("sg", String.valueOf(sg.get())); + map.put("info", info.toString()); + return map; + } + + @Override + public void getAttendanceInfo(Long projectId,Long timeType,Map map){ + //构建数据 + //timeType 1:今天 2:本周 3:本月 + Long zrs = 0L; //总人数 + Long cqr = 0L; //出勤人数 + BigDecimal cql = BigDecimal.ZERO; //出勤率 + //查询此项目的所有人员 + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(SubConstructionUser::getProjectId, projectId); + List list = constructionUserService.list(lqw); + //根据分包和班组的id进行分类 统计都有多少人 未入场人员没有两项数据 无法统计 仅能计算为总数 + List fbList = new ArrayList<>(); + List bzList = new ArrayList<>(); + for (SubConstructionUser constructionUser : list) { + //统计该项目下的分包和班组各应有多少人 + if (constructionUser.getTeamId() != null && constructionUser.getContractorId() != null){ + //两个都有的情况 + //根据其角色来区分 0-施工人员 1-管理人员 2-分包管理人员 + if (constructionUser.getUserRole().equals("2")){ + checkAndSetValue(fbList,constructionUser,2,timeType,projectId); + } else if (constructionUser.getUserRole().equals("0")) { + checkAndSetValue(bzList,constructionUser,0,timeType,projectId); + } + }else if (constructionUser.getTeamId() == null && constructionUser.getContractorId() != null){ + //班组为空,分包不为空的情况 两个都为空不统计 班组不空分包空 不存在这种情况 + //根据其角色来区分 0-施工人员 1-管理人员 2-分包管理人员 + if (constructionUser.getUserRole().equals("2")){ + checkAndSetValue(fbList,constructionUser,2,timeType,projectId); + } + } + } + + //总人数 + zrs = (long) fbList.size() + bzList.size(); + //总出勤人 + long fbcqr = 0L; + long bzcqr = 0L; + //统计两个列表里的 到岗率 + for (RyglWebSocketVo vo : fbList) { + if (vo.getZrs()>0){ + vo.setDgl( + //到岗人数/总人数 四舍五入 一位小数 + BigDecimal.valueOf(vo.getDgrs()).divide(BigDecimal.valueOf(vo.getZrs()), 1, RoundingMode.HALF_UP) + ); + }else { + vo.setDgl(BigDecimal.ZERO); + } + fbcqr = fbcqr + vo.getDgrs(); + } + + for (RyglWebSocketVo vo : bzList) { + if (vo.getZrs()>0){ + vo.setDgl( + //到岗人数/总人数 四舍五入 一位小数 + BigDecimal.valueOf(vo.getDgrs()).divide(BigDecimal.valueOf(vo.getZrs()), 1, RoundingMode.HALF_UP) + ); + }else { + vo.setDgl(BigDecimal.ZERO); + } + bzcqr = bzcqr + vo.getDgrs(); + } + //出勤率 + if (zrs != 0L){ + cql = (BigDecimal.valueOf(fbcqr).add(BigDecimal.valueOf(bzcqr))).divide(BigDecimal.valueOf(zrs),1,RoundingMode.HALF_UP); + } + + map.put("zrs", zrs.toString()); + map.put("cqr", cqr.toString()); + map.put("cql", cql.toString()); + map.put("fb", fbList.toString()); + map.put("bz", bzList.toString()); + } + + /** + * getAttendanceInfo附属方法 + */ + private void checkAndSetValue(List ryglWebSocketVoList, SubConstructionUser info,int type,Long time,Long projectId){ + //timeType 1:今天 2:本周 3:本月 此参数的校验放在连接时获取参数进行校验 + if (time == 2L){ + time = 7L; + }else if (time == 3L){ + time = 30L; + } + + Long finalTime = time; + switch (type){ + case 2 -> { + //分包 + //首先判断传入的列表中是否存在该条数据 + ryglWebSocketVoList.stream().filter(item -> item.getZzId().equals(info.getContractorId())).findFirst().ifPresentOrElse( + item -> { + item.setZrs(item.getZrs() + finalTime); + item.setDgrs(item.getDgrs() + getDgrs(info.getSysUserId(),finalTime,projectId)); + }, () -> { + RyglWebSocketVo ryglWebSocketVo = new RyglWebSocketVo(); + //分包组织id + ryglWebSocketVo.setZzId(info.getContractorId()); + //分包组织名称 + if (info.getContractorId() != null) { +// SubContractor byId = subContractorService.getById(info.getContractorId()); + SubContractor byId = baseMapper.getSubContractor(info.getContractorId()); + if (byId != null) { + ryglWebSocketVo.setZzmc(byId.getName()); + } + } + //总人数 先设置1 + ryglWebSocketVo.setZrs(finalTime); + ryglWebSocketVo.setDgrs(getDgrs(info.getSysUserId(),finalTime,projectId)); + ryglWebSocketVoList.add(ryglWebSocketVo); + }); + } + + case 0 -> + //班组 + //首先判断传入的列表中是否存在该条数据 + ryglWebSocketVoList.stream().filter(item -> item.getZzId().equals(info.getTeamId())).findFirst().ifPresentOrElse( + item -> { + item.setZrs(item.getZrs() + finalTime); + item.setDgrs(item.getDgrs() + getDgrs(info.getSysUserId(),finalTime,projectId)); + }, () -> { + RyglWebSocketVo ryglWebSocketVo = new RyglWebSocketVo(); + //分包组织id + ryglWebSocketVo.setZzId(info.getContractorId()); + //分包组织名称 + ryglWebSocketVo.setZzmc(info.getTeamName()); + //总人数 先设置1 + ryglWebSocketVo.setZrs(finalTime); + ryglWebSocketVo.setDgrs(getDgrs(info.getSysUserId(),finalTime,projectId)); + ryglWebSocketVoList.add(ryglWebSocketVo); + }); + } + } + +// // 出勤状态(正常、迟到、早退) +// private static final Set ATTENDANCE_STATUS = new HashSet<>(Arrays.asList(BusAttendanceClockStatusEnum.NORMAL.getValue(), +// LATE.getValue(), LEAVEEARLY.getValue() +// , BusAttendanceClockStatusEnum.REISSUE.getValue())); + + /** + * 获取到岗人数 根据时间类型 返回到岗次数 + */ + private Long getDgrs(Long userId,Long time,Long projectId) { + // 今天所有用户的打卡记录 + List attendanceList; + LambdaQueryWrapper lqw = new LambdaQueryWrapper() + .eq(BusAttendance::getProjectId, projectId) + .eq(BusAttendance::getUserId, userId); + if (time == 1L) { + lqw.eq(BusAttendance::getClockDate, LocalDate.now()); + } else if (time == 2L) { + // 获取本周一和周日的日期 + LocalDate today = LocalDate.now(); + LocalDate monday = today.with(DayOfWeek.MONDAY); + LocalDate sunday = today.with(DayOfWeek.SUNDAY); + lqw.between(BusAttendance::getClockDate, monday, sunday); + }else if (time == 3L){ + // 获取本月第一天和最后一天 + LocalDate today = LocalDate.now(); + LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth()); + LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth()); + lqw.between(BusAttendance::getClockDate, firstDayOfMonth, lastDayOfMonth); + } + lqw.in(BusAttendance::getClockStatus, ATTENDANCE_STATUS) + .apply(" user_id not in (select sys_user_id from sub_construction_user where project_id = {0} and user_role != '0' )", projectId); + attendanceList = this.list(lqw); + if (attendanceList == null || attendanceList.isEmpty()){ + return 0L; + } + + final Long[] count = {0L}; + //根据日期分组 + Map> collect = attendanceList.stream().collect(Collectors.groupingBy(BusAttendance::getClockDate)); + collect.forEach((key, value) -> { + //每一天分组 同一天去重 + List list = value.stream().map(BusAttendance::getUserId).distinct().toList(); + count[0] = count[0] + list.size(); + }); + + return count[0]; + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java index 65c8e8e6..e7bb52cc 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java @@ -98,6 +98,9 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl getRyglOnlineUserInfoData(Long projectId); - - void getAttendanceInfo(Long projectId,Long timeType,Map map); - } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index f812d048..ce47163c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java @@ -26,7 +26,6 @@ import org.dromara.common.core.utils.*; import org.dromara.common.enums.AppUserTypeEnum; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.contractor.domain.SubConstructionUser; import org.dromara.contractor.domain.SubContractor; @@ -39,14 +38,11 @@ import org.dromara.design.service.IDesDesignChangeService; import org.dromara.design.service.IDesUserService; import org.dromara.design.service.IDesVolumeCatalogService; import org.dromara.design.service.IDesVolumeFileService; -import org.dromara.project.domain.BusAttendance; import org.dromara.project.domain.BusUserProjectRelevancy; import org.dromara.project.domain.dto.attendance.SubTodayUserDto; import org.dromara.project.domain.dto.attendance.SubTwoWeekDto; import org.dromara.project.domain.dto.attendance.SubUserAttendanceQueryReq; -import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum; import org.dromara.project.domain.vo.projectteam.BusProjectTeamAppVo; -import org.dromara.project.service.IBusAttendanceService; import org.dromara.project.service.IBusProjectTeamService; import org.dromara.project.service.IBusUserProjectRelevancyService; import org.dromara.system.domain.*; @@ -62,8 +58,6 @@ import org.dromara.system.mapper.*; import org.dromara.system.service.ISysRoleService; import org.dromara.system.service.ISysUserFileService; import org.dromara.system.service.ISysUserService; -import org.dromara.websocket.ChatServerHandler; -import org.dromara.websocket.websocket.domain.vo.RyglWebSocketVo; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Lazy; @@ -71,17 +65,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.temporal.TemporalAdjusters; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static org.dromara.project.domain.enums.BusAttendanceClockStatusEnum.LATE; -import static org.dromara.project.domain.enums.BusAttendanceClockStatusEnum.LEAVEEARLY; - /** * 用户 业务层处理 * @@ -134,12 +120,11 @@ public class SysUserServiceImpl implements ISysUserService, UserService { @Resource private ISysRoleService roleService; - @Resource - private ISubConstructionUserService subConstructionUserService; - @Resource - private ISubContractorService subContractorService; - @Resource - private IBusAttendanceService busAttendanceService; +// @Resource +// private ISubConstructionUserService subConstructionUserService; +// @Resource +// private ISubContractorService subContractorService; + @Override public TableDataInfo selectPageUserList(SysUserBo user, PageQuery pageQuery) { @@ -1592,246 +1577,6 @@ public class SysUserServiceImpl implements ISysUserService, UserService { baseMapper.update(null, wrapper); } - /** - * 获取手机app在线离线数据 + 在线人员坐标 + 分包和施工在线人员数量 - */ - @Override - public Map getRyglOnlineUserInfoData(Long projectId){ - //获取该项目的在线人员 再分辨出种类 - //先获取该项目所有人员 - LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); - lqw.eq(SubConstructionUser::getProjectId, projectId); - //未入场人员也要统计坐标 打卡不统计(存疑) -// lqw.and(lqw1 -> -// lqw1.isNotNull(SubConstructionUser::getTeamId) -// .or() -// .isNotNull(SubConstructionUser::getContractorId)); - List list = subConstructionUserService.list(lqw); - //再去从聊天服务中获取在线的ID - List onlineUserList = ChatServerHandler.getOnlineUserList(); - //构建将要返回的 数据 - List info = new ArrayList<>(); - long zx = 0; //在线 - long lx = 0; //离线 - AtomicLong fb = new AtomicLong(); //分包 - AtomicLong sg = new AtomicLong(); //施工 - //将属于该项目的在线ID过滤出来 - for (SubConstructionUser constructionUser : list) { - if (onlineUserList.contains(constructionUser.getSysUserId().toString())){ - //如果是此项目的在线人员 没有缓存信息 应该是还没来得及发送坐标信息进行缓存 - zx++; - //此项目 在线的 userId 去获取缓存的坐标信息 GpsEquipmentServiceImpl.setData() - String key = "rydw_userId_:" + constructionUser.getSysUserId(); - SysUserVo cacheUserVo = RedisUtils.getCacheObject(key); - if (cacheUserVo == null){ - continue; - } - //去判断是什么种类的用户 - list.stream().filter(item -> item.getSysUserId().equals(constructionUser.getSysUserId())) - .findFirst().ifPresent(item -> { - if (item.getUserRole().equals("0")){ - cacheUserVo.setJslx("施工"); - sg.getAndIncrement(); - } else if (item.getUserRole().equals("2")) { - cacheUserVo.setJslx("分包"); - fb.getAndIncrement(); - } - }); - info.add(cacheUserVo); - }else { - lx++; - } - } - //将数据返回 - Map map = new HashMap<>(); - map.put("zx", String.valueOf(zx)); - map.put("lx", String.valueOf(lx)); - map.put("fb", String.valueOf(fb.get())); - map.put("sg", String.valueOf(sg.get())); - map.put("info", info.toString()); - return map; - } - - @Override - public void getAttendanceInfo(Long projectId,Long timeType,Map map){ - //构建数据 - //timeType 1:今天 2:本周 3:本月 - Long zrs = 0L; //总人数 - Long cqr = 0L; //出勤人数 - BigDecimal cql = BigDecimal.ZERO; //出勤率 - //查询此项目的所有人员 - LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); - lqw.eq(SubConstructionUser::getProjectId, projectId); - List list = subConstructionUserService.list(lqw); - //根据分包和班组的id进行分类 统计都有多少人 未入场人员没有两项数据 无法统计 仅能计算为总数 - List fbList = new ArrayList<>(); - List bzList = new ArrayList<>(); - for (SubConstructionUser constructionUser : list) { - //统计该项目下的分包和班组各应有多少人 - if (constructionUser.getTeamId() != null && constructionUser.getContractorId() != null){ - //两个都有的情况 - //根据其角色来区分 0-施工人员 1-管理人员 2-分包管理人员 - if (constructionUser.getUserRole().equals("2")){ - checkAndSetValue(fbList,constructionUser,2,timeType,projectId); - } else if (constructionUser.getUserRole().equals("0")) { - checkAndSetValue(bzList,constructionUser,0,timeType,projectId); - } - }else if (constructionUser.getTeamId() == null && constructionUser.getContractorId() != null){ - //班组为空,分包不为空的情况 两个都为空不统计 班组不空分包空 不存在这种情况 - //根据其角色来区分 0-施工人员 1-管理人员 2-分包管理人员 - if (constructionUser.getUserRole().equals("2")){ - checkAndSetValue(fbList,constructionUser,2,timeType,projectId); - } - } - } - - //总人数 - zrs = (long) fbList.size() + bzList.size(); - //总出勤人 - long fbcqr = 0L; - long bzcqr = 0L; - //统计两个列表里的 到岗率 - for (RyglWebSocketVo vo : fbList) { - if (vo.getZrs()>0){ - vo.setDgl( - //到岗人数/总人数 四舍五入 一位小数 - BigDecimal.valueOf(vo.getDgrs()).divide(BigDecimal.valueOf(vo.getZrs()), 1, RoundingMode.HALF_UP) - ); - }else { - vo.setDgl(BigDecimal.ZERO); - } - fbcqr = fbcqr + vo.getDgrs(); - } - - for (RyglWebSocketVo vo : bzList) { - if (vo.getZrs()>0){ - vo.setDgl( - //到岗人数/总人数 四舍五入 一位小数 - BigDecimal.valueOf(vo.getDgrs()).divide(BigDecimal.valueOf(vo.getZrs()), 1, RoundingMode.HALF_UP) - ); - }else { - vo.setDgl(BigDecimal.ZERO); - } - bzcqr = bzcqr + vo.getDgrs(); - } - //出勤率 - if (zrs != 0L){ - cql = (BigDecimal.valueOf(fbcqr).add(BigDecimal.valueOf(bzcqr))).divide(BigDecimal.valueOf(zrs),1,RoundingMode.HALF_UP); - } - - map.put("zrs", zrs.toString()); - map.put("cqr", cqr.toString()); - map.put("cql", cql.toString()); - map.put("fb", fbList.toString()); - map.put("bz", bzList.toString()); - } - - /** - * getAttendanceInfo附属方法 - */ - private void checkAndSetValue(List ryglWebSocketVoList, SubConstructionUser info,int type,Long time,Long projectId){ - //timeType 1:今天 2:本周 3:本月 此参数的校验放在连接时获取参数进行校验 - if (time == 2L){ - time = 7L; - }else if (time == 3L){ - time = 30L; - } - - Long finalTime = time; - switch (type){ - case 2 -> { - //分包 - //首先判断传入的列表中是否存在该条数据 - ryglWebSocketVoList.stream().filter(item -> item.getZzId().equals(info.getContractorId())).findFirst().ifPresentOrElse( - item -> { - item.setZrs(item.getZrs() + finalTime); - item.setDgrs(item.getDgrs() + getDgrs(info.getSysUserId(),finalTime,projectId)); - }, () -> { - RyglWebSocketVo ryglWebSocketVo = new RyglWebSocketVo(); - //分包组织id - ryglWebSocketVo.setZzId(info.getContractorId()); - //分包组织名称 - if (info.getContractorId() != null) { - SubContractor byId = subContractorService.getById(info.getContractorId()); - if (byId != null) { - ryglWebSocketVo.setZzmc(byId.getName()); - } - } - //总人数 先设置1 - ryglWebSocketVo.setZrs(finalTime); - ryglWebSocketVo.setDgrs(getDgrs(info.getSysUserId(),finalTime,projectId)); - ryglWebSocketVoList.add(ryglWebSocketVo); - }); - } - - case 0 -> - //班组 - //首先判断传入的列表中是否存在该条数据 - ryglWebSocketVoList.stream().filter(item -> item.getZzId().equals(info.getTeamId())).findFirst().ifPresentOrElse( - item -> { - item.setZrs(item.getZrs() + finalTime); - item.setDgrs(item.getDgrs() + getDgrs(info.getSysUserId(),finalTime,projectId)); - }, () -> { - RyglWebSocketVo ryglWebSocketVo = new RyglWebSocketVo(); - //分包组织id - ryglWebSocketVo.setZzId(info.getContractorId()); - //分包组织名称 - ryglWebSocketVo.setZzmc(info.getTeamName()); - //总人数 先设置1 - ryglWebSocketVo.setZrs(finalTime); - ryglWebSocketVo.setDgrs(getDgrs(info.getSysUserId(),finalTime,projectId)); - ryglWebSocketVoList.add(ryglWebSocketVo); - }); - } - } - - // 出勤状态(正常、迟到、早退) - private static final Set ATTENDANCE_STATUS = new HashSet<>(Arrays.asList(BusAttendanceClockStatusEnum.NORMAL.getValue(), - LATE.getValue(), LEAVEEARLY.getValue() - , BusAttendanceClockStatusEnum.REISSUE.getValue())); - - /** - * 获取到岗人数 根据时间类型 返回到岗次数 - */ - private Long getDgrs(Long userId,Long time,Long projectId) { - // 今天所有用户的打卡记录 - List attendanceList; - LambdaQueryWrapper lqw = new LambdaQueryWrapper() - .eq(BusAttendance::getProjectId, projectId) - .eq(BusAttendance::getUserId, userId); - if (time == 1L) { - lqw.eq(BusAttendance::getClockDate, LocalDate.now()); - } else if (time == 2L) { - // 获取本周一和周日的日期 - LocalDate today = LocalDate.now(); - LocalDate monday = today.with(DayOfWeek.MONDAY); - LocalDate sunday = today.with(DayOfWeek.SUNDAY); - lqw.between(BusAttendance::getClockDate, monday, sunday); - }else if (time == 3L){ - // 获取本月第一天和最后一天 - LocalDate today = LocalDate.now(); - LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth()); - LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth()); - lqw.between(BusAttendance::getClockDate, firstDayOfMonth, lastDayOfMonth); - } - lqw.in(BusAttendance::getClockStatus, ATTENDANCE_STATUS) - .apply(" user_id not in (select sys_user_id from sub_construction_user where project_id = {0} and user_role != '0' )", projectId); - attendanceList = busAttendanceService.list(lqw); - if (attendanceList == null || attendanceList.isEmpty()){ - return 0L; - } - - final Long[] count = {0L}; - //根据日期分组 - Map> collect = attendanceList.stream().collect(Collectors.groupingBy(BusAttendance::getClockDate)); - collect.forEach((key, value) -> { - //每一天分组 同一天去重 - List list = value.stream().map(BusAttendance::getUserId).distinct().toList(); - count[0] = count[0] + list.size(); - }); - - return count[0]; - } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/BigScreenWebSocketServer.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/BigScreenWebSocketServer.java index b241eb9c..39b539fb 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/BigScreenWebSocketServer.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/BigScreenWebSocketServer.java @@ -14,7 +14,7 @@ import org.dromara.cailiaoshebei.service.IBusPurchaseDocService; import org.dromara.common.core.utils.SpringUtils; import org.dromara.materials.domain.vo.materials.MatMaterialsUseDetailVo; import org.dromara.materials.service.IMatMaterialsService; -import org.dromara.system.service.impl.SysUserServiceImpl; +import org.dromara.project.service.impl.BusAttendanceServiceImpl; import org.springframework.stereotype.Component; import java.io.IOException; @@ -86,7 +86,7 @@ public class BigScreenWebSocketServer { IBusPurchaseDocService purchaseDocService = SpringUtils.getBean(IBusPurchaseDocService.class); IBusMrpBaseService mrpBaseService = SpringUtils.getBean(IBusMrpBaseService.class); IMatMaterialsService materialsService = SpringUtils.getBean(IMatMaterialsService.class); - SysUserServiceImpl sysUserService = SpringUtils.getBean(SysUserServiceImpl.class); + BusAttendanceServiceImpl busAttendanceService = SpringUtils.getBean(BusAttendanceServiceImpl.class); Long projectId = Long.parseLong(split[0]); long type = Long.parseLong(split[1]); @@ -100,9 +100,9 @@ public class BigScreenWebSocketServer { throw new RuntimeException("时间类型参数错误"); } //先获取左边坐标得到map - Map infoData = sysUserService.getRyglOnlineUserInfoData(projectId); + Map infoData = busAttendanceService.getRyglOnlineUserInfoData(projectId); //获取右边数据 - sysUserService.getAttendanceInfo(projectId,timeType,infoData); + busAttendanceService.getAttendanceInfo(projectId,timeType,infoData); //返回数据 maps.add(infoData); break; diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/MessageWebSocketServer.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/MessageWebSocketServer.java new file mode 100644 index 00000000..28f4afcf --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/service/MessageWebSocketServer.java @@ -0,0 +1,207 @@ +package org.dromara.websocket.websocket.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import jakarta.websocket.*; +import jakarta.websocket.server.ServerEndpoint; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.model.LoginUser; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.service.impl.SysOssServiceImpl; +import org.dromara.system.service.impl.SysUserServiceImpl; +import org.dromara.websocket.domain.ChatGroup; +import org.dromara.websocket.domain.ChatHistory; +import org.dromara.websocket.domain.enums.ChatRoomEnum; +import org.dromara.websocket.service.Impl.ChatGroupServiceImpl; +import org.dromara.websocket.service.Impl.ChatHistoryServiceImpl; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 项目启动即自动启动的 WebSocket 服务端 + * 端点路径:/websocket/message(可自定义) + */ +@Slf4j +@ServerEndpoint("/websocket/message") // 定义 WebSocket 端点路径 +@Component +public class MessageWebSocketServer { + + private static ChatHistoryServiceImpl chatHistoryService = SpringUtils.getBean(ChatHistoryServiceImpl.class); + private static ChatGroupServiceImpl chatGroupService = SpringUtils.getBean(ChatGroupServiceImpl.class); + private static SysUserServiceImpl sysUserService = SpringUtils.getBean(SysUserServiceImpl.class); + private static SysOssServiceImpl sysOssService = SpringUtils.getBean(SysOssServiceImpl.class); + + // 2. 静态会话存储(线程安全,项目启动时即初始化) + private static final Map ONLINE_SESSIONS = new ConcurrentHashMap<>(); + + + // 3. 静态代码块:项目启动时执行(初始化资源、打印启动日志) + static { + // 此处可添加启动时的初始化逻辑(如加载配置、连接外部资源等) + log.info("✅ 聊天&信息WebSocket 服务端已随项目启动初始化!端点路径:/websocket/message"); + } + + // 使用内存映射替代Redis存储 + private static final ConcurrentHashMap> userSessionMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap sessionUserMap = new ConcurrentHashMap<>(); + //维护用户房间未读数量的映射表 用户+房间->Count + private static final ConcurrentHashMap userRoomCountMap = new ConcurrentHashMap<>(); + //维护一个在线用户列表 + private static final List onlineUserList = new ArrayList<>(); + //对外部暴露方法 + public static List getOnlineUserList() { + return onlineUserList; + } + + /** + * 客户端连接时触发(无需手动启动,有客户端连接时自动调用) + */ + @OnOpen + public void onOpen(Session session) { + // 存储新会话 + ONLINE_SESSIONS.put(session.getId(), session); + log.info("📌 客户端连接成功!会话ID:{},当前在线数:{}", session.getId(), ONLINE_SESSIONS.size()); + // 2. 异步获取并推送初始化数据(避免阻塞连接) + CompletableFuture.runAsync(() -> { + try { + //连接成功过后 获取当前连接携带的token + Map> params = session.getRequestParameterMap(); + String token; + List tokens = params.get("token"); + if (tokens != null && !tokens.isEmpty()) { + token = tokens.getFirst(); + }else { + throw new RuntimeException("未获取到token"); + } + + LoginUser loginUser = LoginHelper.getLoginUser(token.replace("Bearer ", "")); +// log.info("token:{}",token.replace("Bearer%20", "")); +// log.info("用户信息:{}", loginUser); + if (loginUser == null){ + throw new RuntimeException("token过期或失效,请重新获取"); + } + + //userChannelMap -> userSessionMap channelHandlerContexts -> sessions channelUserMap -> sessionUserMap + + //判断是否存在该账号的通道实例列表 + List sessions = userSessionMap.computeIfAbsent(loginUser.getUserId().toString(), k -> new ArrayList<>()); +// List sessions = userSessionMap.get(loginUser.getUserId().toString()); + if (!sessions.contains(session)) { + sessions.add(session); + } + + //把该账号的session实例列表跟账号id关联 一个账号有多个通道实例 + userSessionMap.put(loginUser.getUserId().toString(), sessions); + //把通道实例跟账号id关联 + sessionUserMap.put(session, loginUser.getUserId().toString()); + + if (!onlineUserList.contains(loginUser.getUserId().toString())) { + onlineUserList.add(loginUser.getUserId().toString()); + } +// Channel channel = ctx.channel(); +// channelGroup.add(channel); + + //构建各个聊天房间未读 数量 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(ChatGroup::getMembers, loginUser.getUserId() + ",").or().like(ChatGroup::getMembers, loginUser.getUserId() + "]"); + //拿到该用户所参与的房间列表 + List chatGroups = chatGroupService.list(queryWrapper); + boolean isHaveSystemRoom = false; + if (chatGroups != null && !chatGroups.isEmpty()) { + HashMap roomCounts = new HashMap<>(); + for (ChatGroup chatGroup : chatGroups) { + LambdaQueryWrapper historyLambdaQueryWrapper = new LambdaQueryWrapper<>(); + historyLambdaQueryWrapper.eq(ChatHistory::getGeterId, chatGroup.getId()); + historyLambdaQueryWrapper.ne(ChatHistory::getSenderId, loginUser.getUserId()); + historyLambdaQueryWrapper.eq(ChatHistory::getIsRead, "1"); + List list = chatHistoryService.list(historyLambdaQueryWrapper); + if (list != null && !list.isEmpty()) { + roomCounts.put(loginUser.getUserId() + "+" + chatGroup.getId().toString(), list.size()); + //连接后同步未读消息到内存中 + userRoomCountMap.put(loginUser.getUserId() + "+" + chatGroup.getId().toString(), list.size()); + } + //在遍历的同时寻找是否有系统消息房间 + if (!isHaveSystemRoom && chatGroup.getMembers().contains("[" + ChatRoomEnum.SYSTEM.getRoomId())) { + isHaveSystemRoom = true; + } + } + JSONObject message = new JSONObject(); + message.put("type", "3"); + message.put("messageType", "txt"); + message.put("unReadCount", roomCounts); +// log.info("发送所有未读消息:{}", message); + if (message.get("unReadCount") != null && !roomCounts.isEmpty()) { +// sendMessage(ctx, message.toJSONString()); + } + } + + } catch (Exception e) { + log.error("会话[{}]初始化数据处理失败", session.getId(), e); + } + }); + } + + /** + * 接收客户端消息 + */ + @OnMessage + public void onMessage(String message, Session session) { + log.info("📥 收到会话[{}]消息:{}", session.getId(), message); + // 可选:回复客户端(示例) + + } + + /** + * 客户端断开连接 + */ + @OnClose + public void onClose(Session session, CloseReason reason) { + ONLINE_SESSIONS.remove(session.getId()); + log.info("🔌 客户端断开连接!会话ID:{},原因:{},当前在线数:{}", + session.getId(), reason.getReasonPhrase(), ONLINE_SESSIONS.size()); + } + + /** + * 连接异常 + */ + @OnError + public void onError(Session session, Throwable error) { + log.error("⚠️ 会话[{}]异常:{}", session.getId(), error.getMessage(), error); + } + + // ------------------------------ 工具方法(可选,供其他服务调用) ------------------------------ + /** + * 向所有在线客户端发送消息(项目启动后,其他服务可直接调用) + */ + public static void sendToAll(String message) { + if (ONLINE_SESSIONS.isEmpty()) { + log.warn("⚠️ 无在线客户端,无需发送消息"); + return; + } + ONLINE_SESSIONS.values().forEach(session -> { + if (session.isOpen()) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + log.error("📤 向会话[{}]发送消息失败:{}", session.getId(), e.getMessage()); + } + } + }); + } + + /** + * 获取当前在线数(供外部查询) + */ + public static int getOnlineCount() { + return ONLINE_SESSIONS.size(); + } + +}