新增IoT设备工牌控制器及数据接口
This commit is contained in:
parent
9a52a584ee
commit
01085ecfd2
|
|
@ -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<HttpMessageConverter<?>> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Object> tokenCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Object> deviceCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Long> 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<Map<String, Object>> 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<String> response = restTemplate.getForEntity(url, String.class);
|
||||
String responseBody = response.getBody();
|
||||
|
||||
if (responseBody != null) {
|
||||
// 手动解析JSON
|
||||
Map<String, Object> jsonResponse = objectMapper.readValue(responseBody,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
// 检查登录是否成功
|
||||
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<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>>) 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<String> response = restTemplate.getForEntity(url, String.class);
|
||||
String responseBody = response.getBody();
|
||||
|
||||
if (responseBody != null) {
|
||||
// 手动解析JSON
|
||||
Map<String, Object> jsonResponse = objectMapper.readValue(responseBody,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
if ("true".equals(jsonResponse.get("success"))) {
|
||||
// 获取设备数据
|
||||
Object data = jsonResponse.get("data");
|
||||
if (data instanceof java.util.List) {
|
||||
java.util.List<Map<String, Object>> deviceList =
|
||||
(java.util.List<Map<String, Object>>) 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<String, Object> 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<Map<String, Object>> 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<String> response = restTemplate.getForEntity(url, String.class);
|
||||
String responseBody = response.getBody();
|
||||
|
||||
if (responseBody != null) {
|
||||
// 手动解析JSON
|
||||
Map<String, Object> jsonResponse = objectMapper.readValue(responseBody,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<String> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<String> response = restTemplate.getForEntity(url, String.class);
|
||||
String responseBody = response.getBody();
|
||||
|
||||
if (responseBody != null) {
|
||||
// 手动解析JSON
|
||||
Map<String, Object> jsonResponse = objectMapper.readValue(responseBody,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
if ("true".equals(jsonResponse.get("success"))) {
|
||||
// 处理返回的数据
|
||||
List<Map<String, Object>> data = (List<Map<String, Object>>) jsonResponse.get("data");
|
||||
if (data != null && !data.isEmpty()) {
|
||||
Map<String, Object> deviceData = data.get(0);
|
||||
Map<String, Integer> keyMap = (Map<String, Integer>) deviceData.get("key");
|
||||
List<List<Object>> records = (List<List<Object>>) deviceData.get("records");
|
||||
|
||||
// 转换数据格式
|
||||
List<Map<String, Object>> formattedDevices = new ArrayList<>();
|
||||
for (List<Object> record : records) {
|
||||
Map<String, Object> device = new HashMap<>();
|
||||
for (Map.Entry<String, Integer> 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<String, Object> createMap(String... keyValues) {
|
||||
Map<String, Object> 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<String, Object> createMapWithObjects(Object... keyValues) {
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, GpsData> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Reference in New Issue