diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/bigscreen/service/impl/SysRoleWorkServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/bigscreen/service/impl/SysRoleWorkServiceImpl.java index 040b3a9b..cae4e236 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/bigscreen/service/impl/SysRoleWorkServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/bigscreen/service/impl/SysRoleWorkServiceImpl.java @@ -94,6 +94,7 @@ public class SysRoleWorkServiceImpl extends ServiceImpl lqw = Wrappers.lambdaQuery(); lqw.orderByDesc(SysRoleWork::getCreateTime); lqw.eq(bo.getRoleId() != null, SysRoleWork::getRoleId, bo.getRoleId()); + lqw.eq(bo.getProjectId() != null, SysRoleWork::getProjectId, bo.getProjectId()); lqw.eq(StringUtils.isNotBlank(bo.getLcmc()), SysRoleWork::getLcmc, bo.getLcmc()); lqw.eq(StringUtils.isNotBlank(bo.getLcms()), SysRoleWork::getLcms, bo.getLcms()); lqw.eq(StringUtils.isNotBlank(bo.getLcxq()), SysRoleWork::getLcxq, bo.getLcxq()); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java index 24db5876..e17d7c72 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java @@ -208,4 +208,9 @@ public class SysUserVo implements Serializable { */ private String avatarUrl; + /** + * 角色类型 分包 施工 + */ + private String jslx; + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java index ed7d7a13..e27c6a70 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java @@ -320,4 +320,9 @@ public interface ISysUserService { List selectUserListByContractorId(Long contractorId); void deleteContractorIdByUserId(Long userId); + + 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/system/service/impl/SysUserServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index 270cc271..f812d048 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,6 +26,7 @@ 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; @@ -38,11 +39,14 @@ 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.*; @@ -58,6 +62,8 @@ 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; @@ -65,9 +71,17 @@ 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; + /** * 用户 业务层处理 * @@ -120,6 +134,13 @@ public class SysUserServiceImpl implements ISysUserService, UserService { @Resource private ISysRoleService roleService; + @Resource + private ISubConstructionUserService subConstructionUserService; + @Resource + private ISubContractorService subContractorService; + @Resource + private IBusAttendanceService busAttendanceService; + @Override public TableDataInfo selectPageUserList(SysUserBo user, PageQuery pageQuery) { Page page = baseMapper.selectPageUserList(pageQuery.build(), this.buildQueryWrapper(user)); @@ -1570,4 +1591,247 @@ public class SysUserServiceImpl implements ISysUserService, UserService { wrapper.eq(SysUser::getUserId, userId).set(SysUser::getContractorId, null); 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/domain/vo/RyglWebSocketVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/domain/vo/RyglWebSocketVo.java new file mode 100644 index 00000000..87910217 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/websocket/websocket/domain/vo/RyglWebSocketVo.java @@ -0,0 +1,35 @@ +package org.dromara.websocket.websocket.domain.vo; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class RyglWebSocketVo { + + /** + * 组织id + */ + private Long zzId; + + /** + * 组织名称 + */ + private String zzmc; + + /** + * 总人数 + */ + private Long zrs; + + /** + * 到岗人数 + */ + private Long dgrs; + + /** + * 到岗率 + */ + private BigDecimal dgl; + +} 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 ab35926a..b241eb9c 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 @@ -12,12 +12,9 @@ import org.dromara.cailiaoshebei.domain.vo.BusPurchaseDocVo; import org.dromara.cailiaoshebei.service.IBusMrpBaseService; import org.dromara.cailiaoshebei.service.IBusPurchaseDocService; import org.dromara.common.core.utils.SpringUtils; -import org.dromara.gps.domain.bo.GpsEquipmentSonBo; -import org.dromara.gps.domain.vo.GpsEquipmentSonVo; -import org.dromara.gps.service.IGpsEquipmentSonService; import org.dromara.materials.domain.vo.materials.MatMaterialsUseDetailVo; import org.dromara.materials.service.IMatMaterialsService; -import org.dromara.websocket.websocket.domain.vo.VehicleVo; +import org.dromara.system.service.impl.SysUserServiceImpl; import org.springframework.stereotype.Component; import java.io.IOException; @@ -28,8 +25,6 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import static kotlin.reflect.jvm.internal.impl.builtins.StandardNames.FqNames.list; - /** * 大屏 WebSocket 服务端(支持订阅消息) * 端点路径:/websocket/bigScreen @@ -63,15 +58,18 @@ public class BigScreenWebSocketServer { public void onOpen(Session session) { // 从连接URL的查询参数中获取订阅ID(客户端连接格式:ws://xxx/websocket/bigScreen?subscriptionId=123) Map> params = session.getRequestParameterMap(); + Long timeType; List subscriptionIds = params.get("subscriptionId"); if (subscriptionIds != null && !subscriptionIds.isEmpty()) { this.currentSubscriptionId = subscriptionIds.get(0); // 取第一个订阅ID + timeType = Long.parseLong(params.get("timeType").getFirst()); // 建立映射关系 SUBSCRIPTION_SESSIONS.put(currentSubscriptionId, session); SESSION_TO_SUBSCRIPTION.put(session.getId(), currentSubscriptionId); log.info("📌 客户端订阅成功!订阅ID:{},会话ID:{},当前订阅数:{}", currentSubscriptionId, session.getId(), SUBSCRIPTION_SESSIONS.size()); } else { + timeType = null; log.warn("📌 客户端连接未携带订阅ID!会话ID:{}", session.getId()); } @@ -88,6 +86,8 @@ 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); + Long projectId = Long.parseLong(split[0]); long type = Long.parseLong(split[1]); List> maps = new ArrayList<>(); @@ -95,6 +95,16 @@ public class BigScreenWebSocketServer { case 1: break; case 2: + //判断参数 + if (timeType == null || (timeType != 1L && timeType != 2L && timeType != 3L)){ + throw new RuntimeException("时间类型参数错误"); + } + //先获取左边坐标得到map + Map infoData = sysUserService.getRyglOnlineUserInfoData(projectId); + //获取右边数据 + sysUserService.getAttendanceInfo(projectId,timeType,infoData); + //返回数据 + maps.add(infoData); break; case 3: break;