diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapDeviceFenceRecordController.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapDeviceFenceRecordController.java new file mode 100644 index 00000000..9da9e681 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapDeviceFenceRecordController.java @@ -0,0 +1,67 @@ +package org.dromara.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.system.domain.bo.MapDeviceFenceRecordBo; +import org.dromara.system.domain.vo.MapDeviceFenceRecordVo; +import org.dromara.system.service.IMapDeviceFenceRecordService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +/** + * 设备进出记录控制器 + * + * @author luya + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/mapDeviceFenceRecord") +public class MapDeviceFenceRecordController extends BaseController { + + private final IMapDeviceFenceRecordService mapDeviceFenceRecordService; + + /** + * 查询设备进出记录列表 + */ + @GetMapping("/list") + public TableDataInfo list(MapDeviceFenceRecordBo bo, PageQuery pageQuery) { + return mapDeviceFenceRecordService.queryPageList(bo, pageQuery); + } + + /** + * 导出设备进出记录列表 + */ + @PostMapping("/export") + public void export(MapDeviceFenceRecordBo bo, HttpServletResponse response) throws IOException { + List list = mapDeviceFenceRecordService.queryList(bo); + ExcelUtil.exportExcel(list, "设备进出记录", MapDeviceFenceRecordVo.class, response); + } + + /** + * 获取设备进出记录详细信息 + */ + @GetMapping(value = "/{id}") + public R getInfo(@PathVariable Long id) { + return R.ok(mapDeviceFenceRecordService.queryById(id)); + } + + /** + * 删除设备进出记录 + */ + @DeleteMapping("/{ids}") + public R remove(@PathVariable Long[] ids) { + return toAjax(mapDeviceFenceRecordService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapPolygonController.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapPolygonController.java index 69844bc3..6958d24b 100644 --- a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapPolygonController.java +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/controller/system/MapPolygonController.java @@ -10,13 +10,17 @@ import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; import org.dromara.system.domain.bo.MapPolygonBo; +import org.dromara.system.domain.bo.MapPolygonDeviceBo; +import org.dromara.system.domain.vo.MapPolygonDeviceVo; import org.dromara.system.domain.vo.MapPolygonVo; +import org.dromara.system.service.IMapPolygonDeviceService; import org.dromara.system.service.IMapPolygonService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.List; +import java.util.Map; /** * 电子围栏控制器 @@ -30,11 +34,11 @@ import java.util.List; public class MapPolygonController extends BaseController { private final IMapPolygonService mapPolygonService; + private final IMapPolygonDeviceService mapPolygonDeviceService; /** * 查询电子围栏列表 */ - @SaCheckPermission("system:mapPolygon:list") @GetMapping("/list") public TableDataInfo list(MapPolygonBo mapPolygonBo, PageQuery pageQuery) { return mapPolygonService.queryPageList(mapPolygonBo, pageQuery); @@ -45,7 +49,6 @@ public class MapPolygonController extends BaseController { /** * 获取电子围栏详细信息 */ - @SaCheckPermission("system:mapPolygon:query") @GetMapping(value = "/{id}") public R getInfo(@PathVariable Integer id) { return R.ok(mapPolygonService.queryById(id)); @@ -54,8 +57,6 @@ public class MapPolygonController extends BaseController { /** * 新增电子围栏 */ - @SaCheckPermission("system:mapPolygon:add") - @Log(title = "电子围栏管理", businessType = BusinessType.INSERT) @PostMapping public R add(@Validated @RequestBody MapPolygonBo mapPolygonBo) { return toAjax(mapPolygonService.insertByBo(mapPolygonBo)); @@ -64,8 +65,6 @@ public class MapPolygonController extends BaseController { /** * 修改电子围栏 */ - @SaCheckPermission("system:mapPolygon:edit") - @Log(title = "电子围栏管理", businessType = BusinessType.UPDATE) @PutMapping public R edit(@Validated @RequestBody MapPolygonBo mapPolygonBo) { return toAjax(mapPolygonService.updateByBo(mapPolygonBo)); @@ -74,8 +73,6 @@ public class MapPolygonController extends BaseController { /** * 删除电子围栏 */ - @SaCheckPermission("system:mapPolygon:remove") - @Log(title = "电子围栏管理", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@PathVariable Integer[] ids) { return toAjax(mapPolygonService.deleteWithValidByIds(List.of(ids), true)); @@ -84,12 +81,56 @@ public class MapPolygonController extends BaseController { /** * 修改状态 */ - @SaCheckPermission("system:mapPolygon:edit") - @Log(title = "电子围栏管理", businessType = BusinessType.UPDATE) @PutMapping("/changeStatus") public R changeStatus(@RequestBody MapPolygonBo mapPolygonBo) { return toAjax(mapPolygonService.updateStatus(mapPolygonBo.getId(), mapPolygonBo.getStatus())); } + /** + * 查询围栏绑定的设备列表 + */ + @GetMapping("/deviceList/{polygonId}") + public R> getDeviceList(@PathVariable Integer polygonId) { + return R.ok(mapPolygonDeviceService.queryDeviceListByPolygonId(polygonId)); + } + + /** + * 添加围栏设备绑定 + */ + @PostMapping("/device") + public R addDevice(@Validated @RequestBody MapPolygonDeviceBo mapPolygonDeviceBo) { + return toAjax(mapPolygonDeviceService.insertByBo(mapPolygonDeviceBo)); + } + + /** + * 批量添加围栏设备绑定 + */ + @PostMapping("/device/batch") + public R batchAddDevice(@RequestBody Map params) { + Integer polygonId = (Integer) params.get("polygonId"); + @SuppressWarnings("unchecked") + List deviceCodes = (List) params.get("deviceCodes"); + return toAjax(mapPolygonDeviceService.batchInsert(polygonId, deviceCodes)); + } + + /** + * 删除围栏设备绑定 + */ + @DeleteMapping("/device/{ids}") + public R removeDevice(@PathVariable Long[] ids) { + return toAjax(mapPolygonDeviceService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 根据围栏ID和设备编码列表删除绑定 + */ + @DeleteMapping("/device/batch") + public R batchRemoveDevice(@RequestBody Map params) { + Integer polygonId = (Integer) params.get("polygonId"); + @SuppressWarnings("unchecked") + List deviceCodes = (List) params.get("deviceCodes"); + return toAjax(mapPolygonDeviceService.deleteByPolygonIdAndDeviceCodes(polygonId, deviceCodes)); + } + } diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapDeviceFenceRecord.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapDeviceFenceRecord.java new file mode 100644 index 00000000..ec73d191 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapDeviceFenceRecord.java @@ -0,0 +1,100 @@ +package org.dromara.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 设备进出记录实体 + * + * @author Lion Li + */ +@Data +@TableName("map_device_fence_record") +public class MapDeviceFenceRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 设备编码 + */ + private String deviceCode; + + /** + * 设备类型 + */ + private String deviceType; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 围栏ID + */ + private Integer polygonId; + + /** + * 围栏名称 + */ + private String polygonName; + + /** + * 进出类型 0进入 1离开 + */ + private Short inOutType; + + /** + * 规则类型 0禁入 1禁出 + */ + private Short ruleType; + + /** + * 是否违规 0否 1是 + */ + private Short violation; + + /** + * 位置坐标 + */ + private String location; + + /** + * 记录时间 + */ + private LocalDateTime recordTime; + + /** + * GPS时间戳(毫秒) + */ + private Long gpsTime; + + /** + * 部门ID + */ + private String deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 备注 + */ + private String remark; +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygon.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygon.java index 2acfb73c..2ebc8095 100644 --- a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygon.java +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygon.java @@ -7,15 +7,16 @@ import lombok.EqualsAndHashCode; import org.dromara.common.mybatis.core.domain.BaseEntity; +import java.io.Serializable; + /** * 电子围栏实体 * * @author luya */ @Data -@EqualsAndHashCode(callSuper = true) @TableName("map_polygon") -public class MapPolygon extends BaseEntity { +public class MapPolygon implements Serializable { private static final long serialVersionUID = 1L; @@ -105,4 +106,4 @@ public class MapPolygon extends BaseEntity { */ private Integer range; -} \ No newline at end of file +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygonDevice.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygonDevice.java new file mode 100644 index 00000000..ed194fe1 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/MapPolygonDevice.java @@ -0,0 +1,71 @@ + +package org.dromara.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 电子围栏设备绑定实体 + * + * @author Lion Li + */ +@Data +@TableName("map_polygon_device") +public class MapPolygonDevice implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 围栏ID + */ + private Integer polygonId; + + /** + * 设备编码 + */ + private String deviceCode; + + /** + * 设备类型 + */ + private String deviceType; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 创建者 + */ + private String createBy; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 更新者 + */ + private String updateBy; + + /** + * 备注 + */ + private String remark; +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/bo/MapDeviceFenceRecordBo.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/bo/MapDeviceFenceRecordBo.java new file mode 100644 index 00000000..a3e8ba6a --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/bo/MapDeviceFenceRecordBo.java @@ -0,0 +1,60 @@ +package org.dromara.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.system.domain.MapDeviceFenceRecord; + +/** + * 设备进出记录业务对象 + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = MapDeviceFenceRecord.class, reverseConvertGenerate = false) +public class MapDeviceFenceRecordBo extends MapDeviceFenceRecord { + + private static final long serialVersionUID = 1L; + + /** + * 设备编码 + */ + @NotNull(message = "设备编码不能为空") + private String deviceCode; + + /** + * 设备类型 + */ + @NotNull(message = "设备类型不能为空") + private String deviceType; + + /** + * 围栏ID + */ + @NotNull(message = "围栏ID不能为空") + private Integer polygonId; + + /** + * 进出类型 0进入 1离开 + */ + @NotNull(message = "进出类型不能为空") + private Short inOutType; + + /** + * 规则类型 0禁入 1禁出 + */ + @NotNull(message = "规则类型不能为空") + private Short ruleType; + + /** + * 是否违规 0否 1是 + */ + @NotNull(message = "是否违规不能为空") + private Short violation; + + private String startTime; + + private String endTime; +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/bo/MapPolygonDeviceBo.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/bo/MapPolygonDeviceBo.java new file mode 100644 index 00000000..1ef15c77 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/bo/MapPolygonDeviceBo.java @@ -0,0 +1,45 @@ + +package org.dromara.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.system.domain.MapPolygonDevice; + +/** + * 电子围栏设备绑定业务对象 + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = MapPolygonDevice.class, reverseConvertGenerate = false) +public class MapPolygonDeviceBo extends MapPolygonDevice { + + private static final long serialVersionUID = 1L; + + /** + * 围栏ID + */ + @NotNull(message = "围栏ID不能为空") + private Integer polygonId; + + /** + * 设备编码 + */ + @NotBlank(message = "设备编码不能为空") + private String deviceCode; + + /** + * 设备类型 + */ + @NotBlank(message = "设备类型不能为空") + private String deviceType; + + /** + * 备注 + */ + private String remark; +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/vo/MapDeviceFenceRecordVo.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/vo/MapDeviceFenceRecordVo.java new file mode 100644 index 00000000..036ee587 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/vo/MapDeviceFenceRecordVo.java @@ -0,0 +1,139 @@ +package org.dromara.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.system.domain.MapDeviceFenceRecord; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 设备进出记录视图对象 + * + * @author Lion Li + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = MapDeviceFenceRecord.class) +public class MapDeviceFenceRecordVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 设备编码 + */ + @ExcelProperty(value = "设备编码") + private String deviceCode; + + /** + * 设备类型 + */ + @ExcelProperty(value = "设备类型") + private String deviceType; + + /** + * 设备类型名称 + */ + @ExcelProperty(value = "设备类型名称") + private String deviceTypeName; + + /** + * 设备名称 + */ + @ExcelProperty(value = "设备名称") + private String deviceName; + + /** + * 围栏ID + */ + @ExcelProperty(value = "围栏ID") + private Integer polygonId; + + /** + * 围栏名称 + */ + @ExcelProperty(value = "围栏名称") + private String polygonName; + + /** + * 进出类型 0进入 1离开 + */ + @ExcelProperty(value = "进出类型") + private Short inOutType; + + /** + * 进出类型名称 + */ + @ExcelProperty(value = "进出类型名称") + private String inOutTypeName; + + /** + * 规则类型 0禁入 1禁出 + */ + @ExcelProperty(value = "规则类型") + private Short ruleType; + + /** + * 规则类型名称 + */ + @ExcelProperty(value = "规则类型名称") + private String ruleTypeName; + + /** + * 是否违规 0否 1是 + */ + @ExcelProperty(value = "是否违规") + private Short violation; + + /** + * 是否违规名称 + */ + @ExcelProperty(value = "是否违规名称") + private String violationName; + + /** + * 位置坐标 + */ + @ExcelProperty(value = "位置坐标") + private String location; + + /** + * 记录时间 + */ + @ExcelProperty(value = "记录时间") + private LocalDateTime recordTime; + + /** + * GPS时间戳(毫秒) + */ + @ExcelProperty(value = "GPS时间") + private Long gpsTime; + + /** + * 部门ID + */ + @ExcelProperty(value = "部门ID") + private String deptId; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称") + private String deptName; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/vo/MapPolygonDeviceVo.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/vo/MapPolygonDeviceVo.java new file mode 100644 index 00000000..59a080a2 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/domain/vo/MapPolygonDeviceVo.java @@ -0,0 +1,92 @@ + +package org.dromara.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.dromara.system.domain.MapPolygonDevice; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 电子围栏设备绑定视图对象 + * + * @author Lion Li + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = MapPolygonDevice.class) +public class MapPolygonDeviceVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 围栏ID + */ + @ExcelProperty(value = "围栏ID") + private Integer polygonId; + + /** + * 围栏名称 + */ + @ExcelProperty(value = "围栏名称") + private String polygonName; + + /** + * 设备编码 + */ + @ExcelProperty(value = "设备编码") + private String deviceCode; + + /** + * 设备类型 + */ + @ExcelProperty(value = "设备类型") + private String deviceType; + + /** + * 设备类型名称 + */ + @ExcelProperty(value = "设备类型名称") + private String deviceTypeName; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private LocalDateTime createTime; + + /** + * 创建者 + */ + @ExcelProperty(value = "创建者") + private String createBy; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private LocalDateTime updateTime; + + /** + * 更新者 + */ + @ExcelProperty(value = "更新者") + private String updateBy; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapDeviceFenceRecordMapper.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapDeviceFenceRecordMapper.java new file mode 100644 index 00000000..f6c7c05f --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapDeviceFenceRecordMapper.java @@ -0,0 +1,48 @@ +package org.dromara.system.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.dromara.common.mybatis.annotation.DataColumn; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.system.domain.MapDeviceFenceRecord; +import org.dromara.system.domain.vo.MapDeviceFenceRecordVo; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 设备进出记录Mapper接口 + * + * @author Lion Li + */ +@DS("slave") +public interface MapDeviceFenceRecordMapper extends BaseMapperPlus { + + /** + * 查询设备进出记录分页列表 + */ + Page selectPageList(@Param("page") Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 查询设备进出记录列表 + */ + List selectVoList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 查询设备最近的进出记录 + */ + MapDeviceFenceRecordVo selectLatestRecord(@Param("deviceCode") String deviceCode, + @Param("polygonId") Integer polygonId); + + /** + * 查询指定时间范围内的设备进出记录 + */ + List selectByTimeRange(@Param("deviceCode") String deviceCode, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonDeviceMapper.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonDeviceMapper.java new file mode 100644 index 00000000..fd6317a4 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonDeviceMapper.java @@ -0,0 +1,49 @@ + +package org.dromara.system.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.dromara.common.mybatis.annotation.DataColumn; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.system.domain.MapPolygonDevice; +import org.dromara.system.domain.vo.MapPolygonDeviceVo; + +import java.util.List; + +/** + * 电子围栏设备绑定Mapper接口 + * + * @author luya + */ +@DS("slave") +public interface MapPolygonDeviceMapper extends BaseMapperPlus { + + /** + * 查询围栏绑定的设备列表 + */ + List selectDeviceListByPolygonId(@Param("polygonId") Integer polygonId); + + /** + * 查询设备绑定的围栏列表 + */ + List selectPolygonListByDeviceCode(@Param("deviceCode") String deviceCode); + + /** + * 批量删除围栏设备绑定 + */ + int deleteByPolygonIdAndDeviceCodes(@Param("polygonId") Integer polygonId, @Param("deviceCodes") List deviceCodes); + + int deleteByPolygonId(@Param("polygonId") Integer polygonId); + + /** + * 查询围栏设备绑定分页列表 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "deptId") + }) + Page selectPageList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonMapper.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonMapper.java index 3d1b7e00..01eb7bef 100644 --- a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonMapper.java +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/mapper/MapPolygonMapper.java @@ -57,5 +57,17 @@ public interface MapPolygonMapper extends BaseMapperPlus detectDeviceInOut(); + + /** + * 检测指定设备的进出围栏情况 + * + * @param deviceCode 设备编码 + * @param deviceType 设备类型 + * @return 检测到的进出记录列表 + */ + List detectDeviceInOut(String deviceCode, String deviceType); + + /** + * 检测指定围栏的设备进出情况 + * + * @param polygonId 围栏ID + * @return 检测到的进出记录列表 + */ + List detectDeviceInOut(Integer polygonId); + + /** + * 判断点是否在多边形内 + * + * @param point 点坐标,格式为"经度,纬度" + * @param polygon 多边形坐标,格式为"经度,纬度;经度,纬度;..." + * @return 是否在多边形内 + */ + boolean isPointInPolygon(String point, String polygon); +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/IMapDeviceFenceRecordService.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/IMapDeviceFenceRecordService.java new file mode 100644 index 00000000..ce53030e --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/IMapDeviceFenceRecordService.java @@ -0,0 +1,87 @@ +package org.dromara.system.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.domain.bo.MapDeviceFenceRecordBo; +import org.dromara.system.domain.vo.MapDeviceFenceRecordVo; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 设备进出记录Service接口 + * + * @author Lion Li + */ +public interface IMapDeviceFenceRecordService { + + /** + * 查询设备进出记录 + * + * @param id 主键 + * @return 设备进出记录 + */ + MapDeviceFenceRecordVo queryById(Long id); + + /** + * 查询设备进出记录列表 + * + * @param bo 查询条件 + * @return 设备进出记录列表 + */ + List queryList(MapDeviceFenceRecordBo bo); + + /** + * 分页查询设备进出记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 设备进出记录分页列表 + */ + TableDataInfo queryPageList(MapDeviceFenceRecordBo bo, PageQuery pageQuery); + + /** + * 查询设备最近的进出记录 + * + * @param deviceCode 设备编码 + * @param polygonId 围栏ID + * @return 设备进出记录 + */ + MapDeviceFenceRecordVo queryLatestRecord(String deviceCode, Integer polygonId); + + /** + * 查询指定时间范围内的设备进出记录 + * + * @param deviceCode 设备编码 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 设备进出记录列表 + */ + List queryByTimeRange(String deviceCode, LocalDateTime startTime, LocalDateTime endTime); + + /** + * 新增设备进出记录 + * + * @param bo 设备进出记录 + * @return 是否新增成功 + */ + Boolean insertByBo(MapDeviceFenceRecordBo bo); + + /** + * 批量新增设备进出记录 + * + * @param list 设备进出记录列表 + * @return 是否新增成功 + */ + Boolean batchInsert(List list); + + /** + * 校验并批量删除设备进出记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/IMapPolygonDeviceService.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/IMapPolygonDeviceService.java new file mode 100644 index 00000000..831c10fd --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/IMapPolygonDeviceService.java @@ -0,0 +1,102 @@ + +package org.dromara.system.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.domain.bo.MapPolygonDeviceBo; +import org.dromara.system.domain.vo.MapPolygonDeviceVo; + +import java.util.Collection; +import java.util.List; + +/** + * 电子围栏设备绑定Service接口 + * + * @author Lion Li + */ +public interface IMapPolygonDeviceService { + + /** + * 查询电子围栏设备绑定 + * + * @param id 主键 + * @return 电子围栏设备绑定 + */ + MapPolygonDeviceVo queryById(Long id); + + /** + * 查询电子围栏设备绑定列表 + * + * @param bo 查询条件 + * @return 电子围栏设备绑定列表 + */ + List queryList(MapPolygonDeviceBo bo); + + /** + * 分页查询电子围栏设备绑定列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 电子围栏设备绑定分页列表 + */ + TableDataInfo queryPageList(MapPolygonDeviceBo bo, PageQuery pageQuery); + + /** + * 查询围栏绑定的设备列表 + * + * @param polygonId 围栏ID + * @return 设备列表 + */ + List queryDeviceListByPolygonId(Integer polygonId); + + /** + * 查询设备绑定的围栏列表 + * + * @param deviceCode 设备编码 + * @return 围栏列表 + */ + List queryPolygonListByDeviceCode(String deviceCode); + + /** + * 新增电子围栏设备绑定 + * + * @param bo 电子围栏设备绑定 + * @return 是否新增成功 + */ + Boolean insertByBo(MapPolygonDeviceBo bo); + + /** + * 批量新增电子围栏设备绑定 + * + * @param polygonId 围栏ID + * @param deviceCodes 设备编码列表 + * @return 是否新增成功 + */ + Boolean batchInsert(Integer polygonId, List deviceCodes); + + /** + * 修改电子围栏设备绑定 + * + * @param bo 电子围栏设备绑定 + * @return 是否修改成功 + */ + Boolean updateByBo(MapPolygonDeviceBo bo); + + /** + * 校验并批量删除电子围栏设备绑定信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 根据围栏ID和设备编码列表删除绑定 + * + * @param polygonId 围栏ID + * @param deviceCodes 设备编码列表 + * @return 是否删除成功 + */ + Boolean deleteByPolygonIdAndDeviceCodes(Integer polygonId, List deviceCodes); +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/FenceDetectionServiceImpl.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/FenceDetectionServiceImpl.java new file mode 100644 index 00000000..989b646f --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/FenceDetectionServiceImpl.java @@ -0,0 +1,695 @@ +package org.dromara.system.service.impl; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.system.domain.MapDeviceFenceRecord; +import org.dromara.system.domain.MapPolygon; +import org.dromara.system.domain.MapPolygonDevice; +import org.dromara.system.domain.TDevice; +import org.dromara.system.domain.bo.MapDeviceFenceRecordBo; +import org.dromara.system.domain.vo.MapDeviceFenceRecordVo; +import org.dromara.system.domain.vo.MapPolygonDeviceVo; +import org.dromara.system.mapper.MapPolygonDeviceMapper; +import org.dromara.system.mapper.MapPolygonMapper; +import org.dromara.system.mapper.TDeviceMapper; +import org.dromara.system.service.FenceDetectionService; +import org.dromara.system.service.IMapDeviceFenceRecordService; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.*; + +/** + * 设备进出检测服务实现 + * + * @author luya + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class FenceDetectionServiceImpl implements FenceDetectionService { + + private final MapPolygonDeviceMapper mapPolygonDeviceMapper; + private final MapPolygonMapper mapPolygonMapper; + private final TDeviceMapper tDeviceMapper; + private final IMapDeviceFenceRecordService mapDeviceFenceRecordService; + + /** + * Redis中设备位置的key前缀 + */ + private static final String DEVICE_LOCATION_KEY_PREFIX = "online_users:"; + + @Override + public List detectDeviceInOut() { + List result = new ArrayList<>(); + + // 获取所有围栏设备绑定关系 + List bindings = mapPolygonDeviceMapper.selectVoList( + Wrappers.lambdaQuery(MapPolygonDevice.class) + ); + + // 按设备分组 + Map> deviceBindings = new HashMap<>(); + for (MapPolygonDeviceVo binding : bindings) { + String key = binding.getDeviceType() + ":" + binding.getDeviceCode(); + deviceBindings.computeIfAbsent(key, k -> new ArrayList<>()).add(binding); + } + + // 检测每个设备的进出情况 + for (Map.Entry> entry : deviceBindings.entrySet()) { + String[] parts = entry.getKey().split(":"); + String deviceType = parts[0]; + String deviceCode = parts[1]; + + List records = detectDeviceInOut(deviceCode, deviceType); + result.addAll(records); + } + + return result; + } + public List detectDeviceInOut(String deviceCode, String deviceType) { + List result = new ArrayList<>(); + + // 从Redis获取设备当前位置信息 + Map locationInfo = getDeviceLocationInfo(deviceType, deviceCode); + if (locationInfo == null) { + log.debug("设备 {}:{} 未找到位置信息", deviceType, deviceCode); + return result; + } + + // 获取设备位置和GPS时间 + String location = locationInfo.get("lng") + "," + locationInfo.get("lat"); + Long gpsTime = extractGpsTime(locationInfo); + + // 获取设备绑定的围栏列表 + List bindings = mapPolygonDeviceMapper.selectVoList( + Wrappers.lambdaQuery(MapPolygonDevice.class) + .eq(MapPolygonDevice::getDeviceCode, deviceCode) + .eq(MapPolygonDevice::getDeviceType, deviceType) + ); + + // 检测设备在每个围栏中的情况 + for (MapPolygonDeviceVo binding : bindings) { + MapPolygon polygon = mapPolygonMapper.selectById(binding.getPolygonId()); + if (polygon == null || polygon.getStatus() == null || polygon.getStatus() != 1) { + continue; + } + + // 判断设备是否在围栏内 + double longitude = Double.parseDouble(locationInfo.get("lng").toString()); + double latitude = Double.parseDouble(locationInfo.get("lat").toString()); + boolean inside = mapPolygonMapper.isDeviceInFence(polygon.getId(), longitude, latitude); + + // 根据围栏规则判断是否需要处理这个位置 + if (!shouldProcessByRule(polygon.getRuleType(), inside)) { + // 设备处于正常状态,不需要记录 + log.debug("设备 {} 在围栏 {} 处于正常状态(规则:{}, 位置:{}),跳过处理", + deviceCode, polygon.getName(), + polygon.getRuleType() == 0 ? "禁入" : "禁出", + inside ? "内" : "外"); + continue; + } + + // 获取设备最近一次记录 + MapDeviceFenceRecordVo latestRecord = mapDeviceFenceRecordService.queryLatestRecord(deviceCode, polygon.getId()); + + // 判断是否需要创建新记录 + MapDeviceFenceRecordBo record = determineAndCreateRecord( + deviceCode, deviceType, polygon, location, gpsTime, + inside, latestRecord, locationInfo + ); + + if (record != null) { + result.add(record); + } + } + + return result; + } + + /** + * 根据围栏规则判断是否需要处理当前位置 + * @param ruleType 0禁入 1禁出 + * @param inside 是否在围栏内 + * @return true表示需要处理(违规状态),false表示不需要处理(正常状态) + */ + private boolean shouldProcessByRule(short ruleType, boolean inside) { + + // 禁入围栏:只在围栏内时需要处理(违规) + if (ruleType == 0) { + return inside; + } + + // 禁出围栏:只在围栏外时需要处理(违规) + if (ruleType == 1) { + return !inside; + } + + return false; + } + + /** + * 提取GPS时间 + */ + private Long extractGpsTime(Map locationInfo) { + Object gpsTimeObj = locationInfo.get("gpsTime"); + if (gpsTimeObj instanceof Date) { + return ((Date) gpsTimeObj).getTime(); + } else if (gpsTimeObj instanceof Long) { + return (Long) gpsTimeObj; + } else if (gpsTimeObj instanceof String) { + try { + return Long.parseLong((String) gpsTimeObj); + } catch (NumberFormatException e) { + log.warn("GPS时间格式错误: {}", gpsTimeObj); + } + } + return System.currentTimeMillis(); + } + + /** + * 判断并创建记录 + */ + private MapDeviceFenceRecordBo determineAndCreateRecord( + String deviceCode, String deviceType, MapPolygon polygon, + String location, Long gpsTime, boolean inside, + MapDeviceFenceRecordVo latestRecord, Map locationInfo) { + + // 计算违规类型(用于记录) + boolean isViolation = true; // 能进入这个方法的一定是违规状态 + + // 如果没有历史记录 + if (latestRecord == null) { + // 首次违规,记录进入状态 + log.info("设备 {} 首次违规进入围栏 {},规则:{}", + deviceCode, polygon.getName(), + polygon.getRuleType() == 0 ? "禁入" : "禁出"); + return createRecord(deviceCode, deviceType, polygon, location, gpsTime, + (short)0, // 进入 + isViolation, locationInfo); + } + + // 解析上次记录的状态 + Short lastInOutType = latestRecord.getInOutType(); + Long lastGpsTime = latestRecord.getGpsTime(); + + // 检查GPS时间是否变化 + boolean gpsTimeChanged = lastGpsTime == null || !lastGpsTime.equals(gpsTime); + + // 情况1:状态变化(上次是离开/持续,这次是进入) + if (lastInOutType != null && lastInOutType == 1 || lastInOutType == 2) { + // 上次是离开或持续违规,这次是新进入 + log.debug("设备 {} 重新进入违规状态", deviceCode); + return createRecord(deviceCode, deviceType, polygon, location, gpsTime, + (short)0, // 进入 + isViolation, locationInfo); + } + + // 情况2:持续违规,需要定时记录 + if (gpsTimeChanged) { + if (shouldRecordPersistentViolation(latestRecord, gpsTime)) { + log.debug("设备 {} 持续违规,时间间隔超过阈值", deviceCode); + return createRecord(deviceCode, deviceType, polygon, location, gpsTime, + (short)2, // 持续违规 + isViolation, locationInfo); + } + } + + // 情况3:不需要记录 + return null; + } + + /** + * 判断是否需要记录持续违规 + */ + private boolean shouldRecordPersistentViolation(MapDeviceFenceRecordVo latestRecord, Long currentGpsTime) { + if (latestRecord == null) { + return true; + } + + Long lastGpsTime = latestRecord.getGpsTime(); + if (lastGpsTime == null || currentGpsTime == null) { + return true; + } + + // 获取持续违规记录间隔(默认5分钟) + long intervalMinutes = getPersistentViolationInterval(); + long intervalMillis = intervalMinutes * 60 * 1000; + + // 如果上次记录时间距离现在超过间隔,需要再次记录 + return (currentGpsTime - lastGpsTime) >= intervalMillis; + } + + /** + * 创建进出记录 + */ + private MapDeviceFenceRecordBo createRecord( + String deviceCode, String deviceType, MapPolygon polygon, + String location, Long gpsTime, Short inOutType, + boolean isViolation, Map locationInfo) { + + MapDeviceFenceRecordBo record = new MapDeviceFenceRecordBo(); + + // 设备信息 + record.setDeviceCode(deviceCode); + record.setDeviceType(deviceType); + record.setDeviceName(getDeviceName(deviceCode, deviceType, locationInfo)); + + // 围栏信息 + record.setPolygonId(polygon.getId()); + record.setPolygonName(polygon.getName()); + record.setRuleType(polygon.getRuleType()); + + // 记录类型 + record.setInOutType(inOutType); // 0进入 1离开 2持续违规 + record.setViolation(isViolation ? (short)1 : (short)0); + + // 位置和时间 + record.setLocation(location); + record.setGpsTime(gpsTime); + record.setRecordTime(DateUtil.toLocalDateTime(new Date())); + + // 部门信息 + if (locationInfo != null) { + Object deptId = locationInfo.get("deptId"); + if (deptId != null) { + record.setDeptId(deptId.toString()); + } + + Object deptName = locationInfo.get("deptName"); + if (deptName != null) { + record.setDeptName(deptName.toString()); + } + } + + // 生成备注 + record.setRemark(generateRemark(polygon, inOutType, isViolation)); + + return record; + } + + /** + * 生成备注信息 + */ + private String generateRemark(MapPolygon polygon, Short inOutType, boolean isViolation) { + StringBuilder remark = new StringBuilder(); + + String ruleDesc = polygon.getRuleType() == 0 ? "禁入" : "禁出"; + String positionDesc = ""; + if (polygon.getRuleType() == 0) { + positionDesc = "在围栏内"; + } else { + positionDesc = "在围栏外"; + } + + remark.append("【").append(ruleDesc).append("围栏】"); + + if (inOutType == 0) { + remark.append("进入围栏,当前").append(positionDesc); + } else if (inOutType == 1) { + remark.append("离开围栏,当前").append(positionDesc); + } else if (inOutType == 2) { + remark.append("持续").append(positionDesc); + } + + if (isViolation) { + remark.append(",触发告警"); + } + + return remark.toString(); + } + + /** + * 获取设备名称 + */ + private String getDeviceName(String deviceCode, String deviceType, Map locationInfo) { + if (locationInfo != null && locationInfo.get("deviceName") != null) { + return locationInfo.get("deviceName").toString(); + } + + try { + TDevice device = tDeviceMapper.selectOne( + Wrappers.lambdaQuery(TDevice.class) + .eq(TDevice::getDeviceCode, deviceCode) + .eq(TDevice::getDeviceType, deviceType) + .last("LIMIT 1") + ); + if (device != null) { + return device.getRemark1(); + } + } catch (Exception e) { + log.warn("查询设备名称失败: {}", e.getMessage()); + } + + return ""; + } + + /** + * 获取持续违规记录间隔 + */ + private long getPersistentViolationInterval() { + return 5L; // 5分钟 + } + + + @Override + public List detectDeviceInOut(Integer polygonId) { + List result = new ArrayList<>(); + + // 获取围栏信息 + MapPolygon polygon = mapPolygonMapper.selectById(polygonId); + if (polygon == null || polygon.getStatus() == null || polygon.getStatus() != 1) { + // 围栏不存在或未启用 + return result; + } + + // 获取围栏绑定的设备列表 + List bindings = mapPolygonDeviceMapper.selectDeviceListByPolygonId(polygonId); + + // 检测每个设备的进出情况 + for (MapPolygonDeviceVo binding : bindings) { + String deviceCode = binding.getDeviceCode(); + String deviceType = binding.getDeviceType(); + + // 从Redis获取设备当前位置信息 + Map locationInfo = getDeviceLocationInfo(deviceType, deviceCode); + if (locationInfo == null) { + log.debug("设备 {}:{} 未找到位置信息", deviceType, deviceCode); + continue; + } + + // 获取设备位置字符串 + String location = locationInfo.get("lng") + "," + locationInfo.get("lat"); + + // 判断设备是否在围栏内(考虑生效范围) + // 解析设备位置 + double longitude = Double.parseDouble(locationInfo.get("lng").toString()); + double latitude = Double.parseDouble(locationInfo.get("lat").toString()); + + // 使用PostGIS的ST_DWithin判断设备是否在围栏生效范围内 + boolean inside = mapPolygonMapper.isDeviceInFence(polygon.getId(), longitude, latitude); + + // 获取设备最近一次进出记录 + MapDeviceFenceRecordVo latestRecord = mapDeviceFenceRecordService.queryLatestRecord(deviceCode, polygonId); + + // 判断是否发生进出事件 + if (latestRecord == null) { + // 没有历史记录,记录当前位置 + if (inside) { + MapDeviceFenceRecordBo record = createRecord(deviceCode, deviceType, polygon, location, (short) 0, locationInfo); + result.add(record); + } + } else { + // 有历史记录,比较当前位置状态 + boolean wasInside = latestRecord.getInOutType() == 0; + + // 检查位置和GPS时间是否未变化 + boolean locationUnchanged = isLocationAndTimeUnchanged(latestRecord, locationInfo); + + if (inside && !wasInside) { + // 进入围栏 + MapDeviceFenceRecordBo record = createRecord(deviceCode, deviceType, polygon, location, (short) 0, locationInfo); + result.add(record); + } else if (!inside && wasInside) { + // 离开围栏 + MapDeviceFenceRecordBo record = createRecord(deviceCode, deviceType, polygon, location, (short) 1, locationInfo); + result.add(record); + } else if (locationUnchanged) { + // 位置和GPS时间未变化,不创建新记录 + log.debug("设备 {}:{} 在围栏 {} 中位置和GPS时间未变化,跳过记录", deviceType, deviceCode, polygon.getName()); + continue; + } + } + } + + return result; + } + + @Override + public boolean isPointInPolygon(String point, String polygon) { + if (StringUtils.isBlank(point) || StringUtils.isBlank(polygon)) { + return false; + } + + // 解析点坐标 + String[] pointParts = point.split(","); + if (pointParts.length != 2) { + return false; + } + + double px, py; + try { + px = Double.parseDouble(pointParts[0].trim()); + py = Double.parseDouble(pointParts[1].trim()); + } catch (NumberFormatException e) { + log.error("解析点坐标失败: {}", point, e); + return false; + } + + // 解析多边形坐标 + String[] polygonParts = polygon.split(";"); + if (polygonParts.length < 3) { + return false; + } + + List vertices = new ArrayList<>(); + for (String part : polygonParts) { + String[] coords = part.split(","); + if (coords.length != 2) { + continue; + } + + try { + double x = Double.parseDouble(coords[0].trim()); + double y = Double.parseDouble(coords[1].trim()); + vertices.add(new double[]{x, y}); + } catch (NumberFormatException e) { + log.error("解析多边形坐标失败: {}", part, e); + } + } + + if (vertices.size() < 3) { + return false; + } + + // 使用射线法判断点是否在多边形内 + return isPointInPolygon(px, py, vertices); + } + + /** + * 使用射线法判断点是否在多边形内 + * + * @param px 点的X坐标 + * @param py 点的Y坐标 + * @param vertices 多边形顶点列表 + * @return 是否在多边形内 + */ + private boolean isPointInPolygon(double px, double py, List vertices) { + int n = vertices.size(); + boolean inside = false; + + for (int i = 0, j = n - 1; i < n; j = i++) { + double[] vi = vertices.get(i); + double[] vj = vertices.get(j); + + if (((vi[1] > py) != (vj[1] > py)) && + (px < (vj[0] - vi[0]) * (py - vi[1]) / (vj[1] - vi[1]) + vi[0])) { + inside = !inside; + } + } + + return inside; + } + + /** + * 从Redis获取设备位置 + * + * @param deviceType 设备类型 + * @param deviceCode 设备编码 + * @return 设备位置,格式为"经度,纬度" + */ + private String getDeviceLocation(String deviceType, String deviceCode) { + String key = DEVICE_LOCATION_KEY_PREFIX + deviceType + ":" + deviceCode; + String location = RedisUtils.getCacheObject(key); + if (StringUtils.isNotBlank(location)) { + try { + // 尝试解析JSON格式的位置数据 + Map locationMap = JSON.parseObject(location, Map.class); + if (locationMap.containsKey("lng") && locationMap.containsKey("lat")) { + return locationMap.get("lng") + "," + locationMap.get("lat"); + } else if (locationMap.containsKey("longitude") && locationMap.containsKey("latitude")) { + return locationMap.get("longitude") + "," + locationMap.get("latitude"); + } + } catch (Exception e) { + log.error("解析设备位置数据失败: {}", location, e); + } + } + return location; + } + + /** + * 从Redis获取设备位置信息 + * + * @param deviceType 设备类型 + * @param deviceCode 设备编码 + * @return 设备位置信息Map,包含lng(经度)、lat(纬度)、gpsTime(GPS时间) + */ + private Map getDeviceLocationInfo(String deviceType, String deviceCode) { + String key = DEVICE_LOCATION_KEY_PREFIX + deviceType + ":" + deviceCode; + String location = RedisUtils.getCacheObject(key); + if (StringUtils.isNotBlank(location)) { + try { + // 解析JSON格式的位置数据 + Map locationMap = JSON.parseObject(location, Map.class); + // 提取经纬度信息 + if (locationMap.containsKey("lng") && locationMap.containsKey("lat")) { + return locationMap; + } else if (locationMap.containsKey("longitude") && locationMap.containsKey("latitude")) { + // 统一使用lng和lat作为键名 + Map result = new HashMap<>(locationMap); + result.put("lng", locationMap.get("longitude")); + result.put("lat", locationMap.get("latitude")); + return result; + } + } catch (Exception e) { + log.error("解析设备位置数据失败: {}", location, e); + } + } + return null; + } + + /** + * 从Redis获取设备GPS时间 + * + * @param deviceType 设备类型 + * @param deviceCode 设备编码 + * @return GPS时间戳(毫秒) + */ + private Long getDeviceGpsTime(String deviceType, String deviceCode) { + Map locationInfo = getDeviceLocationInfo(deviceType, deviceCode); + if (locationInfo != null && locationInfo.containsKey("gpsTime")) { + try { + Object gpsTimeObj = locationInfo.get("gpsTime"); + if (gpsTimeObj instanceof Number) { + return ((Number) gpsTimeObj).longValue(); + } else if (gpsTimeObj instanceof String) { + return Long.parseLong((String) gpsTimeObj); + } + } catch (Exception e) { + log.error("解析设备GPS时间失败: {}", locationInfo.get("gpsTime"), e); + } + } + return null; + } + + /** + * 判断设备位置和GPS时间是否与最近记录相同 + * + * @param latestRecord 最近一次进出记录 + * @param locationInfo 当前位置信息 + * @return true表示相同,false表示不同 + */ + private boolean isLocationAndTimeUnchanged(MapDeviceFenceRecordVo latestRecord, Map locationInfo) { + if (latestRecord == null || locationInfo == null) { + return false; + } + + // 比较位置 + String currentLocation = locationInfo.get("lng") + "," + locationInfo.get("lat"); + if (!currentLocation.equals(latestRecord.getLocation())) { + return false; + } + + // 比较GPS时间 + Long currentGpsTime = null; + if (locationInfo.containsKey("gpsTime")) { + try { + Object gpsTimeObj = locationInfo.get("gpsTime"); + if (gpsTimeObj instanceof Number) { + currentGpsTime = ((Number) gpsTimeObj).longValue(); + } else if (gpsTimeObj instanceof String) { + currentGpsTime = Long.parseLong((String) gpsTimeObj); + } + } catch (Exception e) { + log.error("解析设备GPS时间失败: {}", locationInfo.get("gpsTime"), e); + } + } + + // 如果最新记录中有GPS时间,进行比较 + if (latestRecord.getGpsTime() != null && currentGpsTime != null) { + return latestRecord.getGpsTime().equals(currentGpsTime); + } + + // 如果没有GPS时间信息,则认为位置相同 + return true; + } + + /** + * 创建进出记录 + * + * @param deviceCode 设备编码 + * @param deviceType 设备类型 + * @param polygon 围栏信息 + * @param location 位置坐标 + * @param inOutType 进出类型 0进入 1离开 + * @param locationInfo 设备位置信息Map,包含lng(经度)、lat(纬度)、gpsTime(GPS时间) + * @return 进出记录 + */ + private MapDeviceFenceRecordBo createRecord(String deviceCode, String deviceType, MapPolygon polygon, + String location, short inOutType, Map locationInfo) { + MapDeviceFenceRecordBo record = new MapDeviceFenceRecordBo(); + record.setDeviceCode(deviceCode); + record.setDeviceType(deviceType); + record.setPolygonId(polygon.getId()); + record.setPolygonName(polygon.getName()); + record.setInOutType(inOutType); + record.setRuleType(polygon.getRuleType()); + + // 判断是否违规 + short violation = 0; + if (inOutType == 0 && polygon.getRuleType() != null && polygon.getRuleType() == 0) { + // 进入围栏且规则为禁入,则违规 + violation = 1; + } else if (inOutType == 1 && polygon.getRuleType() != null && polygon.getRuleType() == 1) { + // 离开围栏且规则为禁出,则违规 + violation = 1; + } + record.setViolation(violation); + + record.setLocation(location); + record.setRecordTime(LocalDateTime.now()); + record.setDeptId(polygon.getDeptId()); + record.setDeptName(polygon.getDeptName()); + + // 设置GPS时间 + if (locationInfo != null && locationInfo.containsKey("gpsTime")) { + try { + Object gpsTimeObj = locationInfo.get("gpsTime"); + if (gpsTimeObj instanceof Number) { + record.setGpsTime(((Number) gpsTimeObj).longValue()); + } else if (gpsTimeObj instanceof String) { + record.setGpsTime(Long.parseLong((String) gpsTimeObj)); + } + } catch (Exception e) { + log.error("解析设备GPS时间失败: {}", locationInfo.get("gpsTime"), e); + } + } + + // 获取设备名称 + TDevice device = tDeviceMapper.selectOne( + Wrappers.lambdaQuery(TDevice.class) + .eq(TDevice::getDeviceCode, deviceCode) + .eq(TDevice::getDeviceType, deviceType) + ); + if (device != null) { + record.setDeviceName(device.getPoliceName()); + } + + return record; + } +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapDeviceFenceRecordServiceImpl.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapDeviceFenceRecordServiceImpl.java new file mode 100644 index 00000000..8874e71b --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapDeviceFenceRecordServiceImpl.java @@ -0,0 +1,242 @@ +package org.dromara.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.domain.MapDeviceFenceRecord; +import org.dromara.system.domain.MapPolygon; +import org.dromara.system.domain.TDevice; +import org.dromara.system.domain.bo.MapDeviceFenceRecordBo; +import org.dromara.system.domain.vo.MapDeviceFenceRecordVo; +import org.dromara.system.mapper.MapDeviceFenceRecordMapper; +import org.dromara.system.mapper.MapPolygonMapper; +import org.dromara.system.mapper.TDeviceMapper; +import org.dromara.system.service.IMapDeviceFenceRecordService; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 设备进出记录Service业务层处理 + * + * @author luya + */ +@RequiredArgsConstructor +@Service +public class MapDeviceFenceRecordServiceImpl implements IMapDeviceFenceRecordService { + + private final MapDeviceFenceRecordMapper baseMapper; + private final MapPolygonMapper mapPolygonMapper; + private final TDeviceMapper tDeviceMapper; + + @Override + public MapDeviceFenceRecordVo queryById(Long id) { + MapDeviceFenceRecordVo vo = baseMapper.selectVoById(id); + if (vo != null) { + fillDeviceTypeName(vo); + fillInOutTypeName(vo); + fillRuleTypeName(vo); + fillViolationName(vo); + } + return vo; + } + + @Override + public List queryList(MapDeviceFenceRecordBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List list = baseMapper.selectVoList(lqw); + list.forEach(this::fillDeviceTypeName); + list.forEach(this::fillInOutTypeName); + list.forEach(this::fillRuleTypeName); + list.forEach(this::fillViolationName); + return list; + } + + private LambdaQueryWrapper buildQueryWrapper(MapDeviceFenceRecordBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(ObjectUtil.isNotNull(bo.getDeviceCode()), MapDeviceFenceRecord::getDeviceCode, bo.getDeviceCode()); + lqw.eq(ObjectUtil.isNotNull(bo.getDeviceType()), MapDeviceFenceRecord::getDeviceType, bo.getDeviceType()); + lqw.eq(ObjectUtil.isNotNull(bo.getPolygonId()), MapDeviceFenceRecord::getPolygonId, bo.getPolygonId()); + lqw.eq(ObjectUtil.isNotNull(bo.getInOutType()), MapDeviceFenceRecord::getInOutType, bo.getInOutType()); + lqw.eq(ObjectUtil.isNotNull(bo.getRuleType()), MapDeviceFenceRecord::getRuleType, bo.getRuleType()); + lqw.like(ObjectUtil.isNotNull(bo.getDeviceName()),MapDeviceFenceRecord::getDeviceName,bo.getDeviceName()); + lqw.eq(ObjectUtil.isNotNull(bo.getViolation()), MapDeviceFenceRecord::getViolation, bo.getViolation()); + lqw.between(ObjectUtil.isNotNull(bo.getStartTime()) && ObjectUtil.isNotNull(bo.getEndTime()), MapDeviceFenceRecord::getRecordTime, bo.getStartTime(), bo.getEndTime()); + lqw.orderByDesc(MapDeviceFenceRecord::getRecordTime); + return lqw; + } + + @Override + public TableDataInfo queryPageList(MapDeviceFenceRecordBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + result.getRecords().forEach(this::fillDeviceTypeName); + result.getRecords().forEach(this::fillInOutTypeName); + result.getRecords().forEach(this::fillRuleTypeName); + result.getRecords().forEach(this::fillViolationName); + return TableDataInfo.build(result); + } + + @Override + public MapDeviceFenceRecordVo queryLatestRecord(String deviceCode, Integer polygonId) { + MapDeviceFenceRecordVo vo = baseMapper.selectLatestRecord(deviceCode, polygonId); + if (vo != null) { + fillDeviceTypeName(vo); + fillInOutTypeName(vo); + fillRuleTypeName(vo); + fillViolationName(vo); + } + return vo; + } + + @Override + public List queryByTimeRange(String deviceCode, LocalDateTime startTime, LocalDateTime endTime) { + List list = baseMapper.selectByTimeRange(deviceCode, startTime, endTime); + list.forEach(this::fillDeviceTypeName); + list.forEach(this::fillInOutTypeName); + list.forEach(this::fillRuleTypeName); + list.forEach(this::fillViolationName); + return list; + } + + @Override + public Boolean insertByBo(MapDeviceFenceRecordBo bo) { + MapDeviceFenceRecord add = MapstructUtils.convert(bo, MapDeviceFenceRecord.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + @Override + public Boolean batchInsert(List list) { + if (list == null || list.isEmpty()) { + return false; + } + List records = MapstructUtils.convert(list, MapDeviceFenceRecord.class); + records.forEach(this::validEntityBeforeSave); + return baseMapper.insertBatch(records); + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(MapDeviceFenceRecord entity) { + // 校验设备是否存在 + TDevice device = tDeviceMapper.selectOne( + Wrappers.lambdaQuery(TDevice.class) + .eq(TDevice::getDeviceCode, entity.getDeviceCode()) + .eq(TDevice::getDeviceType, entity.getDeviceType()) + ); + if (device == null) { + throw new RuntimeException("设备不存在"); + } + // 设置设备名称 + if (StringUtils.isBlank(entity.getDeviceName())) { + entity.setDeviceName(device.getPoliceName()); + } + + // 校验围栏是否存在 + MapPolygon polygon = mapPolygonMapper.selectById(entity.getPolygonId()); + if (polygon == null) { + throw new RuntimeException("围栏不存在"); + } + // 设置围栏名称 + if (StringUtils.isBlank(entity.getPolygonName())) { + entity.setPolygonName(polygon.getName()); + } + // 设置部门信息 + if (StringUtils.isBlank(entity.getDeptId())) { + entity.setDeptId(polygon.getDeptId()); + } + if (StringUtils.isBlank(entity.getDeptName())) { + entity.setDeptName(polygon.getDeptName()); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 进行有效性校验 + for (Long id : ids) { + MapDeviceFenceRecordVo vo = queryById(id); + if (vo == null) { + throw new RuntimeException("记录不存在"); + } + } + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 填充设备类型名称 + */ + private void fillDeviceTypeName(MapDeviceFenceRecordVo vo) { + if (vo == null || StringUtils.isBlank(vo.getDeviceType())) { + return; + } + vo.setDeviceTypeName(getDeviceTypeName(vo.getDeviceType())); + } + + /** + * 填充进出类型名称 + */ + private void fillInOutTypeName(MapDeviceFenceRecordVo vo) { + if (vo == null || vo.getInOutType() == null) { + return; + } + vo.setInOutTypeName(vo.getInOutType() == 0 ? "进入" : "离开"); + } + + /** + * 填充规则类型名称 + */ + private void fillRuleTypeName(MapDeviceFenceRecordVo vo) { + if (vo == null || vo.getRuleType() == null) { + return; + } + vo.setRuleTypeName(vo.getRuleType() == 0 ? "禁入" : "禁出"); + } + + /** + * 填充是否违规名称 + */ + private void fillViolationName(MapDeviceFenceRecordVo vo) { + if (vo == null || vo.getViolation() == null) { + return; + } + vo.setViolationName(vo.getViolation() == 0 ? "否" : "是"); + } + + /** + * 获取设备类型名称 + */ + private String getDeviceTypeName(String deviceType) { + // 这里可以根据实际业务逻辑设置设备类型名称 + // 例如从字典表中获取 + switch (deviceType) { + case "1": + return "执法记录仪"; + case "2": + return "车载终端"; + case "3": + return "对讲机"; + case "4": + return "单兵装备"; + case "5": + return "无人机"; + default: + return "未知设备"; + } + } +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonDeviceServiceImpl.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonDeviceServiceImpl.java new file mode 100644 index 00000000..e080a0c2 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonDeviceServiceImpl.java @@ -0,0 +1,250 @@ + +package org.dromara.system.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.system.domain.MapPolygon; +import org.dromara.system.domain.MapPolygonDevice; +import org.dromara.system.domain.TDevice; +import org.dromara.system.domain.bo.MapPolygonDeviceBo; +import org.dromara.system.domain.vo.MapPolygonDeviceVo; +import org.dromara.system.mapper.MapPolygonDeviceMapper; +import org.dromara.system.mapper.MapPolygonMapper; +import org.dromara.system.mapper.TDeviceMapper; +import org.dromara.system.service.IMapPolygonDeviceService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 电子围栏设备绑定Service业务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class MapPolygonDeviceServiceImpl implements IMapPolygonDeviceService { + + private final MapPolygonDeviceMapper baseMapper; + private final MapPolygonMapper mapPolygonMapper; + private final TDeviceMapper tDeviceMapper; + + @Override + public MapPolygonDeviceVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + @Override + public List queryList(MapPolygonDeviceBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(MapPolygonDeviceBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(ObjectUtil.isNotNull(bo.getPolygonId()), MapPolygonDevice::getPolygonId, bo.getPolygonId()); + lqw.eq(StringUtils.isNotBlank(bo.getDeviceCode()), MapPolygonDevice::getDeviceCode, bo.getDeviceCode()); + lqw.eq(StringUtils.isNotBlank(bo.getDeviceType()), MapPolygonDevice::getDeviceType, bo.getDeviceType()); + lqw.orderByDesc(MapPolygonDevice::getCreateTime); + return lqw; + } + + @Override + public TableDataInfo queryPageList(MapPolygonDeviceBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + @Override + public List queryDeviceListByPolygonId(Integer polygonId) { + List list = baseMapper.selectDeviceListByPolygonId(polygonId); + // 填充设备类型名称 + fillDeviceTypeName(list); + return list; + } + + @Override + public List queryPolygonListByDeviceCode(String deviceCode) { + List list = baseMapper.selectPolygonListByDeviceCode(deviceCode); + // 填充围栏名称 + fillPolygonName(list); + return list; + } + + @Override + public Boolean insertByBo(MapPolygonDeviceBo bo) { + MapPolygonDevice add = MapstructUtils.convert(bo, MapPolygonDevice.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + @Override + public Boolean batchInsert(Integer polygonId, List deviceCodes) { + if (deviceCodes == null || deviceCodes.isEmpty()) { + return false; + } + + // 获取围栏信息 + MapPolygon polygon = mapPolygonMapper.selectById(polygonId); + if (polygon == null) { + throw new RuntimeException("围栏不存在"); + } + + // 查询设备信息 + List devices = tDeviceMapper.selectList( + Wrappers.lambdaQuery(TDevice.class) + .in(TDevice::getDeviceCode, deviceCodes) + ); + + if (devices == null || devices.isEmpty()) { + throw new RuntimeException("设备不存在"); + } + + // 构建绑定关系列表 + List list = new ArrayList<>(); + for (TDevice device : devices) { + MapPolygonDevice binding = new MapPolygonDevice(); + binding.setPolygonId(polygonId); + binding.setDeviceCode(device.getDeviceCode()); + binding.setDeviceType(device.getDeviceType()); + list.add(binding); + } + + baseMapper.deleteByPolygonId(polygonId); + // 批量插入 + return baseMapper.insertBatch(list); + } + + @Override + public Boolean updateByBo(MapPolygonDeviceBo bo) { + MapPolygonDevice update = MapstructUtils.convert(bo, MapPolygonDevice.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(MapPolygonDevice entity) { + // 校验围栏是否存在 + MapPolygon polygon = mapPolygonMapper.selectById(entity.getPolygonId()); + if (polygon == null) { + throw new RuntimeException("围栏不存在"); + } + + // 校验设备是否存在 + TDevice device = tDeviceMapper.selectOne( + Wrappers.lambdaQuery(TDevice.class) + .eq(TDevice::getDeviceCode, entity.getDeviceCode()) + .eq(TDevice::getDeviceType, entity.getDeviceType()) + ); + if (device == null) { + throw new RuntimeException("设备不存在"); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 进行有效性校验 + for (Long id : ids) { + MapPolygonDeviceVo vo = queryById(id); + if (vo == null) { + throw new RuntimeException("绑定关系不存在"); + } + } + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + @Override + public Boolean deleteByPolygonIdAndDeviceCodes(Integer polygonId, List deviceCodes) { + if (deviceCodes == null || deviceCodes.isEmpty()) { + return false; + } + return baseMapper.deleteByPolygonIdAndDeviceCodes(polygonId, deviceCodes) > 0; + } + + /** + * 填充设备类型名称 + */ + private void fillDeviceTypeName(List list) { + if (list == null || list.isEmpty()) { + return; + } + + // 根据设备类型获取类型名称 + for (MapPolygonDeviceVo vo : list) { + if (StringUtils.isNotBlank(vo.getDeviceType())) { + // 这里可以根据实际业务逻辑设置设备类型名称 + // 例如从字典表中获取 + vo.setDeviceTypeName(getDeviceTypeName(vo.getDeviceType())); + } + } + } + + /** + * 填充围栏名称 + */ + private void fillPolygonName(List list) { + if (list == null || list.isEmpty()) { + return; + } + + // 获取所有围栏ID + List polygonIds = list.stream() + .map(MapPolygonDeviceVo::getPolygonId) + .distinct() + .collect(Collectors.toList()); + + // 查询围栏信息 + List polygons = mapPolygonMapper.selectBatchIds(polygonIds); + + // 构建围栏ID到围栏名称的映射 + java.util.Map polygonNameMap = polygons.stream() + .collect(Collectors.toMap(MapPolygon::getId, MapPolygon::getName)); + + // 填充围栏名称 + for (MapPolygonDeviceVo vo : list) { + vo.setPolygonName(polygonNameMap.get(vo.getPolygonId())); + } + } + + /** + * 获取设备类型名称 + */ + private String getDeviceTypeName(String deviceType) { + // 这里可以根据实际业务逻辑设置设备类型名称 + // 例如从字典表中获取 + switch (deviceType) { + case "1": + return "执法记录仪"; + case "2": + return "车载终端"; + case "3": + return "对讲机"; + case "4": + return "单兵装备"; + case "5": + return "无人机"; + default: + return "未知设备"; + } + } +} diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonServiceImpl.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonServiceImpl.java index 51faba66..6528c35e 100644 --- a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonServiceImpl.java +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/service/impl/MapPolygonServiceImpl.java @@ -54,12 +54,12 @@ public class MapPolygonServiceImpl implements IMapPolygonService { query.setLayerId(bo.getLayerId()); query.setStatus(bo.getStatus()); query.setRuleType(bo.getRuleType()); - + // 使用数据库分页查询 Page page = pageQuery.build(); List records = baseMapper.selectPageWithGeometry(page, query); page.setRecords(records); - + return TableDataInfo.build(page); } @@ -77,7 +77,7 @@ public class MapPolygonServiceImpl implements IMapPolygonService { query.setLayerId(bo.getLayerId()); query.setStatus(bo.getStatus()); query.setRuleType(bo.getRuleType()); - + return baseMapper.selectListWithGeometry(query); } @@ -101,6 +101,7 @@ public class MapPolygonServiceImpl implements IMapPolygonService { public Boolean insertByBo(MapPolygonBo bo) { MapPolygon add = org.dromara.common.core.utils.MapstructUtils.convert(bo, MapPolygon.class); validEntityBeforeSave(add); + add.setStatus((short) 1); boolean flag = baseMapper.insertWithGeometry(add) > 0; if (flag) { bo.setId(add.getId()); diff --git a/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/task/FenceDetectionTask.java b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/task/FenceDetectionTask.java new file mode 100644 index 00000000..e0eb9817 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/java/org/dromara/system/task/FenceDetectionTask.java @@ -0,0 +1,94 @@ +package org.dromara.system.task; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.system.domain.bo.MapDeviceFenceRecordBo; +import org.dromara.system.service.FenceDetectionService; +import org.dromara.system.service.IMapDeviceFenceRecordService; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 设备进出检测定时任务 + * + * @author luya + */ +@Slf4j +@RequiredArgsConstructor +@Component +public class FenceDetectionTask { + + private final FenceDetectionService fenceDetectionService; + private final IMapDeviceFenceRecordService mapDeviceFenceRecordService; + + /** + * 定时检测设备进出围栏情况 + * 每隔1分钟执行一次 + */ + @Scheduled(cron = "0 */2 * * * ?") + public void detectDeviceInOut() { + log.debug("开始检测设备进出围栏情况"); + + try { + // 检测设备进出围栏情况 + List records = fenceDetectionService.detectDeviceInOut(); + + if (!records.isEmpty()) { + // 保存进出记录 + boolean success = mapDeviceFenceRecordService.batchInsert(records); + + if (success) { + log.debug("检测到 {} 条设备进出记录", records.size()); + + // 处理违规记录 + processViolationRecords(records); + } else { + log.error("保存设备进出记录失败"); + } + } else { + log.debug("未检测到设备进出记录"); + } + } catch (Exception e) { + log.error("检测设备进出围栏情况失败", e); + } + + log.debug("结束检测设备进出围栏情况"); + } + + /** + * 处理违规记录 + * + * @param records 进出记录列表 + */ + private void processViolationRecords(List records) { + for (MapDeviceFenceRecordBo record : records) { + if (record.getViolation() != null && record.getViolation() == 1) { + // 发送违规提醒 + sendViolationAlert(record); + } + } + } + + /** + * 发送违规提醒 + * + * @param record 进出记录 + */ + private void sendViolationAlert(MapDeviceFenceRecordBo record) { + try { + // TODO: 实现违规提醒逻辑 + // 可以使用WebSocket向客户端推送实时提醒 + // 或者使用消息队列异步处理提醒通知 + log.warn("设备 {}:{} {} 围栏 {},违规类型:{}", + record.getDeviceType(), + record.getDeviceCode(), + record.getInOutType() == 0 ? "进入" : "离开", + record.getPolygonName(), + record.getRuleType() == 0 ? "禁入" : "禁出"); + } catch (Exception e) { + log.error("发送违规提醒失败", e); + } + } +} diff --git a/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapDeviceFenceRecordMapper.xml b/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapDeviceFenceRecordMapper.xml new file mode 100644 index 00000000..f5c51175 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapDeviceFenceRecordMapper.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonDeviceMapper.xml b/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonDeviceMapper.xml new file mode 100644 index 00000000..90026171 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonDeviceMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM map_polygon_device + WHERE polygon_id = #{polygonId} + AND device_code IN + + #{code} + + + + + DELETE FROM map_polygon_device + WHERE polygon_id = #{polygonId} + + + + + + diff --git a/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonMapper.xml b/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonMapper.xml index 1ff8189f..a54720ae 100644 --- a/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonMapper.xml +++ b/stwzhj-modules/wzhj-system/src/main/resources/mapper/system/MapPolygonMapper.xml @@ -120,5 +120,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE id = #{id} + + + diff --git a/stwzhj-modules/wzhj-system/src/main/resources/sql/map_device_fence_record.sql b/stwzhj-modules/wzhj-system/src/main/resources/sql/map_device_fence_record.sql new file mode 100644 index 00000000..e5562f92 --- /dev/null +++ b/stwzhj-modules/wzhj-system/src/main/resources/sql/map_device_fence_record.sql @@ -0,0 +1,36 @@ +-- ---------------------------- +-- Table structure for map_device_fence_record +-- ---------------------------- +DROP TABLE IF EXISTS "public"."map_device_fence_record"; +CREATE TABLE "public"."map_device_fence_record" ( + "id" int4 NOT NULL DEFAULT nextval('map_elements_id_seq'::regclass), + "device_code" varchar(100) COLLATE "pg_catalog"."default", + "device_type" varchar(16) COLLATE "pg_catalog"."default", + "device_name" varchar(50) COLLATE "pg_catalog"."default", + "polygon_id" int4, + "polygon_name" varchar(254) COLLATE "pg_catalog"."default", + "in_out_type" int2, + "rule_type" int2, + "violation" int2, + "location" varchar(200) COLLATE "pg_catalog"."default", + "record_time" timestamp(0) DEFAULT CURRENT_TIMESTAMP, + "dept_id" varchar(254) COLLATE "pg_catalog"."default", + "dept_name" varchar(255) COLLATE "pg_catalog"."default", + "remark" varchar(254) COLLATE "pg_catalog"."default" +) +; +COMMENT ON COLUMN "public"."map_device_fence_record"."id" IS 'ID'; +COMMENT ON COLUMN "public"."map_device_fence_record"."device_code" IS '设备编码'; +COMMENT ON COLUMN "public"."map_device_fence_record"."device_type" IS '设备类型'; +COMMENT ON COLUMN "public"."map_device_fence_record"."device_name" IS '设备名称'; +COMMENT ON COLUMN "public"."map_device_fence_record"."polygon_id" IS '围栏ID'; +COMMENT ON COLUMN "public"."map_device_fence_record"."polygon_name" IS '围栏名称'; +COMMENT ON COLUMN "public"."map_device_fence_record"."in_out_type" IS '进出类型 0进入 1离开'; +COMMENT ON COLUMN "public"."map_device_fence_record"."rule_type" IS '规则类型 0禁入 1禁出'; +COMMENT ON COLUMN "public"."map_device_fence_record"."violation" IS '是否违规 0否 1是'; +COMMENT ON COLUMN "public"."map_device_fence_record"."location" IS '位置坐标'; +COMMENT ON COLUMN "public"."map_device_fence_record"."record_time" IS '记录时间'; +COMMENT ON COLUMN "public"."map_device_fence_record"."dept_id" IS '部门ID'; +COMMENT ON COLUMN "public"."map_device_fence_record"."dept_name" IS '部门名称'; +COMMENT ON COLUMN "public"."map_device_fence_record"."remark" IS '备注'; +COMMENT ON TABLE "public"."map_device_fence_record" IS '设备进出记录';