Pārlūkot izejas kodu

新增数据融合引擎和坐标点位抽稀功能

- 添加EvtFusionEngine类作为数据融合引擎的基础类
- 实现PointFusionEngine类,提供坐标点位抽稀功能
- 在application.yml中添加evt-fusion相关配置
- 在mqtt配置中启用客户端并设置相关参数
- 优化GeoUtils类,增加计算两点距离的方法
chen.cheng 9 mēneši atpakaļ
vecāks
revīzija
4992eee684

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

@@ -0,0 +1,113 @@
+package com.ruoyi.bd.service.engine;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.utils.DateTimeUtil;
+import com.ruoyi.common.utils.StringUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public abstract class EvtFusionEngine {
+    private final static Logger logger = LoggerFactory.getLogger(EvtFusionEngine.class);
+    private final ConcurrentHashMap<String, LocationInfo> evtBucket = new ConcurrentHashMap<>();
+
+    private final List<LinkedBlockingQueue<JSONObject>> messageQueueList = new ArrayList<>(5);
+
+    private String engineName;
+
+    @Value("${evt-fusion.thread-pool-size:5}")
+    private int threadPoolSize;
+
+    @Resource(name = "threadPoolTaskExecutor")
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    @PostConstruct
+    public void init() {
+        for (int i = 0; i < threadPoolSize; i++) {
+            messageQueueList.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);
+        LinkedBlockingQueue<JSONObject> messageQueue = messageQueueList.get(bucketIndex);
+        messageQueue.offer(msg);
+    }
+
+    public abstract Boolean check(LocationInfo locationInfo, LocationInfo msg);
+
+    public abstract String getKey(LocationInfo msg);
+
+    public abstract void getBizId(LocationInfo msg);
+
+    public abstract void processOlderData(LocationInfo msg);
+
+    public void start() {
+        for (int i = 0; i < threadPoolSize; i++) {
+            int finalI = i;
+            threadPoolTaskExecutor.execute(() -> {
+                while (true) {
+                    try {
+                        JSONObject msg = messageQueueList.get(finalI).take();
+                        LocationInfo locationInfoNew = new LocationInfo(msg.getDouble("latitude"), msg.getDouble("longitude"), msg.getLong("srcTimestamp"));
+                        String key = getKey(locationInfoNew);
+                        LocationInfo locationInfo = evtBucket.get(key);
+                        if (ObjectUtils.isEmpty(locationInfo)) {
+                            locationInfoNew.setEvtTimestamp(DateTimeUtil.timestampMillis());
+                            getBizId(locationInfoNew);
+                            evtBucket.put(key, locationInfoNew);
+                            continue;
+                        }
+                        // 判断消息是否需要融合
+                        if (!check(locationInfo, locationInfoNew)) {
+                            // 老数据释放
+                            processOlderData(locationInfo);
+                        }
+                        getBizId(locationInfoNew);
+                        locationInfo.setSrcTimestamp(locationInfoNew.getSrcTimestamp());
+                        evtBucket.put(key, locationInfoNew);
+                    } 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
+    }
+
+}

+ 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;
+    }
+}

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

@@ -0,0 +1,29 @@
+package com.ruoyi.bd.service.engine.impl;
+
+import com.ruoyi.bd.service.engine.EvtFusionEngine;
+import com.ruoyi.bd.service.engine.LocationInfo;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FenceBreakInEngine extends EvtFusionEngine {
+    // 冲缓存中读取围栏信息
+    @Override
+    public Boolean check(LocationInfo locationInfo, LocationInfo msg) {
+        return null;
+    }
+
+    @Override
+    public String getKey(LocationInfo msg) {
+        return null;
+    }
+
+    @Override
+    public void getBizId(LocationInfo msg) {
+
+    }
+
+    @Override
+    public void processOlderData(LocationInfo msg) {
+
+    }
+}

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

@@ -0,0 +1,61 @@
+package com.ruoyi.bd.service.engine.impl;
+
+import com.ruoyi.bd.service.engine.EvtFusionEngine;
+import com.ruoyi.bd.service.engine.LocationInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * The type Point fusion engine.
+ *
+ * @author chen.cheng
+ */
+@Service
+public class PointFusionEngine extends EvtFusionEngine {
+
+    /**
+     * 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;
+
+    @PostConstruct
+    public void init() {
+        this.setEngineName("坐标点位抽稀");
+    }
+
+    @Override
+    public Boolean check(LocationInfo older, LocationInfo newer) {
+        long gap = newer.getSrcTimestamp() - older.getSrcTimestamp();
+        // 将gap 转换为秒
+        gap = gap / 1000;
+        return gap >= MAX_POINT_TIME_GAP;
+    }
+
+    @Override
+    public String getKey(LocationInfo msg) {
+        return msg.getMsg().getString("deviceId");
+    }
+
+    @Override
+    public void getBizId(LocationInfo msg) {
+
+    }
+
+    @Override
+    public void processOlderData(LocationInfo msg) {
+
+    }
+}

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

@@ -81,3 +81,21 @@ spring:
         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

+ 7 - 2
bd-location/src/main/resources/application.yml

@@ -16,7 +16,7 @@ ruoyi:
 # 开发环境配置
 server:
   # 服务器的HTTP端口,默认为8080
-  port: 2012
+  port: 18080
   servlet:
     # 应用的访问路径
     context-path: /tfc
@@ -52,7 +52,7 @@ spring:
     # 国际化资源文件路径
     basename: i18n/messages
   profiles:
-    active: hm
+    active: druid
   # 文件上传
   servlet:
     multipart:
@@ -118,3 +118,8 @@ xss:
   # 匹配链接
   urlPatterns: /system/*,/monitor/*,/tool/*
 
+evt-fusion:
+  # 默认为false
+  enabled: false
+  thread-pool-size: 5
+

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

@@ -1,95 +0,0 @@
-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));
-    }
-}

+ 0 - 95
ruoyi-admin/src/main/java/com/ruoyi/web/controller/bd/BdFenceVioEvtController.java

@@ -1,95 +0,0 @@
-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);
-    }
-
-    /**
-     * 导出围栏闯禁事件列表
-     */
-    @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));
-    }
-}

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

@@ -0,0 +1,646 @@
+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);
+    }
+
+    /**
+     * 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) {
+
+    }
+
+    /**
+     * 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();
+    }
+
+}

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

@@ -20,6 +20,22 @@ public class GeoUtils {
     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))
      *