Browse Source

+bd-uwb坐标监听

chen.cheng 8 months ago
parent
commit
6745f7439f
75 changed files with 6177 additions and 39 deletions
  1. 1 0
      .gitignore
  2. 23 0
      bd-location/Dockerfile
  3. 13 0
      bd-location/build.xml
  4. 104 0
      bd-location/deploy.sh
  5. 131 0
      bd-location/pom.xml
  6. 20 0
      bd-location/src/main/java/com/ruoyi/BDApplication.java
  7. 18 0
      bd-location/src/main/java/com/ruoyi/RuoYiServletInitializer.java
  8. 83 0
      bd-location/src/main/java/com/ruoyi/bd/domain/BdDevcTrail.java
  9. 135 0
      bd-location/src/main/java/com/ruoyi/bd/domain/BdDevcTrailUwb.java
  10. 142 0
      bd-location/src/main/java/com/ruoyi/bd/domain/BdFenceInfo.java
  11. 162 0
      bd-location/src/main/java/com/ruoyi/bd/domain/BdFenceVioEvt.java
  12. 48 0
      bd-location/src/main/java/com/ruoyi/bd/domain/UWBAuth.java
  13. 61 0
      bd-location/src/main/java/com/ruoyi/bd/mapper/BdDevcTrailMapper.java
  14. 63 0
      bd-location/src/main/java/com/ruoyi/bd/mapper/BdDevcTrailUwbMapper.java
  15. 62 0
      bd-location/src/main/java/com/ruoyi/bd/mapper/BdFenceInfoMapper.java
  16. 62 0
      bd-location/src/main/java/com/ruoyi/bd/mapper/BdFenceVioEvtMapper.java
  17. 61 0
      bd-location/src/main/java/com/ruoyi/bd/service/IBdDevcTrailService.java
  18. 63 0
      bd-location/src/main/java/com/ruoyi/bd/service/IBdDevcTrailUwbService.java
  19. 62 0
      bd-location/src/main/java/com/ruoyi/bd/service/IBdFenceInfoService.java
  20. 62 0
      bd-location/src/main/java/com/ruoyi/bd/service/IBdFenceVioEvtService.java
  21. 120 0
      bd-location/src/main/java/com/ruoyi/bd/service/engine/EvtFusionEngine.java
  22. 30 0
      bd-location/src/main/java/com/ruoyi/bd/service/engine/IFusionEngine.java
  23. 79 0
      bd-location/src/main/java/com/ruoyi/bd/service/engine/LocationInfo.java
  24. 152 0
      bd-location/src/main/java/com/ruoyi/bd/service/engine/impl/FenceBreakInEngine.java
  25. 100 0
      bd-location/src/main/java/com/ruoyi/bd/service/engine/impl/PointFusionEngine.java
  26. 161 0
      bd-location/src/main/java/com/ruoyi/bd/service/engine/impl/RoomBreakInEngine.java
  27. 96 0
      bd-location/src/main/java/com/ruoyi/bd/service/impl/BdDevcTrailServiceImpl.java
  28. 97 0
      bd-location/src/main/java/com/ruoyi/bd/service/impl/BdDevcTrailUwbServiceImpl.java
  29. 98 0
      bd-location/src/main/java/com/ruoyi/bd/service/impl/BdFenceInfoServiceImpl.java
  30. 97 0
      bd-location/src/main/java/com/ruoyi/bd/service/impl/BdFenceVioEvtServiceImpl.java
  31. 10 0
      bd-location/src/main/java/com/ruoyi/bd/service/rpc/UWBWebService.java
  32. 111 0
      bd-location/src/main/java/com/ruoyi/bd/socket/FenceVioEvtSocketServer.java
  33. 110 0
      bd-location/src/main/java/com/ruoyi/bd/socket/PointWebSocketServer.java
  34. 87 0
      bd-location/src/main/java/com/ruoyi/bd/socket/UWBSocket/UWBSocketClient.java
  35. 25 0
      bd-location/src/main/java/com/ruoyi/common/BDConst.java
  36. 21 0
      bd-location/src/main/java/com/ruoyi/common/enums/EvtStatus.java
  37. 23 0
      bd-location/src/main/java/com/ruoyi/common/enums/EvtType.java
  38. 22 0
      bd-location/src/main/java/com/ruoyi/common/enums/FenceType.java
  39. 31 0
      bd-location/src/main/java/com/ruoyi/mqtt/UWBLocationListener.java
  40. 65 0
      bd-location/src/main/java/com/ruoyi/mqtt/UWBLocationSubscribeListener.java
  41. 30 0
      bd-location/src/main/java/com/ruoyi/task/BaseTask.java
  42. 24 0
      bd-location/src/main/java/com/ruoyi/task/taskiml/ObjTailTask.java
  43. 104 0
      bd-location/src/main/java/com/ruoyi/web/controller/bd/BdDevcTrailController.java
  44. 138 0
      bd-location/src/main/java/com/ruoyi/web/controller/bd/BdDevcTrailUwbController.java
  45. 95 0
      bd-location/src/main/java/com/ruoyi/web/controller/bd/BdFenceInfoController.java
  46. 101 0
      bd-location/src/main/java/com/ruoyi/web/controller/bd/BdFenceVioEvtController.java
  47. 20 0
      bd-location/src/main/java/com/ruoyi/web/core/config/MqttCfg.java
  48. 125 0
      bd-location/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java
  49. 134 0
      bd-location/src/main/java/com/ruoyi/web/core/config/UWBCfg.java
  50. 20 0
      bd-location/src/main/java/com/ruoyi/web/core/config/forest/UWBService.java
  51. 1 0
      bd-location/src/main/resources/META-INF/spring-devtools.properties
  52. 110 0
      bd-location/src/main/resources/application-druid.yml
  53. 105 0
      bd-location/src/main/resources/application-hm.yml
  54. 108 0
      bd-location/src/main/resources/application-k8s.yml
  55. 104 0
      bd-location/src/main/resources/application-prod.yml
  56. 128 0
      bd-location/src/main/resources/application.yml
  57. 2 0
      bd-location/src/main/resources/banner.txt
  58. 38 0
      bd-location/src/main/resources/i18n/messages.properties
  59. 93 0
      bd-location/src/main/resources/logback.xml
  60. 82 0
      bd-location/src/main/resources/mapper/bd/BdDevcTrailMapper.xml
  61. 132 0
      bd-location/src/main/resources/mapper/bd/BdDevcTrailUwbMapper.xml
  62. 114 0
      bd-location/src/main/resources/mapper/bd/BdFenceInfoMapper.xml
  63. 125 0
      bd-location/src/main/resources/mapper/bd/BdFenceVioEvtMapper.xml
  64. 20 0
      bd-location/src/main/resources/mybatis/mybatis-config.xml
  65. 1 0
      bd-location/version
  66. 46 0
      pom.xml
  67. 13 0
      ruoyi-common/pom.xml
  68. 665 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DateTimeUtil.java
  69. 38 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ExpressUtil.java
  70. 259 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/StreamUtils.java
  71. 3 3
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java
  72. 72 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/geo/CoordinateConverter.java
  73. 197 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/geo/GeoUtils.java
  74. 18 35
      ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java
  75. 1 1
      ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java

+ 1 - 0
.gitignore

@@ -45,3 +45,4 @@ nbdist/
 !*/build/*.java
 !*/build/*.html
 !*/build/*.xml
+out/*

+ 23 - 0
bd-location/Dockerfile

@@ -0,0 +1,23 @@
+# 使用官方的 OpenJDK 8 镜像作为基础镜像
+FROM openjdk:8-jdk-alpine
+# author
+MAINTAINER hs-bd
+
+# 创建存放上传文件的目录
+RUN mkdir -p /opt/project/ruoyi/ruoyi-backend/upload-file-path
+# 创建存放日志的目录
+RUN mkdir -p /opt/project/ruoyi/ruoyi-backend/logs
+# 安装字体文件
+RUN mkdir -p /etc/apk/
+RUN touch /etc/apk/repositories
+RUN echo -e 'https://mirrors.aliyun.com/alpine/v3.6/main/\nhttps://mirrors.aliyun.com/alpine/v3.6/community/' > /etc/apk/repositories
+RUN set -xe && apk --no-cache add ttf-dejavu fontconfig
+
+# 设置工作目录
+WORKDIR /opt/project/ruoyi/ruoyi-backend
+# 将构建好的 JAR 文件复制到容器中
+COPY ./target/bd-location.jar bd-location.jar
+# 暴露应用程序端口
+EXPOSE 8080
+# 启动应用程序
+CMD ["nohup","java","-jar","/opt/project/ruoyi/ruoyi-backend/bd-location.jar", "--spring.profiles.active=k8s" , ">", "/dev/null","2>&1" ,"&"]

+ 13 - 0
bd-location/build.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="bd-location" basedir=".">
+    <property environment="SystemVariable"/>
+    <property name="buildName" value="bd-location"/>
+    <!--默认目标-->
+    <target name="Build" depends="copy-out"/>
+    <!--所有的打包最终需要文件copy到特定目录,方便jenkins上取包-->
+    <target name="copy-out">
+        <copy todir="${basedir}/../out" overwrite="true" failonerror="no">
+            <fileset file="${basedir}/target/${buildName}.jar"/>
+        </copy>
+    </target>
+</project>

+ 104 - 0
bd-location/deploy.sh

@@ -0,0 +1,104 @@
+#!/bin/bash
+
+# 设置变量
+PROJECT_NAME="bd-app-backend"
+DOCKER_IMAGE_NAME="bd-app-backend"
+REMOTE_REGISTRY=${REMOTE_REGISTRY:-"docker.xt.wenhq.top:8083/bd"}
+VERSION_FILE="version"
+LOG_FILE="deploy.log"
+
+# 日志函数
+log() {
+  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
+}
+
+# 读取当前版本号
+CURRENT_VERSION=$(cat $VERSION_FILE)
+if [ -z "$CURRENT_VERSION" ]; then
+  CURRENT_VERSION="0.0"
+fi
+
+# 分离大版本号和小版本号
+CURRENT_MAJOR=$(echo $CURRENT_VERSION | cut -d'.' -f1)
+CURRENT_MINOR=$(echo $CURRENT_VERSION | cut -d'.' -f2)
+
+# 用户选择是否修改大版本号
+read -p "是否修改大版本号?(a/i/ne): " modify_major_version
+
+if [ "$modify_major_version" == "a" ]; then
+  CURRENT_MAJOR=$((CURRENT_MAJOR + 1))
+  CURRENT_MINOR=0  # 重置小版本号
+elif [ "$modify_major_version" == "i" ]; then
+  CURRENT_MINOR=$((CURRENT_MINOR + 1))  # 增加小版本号
+else
+  echo "跳过版本号修改"
+fi
+
+# 更新版本号
+NEXT_VERSION="${CURRENT_MAJOR}.${CURRENT_MINOR}"
+echo $NEXT_VERSION > $VERSION_FILE
+echo  -e "\e[1;34;42m版本号:" $NEXT_VERSION " 日期:$(date +"%Y-%m-%d %H:%M:%S")" "\e[0m"
+# 用户选择执行的步骤
+echo "请选择要执行的步骤(输入数字):"
+echo "1. 编译bd-app-backend项目"
+echo "2. 构建Docker镜像"
+echo "3. 推送Docker镜像到远程仓库"
+echo "4. 执行所有步骤"
+read -p "请输入选项(1/2/3/4): " choice
+
+# 编译项目
+compile_project() {
+  log "开始编译bd-app-backend项目..."
+  mvn clean install
+  if [ $? -ne 0 ]; then
+    log "bd-app-backend项目编译失败。退出脚本。"
+    exit 1
+  fi
+  log "bd-app-backend项目编译完成"
+}
+
+# 构建Docker镜像
+build_docker_image() {
+  log "开始构建Docker镜像..."
+  docker build -t ${REMOTE_REGISTRY}/${DOCKER_IMAGE_NAME}:v${NEXT_VERSION} .
+  if [ $? -ne 0 ]; then
+    log "Docker镜像构建失败。退出脚本。"
+    exit 1
+  fi
+  log "Docker镜像构建完成"
+}
+
+# 推送Docker镜像到远程仓库
+push_docker_image() {
+  log "开始推送Docker镜像到远程仓库..."
+  docker push ${REMOTE_REGISTRY}/${DOCKER_IMAGE_NAME}:v${NEXT_VERSION}
+  if [ $? -ne 0 ]; then
+    log "Docker镜像推送失败。退出脚本。"
+    exit 1
+  fi
+  log "Docker镜像推送完成"
+}
+
+# 根据用户选择执行相应的步骤
+case $choice in
+  1)
+    compile_project
+    ;;
+  2)
+    build_docker_image
+    ;;
+  3)
+    push_docker_image
+    ;;
+  4)
+    compile_project
+    build_docker_image
+    push_docker_image
+    ;;
+  *)
+    log "无效的选项。退出脚本。"
+    exit 1
+    ;;
+esac
+
+log "所有任务完成"

+ 131 - 0
bd-location/pom.xml

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ruoyi</groupId>
+        <artifactId>ruoyi</artifactId>
+        <version>3.8.8</version>
+    </parent>
+    <packaging>jar</packaging>
+    <artifactId>bd-location</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+
+        <!-- swagger3-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-framework</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.dtflys.forest</groupId>
+            <artifactId>forest-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.dreamlu</groupId>
+            <artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.5.15</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+            <!--The configuration of maven-antrun-plugin 打zip包-->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <version>1.8</version>
+                <executions>
+                    <execution>
+                        <id>package</id>
+                        <phase>package</phase>
+                        <configuration>
+                            <target>
+                                <ant antfile="build.xml">
+                                    <target name="Build"/>
+                                </ant>
+                            </target>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 20 - 0
bd-location/src/main/java/com/ruoyi/BDApplication.java

@@ -0,0 +1,20 @@
+package com.ruoyi;
+
+import com.dtflys.forest.springboot.annotation.ForestScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+/**
+ * 启动程序
+ *
+ * @author ruoyi
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+@ForestScan(basePackages = {"com.ruoyi.bd.service.rpc"})
+public class BDApplication {
+    public static void main(String[] args) {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication.run(BDApplication.class, args);
+    }
+}

+ 18 - 0
bd-location/src/main/java/com/ruoyi/RuoYiServletInitializer.java

@@ -0,0 +1,18 @@
+package com.ruoyi;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author ruoyi
+ */
+public class RuoYiServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(BDApplication.class);
+    }
+}

+ 83 - 0
bd-location/src/main/java/com/ruoyi/bd/domain/BdDevcTrail.java

@@ -0,0 +1,83 @@
+package com.ruoyi.bd.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 设备轨迹对象 bd_devc_trail
+ * 
+ * @author ruoyi
+ * @date 2024-11-07
+ */
+public class BdDevcTrail extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /**  */
+    private Long id;
+
+    /** 设备唯一键 */
+    @Excel(name = "设备唯一键")
+    private String devcKey;
+
+    /**  */
+    @Excel(name = "")
+    private String dt;
+
+    /**  */
+    @Excel(name = "")
+    private String poly;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+    public void setDevcKey(String devcKey) 
+    {
+        this.devcKey = devcKey;
+    }
+
+    public String getDevcKey() 
+    {
+        return devcKey;
+    }
+    public void setDt(String dt) 
+    {
+        this.dt = dt;
+    }
+
+    public String getDt() 
+    {
+        return dt;
+    }
+    public void setPoly(String poly) 
+    {
+        this.poly = poly;
+    }
+
+    public String getPoly() 
+    {
+        return poly;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("devcKey", getDevcKey())
+            .append("dt", getDt())
+            .append("poly", getPoly())
+            .append("updateTime", getUpdateTime())
+            .append("createTime", getCreateTime())
+            .append("createBy", getCreateBy())
+            .append("updateBy", getUpdateBy())
+            .toString();
+    }
+}

+ 135 - 0
bd-location/src/main/java/com/ruoyi/bd/domain/BdDevcTrailUwb.java

@@ -0,0 +1,135 @@
+package com.ruoyi.bd.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 室内坐标定位对象 bd_devc_trail_uwb
+ *
+ * @author ruoyi
+ * @date 2024-10-16
+ */
+public class BdDevcTrailUwb extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 设备唯一键 */
+    @Excel(name = "设备唯一键")
+    private String devcKey;
+
+    /** 纬度 */
+    @Excel(name = "纬度")
+    private Double lat;
+
+    /** 经度 */
+    @Excel(name = "经度")
+    private Double lng;
+
+    /** 日期 */
+    @Excel(name = "日期")
+    private String dt;
+
+    private Integer stepIndex;
+
+    private Integer tp;
+
+    /** 所在空间唯一键 */
+    @Excel(name = "所在空间唯一键")
+    private String roomIndex;
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+    public void setDevcKey(String devcKey)
+    {
+        this.devcKey = devcKey;
+    }
+
+    public String getDevcKey()
+    {
+        return devcKey;
+    }
+    public void setLat(Double lat)
+    {
+        this.lat = lat;
+    }
+
+    public Double getLat()
+    {
+        return lat;
+    }
+    public void setLng(Double lng)
+    {
+        this.lng = lng;
+    }
+
+    public Double getLng()
+    {
+        return lng;
+    }
+    public void setDt(String dt)
+    {
+        this.dt = dt;
+    }
+
+    public String getDt()
+    {
+        return dt;
+    }
+    public void setStepIndex(Integer stepIndex)
+    {
+        this.stepIndex = stepIndex;
+    }
+
+    public Integer getStepIndex()
+    {
+        return stepIndex;
+    }
+    public void setTp(Integer tp)
+    {
+        this.tp = tp;
+    }
+
+    public Integer getTp()
+    {
+        return tp;
+    }
+    public void setRoomIndex(String roomIndex)
+    {
+        this.roomIndex = roomIndex;
+    }
+
+    public String getRoomIndex()
+    {
+        return roomIndex;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("devcKey", getDevcKey())
+            .append("lat", getLat())
+            .append("lng", getLng())
+            .append("dt", getDt())
+            .append("stepIndex", getStepIndex())
+            .append("tp", getTp())
+            .append("roomIndex", getRoomIndex())
+            .append("updateTime", getUpdateTime())
+            .append("createTime", getCreateTime())
+            .append("createBy", getCreateBy())
+            .append("updateBy", getUpdateBy())
+            .toString();
+    }
+}

+ 142 - 0
bd-location/src/main/java/com/ruoyi/bd/domain/BdFenceInfo.java

@@ -0,0 +1,142 @@
+package com.ruoyi.bd.domain;
+
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.locationtech.jts.geom.Polygon;
+
+/**
+ * 围栏基础信息对象 bd_fence_info
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+public class BdFenceInfo extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    private Long id;
+
+    /**
+     * 围栏名称
+     */
+    @Excel(name = "围栏名称")
+    private String defenceName;
+
+    /**
+     * 围栏图形坐标
+     */
+    private String poly;
+
+    /**
+     * 中心点
+     */
+    @Excel(name = "中心点")
+    private Double centerLng;
+
+    /**
+     * 中心点
+     */
+    @Excel(name = "中心点")
+    private Double centerLat;
+
+    private Polygon polygon;
+
+    private String fenceType;
+
+    private String locationId;
+
+    private Integer altitude;
+
+
+    public String getLocationId() {
+        return locationId;
+    }
+
+    public void setLocationId(String locationId) {
+        this.locationId = locationId;
+    }
+
+    public Integer getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(Integer altitude) {
+        this.altitude = altitude;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setDefenceName(String defenceName) {
+        this.defenceName = defenceName;
+    }
+
+    public String getDefenceName() {
+        return defenceName;
+    }
+
+    public void setPoly(String poly) {
+        this.poly = poly;
+    }
+
+    public String getPoly() {
+        return poly;
+    }
+
+    public void setCenterLng(Double centerLng) {
+        this.centerLng = centerLng;
+    }
+
+    public Double getCenterLng() {
+        return centerLng;
+    }
+
+    public void setCenterLat(Double centerLat) {
+        this.centerLat = centerLat;
+    }
+
+    public Double getCenterLat() {
+        return centerLat;
+    }
+
+    public Polygon getPolygon() {
+        return polygon;
+    }
+
+    public void setPolygon(Polygon polygon) {
+        this.polygon = polygon;
+    }
+
+    public String getFenceType() {
+        return fenceType;
+    }
+
+    public void setFenceType(String fenceType) {
+        this.fenceType = fenceType;
+    }
+
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("id", getId())
+                .append("defenceName", getDefenceName())
+                .append("poly", getPoly())
+                .append("centerLng", getCenterLng())
+                .append("centerLat", getCenterLat())
+                .append("updateTime", getUpdateTime())
+                .append("createTime", getCreateTime())
+                .append("createBy", getCreateBy())
+                .append("updateBy", getUpdateBy())
+                .toString();
+    }
+}

+ 162 - 0
bd-location/src/main/java/com/ruoyi/bd/domain/BdFenceVioEvt.java

@@ -0,0 +1,162 @@
+package com.ruoyi.bd.domain;
+
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 围栏闯禁事件对象 bd_fence_vio_evt
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+public class BdFenceVioEvt extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    private Long id;
+
+    /**
+     *
+     */
+    @Excel(name = "")
+    private String evtKey;
+
+    /**
+     * 事件类型
+     * 01 围栏闯禁事件
+     */
+    @Excel(name = "事件类型01 围栏闯禁事件")
+    private String evtType;
+
+    /**
+     * 事件描述
+     */
+    @Excel(name = "事件描述")
+    private String evtDesc;
+
+    /**
+     * 经度
+     */
+    @Excel(name = "经度")
+    private Double lng;
+
+    /**
+     * 维度
+     */
+    @Excel(name = "维度")
+    private Double lat;
+
+    /**
+     * 围栏id
+     */
+    @Excel(name = "围栏id")
+    private Long fenceId;
+
+    private String evtStatus;
+
+    private String evtTime;
+
+    private Long locationId;
+
+    public Long getLocationId() {
+        return locationId;
+    }
+
+    public void setLocationId(Long locationId) {
+        this.locationId = locationId;
+    }
+
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setEvtKey(String evtKey) {
+        this.evtKey = evtKey;
+    }
+
+    public String getEvtKey() {
+        return evtKey;
+    }
+
+    public void setEvtType(String evtType) {
+        this.evtType = evtType;
+    }
+
+    public String getEvtType() {
+        return evtType;
+    }
+
+    public void setEvtDesc(String evtDesc) {
+        this.evtDesc = evtDesc;
+    }
+
+    public String getEvtDesc() {
+        return evtDesc;
+    }
+
+    public void setLng(Double lng) {
+        this.lng = lng;
+    }
+
+    public Double getLng() {
+        return lng;
+    }
+
+    public void setLat(Double lat) {
+        this.lat = lat;
+    }
+
+    public Double getLat() {
+        return lat;
+    }
+
+    public void setFenceId(Long fenceId) {
+        this.fenceId = fenceId;
+    }
+
+    public Long getFenceId() {
+        return fenceId;
+    }
+
+    public String getEvtStatus() {
+        return evtStatus;
+    }
+
+    public void setEvtStatus(String evtStatus) {
+        this.evtStatus = evtStatus;
+    }
+
+    public String getEvtTime() {
+        return evtTime;
+    }
+
+    public void setEvtTime(String evtTime) {
+        this.evtTime = evtTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("id", getId())
+                .append("evtKey", getEvtKey())
+                .append("evtType", getEvtType())
+                .append("evtDesc", getEvtDesc())
+                .append("lng", getLng())
+                .append("lat", getLat())
+                .append("fenceId", getFenceId())
+                .append("updateTime", getUpdateTime())
+                .append("createTime", getCreateTime())
+                .append("createBy", getCreateBy())
+                .append("updateBy", getUpdateBy())
+                .toString();
+    }
+}

+ 48 - 0
bd-location/src/main/java/com/ruoyi/bd/domain/UWBAuth.java

@@ -0,0 +1,48 @@
+package com.ruoyi.bd.domain;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+
+public class UWBAuth {
+    @JSONField(name = "token")
+    private String accessToken;
+
+    @JSONField(name = "register")
+    private String register;
+
+    @JSONField(name = "key")
+    private String key;
+
+    private String tagId;
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public String getRegister() {
+        return register;
+    }
+
+    public void setRegister(String register) {
+        this.register = register;
+    }
+
+    public void setTagId(String tagId) {
+        this.tagId = tagId;
+    }
+
+    public String getTagId() {
+        return tagId;
+    }
+
+    public String getKey() {
+        return this.register;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+}

+ 61 - 0
bd-location/src/main/java/com/ruoyi/bd/mapper/BdDevcTrailMapper.java

@@ -0,0 +1,61 @@
+package com.ruoyi.bd.mapper;
+
+import java.util.List;
+import com.ruoyi.bd.domain.BdDevcTrail;
+
+/**
+ * 设备轨迹Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2024-11-07
+ */
+public interface BdDevcTrailMapper 
+{
+    /**
+     * 查询设备轨迹
+     * 
+     * @param id 设备轨迹主键
+     * @return 设备轨迹
+     */
+    public BdDevcTrail selectBdDevcTrailById(Long id);
+
+    /**
+     * 查询设备轨迹列表
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 设备轨迹集合
+     */
+    public List<BdDevcTrail> selectBdDevcTrailList(BdDevcTrail bdDevcTrail);
+
+    /**
+     * 新增设备轨迹
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 结果
+     */
+    public int insertBdDevcTrail(BdDevcTrail bdDevcTrail);
+
+    /**
+     * 修改设备轨迹
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 结果
+     */
+    public int updateBdDevcTrail(BdDevcTrail bdDevcTrail);
+
+    /**
+     * 删除设备轨迹
+     * 
+     * @param id 设备轨迹主键
+     * @return 结果
+     */
+    public int deleteBdDevcTrailById(Long id);
+
+    /**
+     * 批量删除设备轨迹
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBdDevcTrailByIds(Long[] ids);
+}

+ 63 - 0
bd-location/src/main/java/com/ruoyi/bd/mapper/BdDevcTrailUwbMapper.java

@@ -0,0 +1,63 @@
+package com.ruoyi.bd.mapper;
+
+import java.util.List;
+
+import com.ruoyi.bd.domain.BdDevcTrailUwb;
+
+/**
+ * 室内坐标定位Mapper接口
+ *
+ * @author ruoyi
+ * @date 2024-10-16
+ */
+public interface BdDevcTrailUwbMapper {
+    /**
+     * 查询室内坐标定位
+     *
+     * @param id 室内坐标定位主键
+     * @return 室内坐标定位
+     */
+    public BdDevcTrailUwb selectBdDevcTrailUwbById(Long id);
+
+    /**
+     * 查询室内坐标定位列表
+     *
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 室内坐标定位集合
+     */
+    public List<BdDevcTrailUwb> selectBdDevcTrailUwbList(BdDevcTrailUwb bdDevcTrailUwb);
+
+    /**
+     * 新增室内坐标定位
+     *
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 结果
+     */
+    public int insertBdDevcTrailUwb(BdDevcTrailUwb bdDevcTrailUwb);
+
+    /**
+     * 修改室内坐标定位
+     *
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 结果
+     */
+    public int updateBdDevcTrailUwb(BdDevcTrailUwb bdDevcTrailUwb);
+
+    /**
+     * 删除室内坐标定位
+     *
+     * @param id 室内坐标定位主键
+     * @return 结果
+     */
+    public int deleteBdDevcTrailUwbById(Long id);
+
+    /**
+     * 批量删除室内坐标定位
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBdDevcTrailUwbByIds(Long[] ids);
+
+    void saveObjTail(String dt);
+}

+ 62 - 0
bd-location/src/main/java/com/ruoyi/bd/mapper/BdFenceInfoMapper.java

@@ -0,0 +1,62 @@
+package com.ruoyi.bd.mapper;
+
+import com.ruoyi.bd.domain.BdFenceInfo;
+
+import java.util.List;
+
+/**
+ * 围栏基础信息Mapper接口
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+public interface BdFenceInfoMapper
+{
+    /**
+     * 查询围栏基础信息
+     *
+     * @param id 围栏基础信息主键
+     * @return 围栏基础信息
+     */
+    public BdFenceInfo selectBdFenceInfoById(Long id);
+
+    /**
+     * 查询围栏基础信息列表
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 围栏基础信息集合
+     */
+    public List<BdFenceInfo> selectBdFenceInfoList(BdFenceInfo bdFenceInfo);
+
+    /**
+     * 新增围栏基础信息
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 结果
+     */
+    public int insertBdFenceInfo(BdFenceInfo bdFenceInfo);
+
+    /**
+     * 修改围栏基础信息
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 结果
+     */
+    public int updateBdFenceInfo(BdFenceInfo bdFenceInfo);
+
+    /**
+     * 删除围栏基础信息
+     *
+     * @param id 围栏基础信息主键
+     * @return 结果
+     */
+    public int deleteBdFenceInfoById(Long id);
+
+    /**
+     * 批量删除围栏基础信息
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBdFenceInfoByIds(Long[] ids);
+}

+ 62 - 0
bd-location/src/main/java/com/ruoyi/bd/mapper/BdFenceVioEvtMapper.java

@@ -0,0 +1,62 @@
+package com.ruoyi.bd.mapper;
+
+import com.ruoyi.bd.domain.BdFenceVioEvt;
+
+import java.util.List;
+
+/**
+ * 围栏闯禁事件Mapper接口
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+public interface BdFenceVioEvtMapper
+{
+    /**
+     * 查询围栏闯禁事件
+     *
+     * @param id 围栏闯禁事件主键
+     * @return 围栏闯禁事件
+     */
+    public BdFenceVioEvt selectBdFenceVioEvtById(Long id);
+
+    /**
+     * 查询围栏闯禁事件列表
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 围栏闯禁事件集合
+     */
+    public List<BdFenceVioEvt> selectBdFenceVioEvtList(BdFenceVioEvt bdFenceVioEvt);
+
+    /**
+     * 新增围栏闯禁事件
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 结果
+     */
+    public int insertBdFenceVioEvt(BdFenceVioEvt bdFenceVioEvt);
+
+    /**
+     * 修改围栏闯禁事件
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 结果
+     */
+    public int updateBdFenceVioEvt(BdFenceVioEvt bdFenceVioEvt);
+
+    /**
+     * 删除围栏闯禁事件
+     *
+     * @param id 围栏闯禁事件主键
+     * @return 结果
+     */
+    public int deleteBdFenceVioEvtById(Long id);
+
+    /**
+     * 批量删除围栏闯禁事件
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBdFenceVioEvtByIds(Long[] ids);
+}

+ 61 - 0
bd-location/src/main/java/com/ruoyi/bd/service/IBdDevcTrailService.java

@@ -0,0 +1,61 @@
+package com.ruoyi.bd.service;
+
+import java.util.List;
+import com.ruoyi.bd.domain.BdDevcTrail;
+
+/**
+ * 设备轨迹Service接口
+ * 
+ * @author ruoyi
+ * @date 2024-11-07
+ */
+public interface IBdDevcTrailService 
+{
+    /**
+     * 查询设备轨迹
+     * 
+     * @param id 设备轨迹主键
+     * @return 设备轨迹
+     */
+    public BdDevcTrail selectBdDevcTrailById(Long id);
+
+    /**
+     * 查询设备轨迹列表
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 设备轨迹集合
+     */
+    public List<BdDevcTrail> selectBdDevcTrailList(BdDevcTrail bdDevcTrail);
+
+    /**
+     * 新增设备轨迹
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 结果
+     */
+    public int insertBdDevcTrail(BdDevcTrail bdDevcTrail);
+
+    /**
+     * 修改设备轨迹
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 结果
+     */
+    public int updateBdDevcTrail(BdDevcTrail bdDevcTrail);
+
+    /**
+     * 批量删除设备轨迹
+     * 
+     * @param ids 需要删除的设备轨迹主键集合
+     * @return 结果
+     */
+    public int deleteBdDevcTrailByIds(Long[] ids);
+
+    /**
+     * 删除设备轨迹信息
+     * 
+     * @param id 设备轨迹主键
+     * @return 结果
+     */
+    public int deleteBdDevcTrailById(Long id);
+}

+ 63 - 0
bd-location/src/main/java/com/ruoyi/bd/service/IBdDevcTrailUwbService.java

@@ -0,0 +1,63 @@
+package com.ruoyi.bd.service;
+
+import java.util.List;
+import com.ruoyi.bd.domain.BdDevcTrailUwb;
+
+/**
+ * 室内坐标定位Service接口
+ * 
+ * @author ruoyi
+ * @date 2024-10-16
+ */
+public interface IBdDevcTrailUwbService 
+{
+    /**
+     * 查询室内坐标定位
+     * 
+     * @param id 室内坐标定位主键
+     * @return 室内坐标定位
+     */
+    public BdDevcTrailUwb selectBdDevcTrailUwbById(Long id);
+
+    /**
+     * 查询室内坐标定位列表
+     * 
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 室内坐标定位集合
+     */
+    public List<BdDevcTrailUwb> selectBdDevcTrailUwbList(BdDevcTrailUwb bdDevcTrailUwb);
+
+    /**
+     * 新增室内坐标定位
+     * 
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 结果
+     */
+    public int insertBdDevcTrailUwb(BdDevcTrailUwb bdDevcTrailUwb);
+
+    /**
+     * 修改室内坐标定位
+     * 
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 结果
+     */
+    public int updateBdDevcTrailUwb(BdDevcTrailUwb bdDevcTrailUwb);
+
+    /**
+     * 批量删除室内坐标定位
+     * 
+     * @param ids 需要删除的室内坐标定位主键集合
+     * @return 结果
+     */
+    public int deleteBdDevcTrailUwbByIds(Long[] ids);
+
+    /**
+     * 删除室内坐标定位信息
+     * 
+     * @param id 室内坐标定位主键
+     * @return 结果
+     */
+    public int deleteBdDevcTrailUwbById(Long id);
+
+    public void saveUwbTrail();
+}

+ 62 - 0
bd-location/src/main/java/com/ruoyi/bd/service/IBdFenceInfoService.java

@@ -0,0 +1,62 @@
+package com.ruoyi.bd.service;
+
+import com.ruoyi.bd.domain.BdFenceInfo;
+
+import java.util.List;
+
+/**
+ * 围栏基础信息Service接口
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+public interface IBdFenceInfoService
+{
+    /**
+     * 查询围栏基础信息
+     *
+     * @param id 围栏基础信息主键
+     * @return 围栏基础信息
+     */
+    public BdFenceInfo selectBdFenceInfoById(Long id);
+
+    /**
+     * 查询围栏基础信息列表
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 围栏基础信息集合
+     */
+    public List<BdFenceInfo> selectBdFenceInfoList(BdFenceInfo bdFenceInfo);
+
+    /**
+     * 新增围栏基础信息
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 结果
+     */
+    public int insertBdFenceInfo(BdFenceInfo bdFenceInfo);
+
+    /**
+     * 修改围栏基础信息
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 结果
+     */
+    public int updateBdFenceInfo(BdFenceInfo bdFenceInfo);
+
+    /**
+     * 批量删除围栏基础信息
+     *
+     * @param ids 需要删除的围栏基础信息主键集合
+     * @return 结果
+     */
+    public int deleteBdFenceInfoByIds(Long[] ids);
+
+    /**
+     * 删除围栏基础信息信息
+     *
+     * @param id 围栏基础信息主键
+     * @return 结果
+     */
+    public int deleteBdFenceInfoById(Long id);
+}

+ 62 - 0
bd-location/src/main/java/com/ruoyi/bd/service/IBdFenceVioEvtService.java

@@ -0,0 +1,62 @@
+package com.ruoyi.bd.service;
+
+import com.ruoyi.bd.domain.BdFenceVioEvt;
+
+import java.util.List;
+
+/**
+ * 围栏闯禁事件Service接口
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+public interface IBdFenceVioEvtService
+{
+    /**
+     * 查询围栏闯禁事件
+     *
+     * @param id 围栏闯禁事件主键
+     * @return 围栏闯禁事件
+     */
+    public BdFenceVioEvt selectBdFenceVioEvtById(Long id);
+
+    /**
+     * 查询围栏闯禁事件列表
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 围栏闯禁事件集合
+     */
+    public List<BdFenceVioEvt> selectBdFenceVioEvtList(BdFenceVioEvt bdFenceVioEvt);
+
+    /**
+     * 新增围栏闯禁事件
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 结果
+     */
+    public int insertBdFenceVioEvt(BdFenceVioEvt bdFenceVioEvt);
+
+    /**
+     * 修改围栏闯禁事件
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 结果
+     */
+    public int updateBdFenceVioEvt(BdFenceVioEvt bdFenceVioEvt);
+
+    /**
+     * 批量删除围栏闯禁事件
+     *
+     * @param ids 需要删除的围栏闯禁事件主键集合
+     * @return 结果
+     */
+    public int deleteBdFenceVioEvtByIds(Long[] ids);
+
+    /**
+     * 删除围栏闯禁事件信息
+     *
+     * @param id 围栏闯禁事件主键
+     * @return 结果
+     */
+    public int deleteBdFenceVioEvtById(Long id);
+}

+ 120 - 0
bd-location/src/main/java/com/ruoyi/bd/service/engine/EvtFusionEngine.java

@@ -0,0 +1,120 @@
+package com.ruoyi.bd.service.engine;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.common.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public abstract class EvtFusionEngine implements IFusionEngine {
+    private final static Logger logger = LoggerFactory.getLogger(EvtFusionEngine.class);
+
+    @Resource
+    private RedisCache redisCache;
+
+    private String engineName;
+
+    @Value("${evt-fusion.thread-pool-size:2}")
+    private int threadPoolSize;
+
+    @Resource(name = "threadPoolTaskExecutor")
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    public void init() {
+        List<LinkedBlockingQueue<JSONObject>> queue = getQueue();
+        for (int i = 0; i < threadPoolSize; i++) {
+            queue.add(new LinkedBlockingQueue<>());
+        }
+    }
+
+
+    public void setEngineName(String name) {
+        this.engineName = name;
+    }
+
+    public void push(JSONObject msg) {
+        String key = msg.getString("key");
+        if (StringUtils.isEmpty(key)) {
+            return;
+        }
+        int bucketIndex = computeHashModulo(key, threadPoolSize);
+        List<LinkedBlockingQueue<JSONObject>> queue = getQueue();
+        LinkedBlockingQueue<JSONObject> messageQueue = queue.get(bucketIndex);
+        messageQueue.offer(msg);
+    }
+
+    public void start() {
+        List<LinkedBlockingQueue<JSONObject>> queue = getQueue();
+        for (int i = 0; i < threadPoolSize; i++) {
+            int finalI = i;
+            threadPoolTaskExecutor.execute(() -> {
+                while (true) {
+                    try {
+                        JSONObject msg = queue.get(finalI).take();
+                        LocationInfo locationInfoNew = new LocationInfo(msg.getDouble("latitude"), msg.getDouble("longitude"), msg.getLong("srcTimestamp"));
+                        locationInfoNew.setMsg(msg);
+                        String key = getKey(locationInfoNew);
+                        if (!redisCache.hasKey(key)) {
+                            getBizId(locationInfoNew);
+                            redisCache.setCacheObject(key, locationInfoNew);
+                            newEvtCallback(locationInfoNew);
+                            continue;
+                        }
+                        LocationInfo locationInfo = redisCache.getCacheObject(key);
+                        locationInfoNew.setEvtTimestamp(DateTimeUtil.timestampMillis());
+                        // 判断消息是否需要融合
+                        if (check(locationInfo, locationInfoNew)) {
+                            locationInfo.setSrcTimestamp(locationInfoNew.getSrcTimestamp());
+                            locationInfo.setHandleTimestamp(DateTimeUtil.timestampMillis());
+                            redisCache.setCacheObject(key, locationInfo);
+                        } else {
+                            // 老数据释放
+                            processOlderData(locationInfo);
+                            getBizId(locationInfoNew);
+                            redisCache.setCacheObject(key, locationInfoNew);
+                            newEvtCallback(locationInfoNew);
+                        }
+                    } catch (InterruptedException e) {
+                        logger.error("{} error", this.engineName, e);
+                        // 重置中断状态
+                        Thread.currentThread().interrupt();
+                        // 根据业务逻辑决定是否继续执行
+                        if (Thread.currentThread().isInterrupted()) {
+                            logger.error("任务已中断,不再继续执行");
+                            break;
+                        }
+                    } catch (Exception e) {
+                        logger.error("{} error", this.engineName, e);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * 计算字符串的哈希值并对某个数取余数。
+     *
+     * @param str    字符串
+     * @param modulo 取余数的基数
+     * @return 字符串哈希值对 modulo 取余的结果
+     */
+    private int computeHashModulo(String str, int modulo) {
+        long hash = 0;
+        long base = 31;
+        long baseMod = base % modulo;  // 预计算乘法因子
+
+        for (char c : str.toCharArray()) {
+            hash = (hash * baseMod + c) % modulo;  // 避免整数溢出
+        }
+
+        return (int) (hash % modulo);  // 最终结果转换为 int
+    }
+
+}

+ 30 - 0
bd-location/src/main/java/com/ruoyi/bd/service/engine/IFusionEngine.java

@@ -0,0 +1,30 @@
+package com.ruoyi.bd.service.engine;
+
+import com.alibaba.fastjson2.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public interface IFusionEngine {
+    default Boolean check(LocationInfo older, LocationInfo newer) {
+        return false;
+    }
+
+    default String getKey(LocationInfo msg) {
+        return "not implement";
+    }
+
+    default void getBizId(LocationInfo msg) {
+    }
+
+    default void processOlderData(LocationInfo msg) {
+    }
+
+    default void newEvtCallback(LocationInfo msg) {
+    }
+
+    default List<LinkedBlockingQueue<JSONObject>> getQueue() {
+       return new ArrayList<>();
+    }
+}

+ 79 - 0
bd-location/src/main/java/com/ruoyi/bd/service/engine/LocationInfo.java

@@ -0,0 +1,79 @@
+package com.ruoyi.bd.service.engine;
+
+import com.alibaba.fastjson2.JSONObject;
+
+public class LocationInfo {
+    double latitude;
+    double longitude;
+    long srcTimestamp; // 时间戳,单位毫秒
+
+    long handleTimestamp;
+
+    long evtTimestamp;
+
+    String bizId;
+
+    JSONObject msg;
+
+    public LocationInfo(double latitude, double longitude, long srcTimestamp) {
+        this.latitude = latitude;
+        this.longitude = longitude;
+        this.srcTimestamp = srcTimestamp;
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(double latitude) {
+        this.latitude = latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(double longitude) {
+        this.longitude = longitude;
+    }
+
+    public long getSrcTimestamp() {
+        return srcTimestamp;
+    }
+
+    public void setSrcTimestamp(long srcTimestamp) {
+        this.srcTimestamp = srcTimestamp;
+    }
+
+    public long getHandleTimestamp() {
+        return handleTimestamp;
+    }
+
+    public void setHandleTimestamp(long handleTimestamp) {
+        this.handleTimestamp = handleTimestamp;
+    }
+
+    public long getEvtTimestamp() {
+        return evtTimestamp;
+    }
+
+    public void setEvtTimestamp(long evtTimestamp) {
+        this.evtTimestamp = evtTimestamp;
+    }
+
+    public JSONObject getMsg() {
+        return msg;
+    }
+
+    public void setMsg(JSONObject msg) {
+        this.msg = msg;
+    }
+
+    public String getBizId() {
+        return bizId;
+    }
+
+    public void setBizId(String bizId) {
+        this.bizId = bizId;
+    }
+}

+ 152 - 0
bd-location/src/main/java/com/ruoyi/bd/service/engine/impl/FenceBreakInEngine.java

@@ -0,0 +1,152 @@
+package com.ruoyi.bd.service.engine.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.bd.domain.BdFenceInfo;
+import com.ruoyi.bd.domain.BdFenceVioEvt;
+import com.ruoyi.bd.service.IBdFenceInfoService;
+import com.ruoyi.bd.service.IBdFenceVioEvtService;
+import com.ruoyi.bd.service.engine.EvtFusionEngine;
+import com.ruoyi.bd.service.engine.LocationInfo;
+import com.ruoyi.bd.socket.FenceVioEvtSocketServer;
+import com.ruoyi.common.BDConst;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.enums.EvtStatus;
+import com.ruoyi.common.enums.EvtType;
+import com.ruoyi.common.enums.FenceType;
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.common.utils.geo.GeoUtils;
+import com.ruoyi.web.core.config.MqttCfg;
+import net.dreamlu.iot.mqtt.spring.client.MqttClientTemplate;
+import org.apache.commons.lang3.ObjectUtils;
+import org.locationtech.jts.geom.Polygon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+@Service
+@ConditionalOnBean(MqttCfg.class)
+public class FenceBreakInEngine extends EvtFusionEngine {
+    private static final Logger logger = LoggerFactory.getLogger(FenceBreakInEngine.class);
+
+    @Autowired
+    private IBdFenceInfoService fenceInfoService;
+
+    @Autowired
+    private IBdFenceVioEvtService vioEvtService;
+
+    @Resource
+    private RedisCache redisCache;
+
+    @Autowired
+    private MqttClientTemplate client;
+
+    /**
+     * The constant MAX_EVT_TIME_GAP.
+     * 单位:秒
+     *
+     * @author chen.cheng
+     */
+    private static final int MAX_EVT_TIME_GAP = 300;
+
+    @Autowired
+    private FenceVioEvtSocketServer fenceVioEvtSocketServer;
+
+    private final List<LinkedBlockingQueue<JSONObject>> messageQueueList = new ArrayList<>(5);
+
+    @PostConstruct
+    public void init() {
+        super.init();
+        this.setEngineName("室外围栏闯禁事件融合");
+        if (!redisCache.hasKey(BDConst.REDIS_KEY.FENCE)) {
+            List<BdFenceInfo> bdFenceInfos = fenceInfoService.selectBdFenceInfoList(new BdFenceInfo() {{
+                setFenceType(FenceType.OUT_SIDE_FENCE_IN.getCode());
+            }});
+            if (CollectionUtils.isEmpty(bdFenceInfos)) {
+                return;
+            }
+            bdFenceInfos.removeIf(item -> ObjectUtils.isEmpty(item.getPoly()) || ObjectUtils.isEmpty(GeoUtils.getPolygon(item.getPoly())));
+
+            redisCache.setCacheList(BDConst.REDIS_KEY.FENCE, bdFenceInfos);
+        }
+    }
+
+    @Override
+    public Boolean check(LocationInfo older, LocationInfo newer) {
+        long gap = newer.getSrcTimestamp() - older.getSrcTimestamp();
+        // 将gap 转换为秒
+        gap = gap / 1000;
+
+        String oldFenceId = older.getMsg().getString("fenceId");
+        String newerFenceId = newer.getMsg().getString("fenceId");
+        return gap <= MAX_EVT_TIME_GAP && oldFenceId.equals(newerFenceId);
+    }
+
+    @Override
+    public List<LinkedBlockingQueue<JSONObject>> getQueue() {
+        return messageQueueList;
+    }
+
+    @Override
+    public String getKey(LocationInfo msg) {
+        return BDConst.REDIS_KEY.FENCE_BREAK_IN_KEY + msg.getMsg().getString("deviceId");
+    }
+
+
+    @Override
+    public void getBizId(LocationInfo msg) {
+        BdFenceVioEvt bdFenceVioEvt = new BdFenceVioEvt();
+        bdFenceVioEvt.setFenceId(msg.getMsg().getLong("fenceId"));
+        bdFenceVioEvt.setLat(msg.getLatitude());
+        bdFenceVioEvt.setLng(msg.getLongitude());
+        bdFenceVioEvt.setEvtStatus(EvtStatus.NEW.getCode());
+        bdFenceVioEvt.setEvtType(EvtType.FENCE_IN.getCode());
+        bdFenceVioEvt.setEvtTime(DateTimeUtil.getDateFromMills(msg.getSrcTimestamp()));
+        bdFenceVioEvt.setEvtDesc(String.format("围栏闯禁: %s", msg.getMsg().getString("fenceName")));
+        vioEvtService.insertBdFenceVioEvt(bdFenceVioEvt);
+        msg.setBizId(bdFenceVioEvt.getId().toString());
+    }
+
+    @Override
+    public void processOlderData(LocationInfo msg) {
+        vioEvtService.updateBdFenceVioEvt(new BdFenceVioEvt() {
+            {
+                setFenceId(msg.getMsg().getLong("fenceId"));
+                setEvtStatus(EvtStatus.DISAPPEAR.getCode());
+            }
+        });
+    }
+
+    @Override
+    public void newEvtCallback(LocationInfo msg) {
+        fenceVioEvtSocketServer.broadcast(msg);
+    }
+
+    public void generateEvt(JSONObject msg, byte[] payload) {
+        List<BdFenceInfo> cacheList = redisCache.getCacheList(BDConst.REDIS_KEY.FENCE);
+        if (CollectionUtils.isEmpty(cacheList)) {
+            return;
+        }
+        Polygon polygon;
+        logger.info("fence info size: {}", msg);
+        for (BdFenceInfo fenceInfo : cacheList) {
+            polygon = GeoUtils.getPolygon(fenceInfo.getPoly());
+            if (GeoUtils.isPointInGeoFence(polygon, msg.getString("longitude"), msg.getString("latitude"))) {
+                logger.info("?>>>>>fence info size: {}", msg);
+                msg.put("fenceId", fenceInfo.getId());
+                msg.put("fenceName", fenceInfo.getDefenceName());
+                client.publish(BDConst.MQTT_TOPIC.EVT_LOCATION_TOPIC, JSON.toJSONBytes(msg));
+                break;
+            }
+        }
+    }
+}

+ 100 - 0
bd-location/src/main/java/com/ruoyi/bd/service/engine/impl/PointFusionEngine.java

@@ -0,0 +1,100 @@
+package com.ruoyi.bd.service.engine.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.bd.domain.BdDevcTrailUwb;
+import com.ruoyi.bd.service.IBdDevcTrailUwbService;
+import com.ruoyi.bd.service.engine.EvtFusionEngine;
+import com.ruoyi.bd.service.engine.LocationInfo;
+import com.ruoyi.bd.socket.PointWebSocketServer;
+import com.ruoyi.common.BDConst;
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.web.core.config.MqttCfg;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * The type Point fusion engine.
+ *
+ * @author chen.cheng
+ */
+@Service
+@ConditionalOnBean(MqttCfg.class)
+public class PointFusionEngine extends EvtFusionEngine {
+
+    @Autowired
+    private IBdDevcTrailUwbService bdDevcTrailUwbService;
+
+    /**
+     * The constant MAX_POINT_DISTANCE.
+     * 单位米
+     *
+     * @author chen.cheng
+     */
+    private static final int MAX_POINT_DISTANCE = 5;
+
+    /**
+     * The constant MAX_POINT_TIME_GAP.
+     * 单位秒
+     *
+     * @author chen.cheng
+     */
+    private static final int MAX_POINT_TIME_GAP = 2;
+
+    @Autowired
+    private PointWebSocketServer webSocketServer;
+
+    private final List<LinkedBlockingQueue<JSONObject>> messageQueueList = new ArrayList<>(5);
+
+    @PostConstruct
+    public void init() {
+        super.init();
+        this.setEngineName("坐标点位抽稀");
+    }
+
+    @Override
+    public Boolean check(LocationInfo older, LocationInfo newer) {
+        long gap = newer.getSrcTimestamp() - older.getSrcTimestamp();
+        // 将gap 转换为秒
+        gap = gap / 1000;
+        boolean isSamePoint = Double.compare(older.getLatitude(), newer.getLatitude()) == 0 && Double.compare(older.getLongitude(), newer.getLongitude()) == 0;
+        return gap <= MAX_POINT_TIME_GAP && isSamePoint;
+    }
+    @Override
+    public String getKey(LocationInfo msg) {
+        return BDConst.REDIS_KEY.BD_POINT_KEY + msg.getMsg().getString("deviceId");
+    }
+
+    @Override
+    public List<LinkedBlockingQueue<JSONObject>> getQueue() {
+        return messageQueueList;
+    }
+
+    @Override
+    public void getBizId(LocationInfo msg) {
+        long evtTimestamp = msg.getEvtTimestamp();
+        BdDevcTrailUwb bdDevcTrailUwb = new BdDevcTrailUwb();
+        bdDevcTrailUwb.setDevcKey(msg.getMsg().getString("deviceId"));
+        bdDevcTrailUwb.setLat(msg.getLatitude());
+        bdDevcTrailUwb.setLng(msg.getLongitude());
+        bdDevcTrailUwb.setDt(DateTimeUtil.currentDateTime(DateTimeUtil.DateFormatter.yyyyMMdd));
+        bdDevcTrailUwb.setTp(MAX_POINT_TIME_GAP);
+        bdDevcTrailUwb.setStepIndex(DateTimeUtil.getStepIndexFromMills(MAX_POINT_TIME_GAP, evtTimestamp));
+        bdDevcTrailUwbService.insertBdDevcTrailUwb(bdDevcTrailUwb);
+        msg.setBizId(bdDevcTrailUwb.getId().toString());
+    }
+
+    @Override
+    public void newEvtCallback(LocationInfo msg) {
+        webSocketServer.sendInfo(msg.getMsg().getString("deviceId"), msg.getMsg().toJSONString());
+    }
+
+    public void pushDevcLocation(LocationInfo msg) {
+        webSocketServer.sendInfo(msg.getMsg().getString("deviceId"), msg.getMsg().toJSONString());
+    }
+}

+ 161 - 0
bd-location/src/main/java/com/ruoyi/bd/service/engine/impl/RoomBreakInEngine.java

@@ -0,0 +1,161 @@
+package com.ruoyi.bd.service.engine.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.bd.domain.BdFenceInfo;
+import com.ruoyi.bd.domain.BdFenceVioEvt;
+import com.ruoyi.bd.service.IBdFenceInfoService;
+import com.ruoyi.bd.service.IBdFenceVioEvtService;
+import com.ruoyi.bd.service.engine.EvtFusionEngine;
+import com.ruoyi.bd.service.engine.LocationInfo;
+import com.ruoyi.bd.socket.FenceVioEvtSocketServer;
+import com.ruoyi.common.BDConst;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.enums.EvtStatus;
+import com.ruoyi.common.enums.EvtType;
+import com.ruoyi.common.enums.FenceType;
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.common.utils.geo.GeoUtils;
+import com.ruoyi.web.core.config.MqttCfg;
+import net.dreamlu.iot.mqtt.spring.client.MqttClientTemplate;
+import org.apache.commons.lang3.ObjectUtils;
+import org.locationtech.jts.geom.Polygon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * The type Room break in engine.
+ */
+@Service
+@ConditionalOnBean(MqttCfg.class)
+public class RoomBreakInEngine extends EvtFusionEngine {
+    private static final Logger logger = LoggerFactory.getLogger(RoomBreakInEngine.class);
+
+    @Autowired
+    private IBdFenceInfoService fenceInfoService;
+
+    @Autowired
+    private IBdFenceVioEvtService vioEvtService;
+
+    @Resource
+    private RedisCache redisCache;
+
+    @Autowired
+    private MqttClientTemplate client;
+
+    @Value("${evt-fusion.fusion-time.room-location:300}")
+    private int MAX_EVT_TIME_GAP;
+
+
+    @Autowired
+    private FenceVioEvtSocketServer fenceVioEvtSocketServer;
+
+    private final List<LinkedBlockingQueue<JSONObject>> messageQueueList = new ArrayList<>(5);
+
+    @PostConstruct
+    public void init() {
+        super.init();
+        this.setEngineName("室内围栏闯禁事件融合");
+        if (!redisCache.hasKey(BDConst.REDIS_KEY.FENCE_ROOM)) {
+            List<BdFenceInfo> bdFenceInfos = fenceInfoService.selectBdFenceInfoList(new BdFenceInfo() {{
+                setFenceType(FenceType.INSIDE_FENCE_IN.getCode());
+            }});
+            if (CollectionUtils.isEmpty(bdFenceInfos)) {
+                return;
+            }
+            bdFenceInfos.removeIf(item -> ObjectUtils.isEmpty(item.getPoly()) || ObjectUtils.isEmpty(GeoUtils.getPolygon(item.getPoly())));
+
+            redisCache.setCacheList(BDConst.REDIS_KEY.FENCE_ROOM, bdFenceInfos);
+        }
+    }
+
+    @Override
+    public Boolean check(LocationInfo older, LocationInfo newer) {
+        long gap = newer.getSrcTimestamp() - older.getSrcTimestamp();
+        // 将gap 转换为秒
+        gap = gap / 1000;
+
+        String oldFenceId = older.getMsg().getString("fenceId");
+        String newerFenceId = newer.getMsg().getString("fenceId");
+        return gap <= MAX_EVT_TIME_GAP && oldFenceId.equals(newerFenceId);
+    }
+
+    @Override
+    public List<LinkedBlockingQueue<JSONObject>> getQueue() {
+        return messageQueueList;
+    }
+
+
+    /**
+     * Gets key. 获取融合对象在redis中的key
+     *
+     * @param msg the msg
+     * @return the key
+     * @author chen.cheng
+     */
+    @Override
+    public String getKey(LocationInfo msg) {
+        return BDConst.REDIS_KEY.FENCE_ROOM_BREAK_IN_KEY + msg.getMsg().getString("deviceId");
+    }
+
+    @Override
+    public void getBizId(LocationInfo msg) {
+        BdFenceVioEvt bdFenceVioEvt = new BdFenceVioEvt();
+        bdFenceVioEvt.setFenceId(msg.getMsg().getLong("fenceId"));
+        bdFenceVioEvt.setLat(msg.getLatitude());
+        bdFenceVioEvt.setLng(msg.getLongitude());
+        bdFenceVioEvt.setLocationId(msg.getMsg().getLong("roomId"));
+        bdFenceVioEvt.setEvtStatus(EvtStatus.NEW.getCode());
+        bdFenceVioEvt.setEvtType(EvtType.FENCE_ROOM_IN.getCode());
+        bdFenceVioEvt.setEvtTime(DateTimeUtil.getDateFromMills(msg.getSrcTimestamp()));
+        bdFenceVioEvt.setEvtDesc(String.format("室内围栏闯禁: %s", msg.getMsg().getString("fenceName")));
+        vioEvtService.insertBdFenceVioEvt(bdFenceVioEvt);
+        msg.setBizId(bdFenceVioEvt.getId().toString());
+    }
+
+    @Override
+    public void processOlderData(LocationInfo msg) {
+        vioEvtService.updateBdFenceVioEvt(new BdFenceVioEvt() {
+            {
+                setFenceId(msg.getMsg().getLong("fenceId"));
+                setEvtStatus(EvtStatus.DISAPPEAR.getCode());
+            }
+        });
+    }
+
+    @Override
+    public void newEvtCallback(LocationInfo msg) {
+        fenceVioEvtSocketServer.broadcast(msg);
+    }
+
+    public void generateEvt(JSONObject msg, byte[] payload) {
+        List<BdFenceInfo> cacheList = redisCache.getCacheList(BDConst.REDIS_KEY.FENCE_ROOM);
+        if (CollectionUtils.isEmpty(cacheList)) {
+            return;
+        }
+        Polygon polygon;
+        logger.info("fence info size: {}", msg);
+        for (BdFenceInfo fenceInfo : cacheList) {
+            polygon = GeoUtils.getPolygon(fenceInfo.getPoly());
+            if (GeoUtils.isPointInGeoFence(polygon, msg.getString("longitude"), msg.getString("latitude"))) {
+                logger.info("?>>>>>fence info size: {}", msg);
+                msg.put("fenceId", fenceInfo.getId());
+                msg.put("fenceName", fenceInfo.getDefenceName());
+                msg.put("roomId", fenceInfo.getLocationId());
+                client.publish(BDConst.MQTT_TOPIC.EVT_ROOM_LOCATION_TOPIC, JSON.toJSONBytes(msg));
+                break;
+            }
+        }
+    }
+}

+ 96 - 0
bd-location/src/main/java/com/ruoyi/bd/service/impl/BdDevcTrailServiceImpl.java

@@ -0,0 +1,96 @@
+package com.ruoyi.bd.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.bd.mapper.BdDevcTrailMapper;
+import com.ruoyi.bd.domain.BdDevcTrail;
+import com.ruoyi.bd.service.IBdDevcTrailService;
+
+/**
+ * 设备轨迹Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2024-11-07
+ */
+@Service
+public class BdDevcTrailServiceImpl implements IBdDevcTrailService 
+{
+    @Autowired
+    private BdDevcTrailMapper bdDevcTrailMapper;
+
+    /**
+     * 查询设备轨迹
+     * 
+     * @param id 设备轨迹主键
+     * @return 设备轨迹
+     */
+    @Override
+    public BdDevcTrail selectBdDevcTrailById(Long id)
+    {
+        return bdDevcTrailMapper.selectBdDevcTrailById(id);
+    }
+
+    /**
+     * 查询设备轨迹列表
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 设备轨迹
+     */
+    @Override
+    public List<BdDevcTrail> selectBdDevcTrailList(BdDevcTrail bdDevcTrail)
+    {
+        return bdDevcTrailMapper.selectBdDevcTrailList(bdDevcTrail);
+    }
+
+    /**
+     * 新增设备轨迹
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 结果
+     */
+    @Override
+    public int insertBdDevcTrail(BdDevcTrail bdDevcTrail)
+    {
+        bdDevcTrail.setCreateTime(DateUtils.getNowDate());
+        return bdDevcTrailMapper.insertBdDevcTrail(bdDevcTrail);
+    }
+
+    /**
+     * 修改设备轨迹
+     * 
+     * @param bdDevcTrail 设备轨迹
+     * @return 结果
+     */
+    @Override
+    public int updateBdDevcTrail(BdDevcTrail bdDevcTrail)
+    {
+        bdDevcTrail.setUpdateTime(DateUtils.getNowDate());
+        return bdDevcTrailMapper.updateBdDevcTrail(bdDevcTrail);
+    }
+
+    /**
+     * 批量删除设备轨迹
+     * 
+     * @param ids 需要删除的设备轨迹主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdDevcTrailByIds(Long[] ids)
+    {
+        return bdDevcTrailMapper.deleteBdDevcTrailByIds(ids);
+    }
+
+    /**
+     * 删除设备轨迹信息
+     * 
+     * @param id 设备轨迹主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdDevcTrailById(Long id)
+    {
+        return bdDevcTrailMapper.deleteBdDevcTrailById(id);
+    }
+}

+ 97 - 0
bd-location/src/main/java/com/ruoyi/bd/service/impl/BdDevcTrailUwbServiceImpl.java

@@ -0,0 +1,97 @@
+package com.ruoyi.bd.service.impl;
+
+import java.util.List;
+
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.bd.mapper.BdDevcTrailUwbMapper;
+import com.ruoyi.bd.domain.BdDevcTrailUwb;
+import com.ruoyi.bd.service.IBdDevcTrailUwbService;
+
+/**
+ * 室内坐标定位Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2024-10-16
+ */
+@Service
+public class BdDevcTrailUwbServiceImpl implements IBdDevcTrailUwbService {
+    @Autowired
+    private BdDevcTrailUwbMapper bdDevcTrailUwbMapper;
+
+    /**
+     * 查询室内坐标定位
+     *
+     * @param id 室内坐标定位主键
+     * @return 室内坐标定位
+     */
+    @Override
+    public BdDevcTrailUwb selectBdDevcTrailUwbById(Long id) {
+        return bdDevcTrailUwbMapper.selectBdDevcTrailUwbById(id);
+    }
+
+    /**
+     * 查询室内坐标定位列表
+     *
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 室内坐标定位
+     */
+    @Override
+    public List<BdDevcTrailUwb> selectBdDevcTrailUwbList(BdDevcTrailUwb bdDevcTrailUwb) {
+        return bdDevcTrailUwbMapper.selectBdDevcTrailUwbList(bdDevcTrailUwb);
+    }
+
+    /**
+     * 新增室内坐标定位
+     *
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 结果
+     */
+    @Override
+    public int insertBdDevcTrailUwb(BdDevcTrailUwb bdDevcTrailUwb) {
+        bdDevcTrailUwb.setCreateTime(DateUtils.getNowDate());
+        return bdDevcTrailUwbMapper.insertBdDevcTrailUwb(bdDevcTrailUwb);
+    }
+
+    /**
+     * 修改室内坐标定位
+     *
+     * @param bdDevcTrailUwb 室内坐标定位
+     * @return 结果
+     */
+    @Override
+    public int updateBdDevcTrailUwb(BdDevcTrailUwb bdDevcTrailUwb) {
+        bdDevcTrailUwb.setUpdateTime(DateUtils.getNowDate());
+        return bdDevcTrailUwbMapper.updateBdDevcTrailUwb(bdDevcTrailUwb);
+    }
+
+    /**
+     * 批量删除室内坐标定位
+     *
+     * @param ids 需要删除的室内坐标定位主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdDevcTrailUwbByIds(Long[] ids) {
+        return bdDevcTrailUwbMapper.deleteBdDevcTrailUwbByIds(ids);
+    }
+
+    /**
+     * 删除室内坐标定位信息
+     *
+     * @param id 室内坐标定位主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdDevcTrailUwbById(Long id) {
+        return bdDevcTrailUwbMapper.deleteBdDevcTrailUwbById(id);
+    }
+
+    @Override
+    public void saveUwbTrail() {
+        String yesterday = DateTimeUtil.yesterday(DateTimeUtil.DateFormatter.yyyyMMdd);
+        bdDevcTrailUwbMapper.saveObjTail(yesterday);
+    }
+}

+ 98 - 0
bd-location/src/main/java/com/ruoyi/bd/service/impl/BdFenceInfoServiceImpl.java

@@ -0,0 +1,98 @@
+package com.ruoyi.bd.service.impl;
+
+import com.ruoyi.bd.domain.BdFenceInfo;
+import com.ruoyi.bd.mapper.BdFenceInfoMapper;
+import com.ruoyi.bd.service.IBdFenceInfoService;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.geo.GeoUtils;
+import org.locationtech.jts.geom.Point;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 围栏基础信息Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+@Service
+public class BdFenceInfoServiceImpl implements IBdFenceInfoService {
+    @Autowired
+    private BdFenceInfoMapper bdFenceInfoMapper;
+
+    /**
+     * 查询围栏基础信息
+     *
+     * @param id 围栏基础信息主键
+     * @return 围栏基础信息
+     */
+    @Override
+    public BdFenceInfo selectBdFenceInfoById(Long id) {
+        return bdFenceInfoMapper.selectBdFenceInfoById(id);
+    }
+
+    /**
+     * 查询围栏基础信息列表
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 围栏基础信息
+     */
+    @Override
+    public List<BdFenceInfo> selectBdFenceInfoList(BdFenceInfo bdFenceInfo) {
+        return bdFenceInfoMapper.selectBdFenceInfoList(bdFenceInfo);
+    }
+
+    /**
+     * 新增围栏基础信息
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 结果
+     */
+    @Override
+    public int insertBdFenceInfo(BdFenceInfo bdFenceInfo) {
+        bdFenceInfo.setCreateTime(DateUtils.getNowDate());
+        Point polyCenter = GeoUtils.getPolyCenter(bdFenceInfo.getPoly());
+        bdFenceInfo.setCenterLat(polyCenter.getY());
+        bdFenceInfo.setCenterLng(polyCenter.getX());
+        return bdFenceInfoMapper.insertBdFenceInfo(bdFenceInfo);
+    }
+
+    /**
+     * 修改围栏基础信息
+     *
+     * @param bdFenceInfo 围栏基础信息
+     * @return 结果
+     */
+    @Override
+    public int updateBdFenceInfo(BdFenceInfo bdFenceInfo) {
+        bdFenceInfo.setUpdateTime(DateUtils.getNowDate());
+        Point polyCenter = GeoUtils.getPolyCenter(bdFenceInfo.getPoly());
+        bdFenceInfo.setCenterLat(polyCenter.getY());
+        bdFenceInfo.setCenterLng(polyCenter.getX());
+        return bdFenceInfoMapper.updateBdFenceInfo(bdFenceInfo);
+    }
+
+    /**
+     * 批量删除围栏基础信息
+     *
+     * @param ids 需要删除的围栏基础信息主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdFenceInfoByIds(Long[] ids) {
+        return bdFenceInfoMapper.deleteBdFenceInfoByIds(ids);
+    }
+
+    /**
+     * 删除围栏基础信息信息
+     *
+     * @param id 围栏基础信息主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdFenceInfoById(Long id) {
+        return bdFenceInfoMapper.deleteBdFenceInfoById(id);
+    }
+}

+ 97 - 0
bd-location/src/main/java/com/ruoyi/bd/service/impl/BdFenceVioEvtServiceImpl.java

@@ -0,0 +1,97 @@
+package com.ruoyi.bd.service.impl;
+
+import com.ruoyi.bd.domain.BdFenceVioEvt;
+import com.ruoyi.bd.mapper.BdFenceVioEvtMapper;
+import com.ruoyi.bd.service.IBdFenceVioEvtService;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 围栏闯禁事件Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+@Service
+public class BdFenceVioEvtServiceImpl implements IBdFenceVioEvtService
+{
+    @Autowired
+    private BdFenceVioEvtMapper bdFenceVioEvtMapper;
+
+    /**
+     * 查询围栏闯禁事件
+     *
+     * @param id 围栏闯禁事件主键
+     * @return 围栏闯禁事件
+     */
+    @Override
+    public BdFenceVioEvt selectBdFenceVioEvtById(Long id)
+    {
+        return bdFenceVioEvtMapper.selectBdFenceVioEvtById(id);
+    }
+
+    /**
+     * 查询围栏闯禁事件列表
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 围栏闯禁事件
+     */
+    @Override
+    public List<BdFenceVioEvt> selectBdFenceVioEvtList(BdFenceVioEvt bdFenceVioEvt)
+    {
+        return bdFenceVioEvtMapper.selectBdFenceVioEvtList(bdFenceVioEvt);
+    }
+
+    /**
+     * 新增围栏闯禁事件
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 结果
+     */
+    @Override
+    public int insertBdFenceVioEvt(BdFenceVioEvt bdFenceVioEvt)
+    {
+        bdFenceVioEvt.setCreateTime(DateUtils.getNowDate());
+        return bdFenceVioEvtMapper.insertBdFenceVioEvt(bdFenceVioEvt);
+    }
+
+    /**
+     * 修改围栏闯禁事件
+     *
+     * @param bdFenceVioEvt 围栏闯禁事件
+     * @return 结果
+     */
+    @Override
+    public int updateBdFenceVioEvt(BdFenceVioEvt bdFenceVioEvt)
+    {
+        bdFenceVioEvt.setUpdateTime(DateUtils.getNowDate());
+        return bdFenceVioEvtMapper.updateBdFenceVioEvt(bdFenceVioEvt);
+    }
+
+    /**
+     * 批量删除围栏闯禁事件
+     *
+     * @param ids 需要删除的围栏闯禁事件主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdFenceVioEvtByIds(Long[] ids)
+    {
+        return bdFenceVioEvtMapper.deleteBdFenceVioEvtByIds(ids);
+    }
+
+    /**
+     * 删除围栏闯禁事件信息
+     *
+     * @param id 围栏闯禁事件主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBdFenceVioEvtById(Long id)
+    {
+        return bdFenceVioEvtMapper.deleteBdFenceVioEvtById(id);
+    }
+}

+ 10 - 0
bd-location/src/main/java/com/ruoyi/bd/service/rpc/UWBWebService.java

@@ -0,0 +1,10 @@
+package com.ruoyi.bd.service.rpc;
+
+import com.dtflys.forest.annotation.*;
+
+import java.util.Map;
+
+public interface UWBWebService {
+    @Post(url = "{uwbBaseUrl}/stage-api/auth/login", dataType = "json")
+    Map exchangeToken(@JSONBody Map<String, Object> data);
+}

+ 111 - 0
bd-location/src/main/java/com/ruoyi/bd/socket/FenceVioEvtSocketServer.java

@@ -0,0 +1,111 @@
+package com.ruoyi.bd.socket;
+
+import com.alibaba.fastjson2.JSON;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.CrossOrigin;
+
+import javax.annotation.PostConstruct;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@ServerEndpoint("/ws/evt/{device}")
+@CrossOrigin(origins = "*")
+@Component
+public class FenceVioEvtSocketServer {
+    private static final Logger logger = LoggerFactory.getLogger(FenceVioEvtSocketServer.class);
+    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+    private static AtomicInteger onlineNum = new AtomicInteger();
+    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
+    private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
+
+    @PostConstruct
+    public void init() {
+        logger.info("WebSocketServer init");
+    }
+
+    //发送消息
+    public void sendMessage(Session session, String message) throws IOException {
+        if (session != null) {
+            synchronized (session) {
+                logger.info("发送数据:{}", message);
+                session.getBasicRemote().sendText(message);
+            }
+        }
+    }
+
+    //给指定用户发送信息
+    public void sendInfo(String userName, String message) {
+        Session session = sessionPools.get(userName);
+        if (session == null) return;
+        try {
+            sendMessage(session, message);
+        } catch (Exception e) {
+            logger.info("server get {}", e.getMessage());
+        }
+    }
+
+    // 群发消息
+    public void broadcast(Object message) {
+        for (Session session : sessionPools.values()) {
+            try {
+                sendMessage(session, JSON.toJSONString(message));
+            } catch (Exception e) {
+                logger.info("server get {}", e.getMessage());
+            }
+        }
+    }
+
+    //建立连接成功调用
+    @OnOpen
+    public void onOpen(Session session, @PathParam(value = "device") String device) {
+        sessionPools.put(device, session);
+        addOnlineCount();
+    }
+
+    //关闭连接时调用
+    @OnClose
+    public void onClose(@PathParam(value = "device") String device) {
+        sessionPools.remove(device);
+        subOnlineCount();
+        logger.info("{}断开webSocket连接!当前人数为:{}", device, onlineNum);
+    }
+
+    //收到客户端信息后,根据接收人的username把消息推下去或者群发
+    // to=-1群发消息
+    @OnMessage
+    public void onMessage(String message) throws IOException {
+        logger.info("server get {}", message);
+    }
+
+    //错误时调用
+    @OnError
+    public void onError(Session session, Throwable throwable) {
+        logger.info("server get {}", throwable.getMessage());
+    }
+
+    public static void addOnlineCount() {
+        onlineNum.incrementAndGet();
+    }
+
+    public static void subOnlineCount() {
+        onlineNum.decrementAndGet();
+    }
+
+    public static AtomicInteger getOnlineNumber() {
+        return onlineNum;
+    }
+
+    public static ConcurrentHashMap<String, Session> getSessionPools() {
+        return sessionPools;
+    }
+}

+ 110 - 0
bd-location/src/main/java/com/ruoyi/bd/socket/PointWebSocketServer.java

@@ -0,0 +1,110 @@
+package com.ruoyi.bd.socket;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.CrossOrigin;
+
+import javax.annotation.PostConstruct;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@ServerEndpoint("/ws/point/{device}")
+@CrossOrigin(origins = "*")
+@Component
+public class PointWebSocketServer {
+    private static final Logger logger = LoggerFactory.getLogger(PointWebSocketServer.class);
+    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+    private static AtomicInteger onlineNum = new AtomicInteger();
+    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
+    private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
+
+    @PostConstruct
+    public void init() {
+        logger.info("WebSocketServer init");
+    }
+
+    //发送消息
+    public void sendMessage(Session session, String message) throws IOException {
+        if (session != null) {
+            synchronized (session) {
+                logger.info("发送数据:{}", message);
+                session.getBasicRemote().sendText(message);
+            }
+        }
+    }
+
+    //给指定用户发送信息
+    public void sendInfo(String userName, String message) {
+        Session session = sessionPools.get(userName);
+        if (session == null) return;
+        try {
+            sendMessage(session, message);
+        } catch (Exception e) {
+            logger.info("server get {}", e.getMessage());
+        }
+    }
+
+    // 群发消息
+    public void broadcast(String message) {
+        for (Session session : sessionPools.values()) {
+            try {
+                sendMessage(session, message);
+            } catch (Exception e) {
+                logger.info("server get {}", e.getMessage());
+            }
+        }
+    }
+
+    //建立连接成功调用
+    @OnOpen
+    public void onOpen(Session session, @PathParam(value = "device") String device) {
+        sessionPools.put(device, session);
+        addOnlineCount();
+    }
+
+    //关闭连接时调用
+    @OnClose
+    public void onClose(@PathParam(value = "device") String device) {
+        sessionPools.remove(device);
+        subOnlineCount();
+        logger.info("{}断开webSocket连接!当前人数为:{}", device, onlineNum);
+    }
+
+    //收到客户端信息后,根据接收人的username把消息推下去或者群发
+    // to=-1群发消息
+    @OnMessage
+    public void onMessage(String message) throws IOException {
+        logger.info("server get {}", message);
+    }
+
+    //错误时调用
+    @OnError
+    public void onError(Session session, Throwable throwable) {
+        logger.info("server get {}", throwable.getMessage());
+    }
+
+    public static void addOnlineCount() {
+        onlineNum.incrementAndGet();
+    }
+
+    public static void subOnlineCount() {
+        onlineNum.decrementAndGet();
+    }
+
+    public static AtomicInteger getOnlineNumber() {
+        return onlineNum;
+    }
+
+    public static ConcurrentHashMap<String, Session> getSessionPools() {
+        return sessionPools;
+    }
+}

+ 87 - 0
bd-location/src/main/java/com/ruoyi/bd/socket/UWBSocket/UWBSocketClient.java

@@ -0,0 +1,87 @@
+package com.ruoyi.bd.socket.UWBSocket;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.bd.domain.UWBAuth;
+import com.ruoyi.common.BDConst;
+import net.dreamlu.iot.mqtt.core.client.MqttClient;
+import net.dreamlu.iot.mqtt.spring.client.MqttClientTemplate;
+import org.java_websocket.drafts.Draft;
+import org.java_websocket.handshake.ServerHandshake;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+public class UWBSocketClient extends org.java_websocket.client.WebSocketClient {
+
+    private static final Logger log = LoggerFactory.getLogger(UWBSocketClient.class);
+
+    private UWBAuth authToken;
+
+    @Autowired
+    private MqttClientTemplate client;
+
+    public UWBAuth getAuthToken() {
+        return authToken;
+    }
+
+    public void setAuthToken(UWBAuth authToken) {
+        this.authToken = authToken;
+    }
+
+    public UWBSocketClient(URI serverUri) {
+        super(serverUri);
+    }
+
+    public UWBSocketClient(URI serverUri, Draft protocolDraft) {
+        super(serverUri, protocolDraft);
+    }
+
+    public UWBSocketClient(URI serverUri, Draft protocolDraft, Map<String, String> httpHeaders, int connectTimeout) {
+        super(serverUri, protocolDraft, httpHeaders, connectTimeout);
+    }
+
+    @Override
+    public void onOpen(ServerHandshake serverHandshake) {
+        log.debug("[websocket] 连接成功,{}", JSON.toJSONString(this.authToken));
+        send(JSON.toJSONString(this.authToken));
+    }
+
+
+    @Override
+    public void onMessage(String message) {
+        log.debug("[websocket] 收到消息={}", message);
+        JSONObject data = JSON.parseObject(message);
+        if (!"handshake".equals(data.getString("message"))) {
+            client.publish(String.format(BDConst.MQTT_TOPIC.UWB_VIDEO_TRACE_POINT, "847F3"), JSON.toJSONBytes(data));
+            // TODO 推送轨迹
+            // client.publish(String.format(BDConst.MQTT_TOPIC.UWB_VIDEO_TRACE, "847F3"), JSON.toJSONBytes(data));
+            return;
+        }
+        this.authToken.setTagId("847F3");
+        log.debug("[websocket] 推送={}", JSON.toJSONString(this.authToken));
+        // tagId要去掉前缀0
+        send(JSON.toJSONString(this.authToken));
+    }
+
+    @Override
+    public void onMessage(ByteBuffer bytes) {
+        log.debug("[websocket] 收到消息={}", JSON.parseObject(bytes.array()));
+
+    }
+
+    @Override
+    public void onClose(int i, String s, boolean b) {
+        log.info("[websocket] 退出连接");
+    }
+
+    @Override
+    public void onError(Exception e) {
+        log.info("[websocket] 连接错误={}", e.getMessage());
+    }
+
+}

+ 25 - 0
bd-location/src/main/java/com/ruoyi/common/BDConst.java

@@ -0,0 +1,25 @@
+package com.ruoyi.common;
+
+public interface BDConst {
+    public interface REDIS_KEY {
+        String BD_POINT_KEY = "bd:point:";
+        String FENCE_BREAK_IN_KEY = "fence:break:in:";
+        String FENCE = "fence";
+
+        String FENCE_ROOM = "fence:room";
+
+        String FENCE_ROOM_BREAK_IN_KEY = "fence:room:break:in:";
+    }
+
+    public interface MQTT_TOPIC {
+        String POINT_LOCATION_TOPIC = "/uwb/point/#";
+        String EVT_LOCATION_TOPIC = "/uwb/evt";
+
+        String EVT_ROOM_LOCATION_TOPIC = "/uwb/room/evt";
+
+        String DEVICE_LOCATION_TOPIC = "/uwb/point/%s";
+
+        String UWB_VIDEO_TRACE_POINT = "/uwb/video-trace/point/%s";
+
+    }
+}

+ 21 - 0
bd-location/src/main/java/com/ruoyi/common/enums/EvtStatus.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.enums;
+
+public enum EvtStatus {
+    NEW("0", "新增"), DISAPPEAR("1", "消散");
+
+    private final String code;
+    private final String info;
+
+    EvtStatus(String code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}

+ 23 - 0
bd-location/src/main/java/com/ruoyi/common/enums/EvtType.java

@@ -0,0 +1,23 @@
+package com.ruoyi.common.enums;
+
+public enum EvtType {
+    FENCE_IN("1", "围栏闯禁"),
+
+    FENCE_ROOM_IN("2", "室内围栏闯禁");
+
+    private final String code;
+    private final String info;
+
+    EvtType(String code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}

+ 22 - 0
bd-location/src/main/java/com/ruoyi/common/enums/FenceType.java

@@ -0,0 +1,22 @@
+package com.ruoyi.common.enums;
+
+public enum FenceType {
+    OUT_SIDE_FENCE_IN("1", "室外围栏闯禁"),
+    INSIDE_FENCE_IN("2", "室外围栏闯禁");
+
+    private final String code;
+    private final String info;
+
+    FenceType(String code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}

+ 31 - 0
bd-location/src/main/java/com/ruoyi/mqtt/UWBLocationListener.java

@@ -0,0 +1,31 @@
+package com.ruoyi.mqtt;
+
+import net.dreamlu.iot.mqtt.core.client.MqttClientCreator;
+import net.dreamlu.iot.mqtt.spring.client.event.MqttConnectedEvent;
+import net.dreamlu.iot.mqtt.spring.client.event.MqttDisconnectEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UWBLocationListener {
+    private static final Logger logger = LoggerFactory.getLogger(UWBLocationListener.class);
+
+    @Autowired
+    private MqttClientCreator mqttClientCreator;
+
+    @EventListener
+    public void onConnected(MqttConnectedEvent event) {
+        logger.info("MqttConnectedEvent:{}", event);
+    }
+
+    @EventListener
+    public void onDisconnect(MqttDisconnectEvent event) {
+        // 离线时更新重连时的密码,适用于类似阿里云 mqtt clientId 连接带时间戳的方式
+        logger.info("MqttDisconnectEvent:{}", event);
+        // 在断线时更新 clientId、username、password
+        mqttClientCreator.clientId("newClient" + System.currentTimeMillis()).username("newUserName").password("newPassword");
+    }
+}

+ 65 - 0
bd-location/src/main/java/com/ruoyi/mqtt/UWBLocationSubscribeListener.java

@@ -0,0 +1,65 @@
+package com.ruoyi.mqtt;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.bd.service.engine.impl.FenceBreakInEngine;
+import com.ruoyi.bd.service.engine.impl.PointFusionEngine;
+import com.ruoyi.bd.service.engine.impl.RoomBreakInEngine;
+import com.ruoyi.common.BDConst;
+import com.ruoyi.web.core.config.MqttCfg;
+import net.dreamlu.iot.mqtt.codec.MqttQoS;
+import net.dreamlu.iot.mqtt.spring.client.MqttClientSubscribe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.nio.charset.StandardCharsets;
+
+@Service
+@ConditionalOnBean(MqttCfg.class)
+public class UWBLocationSubscribeListener {
+    private static final Logger logger = LoggerFactory.getLogger(UWBLocationSubscribeListener.class);
+
+    @Autowired
+    private PointFusionEngine pointFusionEngine;
+
+    @Autowired
+    private FenceBreakInEngine fenceBreakInEngine;
+
+    @Autowired
+    private RoomBreakInEngine roomBreakInEngine;
+
+    @PostConstruct
+    public void init() {
+        pointFusionEngine.start();
+        fenceBreakInEngine.start();
+        roomBreakInEngine.start();
+    }
+
+    @MqttClientSubscribe(value = BDConst.MQTT_TOPIC.POINT_LOCATION_TOPIC)
+    public void subQos0(String topic, byte[] payload) {
+        logger.debug("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
+        JSONObject jsonObject = JSON.parseObject(payload);
+        pointFusionEngine.push(jsonObject);
+        fenceBreakInEngine.generateEvt(JSON.parseObject(payload), payload);
+        roomBreakInEngine.generateEvt(JSON.parseObject(payload), payload);
+    }
+
+    @MqttClientSubscribe(value = BDConst.MQTT_TOPIC.EVT_LOCATION_TOPIC, qos = MqttQoS.QOS0)
+    public void subQos1(String topic, byte[] payload) {
+        logger.debug("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
+        fenceBreakInEngine.push(JSON.parseObject(payload));
+    }
+
+    @MqttClientSubscribe(value = BDConst.MQTT_TOPIC.EVT_ROOM_LOCATION_TOPIC, qos = MqttQoS.QOS0)
+    public void subQos2(String topic, byte[] payload) {
+        logger.debug("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
+        roomBreakInEngine.push(JSON.parseObject(payload));
+    }
+
+
+}

+ 30 - 0
bd-location/src/main/java/com/ruoyi/task/BaseTask.java

@@ -0,0 +1,30 @@
+package com.ruoyi.task;
+
+import cn.hutool.core.map.MapUtil;
+import com.ruoyi.quartz.domain.SysJob;
+import org.quartz.Scheduler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Map;
+
+public abstract class BaseTask {
+    public static final Logger log = LoggerFactory.getLogger(BaseTask.class);
+
+    @Autowired
+    private Scheduler scheduler;
+
+    public abstract void run(SysJob taskProperties);
+
+    public void execute() {
+        try {
+            Map<String, Object> wrappedMap = scheduler.getCurrentlyExecutingJobs().get(0).getJobDetail().getJobDataMap().getWrappedMap();
+            SysJob taskProperties = MapUtil.get(wrappedMap, "TASK_PROPERTIES", SysJob.class);
+            log.info("开始执行任务,任务名:{}", taskProperties.getJobName());
+            run(taskProperties);
+        } catch (Exception e) {
+            log.error("执行任务失败", e);
+        }
+    }
+}

+ 24 - 0
bd-location/src/main/java/com/ruoyi/task/taskiml/ObjTailTask.java

@@ -0,0 +1,24 @@
+package com.ruoyi.task.taskiml;
+
+import com.ruoyi.bd.service.IBdDevcTrailUwbService;
+import com.ruoyi.quartz.domain.SysJob;
+import com.ruoyi.task.BaseTask;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 定时任务调度测试
+ *
+ * @author ruoyi
+ */
+@Component("objTailTask")
+public class ObjTailTask extends BaseTask {
+
+    @Autowired
+    private IBdDevcTrailUwbService bdDevcTrailUwbService;
+
+    @Override
+    public void run(SysJob taskProperties) {
+        bdDevcTrailUwbService.saveUwbTrail();
+    }
+}

+ 104 - 0
bd-location/src/main/java/com/ruoyi/web/controller/bd/BdDevcTrailController.java

@@ -0,0 +1,104 @@
+package com.ruoyi.bd.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.bd.domain.BdDevcTrail;
+import com.ruoyi.bd.service.IBdDevcTrailService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 设备轨迹Controller
+ * 
+ * @author ruoyi
+ * @date 2024-11-07
+ */
+@RestController
+@RequestMapping("/bd/devcTrail")
+public class BdDevcTrailController extends BaseController
+{
+    @Autowired
+    private IBdDevcTrailService bdDevcTrailService;
+
+    /**
+     * 查询设备轨迹列表
+     */
+    @PreAuthorize("@ss.hasPermi('bd:devcTrail:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BdDevcTrail bdDevcTrail)
+    {
+        startPage();
+        List<BdDevcTrail> list = bdDevcTrailService.selectBdDevcTrailList(bdDevcTrail);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出设备轨迹列表
+     */
+    @PreAuthorize("@ss.hasPermi('bd:devcTrail:export')")
+    @Log(title = "设备轨迹", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BdDevcTrail bdDevcTrail)
+    {
+        List<BdDevcTrail> list = bdDevcTrailService.selectBdDevcTrailList(bdDevcTrail);
+        ExcelUtil<BdDevcTrail> util = new ExcelUtil<BdDevcTrail>(BdDevcTrail.class);
+        util.exportExcel(response, list, "设备轨迹数据");
+    }
+
+    /**
+     * 获取设备轨迹详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('bd:devcTrail:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return success(bdDevcTrailService.selectBdDevcTrailById(id));
+    }
+
+    /**
+     * 新增设备轨迹
+     */
+    @PreAuthorize("@ss.hasPermi('bd:devcTrail:add')")
+    @Log(title = "设备轨迹", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BdDevcTrail bdDevcTrail)
+    {
+        return toAjax(bdDevcTrailService.insertBdDevcTrail(bdDevcTrail));
+    }
+
+    /**
+     * 修改设备轨迹
+     */
+    @PreAuthorize("@ss.hasPermi('bd:devcTrail:edit')")
+    @Log(title = "设备轨迹", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BdDevcTrail bdDevcTrail)
+    {
+        return toAjax(bdDevcTrailService.updateBdDevcTrail(bdDevcTrail));
+    }
+
+    /**
+     * 删除设备轨迹
+     */
+    @PreAuthorize("@ss.hasPermi('bd:devcTrail:remove')")
+    @Log(title = "设备轨迹", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(bdDevcTrailService.deleteBdDevcTrailByIds(ids));
+    }
+}

+ 138 - 0
bd-location/src/main/java/com/ruoyi/web/controller/bd/BdDevcTrailUwbController.java

@@ -0,0 +1,138 @@
+package com.ruoyi.web.controller.bd;
+
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.bd.domain.BdDevcTrailUwb;
+import com.ruoyi.bd.service.IBdDevcTrailUwbService;
+import com.ruoyi.bd.service.rpc.UWBWebService;
+import com.ruoyi.common.BDConst;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import net.dreamlu.iot.mqtt.spring.client.MqttClientTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 室内坐标定位Controller
+ *
+ * @author ruoyi
+ * @date 2024-10-16
+ */
+@RestController
+@CrossOrigin
+@RequestMapping("/bd/devcTrailUwb")
+public class BdDevcTrailUwbController extends BaseController {
+    @Autowired
+    private IBdDevcTrailUwbService bdDevcTrailUwbService;
+
+    @Autowired
+    private MqttClientTemplate client;
+
+    @Autowired
+    private UWBWebService uwbWebService;
+
+    /**
+     * 查询室内坐标定位列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(BdDevcTrailUwb bdDevcTrailUwb) {
+        startPage();
+        List<BdDevcTrailUwb> list = bdDevcTrailUwbService.selectBdDevcTrailUwbList(bdDevcTrailUwb);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出室内坐标定位列表
+     */
+    @Log(title = "室内坐标定位", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BdDevcTrailUwb bdDevcTrailUwb) {
+        List<BdDevcTrailUwb> list = bdDevcTrailUwbService.selectBdDevcTrailUwbList(bdDevcTrailUwb);
+        ExcelUtil<BdDevcTrailUwb> util = new ExcelUtil<BdDevcTrailUwb>(BdDevcTrailUwb.class);
+        util.exportExcel(response, list, "室内坐标定位数据");
+    }
+
+    @PostMapping("/point")
+    @Anonymous
+    public void pushPoint(@RequestBody BdDevcTrailUwb bdDevcTrailUwb) {
+        Map<String, Object> map = new HashMap() {
+            {
+                put("latitude", bdDevcTrailUwb.getLat());
+                put("longitude", bdDevcTrailUwb.getLng());
+                put("srcTimestamp", DateTimeUtil.timestampMillis());
+                put("key", bdDevcTrailUwb.getDevcKey());
+                put("deviceId", bdDevcTrailUwb.getDevcKey());
+            }
+        };
+        client.publish(String.format(BDConst.MQTT_TOPIC.DEVICE_LOCATION_TOPIC, bdDevcTrailUwb.getDevcKey()), JSON.toJSONBytes(map));
+    }
+
+
+    /**
+     * 获取室内坐标定位详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(bdDevcTrailUwbService.selectBdDevcTrailUwbById(id));
+    }
+
+    @GetMapping(value = "/token/{id}")
+    @Anonymous
+    public AjaxResult getTokenInfo() {
+        return success(uwbWebService.exchangeToken(new HashMap<String,Object>(){
+            {
+                put("username", "admin");
+                put("password", "admin123");
+                put("isfresh", 1);
+            }
+        }));
+    }
+
+    /**
+     * 新增室内坐标定位
+     */
+    @Log(title = "室内坐标定位", businessType = BusinessType.INSERT)
+    @PostMapping
+    @Anonymous
+    public AjaxResult add(@RequestBody BdDevcTrailUwb bdDevcTrailUwb) {
+        return toAjax(bdDevcTrailUwbService.insertBdDevcTrailUwb(bdDevcTrailUwb));
+    }
+
+    /**
+     * 修改室内坐标定位
+     */
+    @Log(title = "室内坐标定位", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BdDevcTrailUwb bdDevcTrailUwb) {
+        return toAjax(bdDevcTrailUwbService.updateBdDevcTrailUwb(bdDevcTrailUwb));
+    }
+
+    /**
+     * 删除室内坐标定位
+     */
+    @Log(title = "室内坐标定位", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(bdDevcTrailUwbService.deleteBdDevcTrailUwbByIds(ids));
+    }
+
+
+}

+ 95 - 0
bd-location/src/main/java/com/ruoyi/web/controller/bd/BdFenceInfoController.java

@@ -0,0 +1,95 @@
+package com.ruoyi.web.controller.bd;
+
+import com.ruoyi.bd.domain.BdFenceInfo;
+import com.ruoyi.bd.service.IBdFenceInfoService;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 围栏基础信息Controller
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+@RestController
+@RequestMapping("/bd/fenceInfo")
+@CrossOrigin
+@Anonymous
+public class BdFenceInfoController extends BaseController {
+    @Autowired
+    private IBdFenceInfoService bdFenceInfoService;
+
+    /**
+     * 查询围栏基础信息列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(BdFenceInfo bdFenceInfo) {
+        startPage();
+        List<BdFenceInfo> list = bdFenceInfoService.selectBdFenceInfoList(bdFenceInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出围栏基础信息列表
+     */
+    @Log(title = "围栏基础信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BdFenceInfo bdFenceInfo) {
+        List<BdFenceInfo> list = bdFenceInfoService.selectBdFenceInfoList(bdFenceInfo);
+        ExcelUtil<BdFenceInfo> util = new ExcelUtil<BdFenceInfo>(BdFenceInfo.class);
+        util.exportExcel(response, list, "围栏基础信息数据");
+    }
+
+    /**
+     * 获取围栏基础信息详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(bdFenceInfoService.selectBdFenceInfoById(id));
+    }
+
+    /**
+     * 新增围栏基础信息
+     */
+    @Log(title = "围栏基础信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BdFenceInfo bdFenceInfo) {
+        return toAjax(bdFenceInfoService.insertBdFenceInfo(bdFenceInfo));
+    }
+
+    /**
+     * 修改围栏基础信息
+     */
+    @Log(title = "围栏基础信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BdFenceInfo bdFenceInfo) {
+        return toAjax(bdFenceInfoService.updateBdFenceInfo(bdFenceInfo));
+    }
+
+    /**
+     * 删除围栏基础信息
+     */
+    @Log(title = "围栏基础信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(bdFenceInfoService.deleteBdFenceInfoByIds(ids));
+    }
+}

+ 101 - 0
bd-location/src/main/java/com/ruoyi/web/controller/bd/BdFenceVioEvtController.java

@@ -0,0 +1,101 @@
+package com.ruoyi.web.controller.bd;
+
+import com.ruoyi.bd.domain.BdFenceVioEvt;
+import com.ruoyi.bd.service.IBdFenceVioEvtService;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 围栏闯禁事件Controller
+ *
+ * @author ruoyi
+ * @date 2024-10-14
+ */
+@RestController
+@RequestMapping("/bd/fenceVioEvt")
+@CrossOrigin
+@Anonymous
+public class BdFenceVioEvtController extends BaseController {
+    @Autowired
+    private IBdFenceVioEvtService bdFenceVioEvtService;
+
+    /**
+     * 查询围栏闯禁事件列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(BdFenceVioEvt bdFenceVioEvt) {
+        startPage();
+        List<BdFenceVioEvt> list = bdFenceVioEvtService.selectBdFenceVioEvtList(bdFenceVioEvt);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/listAll")
+    public TableDataInfo listAll(BdFenceVioEvt bdFenceVioEvt) {
+        List<BdFenceVioEvt> list = bdFenceVioEvtService.selectBdFenceVioEvtList(bdFenceVioEvt);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出围栏闯禁事件列表
+     */
+    @Log(title = "围栏闯禁事件", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BdFenceVioEvt bdFenceVioEvt) {
+        List<BdFenceVioEvt> list = bdFenceVioEvtService.selectBdFenceVioEvtList(bdFenceVioEvt);
+        ExcelUtil<BdFenceVioEvt> util = new ExcelUtil<BdFenceVioEvt>(BdFenceVioEvt.class);
+        util.exportExcel(response, list, "围栏闯禁事件数据");
+    }
+
+    /**
+     * 获取围栏闯禁事件详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(bdFenceVioEvtService.selectBdFenceVioEvtById(id));
+    }
+
+    /**
+     * 新增围栏闯禁事件
+     */
+    @Log(title = "围栏闯禁事件", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BdFenceVioEvt bdFenceVioEvt) {
+        return toAjax(bdFenceVioEvtService.insertBdFenceVioEvt(bdFenceVioEvt));
+    }
+
+    /**
+     * 修改围栏闯禁事件
+     */
+    @Log(title = "围栏闯禁事件", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BdFenceVioEvt bdFenceVioEvt) {
+        return toAjax(bdFenceVioEvtService.updateBdFenceVioEvt(bdFenceVioEvt));
+    }
+
+    /**
+     * 删除围栏闯禁事件
+     */
+    @Log(title = "围栏闯禁事件", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(bdFenceVioEvtService.deleteBdFenceVioEvtByIds(ids));
+    }
+}

+ 20 - 0
bd-location/src/main/java/com/ruoyi/web/core/config/MqttCfg.java

@@ -0,0 +1,20 @@
+package com.ruoyi.web.core.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "bd.mqtt")
+@ConditionalOnProperty(name = "bd.mqtt.enabled", havingValue = "true")
+public class MqttCfg {
+    private boolean enabled;
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+}

+ 125 - 0
bd-location/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java

@@ -0,0 +1,125 @@
+package com.ruoyi.web.core.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import com.ruoyi.common.config.RuoYiConfig;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.models.auth.In;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.ApiKey;
+import springfox.documentation.service.AuthorizationScope;
+import springfox.documentation.service.Contact;
+import springfox.documentation.service.SecurityReference;
+import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+
+/**
+ * Swagger2的接口配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class SwaggerConfig
+{
+    /** 系统基础配置 */
+    @Autowired
+    private RuoYiConfig ruoyiConfig;
+
+    /** 是否开启swagger */
+    @Value("${swagger.enabled}")
+    private boolean enabled;
+
+    /** 设置请求的统一前缀 */
+    @Value("${swagger.pathMapping}")
+    private String pathMapping;
+
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi()
+    {
+        return new Docket(DocumentationType.OAS_30)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
+                // 扫描所有 .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+                /* 设置安全模式,swagger可以设置访问token */
+                .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts())
+                .pathMapping(pathMapping);
+    }
+
+    /**
+     * 安全模式,这里指定token通过Authorization头请求头传递
+     */
+    private List<SecurityScheme> securitySchemes()
+    {
+        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
+        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
+        return apiKeyList;
+    }
+
+    /**
+     * 安全上下文
+     */
+    private List<SecurityContext> securityContexts()
+    {
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        securityContexts.add(
+                SecurityContext.builder()
+                        .securityReferences(defaultAuth())
+                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
+                        .build());
+        return securityContexts;
+    }
+
+    /**
+     * 默认的安全上引用
+     */
+    private List<SecurityReference> defaultAuth()
+    {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
+        return securityReferences;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo()
+    {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:若依管理系统_接口文档")
+                // 描述
+                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
+                // 作者信息
+                .contact(new Contact(ruoyiConfig.getName(), null, null))
+                // 版本
+                .version("版本号:" + ruoyiConfig.getVersion())
+                .build();
+    }
+}

+ 134 - 0
bd-location/src/main/java/com/ruoyi/web/core/config/UWBCfg.java

@@ -0,0 +1,134 @@
+package com.ruoyi.web.core.config;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.annotation.Resource;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.java_websocket.client.WebSocketClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.ruoyi.bd.domain.UWBAuth;
+import com.ruoyi.bd.service.rpc.UWBWebService;
+import com.ruoyi.bd.socket.UWBSocket.UWBSocketClient;
+import com.ruoyi.common.utils.DateTimeUtil;
+
+import cn.hutool.core.map.MapUtil;
+
+@Configuration
+@ConfigurationProperties(prefix = "bd.uwb")
+@ConditionalOnProperty(name = "bd.uwb.enabled", havingValue = "true")
+public class UWBCfg {
+    private static final Logger log = LoggerFactory.getLogger(UWBCfg.class);
+
+    private String uwbSocket;
+
+    private String uwbUsr;
+
+    private String uwbPwd;
+
+    private String uwbHost;
+
+    public String getUwbHost() {
+        return uwbHost;
+    }
+
+    public void setUwbHost(String uwbHost) {
+        this.uwbHost = uwbHost;
+    }
+
+    @Resource
+    private UWBWebService uwbWebService;
+
+    public String getUwbSocket() {
+        return uwbSocket;
+    }
+
+    public void setUwbSocket(String uwbSocket) {
+        this.uwbSocket = uwbSocket;
+    }
+
+    public String getUwbUsr() {
+        return uwbUsr;
+    }
+
+    public void setUwbUsr(String uwbUsr) {
+        this.uwbUsr = uwbUsr;
+    }
+
+    public String getUwbPwd() {
+        return uwbPwd;
+    }
+
+    public void setUwbPwd(String uwbPwd) {
+        this.uwbPwd = uwbPwd;
+    }
+
+    @Bean
+    public WebSocketClient webSocketClient() throws URISyntaxException {
+        String ws = this.getUwbSocket();
+        UWBSocketClient webSocketClient = new UWBSocketClient(new URI(ws));
+        UWBAuth uwbAuth = authUWB();
+        if (ObjectUtils.isEmpty(uwbAuth)) {
+            return null;
+        }
+        webSocketClient.setAuthToken(uwbAuth);
+        webSocketClient.connect();
+        Timer t = new Timer();
+        t.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                if (webSocketClient.isClosed()) {
+                    UWBAuth uwbAuth = authUWB();
+                    if (ObjectUtils.isEmpty(uwbAuth)) {
+                        return;
+                    }
+                    webSocketClient.setAuthToken(uwbAuth);
+                    webSocketClient.reconnect();
+                }
+            }
+        }, 1000, 5000);
+        return webSocketClient;
+    }
+
+    private UWBAuth authUWB() {
+        Map map = uwbWebService.exchangeToken(new HashMap<String, Object>() {
+            {
+                {
+                    put("username", uwbUsr);
+                    put("password", uwbPwd);
+                    put("isfresh", 1);
+                }
+            }
+        });
+        Integer code = MapUtil.getInt(map, "code", 400);
+        if (code != 200) {
+            log.error("获取token失败");
+            return null;
+        }
+        JSONObject data = MapUtil.get(map, "data", JSONObject.class);
+        UWBAuth uwbAuth = new UWBAuth();
+        String accessToken = data.getString("access_token");
+        if (StringUtils.isBlank(accessToken)) {
+            log.error("获取token失败{}", data);
+            return null;
+        }
+        uwbAuth.setAccessToken(data.getString("access_token"));
+        uwbAuth.setRegister(Long.toString(DateTimeUtil.timestampMillis()));
+        return uwbAuth;
+    }
+
+}

+ 20 - 0
bd-location/src/main/java/com/ruoyi/web/core/config/forest/UWBService.java

@@ -0,0 +1,20 @@
+package com.ruoyi.web.core.config.forest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.dtflys.forest.annotation.BindingVar;
+import com.dtflys.forest.reflection.ForestMethod;
+import com.ruoyi.web.core.config.UWBCfg;
+
+@Service("uwbService")
+public class UWBService {
+    @Autowired
+    private UWBCfg uwbCfg;
+
+    @BindingVar("uwbBaseUrl")
+    public String getBaseUrl(ForestMethod method) {
+        return uwbCfg.getUwbHost();
+    }
+
+}

+ 1 - 0
bd-location/src/main/resources/META-INF/spring-devtools.properties

@@ -0,0 +1 @@
+restart.include.json=/com.alibaba.fastjson2.*.jar

+ 110 - 0
bd-location/src/main/resources/application-druid.yml

@@ -0,0 +1,110 @@
+# 数据源配置
+spring:
+  # redis 配置
+  redis:
+    # 地址
+    host: 172.192.10.105
+    # 端口,默认为6379
+    port: 30013
+    # 数据库索引
+    database: 2
+    # 密码
+    password:
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: 3s
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 主库数据源
+      master:
+        url: jdbc:mysql://172.192.10.105:30002/hs_cps?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+        username: root
+        password: root
+      # 从库数据源
+      slave:
+        # 从数据源开关/默认关闭
+        enabled: false
+        url:
+        username:
+        password:
+      # 初始连接数
+      initialSize: 5
+      # 最小连接池数量
+      minIdle: 10
+      # 最大连接池数量
+      maxActive: 20
+      # 配置获取连接等待超时的时间
+      maxWait: 60000
+      # 配置连接超时时间
+      connectTimeout: 30000
+      # 配置网络超时时间
+      socketTimeout: 60000
+      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      timeBetweenEvictionRunsMillis: 60000
+      # 配置一个连接在池中最小生存的时间,单位是毫秒
+      minEvictableIdleTimeMillis: 300000
+      # 配置一个连接在池中最大生存的时间,单位是毫秒
+      maxEvictableIdleTimeMillis: 900000
+      # 配置检测连接是否有效
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 设置白名单,不填则允许所有访问
+        allow:
+        url-pattern: /druid/*
+        # 控制台管理用户名和密码
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 慢SQL记录
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+mqtt:
+  client:
+    enabled: true
+    ip: 200.200.19.121
+    port: 31005
+    name: uwb-location-client
+    client-id: uwb-000001
+    global-subscribe:
+    timeout: 5
+    reconnect: true
+    re-interval: 5000
+    version: mqtt_3_1_1
+    read-buffer-size: 8KB
+    max-bytes-in-message: 10MB
+    keep-alive-secs: 60
+    clean-session: true
+    ssl:
+      enabled: false
+bd:
+  mqtt:
+    enabled: true
+  uwb:
+    enabled: true
+    uwb-socket: ws://172.192.13.77:2223/socket/websocket/pollingArea
+    uwb-usr: admin
+    uwb-pwd: admin123
+    uwb-host: http://172.192.13.77:2223

+ 105 - 0
bd-location/src/main/resources/application-hm.yml

@@ -0,0 +1,105 @@
+# 数据源配置
+spring:
+  # redis 配置
+  redis:
+    # 地址
+    host: 192.168.0.100
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 1
+    # 密码
+    password:
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: 3s
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 主库数据源
+      master:
+        url: jdbc:mysql://192.168.0.100:13306/hs_cps?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+        username: root
+        password: ct!QAZ
+      # 从库数据源
+      slave:
+        # 从数据源开关/默认关闭
+        enabled: false
+        url:
+        username:
+        password:
+      # 初始连接数
+      initialSize: 5
+      # 最小连接池数量
+      minIdle: 10
+      # 最大连接池数量
+      maxActive: 20
+      # 配置获取连接等待超时的时间
+      maxWait: 60000
+      # 配置连接超时时间
+      connectTimeout: 30000
+      # 配置网络超时时间
+      socketTimeout: 60000
+      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      timeBetweenEvictionRunsMillis: 60000
+      # 配置一个连接在池中最小生存的时间,单位是毫秒
+      minEvictableIdleTimeMillis: 300000
+      # 配置一个连接在池中最大生存的时间,单位是毫秒
+      maxEvictableIdleTimeMillis: 900000
+      # 配置检测连接是否有效
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 设置白名单,不填则允许所有访问
+        allow:
+        url-pattern: /druid/*
+        # 控制台管理用户名和密码
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 慢SQL记录
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+
+mqtt:
+  client:
+    enabled: true
+    ip: xt.wenhq.top
+    port: 8581
+    name: uwb-location-client
+    client-id: uwb-000001
+    global-subscribe:
+    timeout: 5
+    reconnect: true
+    re-interval: 5000
+    version: mqtt_3_1_1
+    read-buffer-size: 8KB
+    max-bytes-in-message: 10MB
+    keep-alive-secs: 60
+    clean-session: true
+    ssl:
+      enabled: false
+bd:
+  mqtt:
+    enabled: true

+ 108 - 0
bd-location/src/main/resources/application-k8s.yml

@@ -0,0 +1,108 @@
+server:
+  servlet:
+    # 应用的访问路径
+    context-path: /prod-api
+# 数据源配置
+spring:
+  # redis 配置
+  redis:
+    # 地址
+    host: 200.200.19.121
+    # 端口,默认为6379
+    port: 31001
+    # 数据库索引
+    database: 3
+    # 密码
+    password:
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: 3s
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 主库数据源
+      master:
+        url: jdbc:mysql://200.200.19.121:31000/hs_cps?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+        username: root
+        password: root
+      # 从库数据源
+      slave:
+        # 从数据源开关/默认关闭
+        enabled: false
+        url:
+        username:
+        password:
+      # 初始连接数
+      initialSize: 5
+      # 最小连接池数量
+      minIdle: 10
+      # 最大连接池数量
+      maxActive: 20
+      # 配置获取连接等待超时的时间
+      maxWait: 60000
+      # 配置连接超时时间
+      connectTimeout: 30000
+      # 配置网络超时时间
+      socketTimeout: 60000
+      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      timeBetweenEvictionRunsMillis: 60000
+      # 配置一个连接在池中最小生存的时间,单位是毫秒
+      minEvictableIdleTimeMillis: 300000
+      # 配置一个连接在池中最大生存的时间,单位是毫秒
+      maxEvictableIdleTimeMillis: 900000
+      # 配置检测连接是否有效
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 设置白名单,不填则允许所有访问
+        allow:
+        url-pattern: /druid/*
+        # 控制台管理用户名和密码
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 慢SQL记录
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+mqtt:
+  client:
+    enabled: true
+    ip: xt.wenhq.top
+    port: 8581
+    name: uwb-location-client
+    client-id: uwb-000001
+    global-subscribe:
+    timeout: 5
+    reconnect: true
+    re-interval: 5000
+    version: mqtt_3_1_1
+    read-buffer-size: 8KB
+    max-bytes-in-message: 10MB
+    keep-alive-secs: 60
+    clean-session: true
+    ssl:
+      enabled: false
+bd:
+  mqtt:
+    enabled: true

+ 104 - 0
bd-location/src/main/resources/application-prod.yml

@@ -0,0 +1,104 @@
+# 数据源配置
+spring:
+  # redis 配置
+  redis:
+    # 地址
+    host: 127.0.0.1
+    # 端口,默认为6379
+    port: 6379
+    # 数据库索引
+    database: 1
+    # 密码
+    password:
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: 3s
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 主库数据源
+      master:
+        url: jdbc:mysql://127.0.0.1:3306/hs_cps?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
+        username: hscps
+        password: hscps2024
+      # 从库数据源
+      slave:
+        # 从数据源开关/默认关闭
+        enabled: false
+        url:
+        username:
+        password:
+      # 初始连接数
+      initialSize: 5
+      # 最小连接池数量
+      minIdle: 10
+      # 最大连接池数量
+      maxActive: 20
+      # 配置获取连接等待超时的时间
+      maxWait: 60000
+      # 配置连接超时时间
+      connectTimeout: 30000
+      # 配置网络超时时间
+      socketTimeout: 60000
+      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      timeBetweenEvictionRunsMillis: 60000
+      # 配置一个连接在池中最小生存的时间,单位是毫秒
+      minEvictableIdleTimeMillis: 300000
+      # 配置一个连接在池中最大生存的时间,单位是毫秒
+      maxEvictableIdleTimeMillis: 900000
+      # 配置检测连接是否有效
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 设置白名单,不填则允许所有访问
+        allow:
+        url-pattern: /druid/*
+        # 控制台管理用户名和密码
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 慢SQL记录
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+mqtt:
+  client:
+    enabled: true
+    ip: xt.wenhq.top
+    port: 8581
+    name: uwb-location-client
+    client-id: uwb-000001
+    global-subscribe:
+    timeout: 5
+    reconnect: true
+    re-interval: 5000
+    version: mqtt_3_1_1
+    read-buffer-size: 8KB
+    max-bytes-in-message: 10MB
+    keep-alive-secs: 60
+    clean-session: true
+    ssl:
+      enabled: false
+bd:
+  mqtt:
+    enabled: false

+ 128 - 0
bd-location/src/main/resources/application.yml

@@ -0,0 +1,128 @@
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: traffic-eff-be
+  # 版本
+  version: 3.8.8
+  # 版权年份
+  copyrightYear: 2024
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
+  profile: /opt/project/ruoyi/ruoyi-backend/upload-file-path
+  # 获取ip地址开关
+  addressEnabled: false
+  # 验证码类型 math 数字计算 char 字符验证
+  captchaType: math
+
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 28080
+  servlet:
+    # 应用的访问路径
+    context-path: /bd-api
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # 连接数满后的排队数,默认为100
+    accept-count: 1000
+    threads:
+      # tomcat最大线程数,默认为200
+      max: 800
+      # Tomcat启动初始化的线程数,默认值10
+      min-spare: 100
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: debug
+    org.springframework: warn
+
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+
+# Spring配置
+spring:
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  profiles:
+    active: druid
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size: 10MB
+      # 设置总上传的文件大小
+      max-request-size: 20MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
+forest:
+  max-connections: 1000        # 连接池最大连接数
+  connect-timeout: 3000        # 连接超时时间,单位为毫秒
+  read-timeout: 3000           # 数据读取超时时间,单位为毫秒
+  backend: okhttp3 # 配置后端HTTP API为 okhttp3
+  ## 日志总开关,打开/关闭Forest请求/响应日志(默认为 true)
+  log-enabled: true
+  ## 打开/关闭Forest请求日志(默认为 true)
+  log-request: true
+  ## 打开/关闭Forest响应状态日志(默认为 true)
+  log-response-status: true
+  ## 打开/关闭Forest响应内容日志(默认为 false)
+  log-response-content: true
+# token配置
+token:
+  # 令牌自定义标识
+  header: Authorization
+  # 令牌密钥
+  secret: abcdefghijklmnopqrstuvwxyz
+  # 令牌有效期(默认30分钟)
+  expireTime: 76000
+
+# MyBatis配置
+mybatis:
+  # 搜索指定包别名
+  typeAliasesPackage: com.ruoyi.**.domain
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 加载全局的配置文件
+  configLocation: classpath:mybatis/mybatis-config.xml
+
+# PageHelper分页插件
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Swagger配置
+swagger:
+  # 是否开启swagger
+  enabled: true
+  # 请求前缀
+  pathMapping: /dev-api
+
+# 防止XSS攻击
+xss:
+  # 过滤开关
+  enabled: true
+  # 排除链接(多个用逗号分隔)
+  excludes: /system/notice
+  # 匹配链接
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+evt-fusion:
+  # 默认为false
+  enabled: false
+  thread-pool-size: 2
+  # 默认为300,单位为秒
+  fusion-time:
+    room-location: 30
+

+ 2 - 0
bd-location/src/main/resources/banner.txt

@@ -0,0 +1,2 @@
+Application Version: ${ruoyi.version}
+Spring Boot Version: ${spring-boot.version}

+ 38 - 0
bd-location/src/main/resources/i18n/messages.properties

@@ -0,0 +1,38 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+login.blocked=很遗憾,访问IP已被列入系统黑名单
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 93 - 0
bd-location/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/opt/project/ruoyi/ruoyi-backend/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.ruoyi" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 82 - 0
bd-location/src/main/resources/mapper/bd/BdDevcTrailMapper.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.bd.mapper.BdDevcTrailMapper">
+    
+    <resultMap type="BdDevcTrail" id="BdDevcTrailResult">
+        <result property="id"    column="id"    />
+        <result property="devcKey"    column="devc_key"    />
+        <result property="dt"    column="dt"    />
+        <result property="poly"    column="poly"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+    </resultMap>
+
+    <sql id="selectBdDevcTrailVo">
+        select id, devc_key, dt, poly, update_time, create_time, create_by, update_by from bd_devc_trail
+    </sql>
+
+    <select id="selectBdDevcTrailList" parameterType="BdDevcTrail" resultMap="BdDevcTrailResult">
+        <include refid="selectBdDevcTrailVo"/>
+        <where>  
+            <if test="devcKey != null  and devcKey != ''"> and devc_key = #{devcKey}</if>
+            <if test="dt != null  and dt != ''"> and dt = #{dt}</if>
+            <if test="poly != null  and poly != ''"> and poly = #{poly}</if>
+        </where>
+    </select>
+    
+    <select id="selectBdDevcTrailById" parameterType="Long" resultMap="BdDevcTrailResult">
+        <include refid="selectBdDevcTrailVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBdDevcTrail" parameterType="BdDevcTrail" useGeneratedKeys="true" keyProperty="id">
+        insert into bd_devc_trail
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="devcKey != null">devc_key,</if>
+            <if test="dt != null">dt,</if>
+            <if test="poly != null">poly,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="devcKey != null">#{devcKey},</if>
+            <if test="dt != null">#{dt},</if>
+            <if test="poly != null">#{poly},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBdDevcTrail" parameterType="BdDevcTrail">
+        update bd_devc_trail
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="devcKey != null">devc_key = #{devcKey},</if>
+            <if test="dt != null">dt = #{dt},</if>
+            <if test="poly != null">poly = #{poly},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBdDevcTrailById" parameterType="Long">
+        delete from bd_devc_trail where id = #{id}
+    </delete>
+
+    <delete id="deleteBdDevcTrailByIds" parameterType="String">
+        delete from bd_devc_trail where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 132 - 0
bd-location/src/main/resources/mapper/bd/BdDevcTrailUwbMapper.xml

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.bd.mapper.BdDevcTrailUwbMapper">
+    
+    <resultMap type="BdDevcTrailUwb" id="BdDevcTrailUwbResult">
+        <result property="id"    column="id"    />
+        <result property="devcKey"    column="devc_key"    />
+        <result property="lat"    column="lat"    />
+        <result property="lng"    column="lng"    />
+        <result property="dt"    column="dt"    />
+        <result property="stepIndex"    column="step_index"    />
+        <result property="tp"    column="tp"    />
+        <result property="roomIndex"    column="room_index"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+    </resultMap>
+
+    <sql id="selectBdDevcTrailUwbVo">
+        select id, devc_key, lat, lng, dt, step_index, tp, room_index, update_time, create_time, create_by, update_by from bd_devc_trail_uwb
+    </sql>
+
+    <select id="selectBdDevcTrailUwbList" parameterType="BdDevcTrailUwb" resultMap="BdDevcTrailUwbResult">
+        <include refid="selectBdDevcTrailUwbVo"/>
+        <where>  
+            <if test="devcKey != null  and devcKey != ''"> and devc_key = #{devcKey}</if>
+            <if test="dt != null  and dt != ''"> and dt = #{dt}</if>
+        </where>
+    </select>
+    
+    <select id="selectBdDevcTrailUwbById" parameterType="Long" resultMap="BdDevcTrailUwbResult">
+        <include refid="selectBdDevcTrailUwbVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBdDevcTrailUwb" parameterType="BdDevcTrailUwb" useGeneratedKeys="true" keyProperty="id">
+        insert into bd_devc_trail_uwb
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="devcKey != null">devc_key,</if>
+            <if test="lat != null">lat,</if>
+            <if test="lng != null">lng,</if>
+            <if test="dt != null">dt,</if>
+            <if test="stepIndex != null">step_index,</if>
+            <if test="tp != null">tp,</if>
+            <if test="roomIndex != null">room_index,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="devcKey != null">#{devcKey},</if>
+            <if test="lat != null">#{lat},</if>
+            <if test="lng != null">#{lng},</if>
+            <if test="dt != null">#{dt},</if>
+            <if test="stepIndex != null">#{stepIndex},</if>
+            <if test="tp != null">#{tp},</if>
+            <if test="roomIndex != null">#{roomIndex},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBdDevcTrailUwb" parameterType="BdDevcTrailUwb">
+        update bd_devc_trail_uwb
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="devcKey != null">devc_key = #{devcKey},</if>
+            <if test="lat != null">lat = #{lat},</if>
+            <if test="lng != null">lng = #{lng},</if>
+            <if test="dt != null">dt = #{dt},</if>
+            <if test="stepIndex != null">step_index = #{stepIndex},</if>
+            <if test="tp != null">tp = #{tp},</if>
+            <if test="roomIndex != null">room_index = #{roomIndex},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBdDevcTrailUwbById" parameterType="Long">
+        delete from bd_devc_trail_uwb where id = #{id}
+    </delete>
+
+    <delete id="deleteBdDevcTrailUwbByIds" parameterType="String">
+        delete from bd_devc_trail_uwb where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <insert id="saveObjTail" parameterType="String">
+        SET SESSION group_concat_max_len = 1024000;
+        INSERT INTO bd_devc_trail (devc_key, dt, poly, update_time, create_time)
+        SELECT devc_key, dt, poly, NOW(), NOW()
+        FROM (SELECT devc_key,
+                     dt,
+                     concat(
+                             '[',
+                             GROUP_CONCAT(
+                                     CONCAT(
+                                             '[',
+                                             lng,
+                                             ',',
+                                             lat,
+                                             ',',
+                                             COALESCE(altitude, '-1'),
+                                             ',',
+                                             step_index,
+                                             ',',
+                                             COALESCE(room_index, '-1'),
+                                             ']'
+                                         ) ORDER BY
+			step_index SEPARATOR ','
+                                 ),
+                             ']'
+                         ) AS poly
+              FROM bd_devc_trail_uwb
+              WHERE dt = #{dt}
+              GROUP BY devc_key,
+                       dt) res ON DUPLICATE KEY
+        UPDATE poly =
+        VALUES (poly), update_time =
+        VALUES (update_time);
+    </insert>
+</mapper>

+ 114 - 0
bd-location/src/main/resources/mapper/bd/BdFenceInfoMapper.xml

@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.bd.mapper.BdFenceInfoMapper">
+
+    <resultMap type="BdFenceInfo" id="BdFenceInfoResult">
+        <result property="id" column="id"/>
+        <result property="defenceName" column="defence_name"/>
+        <result property="poly" column="poly"/>
+        <result property="centerLng" column="center_lng"/>
+        <result property="centerLat" column="center_lat"/>
+        <result property="fenceType" column="fence_type"/>
+        <result property="locationId" column="location_id"/>
+        <result property="altitude" column="altitude"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="createTime" column="create_time"/>
+        <result property="createBy" column="create_by"/>
+        <result property="updateBy" column="update_by"/>
+    </resultMap>
+
+    <sql id="selectBdFenceInfoVo">
+        select id,
+               defence_name,
+               ST_AsText(poly) poly,
+               center_lng,
+               center_lat,
+               fence_type,
+               location_id,
+               altitude,
+               update_time,
+               create_time,
+               create_by,
+               update_by
+        from bd_fence_info
+    </sql>
+
+    <select id="selectBdFenceInfoList" parameterType="BdFenceInfo" resultMap="BdFenceInfoResult">
+        <include refid="selectBdFenceInfoVo"/>
+        <where>
+            <if test="defenceName != null  and defenceName != ''">and defence_name like concat('%', #{defenceName},
+                '%')
+            </if>
+            <if test="fenceType != null  and fenceType != ''">and fence_type = #{fenceType}</if>
+            <if test="locationId != null ">and location_id = #{locationId}</if>
+        </where>
+    </select>
+
+    <select id="selectBdFenceInfoById" parameterType="Long" resultMap="BdFenceInfoResult">
+        <include refid="selectBdFenceInfoVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBdFenceInfo" parameterType="BdFenceInfo" useGeneratedKeys="true" keyProperty="id">
+        insert into bd_fence_info
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="defenceName != null">defence_name,</if>
+            <if test="poly != null">poly,</if>
+            <if test="centerLng != null">center_lng,</if>
+            <if test="centerLat != null">center_lat,</if>
+            <if test="fenceType !=null">fence_type,</if>
+            <if test="locationId !=null">location_id,</if>
+            <if test="altitude != null">altitude,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="defenceName != null">#{defenceName},</if>
+            <if test="poly != null">ST_GeomFromText(#{poly}),</if>
+            <if test="centerLng != null">#{centerLng},</if>
+            <if test="centerLat != null">#{centerLat},</if>
+            <if test="fenceType != null">#{fenceType},</if>
+            <if test="locationId != null">#{locationId},</if>
+            <if test="altitude != null">#{altitude},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+        </trim>
+    </insert>
+
+    <update id="updateBdFenceInfo" parameterType="BdFenceInfo">
+        update bd_fence_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="defenceName != null">defence_name = #{defenceName},</if>
+            <if test="poly != null">poly = ST_GeomFromText(#{poly}),</if>
+            <if test="centerLng != null">center_lng = #{centerLng},</if>
+            <if test="centerLat != null">center_lat = #{centerLat},</if>
+            <if test="fenceType != null">fence_type = #{fenceType},</if>
+            <if test="locationId != null">location_id = #{locationId},</if>
+            <if test="altitude != null">altitude = #{altitude},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBdFenceInfoById" parameterType="Long">
+        delete
+        from bd_fence_info
+        where id = #{id}
+    </delete>
+
+    <delete id="deleteBdFenceInfoByIds" parameterType="String">
+        delete from bd_fence_info where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 125 - 0
bd-location/src/main/resources/mapper/bd/BdFenceVioEvtMapper.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.bd.mapper.BdFenceVioEvtMapper">
+
+    <resultMap type="BdFenceVioEvt" id="BdFenceVioEvtResult">
+        <result property="id" column="id"/>
+        <result property="evtKey" column="evt_key"/>
+        <result property="evtType" column="evt_type"/>
+        <result property="evtDesc" column="evt_desc"/>
+        <result property="lng" column="lng"/>
+        <result property="lat" column="lat"/>
+        <result property="fenceId" column="fence_id"/>
+        <result property="evtStatus" column="evt_status"/>
+        <result property="evtTime" column="evt_time"/>
+        <result property="locationId" column="location_id"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="createTime" column="create_time"/>
+        <result property="createBy" column="create_by"/>
+        <result property="updateBy" column="update_by"/>
+    </resultMap>
+
+    <sql id="selectBdFenceVioEvtVo">
+        select id,
+               evt_key,
+               evt_type,
+               evt_desc,
+               lng,
+               lat,
+               fence_id,
+               evt_status,
+               evt_time,
+               location_id,
+               update_time,
+               create_time,
+               create_by,
+               update_by
+        from bd_fence_vio_evt
+    </sql>
+
+    <select id="selectBdFenceVioEvtList" parameterType="BdFenceVioEvt" resultMap="BdFenceVioEvtResult">
+        <include refid="selectBdFenceVioEvtVo"/>
+        <where>
+            <if test="evtType != null  and evtType != ''">and evt_type = #{evtType}</if>
+            <if test="evtDesc != null  and evtDesc != ''">and evt_desc = #{evtDesc}</if>
+            <if test="evtStatus !=null and evtStatus!=''">and evt_status = #{evtStatus}</if>
+            <if test="locationId !=null">and location_id = #{locationId}</if>
+            <if test="evtTime !=null  and evtTime!=''">and evt_time >= #{evtTime}</if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectBdFenceVioEvtById" parameterType="Long" resultMap="BdFenceVioEvtResult">
+        <include refid="selectBdFenceVioEvtVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertBdFenceVioEvt" parameterType="BdFenceVioEvt" useGeneratedKeys="true" keyProperty="id">
+        insert into bd_fence_vio_evt
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="evtKey != null">evt_key,</if>
+            <if test="evtType != null">evt_type,</if>
+            <if test="evtDesc != null">evt_desc,</if>
+            <if test="lng != null">lng,</if>
+            <if test="lat != null">lat,</if>
+            <if test="fenceId != null">fence_id,</if>
+            <if test="evtStatus !=null">evt_status,</if>
+            <if test="evtTime !=null">evt_time,</if>
+            <if test="locationId != null">location_id,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="evtKey != null">#{evtKey},</if>
+            <if test="evtType != null">#{evtType},</if>
+            <if test="evtDesc != null">#{evtDesc},</if>
+            <if test="lng != null">#{lng},</if>
+            <if test="lat != null">#{lat},</if>
+            <if test="fenceId != null">#{fenceId},</if>
+            <if test="evtStatus !=null">#{evtStatus},</if>
+            <if test="evtTime !=null">#{evtTime},</if>
+            <if test="locationId != null">#{locationId},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+        </trim>
+    </insert>
+
+    <update id="updateBdFenceVioEvt" parameterType="BdFenceVioEvt">
+        update bd_fence_vio_evt
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="evtKey != null">evt_key = #{evtKey},</if>
+            <if test="evtType != null">evt_type = #{evtType},</if>
+            <if test="evtDesc != null">evt_desc = #{evtDesc},</if>
+            <if test="lng != null">lng = #{lng},</if>
+            <if test="lat != null">lat = #{lat},</if>
+            <if test="fenceId != null">fence_id = #{fenceId},</if>
+            <if test="evtStatus !=null">evt_status = #{evtStatus},</if>
+            <if test="evtTime !=null">evt_time = #{evtTime},</if>
+            <if test="locationId != null">location_id = #{locationId},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBdFenceVioEvtById" parameterType="Long">
+        delete
+        from bd_fence_vio_evt
+        where id = #{id}
+    </delete>
+
+    <delete id="deleteBdFenceVioEvtByIds" parameterType="String">
+        delete from bd_fence_vio_evt where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 20 - 0
bd-location/src/main/resources/mybatis/mybatis-config.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+    <!-- 全局参数 -->
+    <settings>
+        <!-- 使全局的映射器启用或禁用缓存 -->
+        <setting name="cacheEnabled"             value="true"   />
+        <!-- 允许JDBC 支持自动生成主键 -->
+        <setting name="useGeneratedKeys"         value="true"   />
+        <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
+        <setting name="defaultExecutorType"      value="SIMPLE" />
+		<!-- 指定 MyBatis 所用日志的具体实现 -->
+        <setting name="logImpl"                  value="SLF4J"  />
+        <!-- 使用驼峰命名法转换字段 -->
+		 <setting name="mapUnderscoreToCamelCase" value="true"/>
+	</settings>
+
+</configuration>

+ 1 - 0
bd-location/version

@@ -0,0 +1 @@
+2.4

+ 46 - 0
pom.xml

@@ -32,6 +32,13 @@
         <velocity.version>2.3</velocity.version>
         <jwt.version>0.9.1</jwt.version>
         <warm-flow>1.3.1</warm-flow>
+        <forest.version>1.5.36</forest.version>
+        <cffu.version>1.0.0-Alpha19</cffu.version>
+        <jts.version>1.18.2</jts.version>
+        <mqttstarter.version>2.3.7</mqttstarter.version>
+        <math.version>3.6.1</math.version>
+        <hutool.version>5.8.26</hutool.version>
+        <java.ws.version>1.5.2</java.ws.version>
     </properties>
 
     <!-- 依赖声明 -->
@@ -200,6 +207,44 @@
                 <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
                 <version>${warm-flow}</version>
             </dependency>
+            <dependency>
+                <groupId>net.dreamlu</groupId>
+                <artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
+                <version>${mqttstarter.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.dtflys.forest</groupId>
+                <artifactId>forest-spring-boot-starter</artifactId>
+                <version>${forest.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.foldright</groupId>
+                <artifactId>cffu</artifactId>
+                <version>${cffu.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.locationtech.jts</groupId>
+                <artifactId>jts-core</artifactId>
+                <version>${jts.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-math3</artifactId>
+                <version>${math.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-bom</artifactId>
+                <version>${hutool.version}</version>
+                <type>pom</type>
+                <!-- 注意这里是import -->
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.java-websocket</groupId>
+                <artifactId>Java-WebSocket</artifactId>
+                <version>${java.ws.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -211,6 +256,7 @@
         <module>ruoyi-generator</module>
         <module>ruoyi-common</module>
         <module>ruoyi-flow</module>
+        <module>bd-location</module>
     </modules>
     <packaging>pom</packaging>
 

+ 13 - 0
ruoyi-common/pom.xml

@@ -125,6 +125,19 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.locationtech.jts</groupId>
+            <artifactId>jts-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 665 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/DateTimeUtil.java

@@ -0,0 +1,665 @@
+package com.ruoyi.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.text.SimpleDateFormat;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.time.temporal.WeekFields;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * The type Date time util.
+ *
+ * @author chen.cheng
+ */
+public class DateTimeUtil {
+
+    /**
+     * Current date time string.
+     *
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String currentDateTime() {
+        return DateTimeUtil.currentDateTime(DateFormatter.yyyy_MM_dd_HHmmss);
+    }
+
+    /**
+     * Parse date date.
+     *
+     * @param dateString the date string
+     * @return the date
+     * @author chen.cheng
+     */
+    public static Date parseDate(String dateString) {
+        return DateTimeUtil.parseDate(dateString, DateFormatter.yyyy_MM_dd_HHmmss);
+    }
+
+    /**
+     * Parse date date.
+     *
+     * @param dateString  the date string
+     * @param formatRegex the format regex
+     * @return the date
+     * @author chen.cheng
+     */
+    public static Date parseDate(String dateString, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegex);
+        LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
+        Instant instant = dateTime.atZone(ZoneId.of("GMT+08:00")).toInstant();
+        return Date.from(instant);
+    }
+
+    /**
+     * Minus day date.
+     *
+     * @param dayNum the day num
+     * @return the date
+     * @author chen.cheng
+     */
+    public static Date minusDay(Long dayNum) {
+        LocalDateTime now = LocalDateTime.now();
+        Instant instant = now.atZone(ZoneId.of("GMT+08:00")).toInstant();
+        return Date.from(instant);
+    }
+
+    /**
+     * Parse date string.
+     *
+     * @param localDateTime the local date time
+     * @param formatRegex   the format regex
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String parseDate(LocalDateTime localDateTime, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegex);
+        return localDateTime.format(formatter);
+    }
+
+    /**
+     * Parse date string.
+     *
+     * @param localDate   the local date
+     * @param formatRegex the format regex
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String parseDate(LocalDate localDate, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegex);
+        return localDate.format(formatter);
+    }
+
+    /**
+     * Current date time string.
+     *
+     * @param format the format
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String currentDateTime(String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        LocalDateTime now = LocalDateTime.now();
+        return now.format(formatter);
+    }
+
+    public static Integer getStepIndexFromMills(Integer step, long mills) {
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(mills), ZoneId.systemDefault());
+        int hour = localDateTime.getHour();
+        int second = localDateTime.getSecond();
+        return hour * 60 * 60 / step + second / step;
+    }
+
+    public static String getDateFromMills(long mills, String formatRegex) {
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(mills), ZoneId.systemDefault());
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegex);
+        return localDateTime.format(formatter);
+    }
+
+    public static String getDateFromMills(long mills) {
+        return getDateFromMills(mills, DateFormatter.yyyy_MM_dd_HHmmss);
+    }
+
+
+    /**
+     * Parse local date local date time.
+     *
+     * @param dateString  the date string
+     * @param formatRegex the format regex
+     * @return the local date time
+     * @author chen.cheng
+     */
+    public static LocalDateTime parseLocalDateTime(String dateString, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegex);
+        LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
+        return dateTime;
+    }
+
+    public static long timestampMillis() {
+        LocalDateTime localDateTime = LocalDateTime.now();
+        // 将 LocalDateTime 转换为 Instant
+        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
+        // 获取毫秒时间戳
+        return instant.toEpochMilli();
+    }
+
+    /**
+     * Parse local date local date.
+     *
+     * @param dateString  the date string
+     * @param formatRegex the format regex
+     * @return the local date
+     * @author chen.cheng
+     */
+    public static LocalDate parseLocalDate(String dateString, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegex);
+        LocalDate dateTime = LocalDate.parse(dateString, formatter);
+        return dateTime;
+    }
+
+    /**
+     * Parse date string string.
+     *
+     * @param dateString  the date string
+     * @param formatRegex the format regex
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String parseDateString(String dateString, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DateFormatter.yyyy_MM_dd_HHmmss);
+        LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
+        return parseDate(dateTime, formatRegex);
+    }
+
+    /**
+     * Parse date string string.
+     *
+     * @param dateString     the date string
+     * @param srcFormatRegex the src format regex
+     * @param formatRegex    the format regex
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String parseDateString(String dateString, String srcFormatRegex, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(srcFormatRegex);
+        LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
+        return parseDate(dateTime, formatRegex);
+    }
+
+    /**
+     * Parse short date string string(yyyyMMdd).
+     *
+     * @param dateString     the date string
+     * @param srcFormatRegex the src format regex
+     * @param formatRegex    the format regex
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String parseShortDateString(String dateString, String srcFormatRegex, String formatRegex) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(srcFormatRegex);
+        LocalDate date = LocalDate.parse(dateString, formatter);
+        return parseDate(date, formatRegex);
+    }
+
+    /**
+     * Days of range list.
+     *
+     * @param startDate the start date
+     * @param endDate   the end date DateTimeUtil.daysOfRange("2020-05-06",
+     *                  "2020-09-02",DateFormatter.yyyy_MM_dd,DateFormatter.yyyyMMdd)
+     * @return the list
+     * @author chen.cheng
+     */
+    public static List<String> daysOfRange(String startDate, String endDate) {
+        return daysOfRange(startDate, endDate, DateFormatter.yyyy_MM_dd, DateFormatter.yyyy_MM_dd);
+    }
+
+    /**
+     * Minus month string.
+     *
+     * @param monthNum   the month num
+     * @param formatRegx the format regx
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String minusMonth(Long monthNum, String formatRegx) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegx);
+        LocalDateTime now = LocalDateTime.now();
+        now = now.minusMonths(monthNum);
+        return now.format(formatter);
+    }
+
+    /**
+     * Minus month string
+     *
+     * @param monthNum   month num
+     * @param formatRegx format regx
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String minusMonth(Integer monthNum, String formatRegx) {
+        return minusMonth(monthNum.longValue(), formatRegx);
+    }
+
+    /**
+     * Yesterday string.
+     *
+     * @param formatRegx the format regx
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String yesterday(String formatRegx) {
+        return minusDay(1L, formatRegx);
+    }
+
+    /**
+     * Minus month string.
+     *
+     * @param monthNum   the month num
+     * @param formatRegx the format regx
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String minusMonth(String date, String formatRegx, Long monthNum) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegx);
+        LocalDateTime localDateTime = parseLocalDateTime(date, formatRegx);
+        localDateTime = localDateTime.minusMonths(monthNum);
+        return localDateTime.format(formatter);
+    }
+
+    /**
+     * Date minus month string
+     *
+     * @param date       date
+     * @param formatRegx format regx
+     * @param monthNum   month num
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String dateMinusMonth(String date, String formatRegx, Long monthNum) {
+        return dateMinusMonth(date, DateFormatter.yyyyMMdd, formatRegx, monthNum);
+    }
+
+    /**
+     * Date minus month string
+     *
+     * @param date          date
+     * @param srcFormatRegx src format regx
+     * @param formatRegx    format regx
+     * @param monthNum      month num
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String dateMinusMonth(String date, String srcFormatRegx, String formatRegx, Long monthNum) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegx);
+        LocalDate localDateTime = parseLocalDate(date, srcFormatRegx);
+        localDateTime = localDateTime.minusMonths(monthNum);
+        return localDateTime.format(formatter);
+    }
+
+    /**
+     * Minus day string.
+     *
+     * @param dayNum     the day num
+     * @param formatRegx the format regx
+     * @return the string
+     * @author chen.cheng
+     */
+    public static String minusDay(Long dayNum, String formatRegx) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatRegx);
+        LocalDateTime now = LocalDateTime.now();
+        now = now.minusDays(dayNum);
+        return now.format(formatter);
+    }
+
+    /**
+     * Days of range list.
+     *
+     * @param startDate    the start date
+     * @param endDate      the end date
+     * @param paramFormat  the param format
+     * @param resultFormat the result format
+     * @return the list
+     * @author chen.cheng
+     */
+    public static List<String> daysOfRange(String startDate, String endDate, String paramFormat, String resultFormat) {
+        DateTimeFormatter resultFormatter = DateTimeFormatter.ofPattern(resultFormat);
+        DateTimeFormatter paramFormatter = DateTimeFormatter.ofPattern(paramFormat);
+        LocalDate dateTimeOfStart = LocalDate.parse(startDate, paramFormatter);
+        LocalDate dateTimeOfEnd = LocalDate.parse(endDate, paramFormatter);
+        List<String> days = new ArrayList<>();
+        while (!dateTimeOfStart.isAfter(dateTimeOfEnd)) {
+            days.add(dateTimeOfStart.format(resultFormatter));
+            dateTimeOfStart = dateTimeOfStart.plusDays(1L);
+        }
+        return days;
+    }
+
+    public static List<String> monthOfRange(String startDate, String endDate, String paramFormat, String resultFormat) {
+        DateTimeFormatter resultFormatter = DateTimeFormatter.ofPattern(resultFormat);
+        DateTimeFormatter paramFormatter = DateTimeFormatter.ofPattern(paramFormat);
+        LocalDate dateTimeOfStart = LocalDate.parse(startDate, paramFormatter);
+        LocalDate dateTimeOfEnd = LocalDate.parse(endDate, paramFormatter);
+        List<String> month = new ArrayList<>();
+        while (!dateTimeOfStart.isAfter(dateTimeOfEnd)) {
+            month.add(dateTimeOfStart.format(resultFormatter));
+            dateTimeOfStart = dateTimeOfStart.plusMonths(1L);
+        }
+        DateTimeFormatter ddFormatter = DateTimeFormatter.ofPattern(DateFormatter.DD);
+        int startDd = Integer.parseInt(dateTimeOfStart.format(ddFormatter));
+        int endDd = Integer.parseInt(dateTimeOfEnd.format(ddFormatter));
+        if (startDd > endDd) {
+            month.add(dateTimeOfEnd.format(resultFormatter));
+        }
+        return month;
+    }
+
+    /**
+     * Days of range list.
+     *
+     * @param startDate   the start date
+     * @param endDate     the end date
+     * @param paramFormat the param format
+     * @return the list
+     * @author chen.cheng
+     */
+    public static List<String> daysOfRange(String startDate, String endDate, String paramFormat) {
+        DateTimeFormatter resultFormatter = DateTimeFormatter.ofPattern(paramFormat);
+        DateTimeFormatter paramFormatter = DateTimeFormatter.ofPattern(paramFormat);
+        LocalDate dateTimeOfStart = LocalDate.parse(startDate, paramFormatter);
+        LocalDate dateTimeOfEnd = LocalDate.parse(endDate, paramFormatter);
+        List<String> days = new ArrayList<>();
+        while (!dateTimeOfStart.isAfter(dateTimeOfEnd)) {
+            days.add(dateTimeOfStart.format(resultFormatter));
+            dateTimeOfStart = dateTimeOfStart.plusDays(1L);
+        }
+        return days;
+    }
+
+    /**
+     * Gets time step of time index.
+     *
+     * @param tp  the tp
+     * @param gap the gap (now + gap minutes)
+     * @return the time step of time index
+     * @author chen.cheng
+     */
+    public static Integer getTimeStepOfTimeIndex(Integer tp, Integer gap) {
+        return getTimeStepIndexOfNow(tp, LocalDateTime.now().plus(gap, ChronoUnit.MINUTES));
+    }
+
+    /**
+     * Get time step index of now integer.
+     *
+     * @param tp         the tp
+     * @param toDateTime the to date time
+     * @return the integer
+     * @author chen.cheng
+     */
+    public static int getTimeStepIndexOfNow(Integer tp, LocalDateTime toDateTime) {
+        LocalDateTime zero = LocalDateTime.of(toDateTime.getYear(), toDateTime.getMonth(), toDateTime.getDayOfMonth(), 0, 0, 0);
+        Duration duration = Duration.between(zero, toDateTime);
+        double minutes = duration.toMinutes();
+        return (int) Math.floor(minutes / tp);
+    }
+
+
+    /**
+     * Gets time from step index. tp is min the format hh:mm:00
+     *
+     * @param stepIndex the step index
+     * @param tp        the tp
+     * @return the time from step index
+     * @author chen.cheng
+     */
+    public static String getTimeFromStepIndex(Integer stepIndex, Integer tp) {
+        Integer stepIndex2Min = stepIndex * tp;
+        String hour = StringUtils.leftPad(String.valueOf(stepIndex2Min / 60), 2, "0");
+        String min = StringUtils.leftPad(String.valueOf(stepIndex2Min % 60), 2, "0");
+        return String.format("%s:%s:%s", hour, min, "00");
+    }
+
+    /**
+     * Gets time from step index with out second.
+     *
+     * @param stepIndex the step index
+     * @param tp        the tp
+     * @return the time from step index with out second
+     * @author chen.cheng
+     */
+    public static String getTimeFromStepIndexWithOutSecond(Integer stepIndex, Integer tp) {
+        Integer stepIndex2Min = stepIndex * tp;
+        String hour = StringUtils.leftPad(String.valueOf(stepIndex2Min / 60), 2, "0");
+        String min = StringUtils.leftPad(String.valueOf(stepIndex2Min % 60), 2, "0");
+        return String.format("%s:%s", hour, min);
+    }
+
+    /**
+     * Gets day of week.
+     *
+     * @param date           the date
+     * @param paramFormatter the param formatter
+     * @return the day of week
+     * @author chen.cheng
+     */
+    public static Integer getDayOfWeek(String date, String paramFormatter) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(paramFormatter);
+        LocalDate l_da1 = LocalDate.parse(date, formatter);
+        DayOfWeek dayOfWeek = l_da1.getDayOfWeek();
+        return dayOfWeek.getValue();
+    }
+
+    /**
+     * Gets ali day of week.
+     *
+     * @param date the date
+     * @return the ali day of week
+     * @author chen.cheng
+     */
+    public static Integer getAliDayOfWeek(String date) {
+        return getDayOfWeek(date, DateFormatter.yyyyMMdd) - 1;
+    }
+
+    /**
+     * Cal week of year integer.
+     *
+     * @return the integer
+     * @author chen.cheng
+     */
+    public static Integer calWeekOfYear() {
+        return calWeekOfYear(currentDateTime(DateFormatter.yyyy_MM_dd));
+    }
+
+    public static Integer calLastWeekOfYear() {
+        return calWeekOfYear(currentDateTime(DateFormatter.yyyy_MM_dd)) - 1;
+    }
+
+    /**
+     * Cal week of year integer.
+     *
+     * @param date the date yyyy-MM-dd
+     * @return the integer
+     * @author chen.cheng
+     */
+    public static Integer calWeekOfYear(String date) {
+        LocalDate localDate = LocalDate.parse(date);
+        // 第一个参数:一周的第一天,不能为空
+        // 第二个参数:第一周的最小天数,从1到7
+        WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 1);
+        return localDate.get(weekFields.weekOfYear());
+    }
+
+    /**
+     * Cal week of year integer.
+     *
+     * @param date           the date
+     * @param paramFormatter the param formatter
+     * @return the integer
+     * @author chen.cheng
+     */
+    public static Integer calWeekOfYear(String date, String paramFormatter) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(paramFormatter);
+        LocalDate localDate = LocalDate.parse(date, formatter);
+
+        return calWeekOfYear(localDate.toString());
+    }
+
+    /**
+     * Get week time section string [ ].
+     *
+     * @param weekIndex the week index
+     * @return the string [ ]
+     * @author chen.cheng
+     */
+    public static String[] getWeekTimeSection(int weekIndex) {
+        return getWeekTimeSection(weekIndex, DateFormatter.yyyyMMdd);
+    }
+
+    /**
+     * Get week time section string [ ]. 获取前几周内的日期范围
+     *
+     * @param weekIndex the week index
+     * @return the string [ ]
+     * @author chen.cheng
+     */
+    public static String[] getWeekTimeSection(int weekIndex, String dateFormat) {
+        // weekIndex为前几周的周数,如:获取当前时间前第二周的时间 weekIndex=2
+        // 获取本周第一天
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.WEEK_OF_MONTH, 0);
+        cal.set(Calendar.DAY_OF_WEEK, 2);
+        Date first = cal.getTime();
+        Date last = cal.getTime();
+        String firstString = new SimpleDateFormat("yyyy-MM-dd").format(first) + " 00:00:00";
+        String lastString = new SimpleDateFormat("yyyy-MM-dd").format(last) + " 23:59:59";
+        DateTimeFormatter formatPatten = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDateTime firstTime = LocalDateTime.parse(firstString, formatPatten);
+        LocalDateTime lastTime = LocalDateTime.parse(lastString, formatPatten);
+        DateTimeFormatter resultDateFormat = DateTimeFormatter.ofPattern(dateFormat);
+        // 开始时间
+        firstTime = firstTime.plusDays(-(weekIndex * 7L));
+        // 结束时间
+        lastTime = lastTime.plusDays(-1);
+        return new String[]{firstTime.format(resultDateFormat), lastTime.format(resultDateFormat)};
+    }
+
+    public static String getFirstDayOfRecentYear(String dateFormat) {
+        // 获取当前日期
+        LocalDate today = LocalDate.now();
+
+        // 计算近一年的年份
+        int recentYear = today.getYear() - 1;
+
+        LocalDate localDate = LocalDate.of(recentYear, today.getMonth(), 1);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
+        return localDate.format(formatter);
+    }
+
+    public static String getFirstDayOfThisYear(String dateFormat) {
+        // 获取当前日期
+        LocalDate today = LocalDate.now();
+
+        // 计算近一年的年份
+        int recentYear = today.getYear();
+
+        // 使用 TemporalAdjusters 来找到近一年的第一天
+        LocalDate localDate = LocalDate.of(recentYear, 1, 1).with(TemporalAdjusters.firstDayOfYear());
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
+        return localDate.format(formatter);
+    }
+
+    public static String getFirstDayOfRecentYear() {
+        return getFirstDayOfRecentYear(DateFormatter.yyyy_MM_dd);
+    }
+
+    public static String getFirstDayOfRecentMonth(String dateFormat) {
+        // 获取当前日期
+        LocalDate today = LocalDate.now();
+        // 使用 TemporalAdjusters 来找到近一个月的第一天
+        LocalDate with = today.minusMonths(1);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
+        return with.format(formatter);
+    }
+
+    public static String getFirstDayOfRecentMonth() {
+        return getFirstDayOfRecentMonth(DateFormatter.yyyy_MM_dd);
+    }
+
+    public static void main(String[] args) {
+        System.out.println(DateTimeUtil.getStepIndexFromMills(10, System.currentTimeMillis()));
+    }
+
+
+    /**
+     * The interface Date formatter.
+     *
+     * @author chen.cheng
+     */
+    public interface DateFormatter {
+        /**
+         * The constant yyyy_MM_dd_HHmmss.
+         *
+         * @author chen.cheng
+         */
+        String yyyy_MM_dd_HHmmss = "yyyy-MM-dd HH:mm:ss";
+
+        /**
+         * The constant yyyyMMddHHmmss.
+         *
+         * @author chen.cheng
+         */
+        String yyyyMMddHHmmss = "yyyyMMddHHmmss";
+
+        /**
+         * The constant yyyy_MM_dd.
+         *
+         * @author chen.cheng
+         */
+        String yyyy_MM_dd = "yyyy-MM-dd";
+
+        String DD = "dd";
+
+        /**
+         * The constant yyyyMMdd.
+         *
+         * @author chen.cheng
+         */
+        String yyyyMMdd = "yyyyMMdd";
+
+        String yyyy_MM = "yyyy-MM";
+
+        /**
+         * The constant yyyyMM.
+         *
+         * @author chen.cheng
+         */
+        String yyyyMM = "yyyyMM";
+
+        /**
+         * The constant yyyy.
+         *
+         * @author chen.cheng
+         */
+        String yyyy = "yyyy";
+
+        /**
+         * To string string.
+         *
+         * @return the string
+         * @author chen.cheng
+         */
+        @Override
+        String toString();
+    }
+
+}

+ 38 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/ExpressUtil.java

@@ -0,0 +1,38 @@
+package com.ruoyi.common.utils;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.map.MapUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.expression.Expression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class ExpressUtil {
+    public static <T> List<Map<String, Object>> hitExpress(T obj, List<Map<String, Object>> indexRangeList) {
+        if (CollectionUtils.isEmpty(indexRangeList)) {
+            return null;
+        }
+        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
+        List<Map<String, Object>> result = new ArrayList<>();
+        Map<String, Object> map = new HashMap<>();
+        BeanUtil.beanToMap(obj, map, false, false);
+        String express;
+        String indexName;
+        Expression expression;
+        Boolean eval;
+        for (Map<String, Object> stringObjectMap : indexRangeList) {
+            indexName = MapUtil.getStr(stringObjectMap, "indexName");
+            express = String.format("%s < ['indexUpperLimit'] && %s >= ['indexLowerLimit']", map.get(indexName), map.get(indexName));
+            expression = spelExpressionParser.parseExpression(express);
+            eval = expression.getValue(stringObjectMap, Boolean.class);
+            if (Boolean.FALSE.equals(eval)) {
+                result.add(stringObjectMap);
+            }
+        }
+        return result;
+    }
+}

+ 259 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/StreamUtils.java

@@ -0,0 +1,259 @@
+package com.ruoyi.common.utils;
+
+import org.apache.commons.collections4.MapUtils;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * stream 流工具类
+ */
+
+public class StreamUtils {
+
+    /**
+     * 将collection过滤
+     *
+     * @param collection 需要转化的集合
+     * @param function 过滤方法
+     * @return 过滤后的list
+     */
+    public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new ArrayList<>();
+        }
+        return collection.stream().filter(function).collect(Collectors.toList());
+    }
+
+    /**
+     * 将collection拼接
+     *
+     * @param collection 需要转化的集合
+     * @param function 拼接方法
+     * @return 拼接后的list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function) {
+        return join(collection, function, StringUtils.SPACE);
+    }
+
+    /**
+     * 将collection拼接
+     *
+     * @param collection 需要转化的集合
+     * @param function 拼接方法
+     * @param delimiter 拼接符
+     * @return 拼接后的list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return StringUtils.EMPTY;
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+    }
+
+    /**
+     * 将collection排序
+     *
+     * @param collection 需要转化的集合
+     * @param comparing 排序方法
+     * @return 排序后的list
+     */
+    public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new ArrayList<>();
+        }
+        return collection.stream().sorted(comparing).collect(Collectors.toList());
+    }
+
+    /**
+     * 将collection转化为类型不变的map<br>
+     * <B>{@code Collection<V>  ---->  Map<K,V>}</B>
+     *
+     * @param collection 需要转化的集合
+     * @param key V类型转化为K类型的lambda方法
+     * @param <V> collection中的泛型
+     * @param <K> map中的key类型
+     * @return 转化后的map
+     */
+    public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new HashMap<>();
+        }
+        return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+    }
+
+    /**
+     * 将Collection转化为map(value类型与collection的泛型不同)<br>
+     * <B>{@code Collection<E> -----> Map<K,V>  }</B>
+     *
+     * @param collection 需要转化的集合
+     * @param key E类型转化为K类型的lambda方法
+     * @param value E类型转化为V类型的lambda方法
+     * @param <E> collection中的泛型
+     * @param <K> map中的key类型
+     * @param <V> map中的value类型
+     * @return 转化后的map
+     */
+    public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new HashMap<>();
+        }
+        return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l));
+    }
+
+    /**
+     * 将collection按照规则(比如有相同的班级id)分类成map<br>
+     * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
+     *
+     * @param collection 需要分类的集合
+     * @param key 分类的规则
+     * @param <E> collection中的泛型
+     * @param <K> map中的key类型
+     * @return 分类后的map
+     */
+    public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new HashMap<>();
+        }
+        return collection.stream().collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+    }
+
+    /**
+     * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B>
+     *
+     * @param collection 需要分类的集合
+     * @param key1 第一个分类的规则
+     * @param key2 第二个分类的规则
+     * @param <E> 集合元素类型
+     * @param <K> 第一个map中的key类型
+     * @param <U> 第二个map中的key类型
+     * @return 分类后的map
+     */
+    public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1,
+        Function<E, U> key2) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new HashMap<>();
+        }
+        return collection.stream().collect(Collectors.groupingBy(key1, LinkedHashMap::new,
+            Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+    }
+
+    /**
+     * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B>
+     *
+     * @param collection 需要分类的集合
+     * @param key1 第一个分类的规则
+     * @param key2 第二个分类的规则
+     * @param <T> 第一个map中的key类型
+     * @param <U> 第二个map中的key类型
+     * @param <E> collection中的泛型
+     * @return 分类后的map
+     */
+    public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1,
+        Function<E, U> key2) {
+        if (CollectionUtils.isEmpty(collection) || key1 == null || key2 == null) {
+            return new HashMap<>();
+        }
+        return collection.stream().collect(
+            Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+    }
+
+    /**
+     * 将collection转化为List集合,但是两者的泛型不同<br>
+     * <B>{@code Collection<E>  ------>  List<T> } </B>
+     *
+     * @param collection 需要转化的集合
+     * @param function collection中的泛型转化为list泛型的lambda表达式
+     * @param <E> collection中的泛型
+     * @param <T> List中的泛型
+     * @return 转化后的list
+     */
+    public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
+        if (CollectionUtils.isEmpty(collection)) {
+            return new ArrayList<>();
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.toList());
+    }
+
+    /**
+     * 将collection转化为Set集合,但是两者的泛型不同<br>
+     * <B>{@code Collection<E>  ------>  Set<T> } </B>
+     *
+     * @param collection 需要转化的集合
+     * @param function collection中的泛型转化为set泛型的lambda表达式
+     * @param <E> collection中的泛型
+     * @param <T> Set中的泛型
+     * @return 转化后的Set
+     */
+    public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
+        if (CollectionUtils.isEmpty(collection) || function == null) {
+            return new HashSet<>();
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.toSet());
+    }
+
+    /**
+     * 合并两个相同key类型的map
+     *
+     * @param map1 第一个需要合并的 map
+     * @param map2 第二个需要合并的 map
+     * @param merge 合并的lambda,将key value1 value2合并成最终的类型,注意value可能为空的情况
+     * @param <K> map中的key类型
+     * @param <X> 第一个 map的value类型
+     * @param <Y> 第二个 map的value类型
+     * @param <V> 最终map的value类型
+     * @return 合并后的map
+     */
+    public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
+        if (MapUtils.isEmpty(map1) && MapUtils.isEmpty(map2)) {
+            return new HashMap<>();
+        }
+        else if (MapUtils.isEmpty(map1)) {
+            map1 = new HashMap<>();
+        }
+        else if (MapUtils.isEmpty(map2)) {
+            map2 = new HashMap<>();
+        }
+        Set<K> key = new HashSet<>();
+        key.addAll(map1.keySet());
+        key.addAll(map2.keySet());
+        Map<K, V> map = new HashMap<>();
+        for (K t : key) {
+            X x = map1.get(t);
+            Y y = map2.get(t);
+            V z = merge.apply(x, y);
+            if (z != null) {
+                map.put(t, z);
+            }
+        }
+        return map;
+    }
+    /**
+     * 将 Set 切分成多个子集
+     * @param originalSet 原始 Set
+     * @param chunkSize 每个子集的大小
+     * @return 切分后的子集列表
+     */
+    public static List<List<String>> splitSet(Set<String> originalSet, int chunkSize) {
+        List<String> list = new ArrayList<>(originalSet);
+        return IntStream.range(0, (int) Math.ceil((double) list.size() / chunkSize))
+                .mapToObj(i -> list.subList(i * chunkSize, Math.min((i + 1) * chunkSize, list.size())))
+                .collect(Collectors.toList());
+    }
+}

+ 3 - 3
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java

@@ -2,7 +2,7 @@ package com.ruoyi.common.utils.file;
 
 /**
  * 媒体类型工具类
- * 
+ *
  * @author ruoyi
  */
 public class MimeTypeUtils
@@ -16,7 +16,7 @@ public class MimeTypeUtils
     public static final String IMAGE_BMP = "image/bmp";
 
     public static final String IMAGE_GIF = "image/gif";
-    
+
     public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
 
     public static final String[] FLASH_EXTENSION = { "swf", "flv" };
@@ -28,7 +28,7 @@ public class MimeTypeUtils
 
     public static final String[] DEFAULT_ALLOWED_EXTENSION = {
             // 图片
-            "bmp", "gif", "jpg", "jpeg", "png",
+            "bmp", "gif", "jpg", "jpeg", "png", "svg",
             // word excel powerpoint
             "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
             // 压缩文件

+ 72 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/geo/CoordinateConverter.java

@@ -0,0 +1,72 @@
+package com.ruoyi.common.utils.geo;
+
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+public class CoordinateConverter {
+    // WGS84椭球体参数
+    private static final double A = 6378137.0; // 长半轴(米)
+    private static final double F = 1 / 298.257223563; // 扁率
+    private static final double B = A * (1 - F); // 短半轴
+    private static final double E = Math.sqrt(1 - (B * B) / (A * A)); // 第一偏心率
+
+    // 将经纬度转换为平面坐标(墨卡托投影)
+    public static double[] latLonToXY(double latitude, double longitude) {
+        double x = A * Math.toRadians(longitude);
+        double y = A * Math.log(Math.tan(Math.PI / 4 + Math.toRadians(latitude) / 2));
+        return new double[]{x, y};
+    }
+
+    // 将平面坐标转换为经纬度(墨卡托投影逆变换)
+    public static double[] xyToLatLon(double x, double y) {
+        double lon = Math.toDegrees(x / A);
+        double lat = Math.toDegrees(2 * Math.atan(Math.exp(y / A)) - Math.PI / 2);
+        return new double[]{lat, lon};
+    }
+
+    // 计算基准经度和比例因子
+    public static double[] calculateParameters(double[] pointA, double[] pointB) {
+        double lambda0 = (pointA[0] + pointB[0]) / (2 * A);
+        double scaleFactor = (pointA[1] - pointB[1]) / (A * Math.log(Math.tan(Math.PI / 4 + Math.toRadians(90) / 2) / Math.tan(Math.PI / 4 + Math.toRadians(-90) / 2)));
+        return new double[]{lambda0, scaleFactor};
+    }
+
+    public static void main(String[] args) {
+        // 定义已知的平面坐标和对应的经纬度
+        double[][] points = {{8, 14.63, 118.86839482667, 32.0131180416999}, {6, 0, 118.869042069359, 32.0131156239015}, {0, 15.3, 118.868399686907, 32.0133391503578}};
+
+        // 提取x, y和对应的经纬度
+        double[] x = new double[points.length];
+        double[] y = new double[points.length];
+        double[] lon = new double[points.length];
+        double[] lat = new double[points.length];
+
+        for (int i = 0; i < points.length; i++) {
+            x[i] = points[i][0];
+            y[i] = points[i][1];
+            lon[i] = points[i][2];
+            lat[i] = points[i][3];
+        }
+
+        // 构建线性方程组Ax=B
+        RealMatrix A = new Array2DRowRealMatrix(new double[][]{x, y,new double[]{1,1,1}});
+        DecompositionSolver solverLon = new QRDecomposition(A).getSolver();
+        DecompositionSolver solverLat = new QRDecomposition(A).getSolver();
+
+        // 解线性方程组得到系数
+        RealVector coeffsLon = solverLon.solve(new ArrayRealVector(lon));
+        RealVector coeffsLat = solverLat.solve(new ArrayRealVector(lat));
+
+        // 计算第四点的经纬度
+        double xNew = 4;
+        double yNew = 4;
+        double lonNew = coeffsLon.getEntry(0) * xNew + coeffsLon.getEntry(1) * yNew + coeffsLon.getEntry(2);
+        double latNew = coeffsLat.getEntry(0) * xNew + coeffsLat.getEntry(1) * yNew + coeffsLat.getEntry(2);
+
+        System.out.println("第四点的经纬度为: " + lonNew + ", " + latNew);
+    }
+}

+ 197 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/geo/GeoUtils.java

@@ -0,0 +1,197 @@
+package com.ruoyi.common.utils.geo;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKTReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class GeoUtils {
+    private static final Logger log = LoggerFactory.getLogger(GeoUtils.class);
+    /**
+     * geometryFactory
+     */
+    private static final GeometryFactory geometryFactory = new GeometryFactory();
+
+    /**
+     * Gets distance.
+     *
+     * @param lat1 the lat 1
+     * @param lon1 the lon 1
+     * @param lat2 the lat 2
+     * @param lon2 the lon 2
+     * @return the distance
+     * @author chen.cheng
+     */
+    public static double getDistance(double lat1, double lon1, double lat2, double lon2) {
+        Point p1 = geometryFactory.createPoint(new Coordinate(lon1, lat1));
+        Point p2 = geometryFactory.createPoint(new Coordinate(lon2, lat2));
+        return p1.distance(p2);
+    }
+
+    /**
+     * Gets poly center.
+     * POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))
+     *
+     * @param wktPolygon the wkt polygon
+     * @return the poly center
+     * @author chen.cheng
+     */
+    public static Point getPolyCenter(String wktPolygon) {
+        // 创建WKTReader对象
+        WKTReader reader = new WKTReader(geometryFactory);
+        // 从WKT字符串中读取几何对象
+        Geometry geometry = null;
+        try {
+            geometry = reader.read(wktPolygon);
+        } catch (ParseException e) {
+            log.info("Invalid WKT string: " + e.getMessage());
+        }
+        // 获取中心点
+        return geometry.getCentroid();
+    }
+
+    /**
+     * getPolygon
+     * POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))
+     *
+     * @param wktPolygon the wkt polygon
+     * @return the poly center
+     * @author chen.cheng
+     */
+    public static Polygon getPolygon(String wktPolygon) {
+        // 创建WKTReader对象
+        WKTReader reader = new WKTReader(geometryFactory);
+        // 从WKT字符串中读取几何对象
+        Polygon geometry = null;
+        try {
+            geometry = (Polygon) reader.read(wktPolygon);
+        } catch (ParseException e) {
+            log.info("Invalid WKT string: " + e.getMessage());
+        }
+        // 获取中心点
+        return geometry;
+    }
+
+    /**
+     * 判断指定的GPS点是否在电子围栏内
+     *
+     * @param fencePointsList 包含电子围栏的经纬度数据的列表,格式为 "经度,纬度"
+     * @param pointStr        指定的GPS点,格式为 "经度,纬度"
+     * @return 如果在电子围栏内则返回true,否则返回false
+     */
+    public static boolean isPointInGeoFence(List<String> fencePointsList, String pointStr) {
+        // 将电子围栏的经纬度数据转换为坐标数组
+        Coordinate[] fencePoints = parseCoordinates(fencePointsList);
+
+        // 将指定的GPS点转换为坐标
+        Coordinate targetPoint = parseCoordinate(pointStr);
+
+        // 创建电子围栏多边形
+        Polygon geoFencePolygon = createPolygon(fencePoints);
+
+        // 创建指定的GPS点
+        Point point = geometryFactory.createPoint(targetPoint);
+
+        // 检查指定的GPS点是否在电子围栏内
+        return geoFencePolygon.contains(point);
+    }
+
+    /**
+     * 判断指定的GPS点是否在电子围栏内
+     *
+     * @param geoFencePolygon geo fence polygon
+     * @param pointStr        指定的GPS点,格式为 "经度,纬度"
+     * @return 如果在电子围栏内则返回true ,否则返回false
+     * @since 2.0.0
+     */
+    public static boolean isPointInGeoFence(Polygon geoFencePolygon, String pointStr) {
+        // 将指定的GPS点转换为坐标
+        Coordinate testPoint = parseCoordinate(pointStr);
+
+        // 创建指定的GPS点
+        Point point = geometryFactory.createPoint(testPoint);
+
+        // 检查指定的GPS点是否在电子围栏内
+        return geoFencePolygon.contains(point);
+    }
+
+    public static boolean isPointInGeoFence(Polygon geoFencePolygon, String lng, String lat) {
+        // 将指定的GPS点转换为坐标
+        Coordinate testPoint = parseCoordinate(lng, lat);
+
+        // 创建指定的GPS点
+        Point point = geometryFactory.createPoint(testPoint);
+
+        // 检查指定的GPS点是否在电子围栏内
+        return geoFencePolygon.contains(point);
+    }
+
+    /**
+     * 判断指定的GPS点是否在电子围栏内
+     *
+     * @param fencePointsList 包含电子围栏的经纬度数据的列表,格式为 "经度,纬度"
+     * @return 如果在电子围栏内则返回true,否则返回false
+     */
+    public static Polygon getPointInGeoFence(List<String> fencePointsList) {
+        // 将电子围栏的经纬度数据转换为坐标数组
+        Coordinate[] fencePoints = parseCoordinates(fencePointsList);
+        // 创建电子围栏
+        return createPolygon(fencePoints);
+    }
+
+
+    /**
+     * 根据GPS点集合创建多边形
+     *
+     * @param coordinates GPS点集合
+     * @return 多边形对象
+     */
+    private static Polygon createPolygon(Coordinate[] coordinates) {
+        // Ensure the polygon is closed by adding the first coordinate at the end if necessary
+        if (!coordinates[0].equals(coordinates[coordinates.length - 1])) {
+            Coordinate[] closedCoordinates = new Coordinate[coordinates.length + 1];
+            System.arraycopy(coordinates, 0, closedCoordinates, 0, coordinates.length);
+            closedCoordinates[closedCoordinates.length - 1] = coordinates[0];
+            coordinates = closedCoordinates;
+        }
+        return geometryFactory.createPolygon(coordinates);
+    }
+
+    /**
+     * 将包含经纬度数据的列表转换为坐标数组
+     *
+     * @param pointsList 包含经纬度数据的列表
+     * @return 坐标数组
+     */
+    private static Coordinate[] parseCoordinates(List<String> pointsList) {
+        Coordinate[] coordinates = new Coordinate[pointsList.size()];
+        for (int i = 0; i < pointsList.size(); i++) {
+            coordinates[i] = parseCoordinate(pointsList.get(i));
+        }
+        return coordinates;
+    }
+
+    /**
+     * 将经纬度数据字符串转换为坐标
+     *
+     * @param pointStr 经纬度数据字符串,格式为 "经度,纬度"
+     * @return 坐标
+     */
+    private static Coordinate parseCoordinate(String pointStr) {
+        String[] parts = pointStr.split(",");
+        double lon = Double.parseDouble(parts[0]);
+        double lat = Double.parseDouble(parts[1]);
+        return new Coordinate(lon, lat);
+    }
+
+    private static Coordinate parseCoordinate(String lng, String lat) {
+        return new Coordinate(Double.parseDouble(lng), Double.parseDouble(lat));
+    }
+}

+ 18 - 35
ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java

@@ -14,26 +14,25 @@ import java.util.Map;
 
 /**
  * spring工具类 方便在非spring管理环境中获取bean
- * 
+ *
  * @author ruoyi
  */
 @Component
-public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 
-{
-    /** Spring应用上下文环境 */
+public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
+    /**
+     * Spring应用上下文环境
+     */
     private static ConfigurableListableBeanFactory beanFactory;
 
     private static ApplicationContext applicationContext;
 
     @Override
-    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
-    {
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
         SpringUtils.beanFactory = beanFactory;
     }
 
     @Override
-    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
-    {
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         SpringUtils.applicationContext = applicationContext;
     }
 
@@ -43,11 +42,9 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      * @param name
      * @return Object 一个以所给名字注册的bean的实例
      * @throws org.springframework.beans.BeansException
-     *
      */
     @SuppressWarnings("unchecked")
-    public static <T> T getBean(String name) throws BeansException
-    {
+    public static <T> T getBean(String name) throws BeansException {
         return (T) beanFactory.getBean(name);
     }
 
@@ -57,10 +54,8 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      * @param clz
      * @return
      * @throws org.springframework.beans.BeansException
-     *
      */
-    public static <T> T getBean(Class<T> clz) throws BeansException
-    {
+    public static <T> T getBean(Class<T> clz) throws BeansException {
         T result = (T) beanFactory.getBean(clz);
         return result;
     }
@@ -71,8 +66,7 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      * @param name
      * @return boolean
      */
-    public static boolean containsBean(String name)
-    {
+    public static boolean containsBean(String name) {
         return beanFactory.containsBean(name);
     }
 
@@ -82,10 +76,8 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      * @param name
      * @return boolean
      * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
-     *
      */
-    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
-    {
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
         return beanFactory.isSingleton(name);
     }
 
@@ -93,10 +85,8 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      * @param name
      * @return Class 注册对象的类型
      * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
-     *
      */
-    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
-    {
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
         return beanFactory.getType(name);
     }
 
@@ -106,22 +96,19 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      * @param name
      * @return
      * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
-     *
      */
-    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
-    {
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
         return beanFactory.getAliases(name);
     }
 
     /**
      * 获取aop代理对象
-     * 
+     *
      * @param invoker
      * @return
      */
     @SuppressWarnings("unchecked")
-    public static <T> T getAopProxy(T invoker)
-    {
+    public static <T> T getAopProxy(T invoker) {
         return (T) AopContext.currentProxy();
     }
 
@@ -130,8 +117,7 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      *
      * @return 当前的环境配置
      */
-    public static String[] getActiveProfiles()
-    {
+    public static String[] getActiveProfiles() {
         return applicationContext.getEnvironment().getActiveProfiles();
     }
 
@@ -140,8 +126,7 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      *
      * @return 当前的环境配置
      */
-    public static String getActiveProfile()
-    {
+    public static String getActiveProfile() {
         final String[] activeProfiles = getActiveProfiles();
         return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
     }
@@ -151,10 +136,8 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
      *
      * @param key 配置文件的key
      * @return 当前的配置文件的值
-     *
      */
-    public static String getRequiredProperty(String key)
-    {
+    public static String getRequiredProperty(String key) {
         return applicationContext.getEnvironment().getRequiredProperty(key);
     }
 

+ 1 - 1
ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java

@@ -13,7 +13,7 @@ public class SqlUtil
     /**
      * 定义常用的 sql关键字
      */
-    public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
+    public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()";
 
     /**
      * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)