12-16-人员管理大屏-修复循环注入版

This commit is contained in:
2025-12-16 18:06:07 +08:00
parent b35f6d6fd2
commit 7bc2f1c832
9 changed files with 484 additions and 282 deletions

View File

@ -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<BusAttendance, BusAt
Page<BusAttendanceVo> queryPageList(BusAttendanceBo bo, PageQuery pageQuery);
@Select("select * from sub_contractor where id = #{id}")
SubContractor getSubContractor(Long id);
}

View File

@ -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<BusAttendance>{
*/
Long getAttendanceUserCountByDate(Long projectId,LocalDate startDate, LocalDate endDate);
Map<String, String> getRyglOnlineUserInfoData(Long projectId);
void getAttendanceInfo(Long projectId,Long timeType,Map<String, String> map);
}

View File

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

View File

@ -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<BusAttendanceMapper, B
private final ChatServerHandler chatServerHandler;
private final ISubUserSalaryDetailService userSalaryDetailService;
// private final ISubUserSalaryDetailService userSalaryDetailService;
private final IBusWorkWageService workWageService;
@Resource
private IdCardEncryptorUtil idCardEncryptorUtil;
// @Resource
// private ISubContractorService subContractorService;
// 出勤状态(正常、迟到、早退)
@ -2766,4 +2771,247 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
return list.stream().map(BusAttendance::getUserId).distinct().count();
}
/**
* 获取手机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 = constructionUserService.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 = constructionUserService.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());
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<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 = this.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

@ -98,6 +98,9 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl<BusProjectTeamM
@Lazy
private ISubContractorService subContractorService;
// @Resource
// private IBusAttendanceService busAttendanceService;
// @Resource
// private ClarityPmAsyncMethod clarityPmAsyncMethod;

View File

@ -321,8 +321,4 @@ public interface ISysUserService {
void deleteContractorIdByUserId(Long userId);
Map<String, String> getRyglOnlineUserInfoData(Long projectId);
void getAttendanceInfo(Long projectId,Long timeType,Map<String, String> map);
}

View File

@ -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<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
@ -1592,246 +1577,6 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
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

@ -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<String, String> infoData = sysUserService.getRyglOnlineUserInfoData(projectId);
Map<String, String> infoData = busAttendanceService.getRyglOnlineUserInfoData(projectId);
//获取右边数据
sysUserService.getAttendanceInfo(projectId,timeType,infoData);
busAttendanceService.getAttendanceInfo(projectId,timeType,infoData);
//返回数据
maps.add(infoData);
break;

View File

@ -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<String, Session> ONLINE_SESSIONS = new ConcurrentHashMap<>();
// 3. 静态代码块:项目启动时执行(初始化资源、打印启动日志)
static {
// 此处可添加启动时的初始化逻辑(如加载配置、连接外部资源等)
log.info("✅ 聊天信息WebSocket 服务端已随项目启动初始化!端点路径:/websocket/message");
}
// 使用内存映射替代Redis存储
private static final ConcurrentHashMap<String, List<Session>> userSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Session, String> sessionUserMap = new ConcurrentHashMap<>();
//维护用户房间未读数量的映射表 用户+房间->Count
private static final ConcurrentHashMap<String, Integer> userRoomCountMap = new ConcurrentHashMap<>();
//维护一个在线用户列表
private static final List<String> onlineUserList = new ArrayList<>();
//对外部暴露方法
public static List<String> 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<String, List<String>> params = session.getRequestParameterMap();
String token;
List<String> 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<Session> sessions = userSessionMap.computeIfAbsent(loginUser.getUserId().toString(), k -> new ArrayList<>());
// List<Session> 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<ChatGroup> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(ChatGroup::getMembers, loginUser.getUserId() + ",").or().like(ChatGroup::getMembers, loginUser.getUserId() + "]");
//拿到该用户所参与的房间列表
List<ChatGroup> chatGroups = chatGroupService.list(queryWrapper);
boolean isHaveSystemRoom = false;
if (chatGroups != null && !chatGroups.isEmpty()) {
HashMap<String, Object> roomCounts = new HashMap<>();
for (ChatGroup chatGroup : chatGroups) {
LambdaQueryWrapper<ChatHistory> historyLambdaQueryWrapper = new LambdaQueryWrapper<>();
historyLambdaQueryWrapper.eq(ChatHistory::getGeterId, chatGroup.getId());
historyLambdaQueryWrapper.ne(ChatHistory::getSenderId, loginUser.getUserId());
historyLambdaQueryWrapper.eq(ChatHistory::getIsRead, "1");
List<ChatHistory> 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();
}
}