12-01-人员定位

This commit is contained in:
2025-12-02 21:57:46 +08:00
parent 3efa899a97
commit 03cf7ebac7
4 changed files with 275 additions and 36 deletions

View File

@ -52,9 +52,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.Duration; import java.time.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
/** /**
@ -605,6 +603,29 @@ public class GpsEquipmentServiceImpl extends ServiceImpl<GpsEquipmentMapper, Gps
WebSocketUtils.publishAll(pushContent); WebSocketUtils.publishAll(pushContent);
} }
} }
//接收一次则将存入Redis中数据累加1
// 获取当天结束时间
LocalDate today = LocalDate.now();
LocalDateTime endOfDay = LocalDateTime.of(today, LocalTime.MAX); // 23:59:59.999999999
long expireAt = endOfDay.atZone(ZoneId.systemDefault()).toEpochSecond();
// 计算剩余秒数
long now = System.currentTimeMillis() / 1000;
long expireSeconds = expireAt - now;
//检查是否存在
boolean rydw = RedisUtils.isExistsObject("rydw_cs");
if (!rydw) {
RedisUtils.setCacheObject("rydw_cs",0L,Duration.ofSeconds(expireSeconds));
}else {
Long cacheObject = RedisUtils.getCacheObject("rydw_cs");
cacheObject = cacheObject + 1;
RedisUtils.setCacheObject("rydw_cs",cacheObject,true);
}
//将打卡用户存入Redis中 过期时间为当天23:59:59
setPersonToRedis(gpsEquipment,expireSeconds);
// 保存到Redis并设置过期监听 // 保存到Redis并设置过期监听
updateDeviceAliveStatus(gpsEquipment.getClientId()); updateDeviceAliveStatus(gpsEquipment.getClientId());
} }
@ -645,5 +666,18 @@ public class GpsEquipmentServiceImpl extends ServiceImpl<GpsEquipmentMapper, Gps
return gpsEquipmentSonService.getUserOrClientDate(bo); return gpsEquipmentSonService.getUserOrClientDate(bo);
} }
/**
* 打卡人员存入redis
*/
private void setPersonToRedis(GpsEquipment entity,long time) {
String key = "rydw_userId_:" + entity.getUserId();
// 先删除可能存在的旧监听器
if (RedisUtils.isExistsObject(key)) {
RedisUtils.deleteObject(key);
}
RedisUtils.setCacheObject(key, entity, Duration.ofSeconds(time));
}
} }

View File

@ -12,6 +12,7 @@ import org.dromara.system.domain.SysUser;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -166,4 +167,29 @@ public class SysUserVo implements Serializable {
* 打卡(0启用打卡 1禁止打卡) * 打卡(0启用打卡 1禁止打卡)
*/ */
private String clock; private String clock;
/**
* 区域
*/
private String qy;
/**
* 在线状态
*/
private String zxzt;
/**
* 最后定位时间
*/
private Date zhdwsj;
/**
* 经度
*/
private BigDecimal jd;
/**
* 纬度
*/
private BigDecimal wd;
} }

View File

@ -237,7 +237,7 @@ public class ChatServerHandler extends SimpleChannelInboundHandler<TextWebSocket
Channel channel = ctx.channel(); Channel channel = ctx.channel();
// 实际上ChannelGroup会自动移除断开连接的channel这里只是演示 // 实际上ChannelGroup会自动移除断开连接的channel这里只是演示
// 向所有已连接的客户端广播客户端离开的消息 // 向所有已连接的客户端广播客户端离开的消息
channelGroup.writeAndFlush(new TextWebSocketFrame("[系统消息] " + sdf.format(new Date()) + "" + channel.remoteAddress() + " 离开聊天\n")); // channelGroup.writeAndFlush(new TextWebSocketFrame("[系统消息] " + sdf.format(new Date()) + "" + channel.remoteAddress() + " 离开聊天\n"));
channelGroup.remove(channel); channelGroup.remove(channel);
log.info("{} 离开聊天,剩余在线人数:{}", channel.remoteAddress(), channelGroup.size()); log.info("{} 离开聊天,剩余在线人数:{}", channel.remoteAddress(), channelGroup.size());

View File

@ -1,19 +1,33 @@
package org.dromara.websocket.websocket.service;// 路径com.ruoyi.web.websocket.InitOnStartWebSocketServer package org.dromara.websocket.websocket.service;// 路径com.ruoyi.web.websocket.InitOnStartWebSocketServer
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import jakarta.websocket.*; import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint; import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.websocket.utils.WebSocketUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.domain.GeoPoint;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.utils.JSTUtil;
import org.dromara.contractor.domain.SubConstructionUser; import org.dromara.contractor.domain.SubConstructionUser;
import org.dromara.contractor.service.impl.SubConstructionUserServiceImpl; import org.dromara.contractor.service.impl.SubConstructionUserServiceImpl;
import org.dromara.gps.domain.GpsEquipmentSon; import org.dromara.gps.domain.GpsEquipmentSon;
import org.dromara.gps.service.impl.GpsEquipmentSonServiceImpl; import org.dromara.project.domain.BusProjectPunchrange;
import org.springframework.beans.factory.annotation.Autowired; import org.dromara.project.service.impl.BusProjectPunchrangeServiceImpl;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.impl.SysUserServiceImpl;
import org.dromara.websocket.ChatServerHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -28,10 +42,15 @@ import java.util.concurrent.ConcurrentHashMap;
@Component @Component
public class RydwWebSocketServer { public class RydwWebSocketServer {
@Autowired // @Autowired
private SubConstructionUserServiceImpl subConstructionUserService; // private SubConstructionUserServiceImpl subConstructionUserService;
@Autowired private final static SubConstructionUserServiceImpl subConstructionUserService = SpringUtils.getBean(SubConstructionUserServiceImpl.class);
private GpsEquipmentSonServiceImpl gpsEquipmentSonService; // @Autowired
// private BusProjectPunchrangeServiceImpl busProjectPunchrangeService;
private final static BusProjectPunchrangeServiceImpl busProjectPunchrangeService = SpringUtils.getBean(BusProjectPunchrangeServiceImpl.class);
// @Autowired
// private SysUserServiceImpl sysUserService;
private final static SysUserServiceImpl sysUserService = SpringUtils.getBean(SysUserServiceImpl.class);
// 2. 静态会话存储(线程安全,项目启动时即初始化) // 2. 静态会话存储(线程安全,项目启动时即初始化)
private static final Map<String, Session> ONLINE_SESSIONS = new ConcurrentHashMap<>(); private static final Map<String, Session> ONLINE_SESSIONS = new ConcurrentHashMap<>();
@ -54,34 +73,15 @@ public class RydwWebSocketServer {
// 2. 异步获取并推送初始化数据(避免阻塞连接) // 2. 异步获取并推送初始化数据(避免阻塞连接)
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
try { try {
//初始化
Map<String,Object> map;
//连接成功过后 获取当前项目下所有成员最新坐标 //连接成功过后 获取当前项目下所有成员最新坐标
Map<String, List<String>> params = session.getRequestParameterMap(); Map<String, List<String>> params = session.getRequestParameterMap();
List<String> subscriptionIds = params.get("projectId"); List<String> subscriptionIds = params.get("projectId");
if (subscriptionIds != null && !subscriptionIds.isEmpty()){ String projectId = subscriptionIds.getFirst();
//拿到所有人员 map = getData(projectId);
LambdaQueryWrapper<SubConstructionUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SubConstructionUser::getProjectId, subscriptionIds.getFirst()); session.getBasicRemote().sendText(JSON.toJSONString(map));
List<SubConstructionUser> list = subConstructionUserService.list(queryWrapper);
//拿到所有人员最新坐标
if (list != null && !list.isEmpty()){
LambdaQueryWrapper<GpsEquipmentSon> lqw = new LambdaQueryWrapper<>();
List<GpsEquipmentSon> gpsList = new ArrayList<>();
for (SubConstructionUser constructionUser : list) {
lqw.clear();
lqw.eq(GpsEquipmentSon::getUserId, constructionUser.getSysUserId());
lqw.orderByDesc(GpsEquipmentSon::getCreateTime);
lqw.last("limit 1");
GpsEquipmentSon one = gpsEquipmentSonService.getOne(lqw);
if (one != null){
gpsList.add(one);
}
}
if (!gpsList.isEmpty()){
WebSocketUtils.sendMessage(Long.valueOf(session.getId()), gpsList.toString());
}
}
}
WebSocketUtils.sendMessage(Long.valueOf(session.getId()), "初始化数据为空");
} catch (Exception e) { } catch (Exception e) {
log.error("会话[{}]初始化数据处理失败", session.getId(), e); log.error("会话[{}]初始化数据处理失败", session.getId(), e);
} }
@ -96,6 +96,22 @@ public class RydwWebSocketServer {
log.info("📥 收到会话[{}]消息:{}", session.getId(), message); log.info("📥 收到会话[{}]消息:{}", session.getId(), message);
// 可选:回复客户端(示例) // 可选:回复客户端(示例)
try { try {
/* 解析消息 暂定json字符串
{
"type": "1", 1表示心跳包
"projectId":"", 项目id
"fbdwId":"", 分包单位id
"bzId":"", 班组id
"msg": ""
}
*/
JSONObject jsonObject = JSON.parseObject(message);
if (jsonObject.get("type").equals("1")){
if (jsonObject.get("projectId") != null){
Map<String, Object> objectMap = getData(jsonObject.get("projectId").toString());
session.getBasicRemote().sendText(JSON.toJSONString(objectMap));
}
}
session.getBasicRemote().sendText("服务端已收到消息:" + message); session.getBasicRemote().sendText("服务端已收到消息:" + message);
} catch (IOException e) { } catch (IOException e) {
log.error("📤 回复会话[{}]失败:{}", session.getId(), e.getMessage()); log.error("📤 回复会话[{}]失败:{}", session.getId(), e.getMessage());
@ -146,4 +162,167 @@ public class RydwWebSocketServer {
public static int getOnlineCount() { public static int getOnlineCount() {
return ONLINE_SESSIONS.size(); return ONLINE_SESSIONS.size();
} }
/**
* 获取数据 用于首次和心跳
*/
public Map<String,Object> getData(String projectId){
Map<String, Object> map = new HashMap<>();
if (projectId != null && !projectId.isEmpty()){
//拿到项目下所有人员
LambdaQueryWrapper<SubConstructionUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SubConstructionUser::getProjectId, projectId);
List<SubConstructionUser> list = subConstructionUserService.list(queryWrapper);
//获取此项目打卡范围
LambdaQueryWrapper<BusProjectPunchrange> busProjectPunchrangeLambdaQueryWrapper = new LambdaQueryWrapper<>();
busProjectPunchrangeLambdaQueryWrapper.eq(BusProjectPunchrange::getProjectId, projectId);
//打卡范围
List<BusProjectPunchrange> busProjectPunchranges = busProjectPunchrangeService.list(busProjectPunchrangeLambdaQueryWrapper);
//通过打卡范围构建下方统计数据
List<Map<String, Map>> allValue = new ArrayList<>();
for (BusProjectPunchrange busProjectPunchrange : busProjectPunchranges) {
Map<String,Object> map2 = new HashMap<>();
map2.put("06:00",0);
map2.put("08:00",0);
map2.put("10:00",0);
map2.put("12:00",0);
map2.put("14:00",0);
map2.put("16:00",0);
map2.put("18:00",0);
Map<String,Map> map1 = new HashMap<>();
map1.put(busProjectPunchrange.getId()+":"+busProjectPunchrange.getPunchName(),map2);
allValue.add(map1);
}
int zrys = 0;
int ycry = 0;
int dwcs = 0;
int zgry = 0;
if (list != null && !list.isEmpty()){
//初始化
List<SysUserVo> gpsList = new ArrayList<>();
List<String> onlineUserList = ChatServerHandler.getOnlineUserList();
//循环班组人员列表
for (SubConstructionUser constructionUser : list) {
//从redis拿到缓存最新坐标
boolean existsObject = RedisUtils.isExistsObject("rydw_userId_:" + constructionUser.getSysUserId());
if (!existsObject){
continue;
}else {
// zgry++;
}
GpsEquipmentSon one = RedisUtils.getCacheObject("rydw_userId_:" + constructionUser.getSysUserId());
if (one != null){
//获取人员
SysUserVo sysUserVo = sysUserService.selectUserById(constructionUser.getSysUserId());
sysUserVo.setJd(one.getLocLongitude());
sysUserVo.setWd(one.getLocLatitude());
sysUserVo.setZhdwsj(one.getCreateTime());
boolean isFind = false;
//循环打卡范围 判断是否是异常状态
for (BusProjectPunchrange busProjectPunchrange : busProjectPunchranges) {
//转换范围
Gson gson = new Gson();
Type listType = new TypeToken<List<String>>() {}.getType();
List<String> coordinates = gson.fromJson(busProjectPunchrange.getPunchRange(), listType);
List<GeoPoint> matchingRange = JSTUtil.findMatchingRange(one.getLocLatitude().toString(), one.getLocLongitude().toString(), coordinates);
//如果范围外为 空
if (matchingRange != null && !matchingRange.isEmpty() && !isFind) {
sysUserVo.setQy(busProjectPunchrange.getPunchName());
isFind = true;
//判断在线状态 如果在线 范围外 则为异常 目前从聊天服务获取
if (onlineUserList.contains(constructionUser.getSysUserId().toString())) {
sysUserVo.setZxzt("在线");
//坐标在范围内在岗
zgry++;
String key = busProjectPunchrange.getId() + ":" + busProjectPunchrange.getPunchName();
//给打卡柱状图赋值
for (Map<String, Map> mapMap : allValue) {
//这个map为 id+名字 : 时间段 MAP
if (mapMap.containsKey(key)){
//找到了时间段map
LocalDateTime dateTime = one.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
// 获取小时数
int hour = dateTime.getHour();
// 根据小时数判断所属区间并增加计数
if (hour >= 6 && hour < 8) {
mapMap.get(mapMap.get(key)).put("06:00", (int)mapMap.get(key).get("06:00") + 1);
} else if (hour >= 8 && hour < 10) {
// map2.put("08:00", ((Integer) map2.get("08:00")) + 1);
mapMap.get(mapMap.get(key)).put("08:00", (int)mapMap.get(key).get("08:00") + 1);
} else if (hour >= 10 && hour < 12) {
// map2.put("10:00", ((Integer) map2.get("10:00")) + 1);
mapMap.get(mapMap.get(key)).put("10:00", (int)mapMap.get(key).get("10:00") + 1);
} else if (hour >= 12 && hour < 14) {
// map2.put("12:00", ((Integer) map2.get("12:00")) + 1);
mapMap.get(mapMap.get(key)).put("12:00", (int)mapMap.get(key).get("12:00") + 1);
} else if (hour >= 14 && hour < 16) {
// map2.put("14:00", ((Integer) map2.get("14:00")) + 1);
mapMap.get(mapMap.get(key)).put("14:00", (int)mapMap.get(key).get("14:00") + 1);
} else if (hour >= 16 && hour < 18) {
// map2.put("16:00", ((Integer) map2.get("16:00")) + 1);
mapMap.get(mapMap.get(key)).put("16:00", (int)mapMap.get(key).get("16:00") + 1);
} else if (hour >= 18 && hour < 20) {
// map2.put("18:00", ((Integer) map2.get("18:00")) + 1);
mapMap.get(mapMap.get(key)).put("18:00", (int)mapMap.get(key).get("18:00") + 1);
}
}
}
}
}
}
//进一步判断状态
if (sysUserVo.getZxzt() == null || sysUserVo.getZxzt().isEmpty()){
if (onlineUserList.contains(constructionUser.getSysUserId().toString()) && !isFind){
sysUserVo.setZxzt("异常");
}else if (!onlineUserList.contains(constructionUser.getSysUserId().toString())){
sysUserVo.setZxzt("离线");
}
}
//统计异常数
if (!isFind){
ycry++;
}
gpsList.add(sysUserVo);
}
// if (constructionUser.getStatus().equals("0")){
// zgry++;
// }
}
//地图实时坐标
map.put("gpsList", gpsList);
//在岗人员
map.put("zgry",zgry);
//异常人员
map.put("ycry",ycry);
//总人员数
// map.put("zrys",list.size());
map.put("zrys",zgry + ycry);
//定位次数
Long cacheObject = RedisUtils.getCacheObject("rydw_");
if (cacheObject != null){
dwcs = cacheObject.intValue();
}
map.put("dwcs",dwcs);
//柱状图数据
map.put("zztsj",allValue);
}
}
return map;
}
} }