From 01085ecfd21c1b916c12a19512e33f1937521e50 Mon Sep 17 00:00:00 2001 From: syruan <321359594@qq.com> Date: Tue, 27 May 2025 09:11:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EIoT=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=B7=A5=E7=89=8C=E6=8E=A7=E5=88=B6=E5=99=A8=E5=8F=8A=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/config/RestTemplateConfig.java | 42 ++ .../iot/controller/IotBadgeController.java | 645 ++++++++++++++++++ .../iot/controller/IotDataController.java | 263 +++++++ .../com/bonus/material/iot/dto/GpsData.java | 53 ++ .../com/bonus/material/iot/dto/PhaseData.java | 30 + 5 files changed, 1033 insertions(+) create mode 100644 bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/config/RestTemplateConfig.java create mode 100644 bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotBadgeController.java create mode 100644 bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotDataController.java create mode 100644 bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/GpsData.java create mode 100644 bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/PhaseData.java diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/config/RestTemplateConfig.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/config/RestTemplateConfig.java new file mode 100644 index 0000000..f850435 --- /dev/null +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/config/RestTemplateConfig.java @@ -0,0 +1,42 @@ +package com.bonus.material.iot.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * RestTemplate配置类 + */ +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + + // 配置消息转换器 + List> converters = new ArrayList<>(); + + // String转换器(支持各种内容类型) + StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8); + stringConverter.setWriteAcceptCharset(false); + converters.add(stringConverter); + + // JSON转换器 + MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); + jsonConverter.setObjectMapper(new ObjectMapper()); + converters.add(jsonConverter); + + restTemplate.setMessageConverters(converters); + + return restTemplate; + } +} \ No newline at end of file diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotBadgeController.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotBadgeController.java new file mode 100644 index 0000000..7ed70bb --- /dev/null +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotBadgeController.java @@ -0,0 +1,645 @@ +package com.bonus.material.iot.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * @author : 阮世耀 + * @version : 1.0 + * @PackagePath: com.bonus.material.iot.controller + * @CreateTime: 2025-05-26 17:23 + * @Description: IoT设备徽章控制器,用于获取设备信息 + */ +@RestController +@RequestMapping("/iot/badge") +public class IotBadgeController { + + @Autowired + private RestTemplate restTemplate; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + // 登录账号配置常量 + private static final String DEFAULT_LOGIN_NAME = "gzzbgsgk"; + private static final String DEFAULT_LOGIN_PASSWORD = "123456"; + + // 使用ConcurrentMap存储令牌和缓存数据 + private final ConcurrentHashMap tokenCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap deviceCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap cacheTimestamps = new ConcurrentHashMap<>(); + + // 缓存过期时间常量 + private static final long TOKEN_EXPIRE_TIME = 20 * 60 * 1000; // 令牌缓存时间(20分钟) + private static final long DEVICE_CACHE_TIME = 5 * 60 * 1000; // 设备缓存时间(5分钟) + + // 定时任务执行器 + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + // 固定的设备ID列表(后期可以修改) + private static final String[] DEVICE_IDS = { + "d7e84fd73e264431ba46303014431ade", "554778b69f394b899f1f2baff023c5be", + "061004dcc750463c974b6fe8be2fc508", "94cd6567b2924174a8526064a2a3d659", + "51679b90409940898e68691eb37f9370", "9ed65c5f6fad4a0ab058cab2b959080b", + "548abdd848f641a9a8afd8f6eb65856c", "5d66b87f5eec4661b6b6aa00e43699f8" + }; + + // 初始化定时清理任务 +// { +// scheduler.scheduleAtFixedRate(this::cleanExpiredCache, 1, 1, TimeUnit.MINUTES); +// } + + /** + * 清理过期缓存 + */ + private void cleanExpiredCache() { + long currentTime = System.currentTimeMillis(); + + // 清理过期的令牌 + Long tokenTime = cacheTimestamps.get("token"); + if (tokenTime != null && currentTime - tokenTime > TOKEN_EXPIRE_TIME) { + tokenCache.remove("currentToken"); + tokenCache.remove("currentUserId"); + cacheTimestamps.remove("token"); + } + + // 清理过期的设备缓存 + deviceCache.entrySet().removeIf(entry -> { + String key = entry.getKey(); + Long timestamp = cacheTimestamps.get("device_" + key); + return timestamp != null && currentTime - timestamp > DEVICE_CACHE_TIME; + }); + + // 清理过期的时间戳记录 + cacheTimestamps.entrySet().removeIf(entry -> { + String key = entry.getKey(); + Long timestamp = entry.getValue(); + if (key.equals("token")) { + return currentTime - timestamp > TOKEN_EXPIRE_TIME; + } else if (key.startsWith("device_")) { + return currentTime - timestamp > DEVICE_CACHE_TIME; + } + return false; + }); + } + + /** + * 获取当前令牌 + */ + private String getCurrentToken() { + return (String) tokenCache.get("currentToken"); + } + + /** + * 获取当前用户ID + */ + private String getCurrentUserId() { + return (String) tokenCache.get("currentUserId"); + } + + /** + * 保存令牌信息 + */ + private void saveTokenInfo(String token, String userId) { + tokenCache.put("currentToken", token); + tokenCache.put("currentUserId", userId); + cacheTimestamps.put("token", System.currentTimeMillis()); + } + + /** + * 清除令牌信息 + */ + private void clearTokenInfo() { + tokenCache.remove("currentToken"); + tokenCache.remove("currentUserId"); + cacheTimestamps.remove("token"); + } + + /** + * 第1步:用户登录接口 + * @param loginName 登录账号(可选,默认使用配置的账号) + * @param loginPassword 登录密码(可选,默认使用配置的密码) + * @param loginType 登录类型,默认ENTERPRISE + * @param language 语言类型,默认cn + * @param apply 应用类型,默认APP + * @param isMd5 MD5验证,默认0 + * @return 登录结果 + */ + @GetMapping("/login") + public ResponseEntity> loginSystem( + @RequestParam(required = false) String loginName, + @RequestParam(required = false) String loginPassword, + @RequestParam(defaultValue = "ENTERPRISE") String loginType, + @RequestParam(defaultValue = "cn") String language, + @RequestParam(defaultValue = "APP") String apply, + @RequestParam(defaultValue = "0") String isMd5) { + + // 使用传入的参数或默认常量 + String actualLoginName = loginName != null ? loginName : DEFAULT_LOGIN_NAME; + String actualLoginPassword = loginPassword != null ? loginPassword : DEFAULT_LOGIN_PASSWORD; + + try { + // 构建请求URL + String url = UriComponentsBuilder.fromHttpUrl("http://jltapp.168bds.com/GetDateServices.asmx/loginSystem") + .queryParam("LoginName", actualLoginName) + .queryParam("LoginPassword", actualLoginPassword) + .queryParam("LoginType", loginType) + .queryParam("language", language) + .queryParam("apply", apply) + .queryParam("ISMD5", isMd5) + .toUriString(); + + // 发送GET请求,使用String类型接收 + ResponseEntity response = restTemplate.getForEntity(url, String.class); + String responseBody = response.getBody(); + + if (responseBody != null) { + // 手动解析JSON + Map jsonResponse = objectMapper.readValue(responseBody, + new TypeReference>() {}); + + // 检查登录是否成功 + if ("true".equals(jsonResponse.get("success"))) { + // 保存令牌和用户ID到内存缓存 + String token = (String) jsonResponse.get("mds"); + String userId = (String) jsonResponse.get("id"); + saveTokenInfo(token, userId); + + return ResponseEntity.ok(jsonResponse); + } else { + return ResponseEntity.badRequest().body(jsonResponse); + } + } else { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "响应为空", + "errorCode", 400 + )); + } + + } catch (Exception e) { + Map errorMap = new HashMap<>(); + errorMap.put("success", "false"); + errorMap.put("msg", "登录失败:" + e.getMessage()); + errorMap.put("errorCode", 500); + return ResponseEntity.internalServerError().body(errorMap); + } + } + + /** + * 获取当前令牌信息 + * @return 当前令牌 + */ + @GetMapping("/current-token") + public ResponseEntity> getTokenInfo() { + String currentToken = getCurrentToken(); + String currentUserId = getCurrentUserId(); + + if (currentToken != null && currentUserId != null) { + return ResponseEntity.ok(createMapWithObjects( + "token", currentToken, + "userId", currentUserId, + "msg", "令牌获取成功" + )); + } else { + return ResponseEntity.badRequest().body(createMapWithObjects( + "msg", "请先登录获取令牌", + "errorCode", 401 + )); + } + } + + /** + * 第2步:查询设备信息接口(带缓存) + * @param deviceId 设备ID(可选,如果不提供则查询所有预设设备) + * @param useCache 是否使用缓存,默认true + * @return 设备信息 + */ + @GetMapping("/devices") + public ResponseEntity> getDeviceInfo( + @RequestParam(required = false) String deviceId, + @RequestParam(defaultValue = "true") boolean useCache) { + + String currentToken = getCurrentToken(); + String currentUserId = getCurrentUserId(); + + // 检查是否已登录 + if (currentToken == null || currentUserId == null) { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "请先登录获取令牌", + "errorCode", 401 + )); + } + + try { + java.util.List> allDevices = new java.util.ArrayList<>(); + + // 如果指定了设备ID,只查询该设备;否则查询所有预设设备 + String[] deviceIdsToQuery = deviceId != null ? new String[]{deviceId} : DEVICE_IDS; + + for (String id : deviceIdsToQuery) { + // 检查缓存 + if (useCache) { + Object cachedDevice = deviceCache.get(id); + Long cacheTime = cacheTimestamps.get("device_" + id); + + if (cachedDevice != null && cacheTime != null && System.currentTimeMillis() - cacheTime < DEVICE_CACHE_TIME) { + if (cachedDevice instanceof java.util.List) { + allDevices.addAll((java.util.List>) cachedDevice); + } + continue; + } + } + + // 构建请求URL + String url = UriComponentsBuilder.fromHttpUrl("http://jltapp.168bds.com/GetDateServices.asmx/GetDate") + .queryParam("method", "loadUser") + .queryParam("user_id", id) + .queryParam("mds", currentToken) + .toUriString(); + + try { + // 发送GET请求,使用String类型接收 + ResponseEntity response = restTemplate.getForEntity(url, String.class); + String responseBody = response.getBody(); + + if (responseBody != null) { + // 手动解析JSON + Map jsonResponse = objectMapper.readValue(responseBody, + new TypeReference>() {}); + + if ("true".equals(jsonResponse.get("success"))) { + // 获取设备数据 + Object data = jsonResponse.get("data"); + if (data instanceof java.util.List) { + java.util.List> deviceList = + (java.util.List>) data; + allDevices.addAll(deviceList); + + // 缓存结果 + if (useCache) { + deviceCache.put(id, deviceList); + cacheTimestamps.put("device_" + id, System.currentTimeMillis()); + } + } + } else { + // 如果某个设备查询失败,记录错误但继续查询其他设备 + System.err.println("设备 " + id + " 查询失败: " + jsonResponse); + } + } + } catch (Exception e) { + System.err.println("设备 " + id + " 查询异常: " + e.getMessage()); + // 继续查询其他设备 + } + } + + // 构建结果 + Map result = createMapWithObjects( + "success", "true", + "errorCode", "200", + "msg", "设备信息查询成功", + "data", allDevices, + "total", allDevices.size(), + "fromCache", useCache + ); + + return ResponseEntity.ok(result); + + } catch (Exception e) { + return ResponseEntity.internalServerError() + .body(createMapWithObjects( + "success", "false", + "msg", "设备信息查询失败:" + e.getMessage(), + "errorCode", 500 + )); + } + } + + /** + * 查询单个设备详细信息 + * @param deviceId 设备ID + * @return 设备详细信息 + */ + @GetMapping("/device/{deviceId}") + public ResponseEntity> getSingleDeviceInfo(@PathVariable String deviceId) { + String currentToken = getCurrentToken(); + String currentUserId = getCurrentUserId(); + + // 检查是否已登录 + if (currentToken == null || currentUserId == null) { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "请先登录获取令牌", + "errorCode", 401 + )); + } + + try { + // 构建请求URL + String url = UriComponentsBuilder.fromHttpUrl("http://jltapp.168bds.com/GetDateServices.asmx/GetDate") + .queryParam("method", "loadUser") + .queryParam("user_id", deviceId) + .queryParam("mds", currentToken) + .toUriString(); + + // 发送GET请求,使用String类型接收 + ResponseEntity response = restTemplate.getForEntity(url, String.class); + String responseBody = response.getBody(); + + if (responseBody != null) { + // 手动解析JSON + Map jsonResponse = objectMapper.readValue(responseBody, + new TypeReference>() {}); + + if ("true".equals(jsonResponse.get("success"))) { + return ResponseEntity.ok(jsonResponse); + } else { + return ResponseEntity.badRequest().body(jsonResponse); + } + } else { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "设备查询失败,响应为空", + "errorCode", 400 + )); + } + + } catch (Exception e) { + return ResponseEntity.internalServerError() + .body(createMapWithObjects( + "success", "false", + "msg", "设备信息查询失败:" + e.getMessage(), + "errorCode", 500 + )); + } + } + + /** + * 获取设备ID列表配置 + * @return 当前配置的设备ID列表 + */ + @GetMapping("/device-ids") + public ResponseEntity> getDeviceIds() { + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "设备ID列表获取成功", + "deviceIds", java.util.Arrays.asList(DEVICE_IDS), + "total", DEVICE_IDS.length + )); + } + + /** + * 清除设备缓存 + * @param deviceId 设备ID(可选,不提供则清除所有缓存) + * @return 清除结果 + */ + @GetMapping("/clear-cache") + public ResponseEntity> clearDeviceCache(@RequestParam(required = false) String deviceId) { + + if (deviceId != null) { + // 清除指定设备缓存 + deviceCache.remove(deviceId); + cacheTimestamps.remove("device_" + deviceId); + } else { + // 清除所有设备缓存 + deviceCache.clear(); + cacheTimestamps.entrySet().removeIf(entry -> entry.getKey().startsWith("device_")); + } + + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "缓存清除成功", + "errorCode", 200 + )); + } + + /** + * 第3步:令牌刷新接口 + * @return 刷新结果 + */ + @GetMapping("/refresh-token") + public ResponseEntity> refreshToken() { + String currentToken = getCurrentToken(); + + // 检查是否已登录 + if (currentToken == null) { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "请先登录获取令牌", + "errorCode", 401 + )); + } + + try { + // 构建请求URL + String url = UriComponentsBuilder.fromHttpUrl("http://jltapp.168bds.com/Command.aspx") + .queryParam("method", "RefreshMds") + .queryParam("mds", currentToken) + .toUriString(); + + // 发送GET请求刷新令牌 + ResponseEntity response = restTemplate.getForEntity(url, String.class); + + // 令牌刷新接口无数据返回,只要请求成功即表示刷新成功 + if (response.getStatusCode().is2xxSuccessful()) { + // 更新令牌时间戳 + cacheTimestamps.put("token", System.currentTimeMillis()); + + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "令牌刷新成功", + "errorCode", 200, + "token", currentToken + )); + } else { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "令牌刷新失败", + "errorCode", response.getStatusCode().value() + )); + } + + } catch (Exception e) { + return ResponseEntity.internalServerError() + .body(createMapWithObjects( + "success", "false", + "msg", "令牌刷新失败:" + e.getMessage(), + "errorCode", 500 + )); + } + } + + /** + * 登出接口 - 清除令牌和缓存 + * @return 登出结果 + */ + @GetMapping("/logout") + public ResponseEntity> logout() { + clearTokenInfo(); + deviceCache.clear(); + cacheTimestamps.entrySet().removeIf(entry -> entry.getKey().startsWith("device_")); + + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "登出成功", + "errorCode", 200 + )); + } + + /** + * 获取缓存统计信息 + * @return 缓存统计 + */ + @GetMapping("/cache-stats") + public ResponseEntity> getCacheStats() { + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "缓存统计获取成功", + "tokenCacheSize", tokenCache.size(), + "deviceCacheSize", deviceCache.size(), + "timestampCacheSize", cacheTimestamps.size(), + "hasValidToken", getCurrentToken() != null + )); + } + + /** + * 定时刷新令牌(建议每分钟调用一次) + * 可以配合定时任务使用 + * @return 刷新结果 + */ + @GetMapping("/auto-refresh") + public ResponseEntity> autoRefreshToken() { + String currentToken = getCurrentToken(); + if (currentToken == null) { + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "无需刷新,当前未登录", + "refreshed", false + )); + } + + return refreshToken(); + } + + /** + * 获取所有设备定位信息 + * @param mapType 地图类型,默认BAIDU + * @return 设备定位信息列表 + */ + @GetMapping("/locations") + public ResponseEntity> getDeviceLocations(@RequestParam(defaultValue = "BAIDU",required = false) String mapType) { + + String currentToken = getCurrentToken(); + String currentUserId = getCurrentUserId(); + + // 检查是否已登录 + if (currentToken == null || currentUserId == null) { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "请先登录获取令牌", + "errorCode", 401 + )); + } + + try { + // 构建请求URL + String url = UriComponentsBuilder.fromHttpUrl("http://jltapp.168bds.com/GetDateServices.asmx/GetDate") + .queryParam("method", "getDeviceListByCustomId") + .queryParam("mds", currentToken) + .queryParam("id", currentUserId) + .queryParam("mapType", mapType) + .toUriString(); + + // 发送GET请求 + ResponseEntity response = restTemplate.getForEntity(url, String.class); + String responseBody = response.getBody(); + + if (responseBody != null) { + // 手动解析JSON + Map jsonResponse = objectMapper.readValue(responseBody, + new TypeReference>() {}); + + if ("true".equals(jsonResponse.get("success"))) { + // 处理返回的数据 + List> data = (List>) jsonResponse.get("data"); + if (data != null && !data.isEmpty()) { + Map deviceData = data.get(0); + Map keyMap = (Map) deviceData.get("key"); + List> records = (List>) deviceData.get("records"); + + // 转换数据格式 + List> formattedDevices = new ArrayList<>(); + for (List record : records) { + Map device = new HashMap<>(); + for (Map.Entry entry : keyMap.entrySet()) { + device.put(entry.getKey(), record.get(entry.getValue())); + } + formattedDevices.add(device); + } + + // 构建返回结果 + return ResponseEntity.ok(createMapWithObjects( + "success", "true", + "msg", "设备定位信息获取成功", + "errorCode", 200, + "total", formattedDevices.size(), + "devices", formattedDevices + )); + } + } + return ResponseEntity.ok(jsonResponse); + } else { + return ResponseEntity.badRequest().body(createMapWithObjects( + "success", "false", + "msg", "响应为空", + "errorCode", 400 + )); + } + + } catch (Exception e) { + return ResponseEntity.internalServerError() + .body(createMapWithObjects( + "success", "false", + "msg", "获取设备定位信息失败:" + e.getMessage(), + "errorCode", 500 + )); + } + } + + /** + * 创建Map的辅助方法(Java 8兼容) + */ + private Map createMap(String... keyValues) { + Map map = new HashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + if (i + 1 < keyValues.length) { + map.put(keyValues[i], keyValues[i + 1]); + } + } + return map; + } + + private Map createMapWithObjects(Object... keyValues) { + Map map = new HashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + if (i + 1 < keyValues.length) { + map.put((String) keyValues[i], keyValues[i + 1]); + } + } + return map; + } +} diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotDataController.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotDataController.java new file mode 100644 index 0000000..b30265a --- /dev/null +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/controller/IotDataController.java @@ -0,0 +1,263 @@ +package com.bonus.material.iot.controller; + +import com.bonus.common.core.web.domain.AjaxResult; +import com.bonus.material.iot.dto.GpsData; +import com.bonus.material.iot.dto.PhaseData; +import org.springframework.web.bind.annotation.*; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author : syruan + * @version : 1.0 + * @CreateTime: 2025-04-02 13:51 + * @Description: 贵州iot设备GPS解析接口 + */ +@RestController +public class IotDataController { + + // 使用线程安全的Map临时存储最新数据 + private static final ConcurrentMap latestData = new ConcurrentHashMap<>(); + + // 接收MQTT数据的接口 + @PostMapping("/api/mqtt/data") + public String processMqttData(@RequestBody String mqttData) { + try { + GpsData gpsData = parseMqttData(mqttData); + // 存储最新数据,key可以用设备UUID + latestData.put(gpsData.getUuid(), gpsData); + return "数据接收并存储成功"; + } catch (Exception e) { + return "数据解析失败: " + e.getMessage(); + } + } + + // 新增接口供HTML页面获取数据 + @GetMapping("/api/mqtt/latest-data") + public AjaxResult getLatestData(@RequestParam(required = false) String uuid) { + if (uuid == null && !latestData.isEmpty()) { + // 如果没有指定UUID,返回第一个设备的数据 + GpsData data = latestData.values().iterator().next(); + return AjaxResult.success(data); + } + return AjaxResult.warn("", latestData.get(uuid)); + } + + private GpsData parseMqttData(String rawData) { + GpsData data = new GpsData(); + + try { + // 1. 解析UUID + int uuidStart = rawData.indexOf("uuid:") + 5; + int uuidEnd = rawData.indexOf("/", uuidStart); + data.setUuid(rawData.substring(uuidStart, uuidEnd)); + + // 2. 解析$GNRMC部分 + int gnrmcStart = rawData.indexOf("$GNRMC:") + 7; + int gnrmcEnd = rawData.indexOf("$GNGGA:", gnrmcStart); + String gnrmc = rawData.substring(gnrmcStart, gnrmcEnd); + + // 处理校验和 + String[] gnrmcMainAndChecksum = gnrmc.split("\\*"); + String checksum = gnrmcMainAndChecksum.length > 1 ? gnrmcMainAndChecksum[1] : ""; + data.setChecksumRMC(checksum); + + // 分割主数据部分 + String[] gnrmcParts = gnrmcMainAndChecksum[0].split(":", -1); + + // 安全设置字段 + if(gnrmcParts.length > 0) { + data.setUtcTime(gnrmcParts[0]); + } + if(gnrmcParts.length > 1) { + data.setStatus(gnrmcParts[1]); + } + if(gnrmcParts.length > 2) { + data.setLatitude(gnrmcParts[2]); + } + if(gnrmcParts.length > 3) { + data.setLatDirection(gnrmcParts[3]); + } + if(gnrmcParts.length > 4) { + data.setLongitude(gnrmcParts[4]); + } + if(gnrmcParts.length > 5) { + data.setLonDirection(gnrmcParts[5]); + } + if(gnrmcParts.length > 6) { + data.setSpeedOverGround(gnrmcParts[6]); + } + if(gnrmcParts.length > 7) { + data.setCourseOverGround(gnrmcParts[7]); + } + if(gnrmcParts.length > 8) { + data.setDate(gnrmcParts[8]); + } + if(gnrmcParts.length > 11) { + data.setModeIndicator(gnrmcParts[11]); + } + if(gnrmcParts.length > 12) { + data.setNavStatus(gnrmcParts[12]); + } + + // 3. 解析$GNGGA部分 + int gnggaStart = gnrmcEnd + 7; + int gnggaEnd = rawData.indexOf("/humi_temp:", gnggaStart); + String gngga = rawData.substring(gnggaStart, gnggaEnd); + String[] gnggaParts = gngga.split(":", -1); + + if(gnggaParts.length > 0) { + data.setUtcTime(gnggaParts[0]); + } + if(gnggaParts.length > 1) { + data.setLatitude(gnggaParts[1]); + } + if(gnggaParts.length > 2) { + data.setLatDirection(gnggaParts[2]); + } + if(gnggaParts.length > 3) { + data.setLongitude(gnggaParts[3]); + } + if(gnggaParts.length > 4) { + data.setLonDirection(gnggaParts[4]); + } + if(gnggaParts.length > 5) { + data.setQuality(gnggaParts[5]); + } + if(gnggaParts.length > 6) { + data.setNumSatellitesUsed(gnggaParts[6]); + } + if(gnggaParts.length > 7) { + data.setHdop(gnggaParts[7]); + } + if(gnggaParts.length > 8) { + data.setAltitude(gnggaParts[8]); + } + if(gnggaParts.length > 10) { + data.setGeoidSeparation(gnggaParts[10]); + } + + // 4. 解析humi_temp + int humiTempStart = gnggaEnd + 11; + int humiTempEnd = rawData.indexOf(":/acc_gry:", humiTempStart); + String humiTemp = rawData.substring(humiTempStart, humiTempEnd); + String[] humiTempParts = humiTemp.split(":"); + if(humiTempParts.length > 0) { + data.setHumidity(humiTempParts[0]); + } + if(humiTempParts.length > 1) { + data.setTemperature(humiTempParts[1]); + } + + // 5. 解析acc_gry + int accGryStart = humiTempEnd + 10; + int accGryEnd = rawData.length(); + // 假设是最后一部分 + if(rawData.indexOf(":/", accGryStart) > 0) { + accGryEnd = rawData.indexOf(":/", accGryStart); + } + String accGry = rawData.substring(accGryStart, accGryEnd); + String[] accGryParts = accGry.split(":"); + if(accGryParts.length > 0) { + data.setAccX(accGryParts[0]); + } + if(accGryParts.length > 1) { + data.setAccY(accGryParts[1]); + } + if(accGryParts.length > 2) { + data.setAccZ(accGryParts[2]); + } + if(accGryParts.length > 3) { + data.setGyroX(accGryParts[3]); + } + if(accGryParts.length > 4) { + data.setGyroY(accGryParts[4]); + } + if(accGryParts.length > 5) { + data.setGyroZ(accGryParts[5]); + } + + + + // 解析A相电数据 + int aPhaseStart = rawData.indexOf("/A_PHEASE:") + 10; + int aPhaseEnd = rawData.indexOf(":/B_PHEASE:", aPhaseStart); + if (aPhaseStart > 0 && aPhaseEnd > aPhaseStart) { + String aPhaseStr = rawData.substring(aPhaseStart, aPhaseEnd); + data.setAPhase(parsePhaseData(aPhaseStr)); + } + + // 解析B相电数据 + int bPhaseStart = rawData.indexOf("/B_PHEASE:") + 10; + int bPhaseEnd = rawData.indexOf(":/C_PHEASE:", bPhaseStart); + if (bPhaseStart > 0 && bPhaseEnd > bPhaseStart) { + String bPhaseStr = rawData.substring(bPhaseStart, bPhaseEnd); + data.setBPhase(parsePhaseData(bPhaseStr)); + } + + // 解析C相电数据 + int cPhaseStart = rawData.indexOf("/C_PHEASE:") + 10; + int cPhaseEnd = rawData.indexOf(":/", cPhaseStart); + if (cPhaseStart > 0 && cPhaseEnd > cPhaseStart) { + String cPhaseStr = rawData.substring(cPhaseStart, cPhaseEnd); + data.setCPhase(parsePhaseData(cPhaseStr)); + } + + // 验证三相数据 + validatePhaseData(data.getAPhase()); + validatePhaseData(data.getBPhase()); + validatePhaseData(data.getCPhase()); + + } catch (Exception e) { + System.err.println("解析过程中发生错误: " + e.getMessage()); + throw new RuntimeException("数据解析失败: " + e.getMessage()); + } + + return data; + } + + // 验证三项电数据 + private void validatePhaseData(PhaseData phase) { + if (phase == null) { + return; + } + + // 验证电压范围 + if (phase.getVoltage() != null) { + double voltage = Double.parseDouble(phase.getVoltage()); + if (voltage < 0 || voltage > 300) { + throw new IllegalArgumentException("电压值超出合理范围"); + } + } + + // 可以添加更多验证规则... + // 频率验证、功率因数验证等 + } + + // 解析三项电数据 + private PhaseData parsePhaseData(String phaseStr) { + String[] parts = phaseStr.split(":"); + if (parts.length < 14) { + return null; + } + + PhaseData phase = new PhaseData(); + phase.setVoltage(parts[0]); + phase.setCurrent(parts[1]); + phase.setActivePower(parts[2]); + phase.setReactivePower(parts[3]); + phase.setPowerFactor(parts[4]); + phase.setFrequency(parts[5]); + phase.setForwardActiveEnergy(parts[6]); + phase.setForwardReactiveEnergy(parts[7]); + phase.setReverseActiveEnergy(parts[8]); + phase.setReverseReactiveEnergy(parts[9]); + phase.setApparentPower(parts[10]); + phase.setHarmonicActivePower(parts[11]); + phase.setFundamentalActivePower(parts[12]); + phase.setFundamentalReactivePower(parts[13]); + + return phase; + } +} \ No newline at end of file diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/GpsData.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/GpsData.java new file mode 100644 index 0000000..8c4f9e2 --- /dev/null +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/GpsData.java @@ -0,0 +1,53 @@ +package com.bonus.material.iot.dto; +/** + * @author : 阮世耀 + * @version : 1.0 + * @PackagePath: com.tencent.wxcloudrun.model.dto + * @CreateTime: 2025-04-02 13:50 + * @Description: iot定位设备上报信息 + */ +import lombok.Data; + +@Data +public class GpsData { + // UUID部分 + private String uuid; + + // $GNRMC部分 + private String utcTime; + private String status; + private String latitude; + private String latDirection; + private String longitude; + private String lonDirection; + private String speedOverGround; + private String courseOverGround; + private String date; + private String modeIndicator; + private String navStatus; + private String checksumRMC; + + // $GNGGA部分 + private String quality; + private String numSatellitesUsed; + private String hdop; + private String altitude; + private String geoidSeparation; + + // 环境数据 + private String humidity; + private String temperature; + + // 加速度和陀螺仪数据 + private String accX; + private String accY; + private String accZ; + private String gyroX; + private String gyroY; + private String gyroZ; + + // 新增三相电数据 + private PhaseData aPhase; + private PhaseData bPhase; + private PhaseData cPhase; +} diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/PhaseData.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/PhaseData.java new file mode 100644 index 0000000..54c23ef --- /dev/null +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/iot/dto/PhaseData.java @@ -0,0 +1,30 @@ +package com.bonus.material.iot.dto; + +import lombok.Data; + +/** + * @author : 阮世耀 + * @version : 1.0 + * @PackagePath: com.tencent.wxcloudrun.model.dto + * @CreateTime: 2025-04-03 15:47 + * @Description: iot定位设备的三相电压数据 + * @ModifyUser: 阮世耀 + * + */ +@Data +public class PhaseData { + private String voltage; // 电压(V) + private String current; // 电流(A) + private String activePower; // 有功功率(W) + private String reactivePower; // 无功功率(VAR) + private String powerFactor; // 功率因素 + private String frequency; // 频率(Hz) + private String forwardActiveEnergy; // 正向有功电度(KWh) + private String forwardReactiveEnergy; // 正向无功电度(kvar·h) + private String reverseActiveEnergy; // 反向有功电度(KWh) + private String reverseReactiveEnergy; // 反向无功电度(kvar·h) + private String apparentPower; // 视在功率(VA) + private String harmonicActivePower; // 谐波有功功率(W) + private String fundamentalActivePower; // 基波有功功率(W) + private String fundamentalReactivePower; // 基波无功功率(VAR) +}