12-16-人员管理大屏

This commit is contained in:
2025-12-16 17:25:31 +08:00
parent ea626dfeff
commit b35f6d6fd2
6 changed files with 326 additions and 6 deletions

View File

@ -94,6 +94,7 @@ public class SysRoleWorkServiceImpl extends ServiceImpl<SysRoleWorkMapper, SysRo
LambdaQueryWrapper<SysRoleWork> 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());

View File

@ -208,4 +208,9 @@ public class SysUserVo implements Serializable {
*/
private String avatarUrl;
/**
* 角色类型 分包 施工
*/
private String jslx;
}

View File

@ -320,4 +320,9 @@ public interface ISysUserService {
List<SysUser> selectUserListByContractorId(Long contractorId);
void deleteContractorIdByUserId(Long userId);
Map<String, String> getRyglOnlineUserInfoData(Long projectId);
void getAttendanceInfo(Long projectId,Long timeType,Map<String, String> map);
}

View File

@ -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<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
Page<SysUserVo> 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<String, String> getRyglOnlineUserInfoData(Long projectId){
//获取该项目的在线人员 再分辨出种类
//先获取该项目所有人员
LambdaQueryWrapper<SubConstructionUser> lqw = new LambdaQueryWrapper<>();
lqw.eq(SubConstructionUser::getProjectId, projectId);
//未入场人员也要统计坐标 打卡不统计(存疑)
// lqw.and(lqw1 ->
// lqw1.isNotNull(SubConstructionUser::getTeamId)
// .or()
// .isNotNull(SubConstructionUser::getContractorId));
List<SubConstructionUser> list = subConstructionUserService.list(lqw);
//再去从聊天服务中获取在线的ID
List<String> onlineUserList = ChatServerHandler.getOnlineUserList();
//构建将要返回的 数据
List<SysUserVo> 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<String, String> 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<String, String> map){
//构建数据
//timeType 1:今天 2:本周 3:本月
Long zrs = 0L; //总人数
Long cqr = 0L; //出勤人数
BigDecimal cql = BigDecimal.ZERO; //出勤率
//查询此项目的所有人员
LambdaQueryWrapper<SubConstructionUser> lqw = new LambdaQueryWrapper<>();
lqw.eq(SubConstructionUser::getProjectId, projectId);
List<SubConstructionUser> list = subConstructionUserService.list(lqw);
//根据分包和班组的id进行分类 统计都有多少人 未入场人员没有两项数据 无法统计 仅能计算为总数
List<RyglWebSocketVo> fbList = new ArrayList<>();
List<RyglWebSocketVo> 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<RyglWebSocketVo> 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<String> 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<BusAttendance> attendanceList;
LambdaQueryWrapper<BusAttendance> lqw = new LambdaQueryWrapper<BusAttendance>()
.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<LocalDate, List<BusAttendance>> collect = attendanceList.stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
collect.forEach((key, value) -> {
//每一天分组 同一天去重
List<Long> list = value.stream().map(BusAttendance::getUserId).distinct().toList();
count[0] = count[0] + list.size();
});
return count[0];
}
}

View File

@ -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;
}

View File

@ -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<String, List<String>> params = session.getRequestParameterMap();
Long timeType;
List<String> 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<Map<String, String>> 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<String, String> infoData = sysUserService.getRyglOnlineUserInfoData(projectId);
//获取右边数据
sysUserService.getAttendanceInfo(projectId,timeType,infoData);
//返回数据
maps.add(infoData);
break;
case 3:
break;