feat(trajectory): 新增轨迹事件消费与落库模型

- 新增 ops_device_trajectory 表及轨迹数据对象、Mapper\n- 消费 trajectory-enter / trajectory-leave 事件并做幂等处理\n- 落地设备进入/离开区域记录,补充停留时长与离开原因字段\n- 在服务层封装轨迹写入、关闭未离场记录等核心逻辑
This commit is contained in:
lzh
2026-03-31 22:56:18 +08:00
parent 11dcb57ff3
commit bf5aa21648
10 changed files with 908 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
package com.viewsh.module.ops.dal.dataobject.trajectory;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.viewsh.framework.tenant.core.db.TenantBaseDO;
import lombok.*;
import java.time.LocalDateTime;
/**
* 设备轨迹记录 DO
* <p>
* 记录工牌设备进出各区域的轨迹
* 一条记录表示一次"进入-离开"周期
*
* @author lzh
*/
@TableName("ops_device_trajectory")
@KeySequence("ops_device_trajectory_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OpsDeviceTrajectoryDO extends TenantBaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 工牌设备ID
*/
private Long deviceId;
/**
* 设备名称(冗余)
*/
private String deviceName;
/**
* 设备备注名称(冗余)
*/
private String nickname;
/**
* 人员ID预留
*/
private Long personId;
/**
* 人员名称(预留)
*/
private String personName;
/**
* 区域ID
*/
private Long areaId;
/**
* 区域名称(冗余)
*/
private String areaName;
/**
* 匹配的 Beacon MAC
*/
private String beaconMac;
/**
* 进入时间
*/
private LocalDateTime enterTime;
/**
* 离开时间
*/
private LocalDateTime leaveTime;
/**
* 停留时长(秒)
*/
private Integer durationSeconds;
/**
* 离开原因
* <p>
* SIGNAL_LOSS - 信号丢失
* AREA_SWITCH - 切换到其他区域
* DEVICE_OFFLINE - 设备离线
*/
private String leaveReason;
/**
* 进入时 RSSI
*/
private Integer enterRssi;
}

View File

@@ -0,0 +1,83 @@
package com.viewsh.module.ops.dal.mysql.trajectory;
import com.viewsh.framework.common.pojo.PageResult;
import com.viewsh.framework.mybatis.core.mapper.BaseMapperX;
import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.viewsh.module.ops.dal.dataobject.trajectory.OpsDeviceTrajectoryDO;
import com.viewsh.module.ops.service.trajectory.dto.TrajectoryPageReqDTO;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 设备轨迹记录 Mapper
*
* @author lzh
*/
@Mapper
public interface OpsDeviceTrajectoryMapper extends BaseMapperX<OpsDeviceTrajectoryDO> {
/**
* 查询设备在某区域最近一条未关闭的轨迹记录
*/
default OpsDeviceTrajectoryDO selectOpenRecord(Long deviceId, Long areaId) {
return selectOne(new LambdaQueryWrapperX<OpsDeviceTrajectoryDO>()
.eq(OpsDeviceTrajectoryDO::getDeviceId, deviceId)
.eq(OpsDeviceTrajectoryDO::getAreaId, areaId)
.isNull(OpsDeviceTrajectoryDO::getLeaveTime)
.orderByDesc(OpsDeviceTrajectoryDO::getEnterTime)
.last("LIMIT 1"));
}
/**
* 查询设备在某区域未关闭的轨迹记录(加锁,防止并发竞态)
*/
default OpsDeviceTrajectoryDO selectOpenRecordForUpdate(Long deviceId, Long areaId) {
return selectOne(new LambdaQueryWrapperX<OpsDeviceTrajectoryDO>()
.eq(OpsDeviceTrajectoryDO::getDeviceId, deviceId)
.eq(OpsDeviceTrajectoryDO::getAreaId, areaId)
.isNull(OpsDeviceTrajectoryDO::getLeaveTime)
.orderByDesc(OpsDeviceTrajectoryDO::getEnterTime)
.last("LIMIT 1 FOR UPDATE"));
}
/**
* 查询设备在某区域、按进入时间精确匹配的未关闭轨迹记录(加锁)
*/
default OpsDeviceTrajectoryDO selectOpenRecordByEnterTimeForUpdate(Long deviceId, Long areaId,
LocalDateTime enterTime) {
return selectOne(new LambdaQueryWrapperX<OpsDeviceTrajectoryDO>()
.eq(OpsDeviceTrajectoryDO::getDeviceId, deviceId)
.eq(OpsDeviceTrajectoryDO::getAreaId, areaId)
.eq(OpsDeviceTrajectoryDO::getEnterTime, enterTime)
.isNull(OpsDeviceTrajectoryDO::getLeaveTime)
.last("LIMIT 1 FOR UPDATE"));
}
/**
* 分页查询轨迹记录
*/
default PageResult<OpsDeviceTrajectoryDO> selectPage(TrajectoryPageReqDTO req) {
return selectPage(req, new LambdaQueryWrapperX<OpsDeviceTrajectoryDO>()
.eqIfPresent(OpsDeviceTrajectoryDO::getDeviceId, req.getDeviceId())
.eqIfPresent(OpsDeviceTrajectoryDO::getAreaId, req.getAreaId())
.betweenIfPresent(OpsDeviceTrajectoryDO::getEnterTime, req.getEnterTime())
.orderByDesc(OpsDeviceTrajectoryDO::getEnterTime));
}
/**
* 查询某设备某天的轨迹时间线(不分页,按进入时间升序)
*/
default List<OpsDeviceTrajectoryDO> selectTimeline(Long deviceId, LocalDate date) {
LocalDateTime start = date.atStartOfDay();
LocalDateTime end = date.plusDays(1).atStartOfDay();
return selectList(new LambdaQueryWrapperX<OpsDeviceTrajectoryDO>()
.eq(OpsDeviceTrajectoryDO::getDeviceId, deviceId)
.ge(OpsDeviceTrajectoryDO::getEnterTime, start)
.lt(OpsDeviceTrajectoryDO::getEnterTime, end)
.orderByAsc(OpsDeviceTrajectoryDO::getEnterTime));
}
}