车辆数据、OTA、IoT 及 BMS 功能更新
以本地开发版本为准,包含仪表盘、设备实时数据、OTA 升级等改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
257bc96045
commit
88021b2912
8
.vite/deps/_metadata.json
Normal file
8
.vite/deps/_metadata.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"hash": "764c59fb",
|
||||||
|
"configHash": "64491d45",
|
||||||
|
"lockfileHash": "e3b0c442",
|
||||||
|
"browserHash": "4636522a",
|
||||||
|
"optimized": {},
|
||||||
|
"chunks": {}
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"java.compile.nullAnalysis.mode": "automatic"
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
}
|
}
|
||||||
138
ota/1.1.1.bin
Normal file
138
ota/1.1.1.bin
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
```
|
||||||
|
String finalNickName = currentUserNickName;
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
if (uploadFile != null && uploadFile.exists()) {
|
||||||
|
String planUserIds = dingTalkProperties.getPlanUserId();
|
||||||
|
if (StringUtils.isNotBlank(planUserIds)) {
|
||||||
|
String mediaId = mediaService.uploadMedia("file", uploadFile.getAbsolutePath());
|
||||||
|
// 支持多人推送,假设多个 userId 以逗号分隔
|
||||||
|
String[] userIds = planUserIds.split(",");
|
||||||
|
for (String userId : userIds) {
|
||||||
|
String cleanUserId = userId.trim();
|
||||||
|
if (StringUtils.isNotBlank(cleanUserId)) {
|
||||||
|
try {
|
||||||
|
robotGroupService.sendFileToUser(cleanUserId, mediaId, originalFilename);
|
||||||
|
log.info("上传的工艺计划表已成功发送给用户: {}", cleanUserId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送文件给用户 {} 异常", cleanUserId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("未配置钉钉工艺计划接收人(planUserId),跳过发送文件");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送文件给工艺计划接收人异常", e);
|
||||||
|
} finally {
|
||||||
|
if (uploadFile != null && uploadFile.exists()) {
|
||||||
|
uploadFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* 发送文件给个人
|
||||||
|
*
|
||||||
|
* @param userId 接收人userId
|
||||||
|
* @param mediaId 媒体文件ID
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public String sendFileToUser(String userId, String mediaId, String fileName) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
String msgParam = String.format("{\"mediaId\":\"%s\",\"fileName\":\"%s\"}",
|
||||||
|
mediaId, fileName);
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setUserIds(Arrays.asList(userId))
|
||||||
|
.setMsgKey("sampleFile")
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("机器人个人文件发送成功, userId: {}, fileName: {}", userId, fileName);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人个人文件发送失败", e);
|
||||||
|
throw new DingException("机器人个人文件发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* 发送Markdown消息给个人 (批量单聊)
|
||||||
|
*
|
||||||
|
* @param userId 接收人userId
|
||||||
|
* @param title 标题
|
||||||
|
* @param text Markdown内容
|
||||||
|
*/
|
||||||
|
public void sendMarkdownToUser(String userId, String title, String text) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
String msgParam = String.format("{\"title\":\"%s\",\"text\":\"%s\"}",
|
||||||
|
title.replace("\"", "\\\""),
|
||||||
|
text.replace("\"", "\\\"").replace("\n", "\\n"));
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setUserIds(Collections.singletonList(userId))
|
||||||
|
.setMsgKey("sampleMarkdown")
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new RuntimeOptions());
|
||||||
|
log.info("机器人个人Markdown消息发送成功, userId: {}, title: {}", userId, title);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人个人Markdown消息发送失败", e);
|
||||||
|
throw new DingException("机器人个人Markdown消息发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* 刷新AccessToken
|
||||||
|
*/
|
||||||
|
private void refresh() {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
GetAccessTokenRequest request = new GetAccessTokenRequest()
|
||||||
|
.setAppKey(properties.getAppKey())
|
||||||
|
.setAppSecret(properties.getAppSecret());
|
||||||
|
|
||||||
|
GetAccessTokenResponse response = client.getAccessToken(request);
|
||||||
|
if (response.getBody() != null) {
|
||||||
|
this.accessToken = response.getBody().getAccessToken();
|
||||||
|
// 设置过期时间,提前100秒刷新
|
||||||
|
this.expireTime = System.currentTimeMillis() + (response.getBody().getExpireIn() * 1000) - 100000;
|
||||||
|
log.info("钉钉AccessToken刷新成功,过期时间: {}", this.expireTime);
|
||||||
|
} else {
|
||||||
|
throw new DingException("刷新AccessToken失败,返回为空");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新钉钉AccessToken失败", e);
|
||||||
|
throw new DingException("刷新钉钉AccessToken失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
138
ota/2.2.0.bin
Normal file
138
ota/2.2.0.bin
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
```
|
||||||
|
String finalNickName = currentUserNickName;
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
if (uploadFile != null && uploadFile.exists()) {
|
||||||
|
String planUserIds = dingTalkProperties.getPlanUserId();
|
||||||
|
if (StringUtils.isNotBlank(planUserIds)) {
|
||||||
|
String mediaId = mediaService.uploadMedia("file", uploadFile.getAbsolutePath());
|
||||||
|
// 支持多人推送,假设多个 userId 以逗号分隔
|
||||||
|
String[] userIds = planUserIds.split(",");
|
||||||
|
for (String userId : userIds) {
|
||||||
|
String cleanUserId = userId.trim();
|
||||||
|
if (StringUtils.isNotBlank(cleanUserId)) {
|
||||||
|
try {
|
||||||
|
robotGroupService.sendFileToUser(cleanUserId, mediaId, originalFilename);
|
||||||
|
log.info("上传的工艺计划表已成功发送给用户: {}", cleanUserId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送文件给用户 {} 异常", cleanUserId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("未配置钉钉工艺计划接收人(planUserId),跳过发送文件");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送文件给工艺计划接收人异常", e);
|
||||||
|
} finally {
|
||||||
|
if (uploadFile != null && uploadFile.exists()) {
|
||||||
|
uploadFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* 发送文件给个人
|
||||||
|
*
|
||||||
|
* @param userId 接收人userId
|
||||||
|
* @param mediaId 媒体文件ID
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public String sendFileToUser(String userId, String mediaId, String fileName) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
String msgParam = String.format("{\"mediaId\":\"%s\",\"fileName\":\"%s\"}",
|
||||||
|
mediaId, fileName);
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setUserIds(Arrays.asList(userId))
|
||||||
|
.setMsgKey("sampleFile")
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("机器人个人文件发送成功, userId: {}, fileName: {}", userId, fileName);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人个人文件发送失败", e);
|
||||||
|
throw new DingException("机器人个人文件发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* 发送Markdown消息给个人 (批量单聊)
|
||||||
|
*
|
||||||
|
* @param userId 接收人userId
|
||||||
|
* @param title 标题
|
||||||
|
* @param text Markdown内容
|
||||||
|
*/
|
||||||
|
public void sendMarkdownToUser(String userId, String title, String text) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
String msgParam = String.format("{\"title\":\"%s\",\"text\":\"%s\"}",
|
||||||
|
title.replace("\"", "\\\""),
|
||||||
|
text.replace("\"", "\\\"").replace("\n", "\\n"));
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setUserIds(Collections.singletonList(userId))
|
||||||
|
.setMsgKey("sampleMarkdown")
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new RuntimeOptions());
|
||||||
|
log.info("机器人个人Markdown消息发送成功, userId: {}, title: {}", userId, title);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人个人Markdown消息发送失败", e);
|
||||||
|
throw new DingException("机器人个人Markdown消息发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* 刷新AccessToken
|
||||||
|
*/
|
||||||
|
private void refresh() {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
GetAccessTokenRequest request = new GetAccessTokenRequest()
|
||||||
|
.setAppKey(properties.getAppKey())
|
||||||
|
.setAppSecret(properties.getAppSecret());
|
||||||
|
|
||||||
|
GetAccessTokenResponse response = client.getAccessToken(request);
|
||||||
|
if (response.getBody() != null) {
|
||||||
|
this.accessToken = response.getBody().getAccessToken();
|
||||||
|
// 设置过期时间,提前100秒刷新
|
||||||
|
this.expireTime = System.currentTimeMillis() + (response.getBody().getExpireIn() * 1000) - 100000;
|
||||||
|
log.info("钉钉AccessToken刷新成功,过期时间: {}", this.expireTime);
|
||||||
|
} else {
|
||||||
|
throw new DingException("刷新AccessToken失败,返回为空");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新钉钉AccessToken失败", e);
|
||||||
|
throw new DingException("刷新钉钉AccessToken失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
24
pom.xml
24
pom.xml
@ -22,7 +22,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>16</java.version>
|
||||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||||
<pagehelper.spring.boot.starter.version>1.4.7</pagehelper.spring.boot.starter.version>
|
<pagehelper.spring.boot.starter.version>1.4.7</pagehelper.spring.boot.starter.version>
|
||||||
<fastjson.version>2.0.58</fastjson.version>
|
<fastjson.version>2.0.58</fastjson.version>
|
||||||
@ -30,6 +30,7 @@
|
|||||||
<commons.io.version>2.19.0</commons.io.version>
|
<commons.io.version>2.19.0</commons.io.version>
|
||||||
<bitwalker.version>1.21</bitwalker.version>
|
<bitwalker.version>1.21</bitwalker.version>
|
||||||
<jwt.version>0.9.1</jwt.version>
|
<jwt.version>0.9.1</jwt.version>
|
||||||
|
<mysql.version>8.0.33</mysql.version>
|
||||||
<kaptcha.version>2.3.3</kaptcha.version>
|
<kaptcha.version>2.3.3</kaptcha.version>
|
||||||
<swagger.version>3.0.0</swagger.version>
|
<swagger.version>3.0.0</swagger.version>
|
||||||
<poi.version>4.1.2</poi.version>
|
<poi.version>4.1.2</poi.version>
|
||||||
@ -44,6 +45,12 @@
|
|||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- SpringBoot 核心包 -->
|
<!-- SpringBoot 核心包 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -102,8 +109,9 @@
|
|||||||
|
|
||||||
<!-- Mysql驱动包 -->
|
<!-- Mysql驱动包 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>${mysql.version}</version>
|
||||||
<!-- <scope>runtime</scope>-->
|
<!-- <scope>runtime</scope>-->
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@ -276,12 +284,6 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>io.springfox</groupId>
|
|
||||||
<artifactId>springfox-boot-starter</artifactId>
|
|
||||||
<version>3.0.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@ -298,8 +300,8 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>16</source>
|
<source>${java.version}</source>
|
||||||
<target>16</target>
|
<target>${java.version}</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ public final class BboxApiConstants {
|
|||||||
public static final int CODE_DEVICE_NOT_REGISTERED = 901; // 设备未在平台注册,如平台未录入
|
public static final int CODE_DEVICE_NOT_REGISTERED = 901; // 设备未在平台注册,如平台未录入
|
||||||
public static final int CODE_DEVICE_DISABLED = 902; // 设备已被禁用
|
public static final int CODE_DEVICE_DISABLED = 902; // 设备已被禁用
|
||||||
public static final int CODE_FILE_NOT_FOUND = 903; // 文件不存在
|
public static final int CODE_FILE_NOT_FOUND = 903; // 文件不存在
|
||||||
|
public static final int CODE_PARAM_INVALID = 904; // 参数无效/缺失
|
||||||
|
|
||||||
// 对应消息文本(可用于统一提示)
|
// 对应消息文本(可用于统一提示)
|
||||||
public static final String MSG_SUCCESS = "成功";
|
public static final String MSG_SUCCESS = "成功";
|
||||||
@ -23,4 +24,5 @@ public final class BboxApiConstants {
|
|||||||
public static final String MSG_DEVICE_NOT_REGISTERED = "设备未在平台注册,如平台未录入";
|
public static final String MSG_DEVICE_NOT_REGISTERED = "设备未在平台注册,如平台未录入";
|
||||||
public static final String MSG_DEVICE_DISABLED = "设备已被禁用";
|
public static final String MSG_DEVICE_DISABLED = "设备已被禁用";
|
||||||
public static final String MSG_FILE_NOT_FOUND = "文件不存在";
|
public static final String MSG_FILE_NOT_FOUND = "文件不存在";
|
||||||
}
|
public static final String MSG_PARAM_INVALID = "参数无效";
|
||||||
|
}
|
||||||
|
|||||||
@ -12,17 +12,61 @@ import com.evobms.framework.web.page.TableSupport;
|
|||||||
*/
|
*/
|
||||||
public class PageUtils extends PageHelper
|
public class PageUtils extends PageHelper
|
||||||
{
|
{
|
||||||
|
/** 设备历史平铺列表允许的每页条数 */
|
||||||
|
public static final int[] HISTORY_PAGE_SIZES = {10, 20, 100, 500};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置请求分页数据
|
* 设置请求分页数据
|
||||||
*/
|
*/
|
||||||
public static void startPage()
|
public static void startPage()
|
||||||
|
{
|
||||||
|
startPage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param doCount 是否执行 COUNT;大数据量历史列表可传 false 避免扫行计数超时
|
||||||
|
*/
|
||||||
|
public static void startPage(boolean doCount)
|
||||||
{
|
{
|
||||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||||
Integer pageNum = pageDomain.getPageNum();
|
Integer pageNum = pageDomain.getPageNum();
|
||||||
Integer pageSize = pageDomain.getPageSize();
|
Integer pageSize = pageDomain.getPageSize();
|
||||||
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
|
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
|
||||||
Boolean reasonable = pageDomain.getReasonable();
|
Boolean reasonable = pageDomain.getReasonable();
|
||||||
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
|
PageHelper.startPage(pageNum, pageSize, doCount, null, null)
|
||||||
|
.setOrderBy(orderBy)
|
||||||
|
.setReasonable(reasonable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史平铺:对时间范围内全部 vehicle_data 做库内分页;pageSize 规范为 10/20/100/500。
|
||||||
|
*
|
||||||
|
* @return 实际使用的每页条数
|
||||||
|
*/
|
||||||
|
public static int startHistoryPage(boolean doCount) {
|
||||||
|
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||||
|
int pageNum = pageDomain.getPageNum() == null || pageDomain.getPageNum() < 1 ? 1 : pageDomain.getPageNum();
|
||||||
|
int pageSize = normalizeHistoryPageSize(pageDomain.getPageSize());
|
||||||
|
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
|
||||||
|
Boolean reasonable = pageDomain.getReasonable();
|
||||||
|
PageHelper.startPage(pageNum, pageSize, doCount, null, null)
|
||||||
|
.setOrderBy(orderBy)
|
||||||
|
.setReasonable(reasonable);
|
||||||
|
return pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将请求的 pageSize 收敛到 10、20、100、500(不足 10 按 10) */
|
||||||
|
public static int normalizeHistoryPageSize(Integer requested) {
|
||||||
|
if (requested == null || requested <= 0) {
|
||||||
|
return HISTORY_PAGE_SIZES[0];
|
||||||
|
}
|
||||||
|
int chosen = HISTORY_PAGE_SIZES[0];
|
||||||
|
for (int allowed : HISTORY_PAGE_SIZES) {
|
||||||
|
if (requested >= allowed) {
|
||||||
|
chosen = allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
package com.evobms.framework.aspectj.lang.annotation;
|
package com.evobms.framework.aspectj.lang.annotation;
|
||||||
|
|
||||||
|
import com.evobms.common.utils.poi.ExcelHandlerAdapter;
|
||||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
|
||||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
|
||||||
import com.evobms.common.utils.poi.ExcelHandlerAdapter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义导出Excel数据注解
|
* 自定义导出Excel数据注解
|
||||||
|
|||||||
@ -10,7 +10,9 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.integration.annotation.ServiceActivator;
|
import org.springframework.integration.annotation.ServiceActivator;
|
||||||
import org.springframework.integration.channel.DirectChannel;
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
|
import org.springframework.integration.channel.ExecutorChannel;
|
||||||
import org.springframework.integration.core.MessageProducer;
|
import org.springframework.integration.core.MessageProducer;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||||
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
|
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
|
||||||
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
|
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
|
||||||
@ -60,6 +62,9 @@ public class MqttConfig {
|
|||||||
@Value("${mqtt.cleanSession}")
|
@Value("${mqtt.cleanSession}")
|
||||||
private boolean cleanSession;
|
private boolean cleanSession;
|
||||||
|
|
||||||
|
@Value("${mqtt.completionTimeout:20000}")
|
||||||
|
private int completionTimeout;
|
||||||
|
|
||||||
// 支持逗号分隔的多个主题,例如:/bbox/EVO0002/#,/bbox/000010000200003/#
|
// 支持逗号分隔的多个主题,例如:/bbox/EVO0002/#,/bbox/000010000200003/#
|
||||||
@Value("#{'${mqtt.subscribeTopic}'.split(',')}")
|
@Value("#{'${mqtt.subscribeTopic}'.split(',')}")
|
||||||
private String[] subscribeTopics;
|
private String[] subscribeTopics;
|
||||||
@ -87,11 +92,17 @@ public class MqttConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MQTT消息接收通道
|
* MQTT消息接收通道 (异步处理,防止入库慢阻塞MQTT底层网络线程导致掉线)
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public MessageChannel mqttInputChannel() {
|
public MessageChannel mqttInputChannel() {
|
||||||
return new DirectChannel();
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(10);
|
||||||
|
executor.setMaxPoolSize(50);
|
||||||
|
executor.setQueueCapacity(1000);
|
||||||
|
executor.setThreadNamePrefix("mqtt-handler-");
|
||||||
|
executor.initialize();
|
||||||
|
return new ExecutorChannel(executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,7 +119,7 @@ public class MqttConfig {
|
|||||||
|
|
||||||
MqttPahoMessageDrivenChannelAdapter adapter =
|
MqttPahoMessageDrivenChannelAdapter adapter =
|
||||||
new MqttPahoMessageDrivenChannelAdapter(clientId + "_inbound", mqttClientFactory(), topics);
|
new MqttPahoMessageDrivenChannelAdapter(clientId + "_inbound", mqttClientFactory(), topics);
|
||||||
adapter.setCompletionTimeout(5000);
|
adapter.setCompletionTimeout(completionTimeout);
|
||||||
// 使用字节载荷转换器,保留原始二进制帧
|
// 使用字节载荷转换器,保留原始二进制帧
|
||||||
DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter();
|
DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter();
|
||||||
converter.setPayloadAsBytes(true);
|
converter.setPayloadAsBytes(true);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.evobms.project.battery.service;
|
package com.evobms.project.battery.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.evobms.project.battery.domain.SubsystemTemperature;
|
import com.evobms.project.battery.domain.SubsystemTemperature;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,7 +10,7 @@ import com.evobms.project.battery.domain.SubsystemTemperature;
|
|||||||
* @author 田志阳
|
* @author 田志阳
|
||||||
* @date 2025-11-15
|
* @date 2025-11-15
|
||||||
*/
|
*/
|
||||||
public interface ISubsystemTemperatureService
|
public interface ISubsystemTemperatureService extends IService<SubsystemTemperature>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 查询可充电储能子系统温度信息
|
* 查询可充电储能子系统温度信息
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.evobms.project.battery.service;
|
package com.evobms.project.battery.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.evobms.project.battery.domain.SubsystemVoltage;
|
import com.evobms.project.battery.domain.SubsystemVoltage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,7 +10,7 @@ import com.evobms.project.battery.domain.SubsystemVoltage;
|
|||||||
* @author 田志阳
|
* @author 田志阳
|
||||||
* @date 2025-11-15
|
* @date 2025-11-15
|
||||||
*/
|
*/
|
||||||
public interface ISubsystemVoltageService
|
public interface ISubsystemVoltageService extends IService<SubsystemVoltage>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 查询可充电储能子系统电压信息
|
* 查询可充电储能子系统电压信息
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnProperty(name = "mqtt.enabled", havingValue = "true")
|
@ConditionalOnProperty(name = "mqtt.legacyReceiver.enabled", havingValue = "true")
|
||||||
public class GBT32960MqttReceiver {
|
public class GBT32960MqttReceiver {
|
||||||
@Value("${mqtt.host}")
|
@Value("${mqtt.host}")
|
||||||
private String broker;
|
private String broker;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import java.math.BigDecimal;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.github.xiaoymin.knife4j.annotations.Ignore;
|
import com.github.xiaoymin.knife4j.annotations.Ignore;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
@ -32,55 +33,68 @@ public class BmsChargeDetail extends BaseEntity
|
|||||||
|
|
||||||
/** 数据时间戳 */
|
/** 数据时间戳 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@ApiModelProperty(value = "数据时间戳")
|
||||||
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private Date timestamp;
|
private Date timestamp;
|
||||||
|
|
||||||
/** 慢充电流(A) */
|
/** 慢充电流(A) */
|
||||||
@Excel(name = "慢充电流(A)")
|
@Excel(name = "慢充电流(A)")
|
||||||
|
@ApiModelProperty(value = "慢充电流(A)")
|
||||||
private BigDecimal acChargeCurrent;
|
private BigDecimal acChargeCurrent;
|
||||||
|
|
||||||
/** 慢充电压(V) */
|
/** 慢充电压(V) */
|
||||||
@Excel(name = "慢充电压(V)")
|
@Excel(name = "慢充电压(V)")
|
||||||
|
@ApiModelProperty(value = "慢充电压(V)")
|
||||||
private BigDecimal acChargeVoltage;
|
private BigDecimal acChargeVoltage;
|
||||||
|
|
||||||
/** 快充电流(A) */
|
/** 快充电流(A) */
|
||||||
@Excel(name = "快充电流(A)")
|
@Excel(name = "快充电流(A)")
|
||||||
|
@ApiModelProperty(value = "快充电流(A)")
|
||||||
private BigDecimal dcChargeCurrent;
|
private BigDecimal dcChargeCurrent;
|
||||||
|
|
||||||
/** 快充电压(V) */
|
/** 快充电压(V) */
|
||||||
@Excel(name = "快充电压(V)")
|
@Excel(name = "快充电压(V)")
|
||||||
|
@ApiModelProperty(value = "快充电压(V)")
|
||||||
private BigDecimal dcChargeVoltage;
|
private BigDecimal dcChargeVoltage;
|
||||||
|
|
||||||
/** 充电时长(min) */
|
/** 充电时长(min) */
|
||||||
@Excel(name = "充电时长(min)")
|
@Excel(name = "充电时长(min)")
|
||||||
|
@ApiModelProperty(value = "充电时长min")
|
||||||
private Long chargeTime;
|
private Long chargeTime;
|
||||||
|
|
||||||
/** 慢充停止码 */
|
/** 慢充停止码 */
|
||||||
@Excel(name = "慢充停止码")
|
@Excel(name = "慢充停止码")
|
||||||
|
@ApiModelProperty(value = "慢充停止码")
|
||||||
private Integer acStopCode;
|
private Integer acStopCode;
|
||||||
|
|
||||||
/** 快充停止码 */
|
/** 快充停止码 */
|
||||||
@Excel(name = "快充停止码")
|
@Excel(name = "快充停止码")
|
||||||
|
@ApiModelProperty(value = "快充停止码")
|
||||||
private Integer dcStopCode;
|
private Integer dcStopCode;
|
||||||
|
|
||||||
/** 慢充CP值 */
|
/** 慢充CP值 */
|
||||||
@Excel(name = "慢充CP值")
|
@Excel(name = "慢充CP值")
|
||||||
|
@ApiModelProperty(value = "慢充CP值")
|
||||||
private Integer acCpValue;
|
private Integer acCpValue;
|
||||||
|
|
||||||
/** 慢充枪阻值 */
|
/** 慢充枪阻值 */
|
||||||
@Excel(name = "慢充枪阻值")
|
@Excel(name = "慢充枪阻值")
|
||||||
|
@ApiModelProperty(value = "慢充枪阻值")
|
||||||
private Integer acGunResistance;
|
private Integer acGunResistance;
|
||||||
|
|
||||||
/** 充电阶段 */
|
/** 充电阶段 */
|
||||||
@Excel(name = "充电阶段")
|
@Excel(name = "充电阶段")
|
||||||
|
@ApiModelProperty(value = "充电阶段")
|
||||||
private Integer chargingStage;
|
private Integer chargingStage;
|
||||||
|
|
||||||
/** 慢充阶段 */
|
/** 慢充阶段 */
|
||||||
@Excel(name = "慢充阶段")
|
@Excel(name = "慢充阶段")
|
||||||
|
@ApiModelProperty(value = "慢充阶段")
|
||||||
private Integer acStage;
|
private Integer acStage;
|
||||||
|
|
||||||
/** 快充阶段 */
|
/** 快充阶段 */
|
||||||
@Excel(name = "快充阶段")
|
@Excel(name = "快充阶段")
|
||||||
|
@ApiModelProperty(value = "快充阶段")
|
||||||
private Integer dcStage;
|
private Integer dcStage;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import java.io.Serial;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
@ -34,14 +35,17 @@ public class BmsChargeTemperature extends BaseEntity
|
|||||||
/** 数据时间戳 */
|
/** 数据时间戳 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private Date timestap;
|
@ApiModelProperty(value = "数据时间戳")
|
||||||
|
private Date timestamp;
|
||||||
|
|
||||||
/** 温度编号(1-8) */
|
/** 温度编号(1-8) */
|
||||||
@Excel(name = "温度编号(1-8)")
|
@Excel(name = "温度编号(1-8)")
|
||||||
|
@ApiModelProperty(value = "温度编号(1-8)")
|
||||||
private Integer tempIndex;
|
private Integer tempIndex;
|
||||||
|
|
||||||
/** 温度(℃) */
|
/** 温度(℃) */
|
||||||
@Excel(name = "温度(℃)")
|
@Excel(name = "温度(℃)")
|
||||||
|
@ApiModelProperty(value = "温度(℃)")
|
||||||
private BigDecimal temperature;
|
private BigDecimal temperature;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
package com.evobms.project.bms.domain;
|
package com.evobms.project.bms.domain;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
@ -20,6 +22,7 @@ import com.evobms.framework.web.domain.BaseEntity;
|
|||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
public class BmsRuntimeData extends BaseEntity
|
public class BmsRuntimeData extends BaseEntity
|
||||||
{
|
{
|
||||||
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 主键ID */
|
/** 主键ID */
|
||||||
@ -31,47 +34,58 @@ public class BmsRuntimeData extends BaseEntity
|
|||||||
|
|
||||||
/** 数据时间戳 */
|
/** 数据时间戳 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@ApiModelProperty(value = "数据时间戳")
|
||||||
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private Date timestap;
|
private Date timestamp;
|
||||||
|
|
||||||
/** 车速(km/h) */
|
/** 车速(km/h) */
|
||||||
@Excel(name = "车速(km/h)")
|
@Excel(name = "车速(km/h)")
|
||||||
|
@ApiModelProperty(value = "车速(km/h)")
|
||||||
private BigDecimal vehicleSpeed;
|
private BigDecimal vehicleSpeed;
|
||||||
|
|
||||||
/** 总里程(km) */
|
/** 总里程(km) */
|
||||||
@Excel(name = "总里程(km)")
|
@Excel(name = "总里程(km)")
|
||||||
|
@ApiModelProperty(value = "总里程(km)")
|
||||||
private Long mileage;
|
private Long mileage;
|
||||||
|
|
||||||
/** 系统电压(V) */
|
/** 系统电压(V) */
|
||||||
@Excel(name = "系统电压(V)")
|
@Excel(name = "系统电压(V)")
|
||||||
|
@ApiModelProperty(value = "系统电压(V)")
|
||||||
private BigDecimal systemVoltage;
|
private BigDecimal systemVoltage;
|
||||||
|
|
||||||
/** 系统电流(A) */
|
/** 系统电流(A) */
|
||||||
@Excel(name = "系统电流(A)")
|
@Excel(name = "系统电流(A)")
|
||||||
|
@ApiModelProperty(value = "系统电流(A)")
|
||||||
private BigDecimal systemCurrent;
|
private BigDecimal systemCurrent;
|
||||||
|
|
||||||
/** 绝缘电阻(KΩ) */
|
/** 绝缘电阻(KΩ) */
|
||||||
@Excel(name = "绝缘电阻(KΩ)")
|
@Excel(name = "绝缘电阻(KΩ)")
|
||||||
|
@ApiModelProperty(value = "绝缘电阻(KΩ)")
|
||||||
private Long insulationResistance;
|
private Long insulationResistance;
|
||||||
|
|
||||||
/** 最大SOC(%) */
|
/** 最大SOC(%) */
|
||||||
@Excel(name = "最大SOC(%)")
|
@Excel(name = "最大SOC(%)")
|
||||||
|
@ApiModelProperty(value = "最大SOC(%)")
|
||||||
private BigDecimal socMax;
|
private BigDecimal socMax;
|
||||||
|
|
||||||
/** 最小SOC(%) */
|
/** 最小SOC(%) */
|
||||||
@Excel(name = "最小SOC(%)")
|
@Excel(name = "最小SOC(%)")
|
||||||
|
@ApiModelProperty(value = "最小SOC(%)")
|
||||||
private BigDecimal socMin;
|
private BigDecimal socMin;
|
||||||
|
|
||||||
/** GPV电压(V) */
|
/** GPV电压(V) */
|
||||||
@Excel(name = "GPV电压(V)")
|
@Excel(name = "GPV电压(V)")
|
||||||
|
@ApiModelProperty(value = "GPV电压(V)")
|
||||||
private BigDecimal gpvVoltage;
|
private BigDecimal gpvVoltage;
|
||||||
|
|
||||||
/** 行车阶段 */
|
/** 行车阶段 */
|
||||||
@Excel(name = "行车阶段")
|
@Excel(name = "行车阶段")
|
||||||
|
@ApiModelProperty(value = "行车阶段")
|
||||||
private Integer runStatus;
|
private Integer runStatus;
|
||||||
|
|
||||||
/** 行车上电阶段 */
|
/** 行车上电阶段 */
|
||||||
@Excel(name = "行车上电阶段")
|
@Excel(name = "行车上电阶段")
|
||||||
|
@ApiModelProperty(value = "行车上电阶段")
|
||||||
private Integer powerStatus;
|
private Integer powerStatus;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package com.evobms.project.bms.domain;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
@ -30,55 +32,79 @@ public class BmsSopData extends BaseEntity
|
|||||||
|
|
||||||
/** 数据时间戳 */
|
/** 数据时间戳 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@ApiModelProperty(value = "数据时间戳")
|
||||||
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private Date timestap;
|
@TableField("`timestamp`")
|
||||||
|
private Date timestamp;
|
||||||
|
|
||||||
/** 10秒放电SOP(A) */
|
/** 10秒放电SOP(A) */
|
||||||
@Excel(name = "10秒放电SOP(A)")
|
@Excel(name = "10秒放电SOP(A)")
|
||||||
|
@ApiModelProperty(value = "10秒放电SOP(A)")
|
||||||
|
@TableField("discharge_sop_10s")
|
||||||
private Long dischargeSop10s;
|
private Long dischargeSop10s;
|
||||||
|
|
||||||
/** 30秒放电SOP(A) */
|
/** 30秒放电SOP(A) */
|
||||||
@Excel(name = "30秒放电SOP(A)")
|
@Excel(name = "30秒放电SOP(A)")
|
||||||
|
@ApiModelProperty(value = "30秒放电SOP(A)")
|
||||||
|
@TableField("discharge_sop_30s")
|
||||||
private Long dischargeSop30s;
|
private Long dischargeSop30s;
|
||||||
|
|
||||||
/** 60秒放电SOP(A) */
|
/** 60秒放电SOP(A) */
|
||||||
@Excel(name = "60秒放电SOP(A)")
|
@Excel(name = "60秒放电SOP(A)")
|
||||||
|
@ApiModelProperty(value = "60秒放电SOP(A)")
|
||||||
|
@TableField("discharge_sop_60s")
|
||||||
private Long dischargeSop60s;
|
private Long dischargeSop60s;
|
||||||
|
|
||||||
/** 持续放电SOP(A) */
|
/** 持续放电SOP(A) */
|
||||||
@Excel(name = "持续放电SOP(A)")
|
@Excel(name = "持续放电SOP(A)")
|
||||||
|
@ApiModelProperty(value = "持续放电SOP(A)")
|
||||||
|
@TableField("discharge_sop_continuous")
|
||||||
private Long dischargeSopContinuous;
|
private Long dischargeSopContinuous;
|
||||||
|
|
||||||
/** 放电融合SOP(A) */
|
/** 放电融合SOP(A) */
|
||||||
@Excel(name = "放电融合SOP(A)")
|
@Excel(name = "放电融合SOP(A)")
|
||||||
|
@ApiModelProperty(value = "放电融合SOP(A)")
|
||||||
private Long dischargeSopTotal;
|
private Long dischargeSopTotal;
|
||||||
|
|
||||||
/** 10秒回馈SOP(A) */
|
/** 10秒回馈SOP(A) */
|
||||||
@Excel(name = "10秒回馈SOP(A)")
|
@Excel(name = "10秒回馈SOP(A)")
|
||||||
|
@ApiModelProperty(value = "10秒回馈SOP(A)")
|
||||||
|
@TableField("regen_sop_10s")
|
||||||
private Long regenSop10s;
|
private Long regenSop10s;
|
||||||
|
|
||||||
/** 30秒回馈SOP(A) */
|
/** 30秒回馈SOP(A) */
|
||||||
@Excel(name = "30秒回馈SOP(A)")
|
@Excel(name = "30秒回馈SOP(A)")
|
||||||
|
@ApiModelProperty(value = "30秒回馈SOP(A)")
|
||||||
|
@TableField("regen_sop_30s")
|
||||||
private Long regenSop30s;
|
private Long regenSop30s;
|
||||||
|
|
||||||
/** 60秒回馈SOP(A) */
|
/** 60秒回馈SOP(A) */
|
||||||
@Excel(name = "60秒回馈SOP(A)")
|
@Excel(name = "60秒回馈SOP(A)")
|
||||||
|
@ApiModelProperty(value = "60秒回馈SOP(A)")
|
||||||
|
@TableField("regen_sop_60s")
|
||||||
private Long regenSop60s;
|
private Long regenSop60s;
|
||||||
|
|
||||||
/** 持续回馈SOP(A) */
|
/** 持续回馈SOP(A) */
|
||||||
@Excel(name = "持续回馈SOP(A)")
|
@Excel(name = "持续回馈SOP(A)")
|
||||||
|
@ApiModelProperty(value = "持续回馈SOP(A)")
|
||||||
|
@TableField("regen_sop_continuous")
|
||||||
private Long regenSopContinuous;
|
private Long regenSopContinuous;
|
||||||
|
|
||||||
/** 回馈融合SOP(A) */
|
/** 回馈融合SOP(A) */
|
||||||
@Excel(name = "回馈融合SOP(A)")
|
@Excel(name = "回馈融合SOP(A)")
|
||||||
|
@ApiModelProperty(value = "回馈融合SOP(A)")
|
||||||
private Long regenSopTotal;
|
private Long regenSopTotal;
|
||||||
|
|
||||||
/** 充电SOP(A) */
|
/** 充电SOP(A) */
|
||||||
@Excel(name = "充电SOP(A)")
|
@Excel(name = "充电SOP(A)")
|
||||||
|
@ApiModelProperty(value = "充电SOP(A)")
|
||||||
|
@TableField("charge_sop")
|
||||||
private Long chargeSop;
|
private Long chargeSop;
|
||||||
|
|
||||||
/** 慢充SOP(A) */
|
/** 慢充SOP(A) */
|
||||||
@Excel(name = "慢充SOP(A)")
|
@Excel(name = "慢充SOP(A)")
|
||||||
|
@ApiModelProperty(value = "慢充SOP(A)")
|
||||||
|
@TableField("ac_charge_sop")
|
||||||
private Long acChargeSop;
|
private Long acChargeSop;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package com.evobms.project.bms.domain;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
@ -32,30 +34,49 @@ public class BmsSystemStatus extends BaseEntity
|
|||||||
/** 数据时间戳 */
|
/** 数据时间戳 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private Date timestap;
|
@ApiModelProperty(value = "数据时间戳")
|
||||||
|
private Date timestamp;
|
||||||
|
|
||||||
/** 继电器状态 */
|
/** 继电器状态 */
|
||||||
@Excel(name = "继电器状态")
|
@Excel(name = "继电器状态")
|
||||||
|
@ApiModelProperty(value = "继电器状态")
|
||||||
private Long relayStatus;
|
private Long relayStatus;
|
||||||
|
|
||||||
/** ON/A+/CC状态 */
|
/** ON/A+/CC状态 */
|
||||||
@Excel(name = "ON/A+/CC状态")
|
@Excel(name = "ON/A+/CC状态")
|
||||||
|
@ApiModelProperty(value = "ON/A+/CC状态")
|
||||||
private Long onaaccStatus;
|
private Long onaaccStatus;
|
||||||
|
|
||||||
/** 系统复位次数 */
|
/** 系统复位次数 */
|
||||||
@Excel(name = "系统复位次数")
|
@Excel(name = "系统复位次数")
|
||||||
|
@ApiModelProperty(value = "系统复位次数")
|
||||||
private Long resetCount;
|
private Long resetCount;
|
||||||
|
|
||||||
/** 电池循环次数 */
|
/** 电池循环次数 */
|
||||||
@Excel(name = "电池循环次数")
|
@Excel(name = "电池循环次数")
|
||||||
|
@ApiModelProperty(value = "电池循环次数")
|
||||||
private Long cycleCount;
|
private Long cycleCount;
|
||||||
|
/** 电池循环次数 */
|
||||||
|
@Excel(name = "电池芯单体数")
|
||||||
|
@ApiModelProperty(value = "电池芯单体数")
|
||||||
|
private Long cellCount;
|
||||||
|
|
||||||
/** 2.5V采样电压(V) */
|
/** 2.5V采样电压(V) */
|
||||||
@Excel(name = "2.5V采样电压(V)")
|
@Excel(name = "2.5V采样电压(V)")
|
||||||
|
@ApiModelProperty(value = "2.5V采样电压(V)")
|
||||||
|
@TableField("sample_25v")
|
||||||
private BigDecimal sample25v;
|
private BigDecimal sample25v;
|
||||||
|
|
||||||
/** 5V采样电压(V) */
|
/** 5V采样电压(V) */
|
||||||
@Excel(name = "5V采样电压(V)")
|
@Excel(name = "5V采样电压(V)")
|
||||||
|
@ApiModelProperty(value = "5V采样电压(V)")
|
||||||
|
@TableField("sample_5v")
|
||||||
private BigDecimal sample5v;
|
private BigDecimal sample5v;
|
||||||
|
|
||||||
|
/** 测温点数量(V) */
|
||||||
|
@Excel(name = "测温点数量(V)")
|
||||||
|
@ApiModelProperty(value = "测温点数量(V)")
|
||||||
|
@TableField("temp_sensor_count")
|
||||||
|
private Integer tempSensorCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.evobms.project.bms.domain;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
@ -32,18 +33,22 @@ public class BmsVoltageChannelData extends BaseEntity
|
|||||||
/** 数据时间戳 */
|
/** 数据时间戳 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
@Excel(name = "数据时间戳", width = 30, dateFormat = "yyyy-MM-dd")
|
||||||
private Date timestap;
|
@ApiModelProperty(value = "数据时间戳")
|
||||||
|
private Date timestamp;
|
||||||
|
|
||||||
/** 通道类型(RTP/RTN) */
|
/** 通道类型(RTP/RTN) */
|
||||||
@Excel(name = "通道类型(RTP/RTN)")
|
@Excel(name = "通道类型(RTP/RTN)")
|
||||||
|
@ApiModelProperty(value = "通道类型(RTP/RTN)")
|
||||||
private String channelType;
|
private String channelType;
|
||||||
|
|
||||||
/** 通道编号 */
|
/** 通道编号 */
|
||||||
@Excel(name = "通道编号")
|
@Excel(name = "通道编号")
|
||||||
|
@ApiModelProperty(value = "通道编号")
|
||||||
private Integer channelIndex;
|
private Integer channelIndex;
|
||||||
|
|
||||||
/** 电压(V) */
|
/** 电压(V) */
|
||||||
@Excel(name = "电压(V)")
|
@Excel(name = "电压(V)")
|
||||||
|
@ApiModelProperty(value = "电压(V)")
|
||||||
private BigDecimal voltage;
|
private BigDecimal voltage;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -289,7 +289,7 @@ public class MqttService {
|
|||||||
// 7. 累计充放电量 - 绝对索引543-553(11字节) | 相对偏移: 519-529
|
// 7. 累计充放电量 - 绝对索引543-553(11字节) | 相对偏移: 519-529
|
||||||
parseChargeDischargeAtIndex(message, 519, deviceSn, frameTime);
|
parseChargeDischargeAtIndex(message, 519, deviceSn, frameTime);
|
||||||
|
|
||||||
parseExtendedDataAtIndex(message, 506, deviceSn, frameTime);
|
parseExtendedDataAtIndex(message, 530, deviceSn, frameTime);
|
||||||
|
|
||||||
log.info("设备 {} 所有数据段解析完成", deviceSn);
|
log.info("设备 {} 所有数据段解析完成", deviceSn);
|
||||||
|
|
||||||
@ -341,40 +341,40 @@ public class MqttService {
|
|||||||
private void parseVehicleDataAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
|
private void parseVehicleDataAtIndex(byte[] message, int startIndex, String deviceSn, Date frameTime) {
|
||||||
try {
|
try {
|
||||||
if (message.length < startIndex + 20) {
|
if (message.length < startIndex + 20) {
|
||||||
log.warn("设备 {}: 整车数据索引越界", deviceSn);
|
/// log.warn("设备 {}: 整车数据索引越界", deviceSn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 索引30: 数据标识 (应该是0x01)
|
// 索引30: 数据标识 (应该是0x01)
|
||||||
byte dataFlag = message[startIndex];
|
byte dataFlag = message[startIndex];
|
||||||
if (dataFlag != 0x01) {
|
if (dataFlag != 0x01) {
|
||||||
log.warn("设备 {}: 整车数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
|
// log.warn("设备 {}: 整车数据标识错误: 0x{}", deviceSn, String.format("%02X", dataFlag));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("设备 {} - 发现整车数据", deviceSn);
|
// log.info("设备 {} - 发现整车数据", deviceSn);
|
||||||
|
|
||||||
// 索引32: 充电状态
|
// 索引32: 充电状态
|
||||||
byte chargeStatus = message[startIndex + 2];
|
byte chargeStatus = message[startIndex + 2];
|
||||||
log.info("设备 {} - 充电状态: 0x{} ({})", deviceSn, String.format("%02X", chargeStatus), getChargeStatusDescription(chargeStatus));
|
// log.info("设备 {} - 充电状态: 0x{} ({})", deviceSn, String.format("%02X", chargeStatus), getChargeStatusDescription(chargeStatus));
|
||||||
|
|
||||||
// 索引40-41: 总电压 (0.1V)
|
// 索引40-41: 总电压 (0.1V)
|
||||||
int totalVoltage = ((message[startIndex + 10] & 0xFF) << 8) | (message[startIndex + 11] & 0xFF);
|
int totalVoltage = ((message[startIndex + 10] & 0xFF) << 8) | (message[startIndex + 11] & 0xFF);
|
||||||
double voltageValue = totalVoltage * 0.1;
|
double voltageValue = totalVoltage * 0.1;
|
||||||
log.info("设备 {} - 总电压: {} V", deviceSn, voltageValue);
|
//log.info("设备 {} - 总电压: {} V", deviceSn, voltageValue);
|
||||||
|
|
||||||
// 索引42-43: 总电流 (0.1A, 偏移10000)
|
// 索引42-43: 总电流 (0.1A, 偏移10000)
|
||||||
int totalCurrent = ((message[startIndex + 12] & 0xFF) << 8) | (message[startIndex + 13] & 0xFF);
|
int totalCurrent = ((message[startIndex + 12] & 0xFF) << 8) | (message[startIndex + 13] & 0xFF);
|
||||||
double currentValue = (totalCurrent - 10000) * 0.1;
|
double currentValue = (totalCurrent - 10000) * 0.1;
|
||||||
log.info("设备 {} - 总电流: {} A", deviceSn, currentValue);
|
// log.info("设备 {} - 总电流: {} A", deviceSn, currentValue);
|
||||||
|
|
||||||
// 索引44: SOC (%)
|
// 索引44: SOC (%)
|
||||||
int soc = message[startIndex + 14] & 0xFF;
|
int soc = message[startIndex + 14] & 0xFF;
|
||||||
log.info("设备 {} - SOC: {} %", deviceSn, soc);
|
//log.info("设备 {} - SOC: {} %", deviceSn, soc);
|
||||||
|
|
||||||
// 索引47-48: 正极绝缘电阻 (Ω)
|
// 索引47-48: 正极绝缘电阻 (Ω)
|
||||||
int insulationResistance = ((message[startIndex + 17] & 0xFF) << 8) | (message[startIndex + 18] & 0xFF);
|
int insulationResistance = ((message[startIndex + 17] & 0xFF) << 8) | (message[startIndex + 18] & 0xFF);
|
||||||
log.info("设备 {} - 正极绝缘电阻: {} Ω", deviceSn, insulationResistance);
|
// log.info("设备 {} - 正极绝缘电阻: {} Ω", deviceSn, insulationResistance);
|
||||||
|
|
||||||
// 保存整车数据
|
// 保存整车数据
|
||||||
saveVehicleData(deviceSn, voltageValue, currentValue, soc, chargeStatus, insulationResistance, frameTime);
|
saveVehicleData(deviceSn, voltageValue, currentValue, soc, chargeStatus, insulationResistance, frameTime);
|
||||||
@ -649,7 +649,7 @@ public class MqttService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("设备 {} - 发现累计充放电数据", deviceSn);
|
// log.info("设备 {} - 发现累计充放电数据", deviceSn);
|
||||||
|
|
||||||
// 跳过索引544-545的保留字节
|
// 跳过索引544-545的保留字节
|
||||||
// 索引546-549: 累计放电电量 (0.1kWh)
|
// 索引546-549: 累计放电电量 (0.1kWh)
|
||||||
@ -661,7 +661,7 @@ public class MqttService {
|
|||||||
double dischargeKwh = dischargeEnergy * 0.1;
|
double dischargeKwh = dischargeEnergy * 0.1;
|
||||||
double chargeKwh = chargeEnergy * 0.1;
|
double chargeKwh = chargeEnergy * 0.1;
|
||||||
|
|
||||||
log.info("设备 {} - 累计充放电量: 放电{}kWh, 充电{}kWh", deviceSn, dischargeKwh, chargeKwh);
|
//log.info("设备 {} - 累计充放电量: 放电{}kWh, 充电{}kWh", deviceSn, dischargeKwh, chargeKwh);
|
||||||
|
|
||||||
saveChargeDischargeSummary(deviceSn, dischargeKwh, chargeKwh, frameTime);
|
saveChargeDischargeSummary(deviceSn, dischargeKwh, chargeKwh, frameTime);
|
||||||
|
|
||||||
@ -1041,7 +1041,7 @@ public class MqttService {
|
|||||||
|
|
||||||
BmsRuntimeData rd = new BmsRuntimeData();
|
BmsRuntimeData rd = new BmsRuntimeData();
|
||||||
rd.setDeviceId(deviceSn);
|
rd.setDeviceId(deviceSn);
|
||||||
rd.setTimestap(frameTime);
|
rd.setTimestamp(frameTime);
|
||||||
rd.setVehicleSpeed(BigDecimal.valueOf(carSpd));
|
rd.setVehicleSpeed(BigDecimal.valueOf(carSpd));
|
||||||
rd.setMileage(mileage);
|
rd.setMileage(mileage);
|
||||||
rd.setSystemVoltage(BigDecimal.valueOf(sysVol));
|
rd.setSystemVoltage(BigDecimal.valueOf(sysVol));
|
||||||
@ -1058,7 +1058,7 @@ public class MqttService {
|
|||||||
|
|
||||||
BmsSopData sop = new BmsSopData();
|
BmsSopData sop = new BmsSopData();
|
||||||
sop.setDeviceId(deviceSn);
|
sop.setDeviceId(deviceSn);
|
||||||
sop.setTimestap(frameTime);
|
sop.setTimestamp(frameTime);
|
||||||
sop.setDischargeSop10s(disSop10);
|
sop.setDischargeSop10s(disSop10);
|
||||||
sop.setDischargeSop30s(disSop30);
|
sop.setDischargeSop30s(disSop30);
|
||||||
sop.setDischargeSop60s(disSop60);
|
sop.setDischargeSop60s(disSop60);
|
||||||
@ -1075,7 +1075,7 @@ public class MqttService {
|
|||||||
|
|
||||||
BmsSystemStatus ss = new BmsSystemStatus();
|
BmsSystemStatus ss = new BmsSystemStatus();
|
||||||
ss.setDeviceId(deviceSn);
|
ss.setDeviceId(deviceSn);
|
||||||
ss.setTimestap(frameTime);
|
ss.setTimestamp(frameTime);
|
||||||
ss.setRelayStatus((long) relayStatus);
|
ss.setRelayStatus((long) relayStatus);
|
||||||
ss.setOnaaccStatus((long) onaaccStatus);
|
ss.setOnaaccStatus((long) onaaccStatus);
|
||||||
ss.setResetCount(resetCount);
|
ss.setResetCount(resetCount);
|
||||||
@ -1093,7 +1093,7 @@ public class MqttService {
|
|||||||
for (int i = 0; i < chargeTemps.length; i++) {
|
for (int i = 0; i < chargeTemps.length; i++) {
|
||||||
BmsChargeTemperature ct = new BmsChargeTemperature();
|
BmsChargeTemperature ct = new BmsChargeTemperature();
|
||||||
ct.setDeviceId(deviceSn);
|
ct.setDeviceId(deviceSn);
|
||||||
ct.setTimestap(frameTime);
|
ct.setTimestamp(frameTime);
|
||||||
ct.setTempIndex(i + 1);
|
ct.setTempIndex(i + 1);
|
||||||
ct.setTemperature(BigDecimal.valueOf(chargeTemps[i]));
|
ct.setTemperature(BigDecimal.valueOf(chargeTemps[i]));
|
||||||
ct.setCreateTime(new Date());
|
ct.setCreateTime(new Date());
|
||||||
@ -1107,7 +1107,7 @@ public class MqttService {
|
|||||||
for (int i = 0; i < rtpVals.length; i++) {
|
for (int i = 0; i < rtpVals.length; i++) {
|
||||||
BmsVoltageChannelData ch = new BmsVoltageChannelData();
|
BmsVoltageChannelData ch = new BmsVoltageChannelData();
|
||||||
ch.setDeviceId(deviceSn);
|
ch.setDeviceId(deviceSn);
|
||||||
ch.setTimestap(frameTime);
|
ch.setTimestamp(frameTime);
|
||||||
ch.setChannelType("RTP");
|
ch.setChannelType("RTP");
|
||||||
ch.setChannelIndex(i + 1);
|
ch.setChannelIndex(i + 1);
|
||||||
ch.setVoltage(BigDecimal.valueOf(rtpVals[i]));
|
ch.setVoltage(BigDecimal.valueOf(rtpVals[i]));
|
||||||
|
|||||||
@ -9,6 +9,13 @@ import com.evobms.project.iot.service.DeviceTokenService;
|
|||||||
import com.evobms.project.system.domain.BmsDevices;
|
import com.evobms.project.system.domain.BmsDevices;
|
||||||
import com.evobms.project.system.service.IBmsDevicesService;
|
import com.evobms.project.system.service.IBmsDevicesService;
|
||||||
import com.evobms.project.ota.service.IOtaTasksService;
|
import com.evobms.project.ota.service.IOtaTasksService;
|
||||||
|
import com.evobms.project.ota.domain.OtaTasks;
|
||||||
|
import com.evobms.project.iot.domain.dto.OtaUploadDto;
|
||||||
|
import com.evobms.project.iot.domain.dto.OtaLoginDto;
|
||||||
|
import com.evobms.project.iot.domain.dto.OtaHistoryResultDTO;
|
||||||
|
import com.evobms.project.iot.domain.dto.OtaProgressResultDTO;
|
||||||
|
import com.evobms.project.iot.domain.dto.OtaUploadResultDTO;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -33,6 +40,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
@ -42,10 +50,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备鉴权令牌接口
|
* 设备鉴权令牌接口
|
||||||
@ -57,7 +62,6 @@ import java.util.Map;
|
|||||||
@RequestMapping("/iot")
|
@RequestMapping("/iot")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class IotAuthController {
|
public class IotAuthController {
|
||||||
private static final Logger log = LoggerFactory.getLogger(IotAuthController.class);
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private IBmsDevicesService bmsDevicesService;
|
private IBmsDevicesService bmsDevicesService;
|
||||||
@ -68,9 +72,13 @@ public class IotAuthController {
|
|||||||
@Value("${token.header:Authorization}")
|
@Value("${token.header:Authorization}")
|
||||||
private String tokenHeader;
|
private String tokenHeader;
|
||||||
|
|
||||||
@Value("${mqtt.host:tcp://192.168.5.23:1883}")
|
@Value("${mqtt.host:tcp://61.182.73.218:11883}")
|
||||||
private String mqttHostUri;
|
private String mqttHostUri;
|
||||||
@Value("${mqtt.username:00000001}")
|
@Value("${mqtt.publicHost:61.182.73.218}")
|
||||||
|
private String mqttPublicHost;
|
||||||
|
@Value("${mqtt.publicPort:11883}")
|
||||||
|
private int mqttPublicPort;
|
||||||
|
@Value("${mqtt.username:`00000001`}")
|
||||||
private String mqttUsername;
|
private String mqttUsername;
|
||||||
@Value("${mqtt.password:741a03a10f3de6b2}")
|
@Value("${mqtt.password:741a03a10f3de6b2}")
|
||||||
private String mqttPassword;
|
private String mqttPassword;
|
||||||
@ -82,7 +90,7 @@ public class IotAuthController {
|
|||||||
private String otaCount;
|
private String otaCount;
|
||||||
|
|
||||||
// OTA文件存储位置:支持 classpath 或 文件系统目录,例如:classpath:/ota 或 /data/ota
|
// OTA文件存储位置:支持 classpath 或 文件系统目录,例如:classpath:/ota 或 /data/ota
|
||||||
@Value("${ota.repo:D:/OTA}")
|
@Value("${ota.repo:./ota}")
|
||||||
private String otaRepo;
|
private String otaRepo;
|
||||||
|
|
||||||
// OTA分片大小(字节),规范为1KB=1024字节
|
// OTA分片大小(字节),规范为1KB=1024字节
|
||||||
@ -187,19 +195,21 @@ public class IotAuthController {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析 MQTT 主机与端口
|
String host = safeTrim(mqttPublicHost);
|
||||||
String host = "192.168.5.23";
|
Integer port = mqttPublicPort > 0 ? mqttPublicPort : null;
|
||||||
int port = 1883;
|
if (StringUtils.isEmpty(host) || port == null) {
|
||||||
try {
|
try {
|
||||||
URI uri = new URI(mqttHostUri);
|
URI uri = new URI(mqttHostUri);
|
||||||
if (uri.getHost() != null) {
|
if (StringUtils.isEmpty(host) && uri.getHost() != null && !uri.getHost().isEmpty()) {
|
||||||
host = uri.getHost();
|
host = uri.getHost();
|
||||||
|
}
|
||||||
|
if (port == null && uri.getPort() != -1) {
|
||||||
|
port = uri.getPort();
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
}
|
}
|
||||||
if (uri.getPort() != -1) {
|
|
||||||
port = uri.getPort();
|
|
||||||
}
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
}
|
||||||
|
if (port == null || port <= 0) port = 11883;
|
||||||
|
|
||||||
String use = fixLengthRight(mqttUsername, 8);
|
String use = fixLengthRight(mqttUsername, 8);
|
||||||
String pass = fixLengthRight(mqttPassword, 16);
|
String pass = fixLengthRight(mqttPassword, 16);
|
||||||
@ -333,7 +343,7 @@ public class IotAuthController {
|
|||||||
// 若存在目标版本,对应固件可用时,动态计算分片总数(总长度含 64 字节校验头);否则回退配置值
|
// 若存在目标版本,对应固件可用时,动态计算分片总数(总长度含 64 字节校验头);否则回退配置值
|
||||||
Integer dynamicCount = computeOtaChunkCount(targetVersion);
|
Integer dynamicCount = computeOtaChunkCount(targetVersion);
|
||||||
if (dynamicCount != null && dynamicCount > 0) {
|
if (dynamicCount != null && dynamicCount > 0) {
|
||||||
// 转为 3 位字符串(左侧补零),例如 7 -> "007"
|
// 转为 3 位字符串(左侧补零), 7 -> "007"
|
||||||
count = fixLengthLeft(String.valueOf(dynamicCount), 3);
|
count = fixLengthLeft(String.valueOf(dynamicCount), 3);
|
||||||
} else {
|
} else {
|
||||||
// 回退使用配置 `ota.count`,默认 "001"
|
// 回退使用配置 `ota.count`,默认 "001"
|
||||||
@ -450,7 +460,7 @@ public class IotAuthController {
|
|||||||
try {
|
try {
|
||||||
int previewLen = Math.min(otaChunkSize, firmwareBytes);
|
int previewLen = Math.min(otaChunkSize, firmwareBytes);
|
||||||
byte[] preview = Arrays.copyOfRange(firmware, 0, previewLen);
|
byte[] preview = Arrays.copyOfRange(firmware, 0, previewLen);
|
||||||
// 变更说明:headerSHA256日志打印为“首片1024字节数据”的摘要,而非整包
|
// headerSHA256日志打印为“首片1024字节数据”的摘要,而非整包
|
||||||
String headerHex = sha256Hex(preview);
|
String headerHex = sha256Hex(preview);
|
||||||
log.info("OTA下载开始: sn={} version={} headerSHA256={} headerMode={} chunkSize={} firmwareBytes={} chunks={}", sn, version, headerHex, "binary32", otaChunkSize, firmwareBytes, chunks);
|
log.info("OTA下载开始: sn={} version={} headerSHA256={} headerMode={} chunkSize={} firmwareBytes={} chunks={}", sn, version, headerHex, "binary32", otaChunkSize, firmwareBytes, chunks);
|
||||||
} catch (Exception ignore) {
|
} catch (Exception ignore) {
|
||||||
@ -481,7 +491,7 @@ public class IotAuthController {
|
|||||||
} catch (Exception ignore) {
|
} catch (Exception ignore) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 后续分片:从首片实际数据(1024)之后开始切片;并在分片前置校验头(摘要基于该分片数据)
|
// 从首片实际数据(1024)之后开始切片;并在分片前置校验头(摘要基于该分片数据)
|
||||||
int firstDataLen = Math.min(otaChunkSize, firmwareBytes);
|
int firstDataLen = Math.min(otaChunkSize, firmwareBytes);
|
||||||
int offset = firstDataLen + (idx - 1) * otaChunkSize;
|
int offset = firstDataLen + (idx - 1) * otaChunkSize;
|
||||||
if (offset >= firmwareBytes) {
|
if (offset >= firmwareBytes) {
|
||||||
@ -518,10 +528,60 @@ public class IotAuthController {
|
|||||||
log.debug("OTA分片发送: sn={} version={} num={} chunkLen={}",
|
log.debug("OTA分片发送: sn={} version={} num={} chunkLen={}",
|
||||||
sn, version, num, chunk.length);
|
sn, version, num, chunk.length);
|
||||||
|
|
||||||
|
double progress = (idx + 1) * 100.0 / Math.max(chunks, 1);
|
||||||
|
try {
|
||||||
|
if (otaTasksService != null) {
|
||||||
|
String deviceId = sn;
|
||||||
|
if (bmsDevicesService != null) {
|
||||||
|
try {
|
||||||
|
BmsDevices q = new BmsDevices();
|
||||||
|
q.setDeviceSn(sn);
|
||||||
|
BmsDevices dev = bmsDevicesService.selectBmsDevicesList1(q);
|
||||||
|
if (dev != null && dev.getDeviceId() != null) {
|
||||||
|
deviceId = dev.getDeviceId();
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
}
|
||||||
|
OtaTasks filter = new OtaTasks();
|
||||||
|
filter.setDeviceId(deviceId);
|
||||||
|
filter.setFirmwareVersion(version);
|
||||||
|
List<OtaTasks> list = otaTasksService.selectOtaTasksList(filter);
|
||||||
|
OtaTasks task = (list != null && !list.isEmpty()) ? list.get(0) : new OtaTasks();
|
||||||
|
boolean isNew = (task.getId() == null || task.getId().isEmpty());
|
||||||
|
if (isNew) {
|
||||||
|
task.setDeviceId(deviceId);
|
||||||
|
task.setTaskName("OTA-" + version);
|
||||||
|
task.setFirmwareVersion(version);
|
||||||
|
task.setStatus("IN_PROGRESS");
|
||||||
|
task.setProgress((long) Math.round((float) progress));
|
||||||
|
task.setStartTime(new Date());
|
||||||
|
task.setCreateBy(sn);
|
||||||
|
otaTasksService.insertOtaTasks(task);
|
||||||
|
} else {
|
||||||
|
task.setProgress((long) Math.round((float) progress));
|
||||||
|
if (idx == chunks - 1) {
|
||||||
|
task.setStatus("DONE");
|
||||||
|
task.setEndTime(new java.util.Date());
|
||||||
|
} else {
|
||||||
|
task.setStatus("IN_PROGRESS");
|
||||||
|
}
|
||||||
|
task.setUpdateTime(new java.util.Date());
|
||||||
|
otaTasksService.updateOtaTasks(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("OTA任务记录更新失败: sn={} version={} err={}", sn, version, ex.getMessage());
|
||||||
|
}
|
||||||
return ResponseEntity
|
return ResponseEntity
|
||||||
.ok()
|
.ok()
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
.contentLength(chunk.length)
|
.contentLength(chunk.length)
|
||||||
|
.header("X-OTA-Chunk-Index", String.valueOf(idx))
|
||||||
|
.header("X-OTA-Chunk-Total", String.valueOf(chunks))
|
||||||
|
.header("X-OTA-Progress", String.format(java.util.Locale.ROOT, "%.2f", progress))
|
||||||
|
.header("X-OTA-Chunk-Size", String.valueOf(otaChunkSize))
|
||||||
|
.header("X-OTA-Firmware-Bytes", String.valueOf(firmwareBytes))
|
||||||
|
.header("X-OTA-Mode", mode)
|
||||||
.body(chunk);
|
.body(chunk);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("OTA分片下载异常: {}", e.getMessage(), e);
|
log.error("OTA分片下载异常: {}", e.getMessage(), e);
|
||||||
@ -552,6 +612,240 @@ public class IotAuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/ota/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
@ApiOperation(value = "上传OTA固件", notes = "上传固件到 ota.repo 指定目录,保存为 <version>.bin", response = OtaUploadResultDTO.class)
|
||||||
|
public OtaUploadResultDTO uploadOta(@RequestParam("version") String version, @RequestParam("file") MultipartFile file) {
|
||||||
|
OtaUploadResultDTO resp = new OtaUploadResultDTO();
|
||||||
|
try {
|
||||||
|
if (StringUtils.isEmpty(version) || file == null || file.isEmpty()) {
|
||||||
|
resp.setCode(BboxApiConstants.CODE_PARAM_INVALID);
|
||||||
|
resp.setSuccess("0");
|
||||||
|
resp.setMsg("version或file缺失");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
if (otaRepo.startsWith("classpath:")) {
|
||||||
|
resp.setCode(BboxApiConstants.CODE_PARAM_INVALID);
|
||||||
|
resp.setSuccess("0");
|
||||||
|
resp.setMsg("classpath仓库不支持上传,请配置文件系统路径");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
Path dir = Paths.get(otaRepo);
|
||||||
|
if (!Files.exists(dir)) {
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
}
|
||||||
|
Path target = dir.resolve(version + ".bin");
|
||||||
|
Files.copy(file.getInputStream(), target, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
resp.setCode(BboxApiConstants.CODE_SUCCESS);
|
||||||
|
resp.setSuccess("1");
|
||||||
|
resp.setPath(target.toString());
|
||||||
|
resp.setSize(Files.size(target));
|
||||||
|
log.info("OTA上传成功: version={} repo={} target={} size={}", version, otaRepo, target.toString(), resp.getSize());
|
||||||
|
// 返回分片元信息
|
||||||
|
Integer chunks = computeOtaChunkCount(version);
|
||||||
|
resp.setChunks(chunks);
|
||||||
|
resp.setChunkSize(otaChunkSize);
|
||||||
|
return resp;
|
||||||
|
} catch (IOException e) {
|
||||||
|
resp.setCode(BboxApiConstants.CODE_FILE_NOT_FOUND);
|
||||||
|
resp.setSuccess("0");
|
||||||
|
resp.setMsg(e.getMessage());
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ota/meta")
|
||||||
|
@ApiOperation(value = "查询OTA元信息", notes = "返回分片总数、分片大小、固件字节数")
|
||||||
|
public Map<String, Object> otaMeta(@RequestParam("version") String version) {
|
||||||
|
Map<String, Object> resp = new HashMap<>();
|
||||||
|
if (StringUtils.isEmpty(version)) {
|
||||||
|
resp.put("code", BboxApiConstants.CODE_PARAM_INVALID);
|
||||||
|
resp.put("success", "0");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
byte[] fw = loadFirmwareBytes(version);
|
||||||
|
if (fw == null) {
|
||||||
|
resp.put("code", BboxApiConstants.CODE_FILE_NOT_FOUND);
|
||||||
|
resp.put("success", "0");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
resp.put("code", BboxApiConstants.CODE_SUCCESS);
|
||||||
|
resp.put("success", "1");
|
||||||
|
resp.put("firmwareBytes", fw.length);
|
||||||
|
resp.put("chunkSize", otaChunkSize);
|
||||||
|
resp.put("chunks", (fw.length + otaChunkSize - 1) / otaChunkSize);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ota/history")
|
||||||
|
@ApiOperation(value = "OTA操作历史", notes = "前端用于展示操作历史列表;sn为空时可从Authorization解析")
|
||||||
|
public OtaHistoryResultDTO otaHistory(@RequestParam(value = "sn", required = false) String sn,
|
||||||
|
@RequestParam(value = "version", required = false) String version,
|
||||||
|
@RequestParam(value = "limit", required = false) Integer limit,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
OtaHistoryResultDTO out = new OtaHistoryResultDTO();
|
||||||
|
String snx = safeTrim(sn);
|
||||||
|
if (StringUtils.isEmpty(snx)) {
|
||||||
|
try {
|
||||||
|
snx = deviceTokenService.resolveSn(request);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(snx)) {
|
||||||
|
out.setCode(BboxApiConstants.CODE_PARAM_INVALID);
|
||||||
|
out.setSuccess("0");
|
||||||
|
out.setMsg("sn不能为空");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (otaTasksService == null) {
|
||||||
|
out.setCode(BboxApiConstants.CODE_NOT_FOUND);
|
||||||
|
out.setSuccess("0");
|
||||||
|
out.setMsg("otaTasksService未启用");
|
||||||
|
out.setSn(snx);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
String deviceId = resolveDeviceIdBySn(snx);
|
||||||
|
OtaTasks filter = new OtaTasks();
|
||||||
|
filter.setDeviceId(deviceId);
|
||||||
|
if (StringUtils.isNotEmpty(version)) {
|
||||||
|
filter.setFirmwareVersion(version);
|
||||||
|
}
|
||||||
|
List<OtaTasks> list = otaTasksService.selectOtaTasksList(filter);
|
||||||
|
List<OtaTasks> sorted = list == null ? new ArrayList<>() : new ArrayList<>(list);
|
||||||
|
sorted.sort(Comparator.comparing(this::resolveOtaSortTime, Comparator.nullsLast(Date::compareTo)).reversed());
|
||||||
|
|
||||||
|
int lim = (limit == null || limit <= 0) ? 50 : limit;
|
||||||
|
if (sorted.size() > lim) {
|
||||||
|
sorted = new ArrayList<>(sorted.subList(0, lim));
|
||||||
|
}
|
||||||
|
|
||||||
|
out.setCode(BboxApiConstants.CODE_SUCCESS);
|
||||||
|
out.setSuccess("1");
|
||||||
|
out.setMsg(BboxApiConstants.MSG_SUCCESS);
|
||||||
|
out.setSn(snx);
|
||||||
|
out.setDeviceId(deviceId);
|
||||||
|
out.setTaskList(sorted);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA 任务进度(管理端轮询)
|
||||||
|
* - 路径:GET /iot/ota/progress
|
||||||
|
* - 参数:sn(可选,缺省时从 Authorization 解析设备)、version(可选,缺省时取该设备最新任务)
|
||||||
|
* - 说明:`progress` 为任务整体 0–100;`totalChunks`/`chunkSize` 与设备登录 `count`、分片下载逻辑对齐;
|
||||||
|
* `downloadedChunks` 由 `progress` 与 `totalChunks` 推算,用于可选的「分片进度」文案(与浏览器上传到 /iot/ota/upload 无关)。
|
||||||
|
* - 响应示例:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "code": 200,
|
||||||
|
* "success": "1",
|
||||||
|
* "msg": "success",
|
||||||
|
* "sn": "137PBP1M11001AFCF0000007",
|
||||||
|
* "deviceId": "BAT_001",
|
||||||
|
* "firmwareVersion": "1.0.1",
|
||||||
|
* "status": "IN_PROGRESS",
|
||||||
|
* "progress": 45,
|
||||||
|
* "totalChunks": 120,
|
||||||
|
* "downloadedChunks": 54,
|
||||||
|
* "chunkSize": 1024,
|
||||||
|
* "startTime": "2026-05-11 10:00:00",
|
||||||
|
* "endTime": null,
|
||||||
|
* "updateTime": "2026-05-11 10:05:00"
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@GetMapping("/ota/progress")
|
||||||
|
@ApiOperation(value = "OTA进度条", notes = "前端轮询展示设备 OTA 任务进度;含 totalChunks/downloadedChunks 供可选分片文案;上传进度请用前端 multipart 的 on-progress")
|
||||||
|
public OtaProgressResultDTO otaProgress(@RequestParam(value = "sn", required = false) String sn,
|
||||||
|
@RequestParam(value = "version", required = false) String version,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
OtaProgressResultDTO out = new OtaProgressResultDTO();
|
||||||
|
String snx = safeTrim(sn);
|
||||||
|
if (StringUtils.isEmpty(snx)) {
|
||||||
|
try {
|
||||||
|
snx = deviceTokenService.resolveSn(request);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(snx)) {
|
||||||
|
out.setCode(BboxApiConstants.CODE_PARAM_INVALID);
|
||||||
|
out.setSuccess("0");
|
||||||
|
out.setMsg("sn不能为空");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (otaTasksService == null) {
|
||||||
|
out.setCode(BboxApiConstants.CODE_NOT_FOUND);
|
||||||
|
out.setSuccess("0");
|
||||||
|
out.setMsg("otaTasksService未启用");
|
||||||
|
out.setSn(snx);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
String deviceId = resolveDeviceIdBySn(snx);
|
||||||
|
OtaTasks filter = new OtaTasks();
|
||||||
|
filter.setDeviceId(deviceId);
|
||||||
|
if (StringUtils.isNotEmpty(version)) {
|
||||||
|
filter.setFirmwareVersion(version);
|
||||||
|
}
|
||||||
|
List<OtaTasks> list = otaTasksService.selectOtaTasksList(filter);
|
||||||
|
List<OtaTasks> sorted = list == null ? new ArrayList<>() : new ArrayList<>(list);
|
||||||
|
sorted.sort(Comparator.comparing(this::resolveOtaSortTime, Comparator.nullsLast(Date::compareTo)).reversed());
|
||||||
|
OtaTasks latest = sorted.isEmpty() ? null : sorted.get(0);
|
||||||
|
|
||||||
|
out.setCode(BboxApiConstants.CODE_SUCCESS);
|
||||||
|
out.setSuccess("1");
|
||||||
|
out.setMsg(BboxApiConstants.MSG_SUCCESS);
|
||||||
|
out.setSn(snx);
|
||||||
|
out.setDeviceId(deviceId);
|
||||||
|
if (latest != null) {
|
||||||
|
out.setFirmwareVersion(latest.getFirmwareVersion());
|
||||||
|
out.setStatus(latest.getStatus());
|
||||||
|
out.setProgress(latest.getProgress() == null ? 0L : latest.getProgress());
|
||||||
|
out.setStartTime(latest.getStartTime());
|
||||||
|
out.setEndTime(latest.getEndTime());
|
||||||
|
out.setUpdateTime(latest.getUpdateTime());
|
||||||
|
} else {
|
||||||
|
out.setFirmwareVersion(version);
|
||||||
|
out.setStatus("NONE");
|
||||||
|
out.setProgress(0L);
|
||||||
|
}
|
||||||
|
attachOtaChunkProgressFields(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为进度接口附加分片元数据:totalChunks 与登录接口 count 同源;downloadedChunks 由任务 progress 反推(与分片下载时写回 DB 的规则一致)。
|
||||||
|
*/
|
||||||
|
private void attachOtaChunkProgressFields(OtaProgressResultDTO out) {
|
||||||
|
out.setChunkSize(otaChunkSize);
|
||||||
|
String fv = out.getFirmwareVersion();
|
||||||
|
if (StringUtils.isEmpty(fv)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Integer total = computeOtaChunkCount(fv);
|
||||||
|
out.setTotalChunks(total);
|
||||||
|
if (total == null || total <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String st = out.getStatus();
|
||||||
|
if (isOtaFinishedSuccess(st)) {
|
||||||
|
out.setDownloadedChunks(total);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long prog = out.getProgress() == null ? 0L : out.getProgress();
|
||||||
|
int est = (int) Math.round(prog * (double) total / 100.0);
|
||||||
|
est = Math.max(0, Math.min(est, total));
|
||||||
|
out.setDownloadedChunks(est);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isOtaFinishedSuccess(String status) {
|
||||||
|
if (StringUtils.isEmpty(status)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String u = status.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return "DONE".equals(u) || "SUCCESS".equals(u) || "COMPLETED".equals(u);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造 OTA 负载(仅 binary32)
|
* 构造 OTA 负载(仅 binary32)
|
||||||
* - 格式:`[32字节SHA256原始摘要] + [固件]`
|
* - 格式:`[32字节SHA256原始摘要] + [固件]`
|
||||||
@ -600,6 +894,27 @@ public class IotAuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveDeviceIdBySn(String sn) {
|
||||||
|
String deviceId = sn;
|
||||||
|
if (bmsDevicesService != null) {
|
||||||
|
try {
|
||||||
|
BmsDevices dev = bmsDevicesService.selectBmsDevicesBySn(sn);
|
||||||
|
if (dev != null && StringUtils.isNotEmpty(dev.getDeviceId())) {
|
||||||
|
deviceId = dev.getDeviceId();
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date resolveOtaSortTime(OtaTasks task) {
|
||||||
|
if (task == null) return null;
|
||||||
|
if (task.getUpdateTime() != null) return task.getUpdateTime();
|
||||||
|
if (task.getCreateTime() != null) return task.getCreateTime();
|
||||||
|
return task.getStartTime();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算 SHA256 的十六进制字符串
|
* 计算 SHA256 的十六进制字符串
|
||||||
* - 输出:小写,长度 64 的 ASCII 字符串
|
* - 输出:小写,长度 64 的 ASCII 字符串
|
||||||
|
|||||||
@ -52,7 +52,7 @@ public class IotOtaController {
|
|||||||
MDC.put("sn", sn);
|
MDC.put("sn", sn);
|
||||||
MDC.put("version", prevVersion);
|
MDC.put("version", prevVersion);
|
||||||
|
|
||||||
// 规范化状态:仅识别 "1" 为成功,其余按失败处理;仍返回成功表示上报接口调用成功
|
|
||||||
String normalizedStatus = "0";
|
String normalizedStatus = "0";
|
||||||
if ("1".equals(statusStr)) {
|
if ("1".equals(statusStr)) {
|
||||||
normalizedStatus = "1";
|
normalizedStatus = "1";
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.evobms.project.iot.domain.dto;
|
||||||
|
|
||||||
|
import com.evobms.project.ota.domain.OtaTasks;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ApiModel(value = "OtaHistoryResultDTO", description = "OTA操作历史查询结果")
|
||||||
|
@Data
|
||||||
|
public class OtaHistoryResultDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "状态码", example = "200")
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否成功,1成功/0失败", example = "1")
|
||||||
|
private String success;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "提示信息", example = "success")
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备SN", example = "137PBP1M11001AFCF0000007")
|
||||||
|
private String sn;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备ID(平台侧deviceId)", example = "BAT_001")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "任务列表(按最近时间倒序)")
|
||||||
|
private List<OtaTasks> taskList;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.evobms.project.iot.domain.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备登录DTO
|
||||||
|
*/
|
||||||
|
@ApiModel(value = "OtaLoginDto", description = "设备登录DTO")
|
||||||
|
@Data
|
||||||
|
public class OtaLoginDto {
|
||||||
|
@ApiModelProperty(value = "固件版本")
|
||||||
|
private String version;
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package com.evobms.project.iot.domain.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@ApiModel(value = "OtaProgressResultDTO", description = "OTA进度查询结果(用于进度条)")
|
||||||
|
@Data
|
||||||
|
public class OtaProgressResultDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "状态码", example = "200")
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否成功,1成功/0失败", example = "1")
|
||||||
|
private String success;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "提示信息", example = "success")
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备SN", example = "137PBP1M11001AFCF0000007")
|
||||||
|
private String sn;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备ID(平台侧deviceId)", example = "BAT_001")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "目标固件版本", example = "v1.0.3")
|
||||||
|
private String firmwareVersion;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "任务状态", example = "IN_PROGRESS")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "进度(0-100)", example = "30")
|
||||||
|
private Long progress;
|
||||||
|
|
||||||
|
/** 与设备登录接口返回的 count、GET /iot/ota/meta 的 chunks 同一口径(按固件字节与 ota.chunk-size 计算) */
|
||||||
|
@ApiModelProperty(value = "OTA 分片总数(与设备侧 count 对齐;服务端根据仓库内固件计算,缺文件时为 null)", example = "120")
|
||||||
|
private Integer totalChunks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于当前任务的 progress 与 totalChunks 推算的已拉取分片数(与 downloadOtaChunk 中按片更新 progress 的规则一致;取整误差 ±1 片属正常)。
|
||||||
|
* 任务状态为 DONE 等终态成功时置为 totalChunks。
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "推算的已下载分片数(来自 progress×totalChunks,仅供展示;无 totalChunks 时为 null)", example = "54")
|
||||||
|
private Integer downloadedChunks;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单分片字节数(与 ota.chunk-size 一致)", example = "1024")
|
||||||
|
private Integer chunkSize;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "开始时间")
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "结束时间")
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "更新时间")
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.evobms.project.iot.domain.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA固件上传DTO
|
||||||
|
*/
|
||||||
|
@ApiModel(value = "OtaUploadDto", description = "OTA固件上传DTO")
|
||||||
|
@Data
|
||||||
|
public class OtaUploadDto {
|
||||||
|
@ApiModelProperty(value = "设备SN码")
|
||||||
|
private String deviceSn;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "固件版本")
|
||||||
|
private String version;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.evobms.project.iot.domain.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA固件上传响应DTO
|
||||||
|
*/
|
||||||
|
@ApiModel(value = "OtaUploadResponseDto", description = "OTA固件上传响应DTO")
|
||||||
|
@Data
|
||||||
|
public class OtaUploadResponseDto {
|
||||||
|
@ApiModelProperty(value = "状态码")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否成功")
|
||||||
|
private String success;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "上传进度")
|
||||||
|
private Integer progress;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "消息")
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.evobms.project.iot.domain.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@ApiModel(value = "OtaUploadResultDTO", description = "OTA固件上传结果")
|
||||||
|
@Data
|
||||||
|
public class OtaUploadResultDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "状态码", example = "200")
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否成功,1成功/0失败", example = "1")
|
||||||
|
private String success;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "提示信息", example = "success")
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "固件落盘路径", example = "E:/OTA/v1.0.3.bin")
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "文件大小(字节)", example = "102400")
|
||||||
|
private Long size;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "分片总数", example = "100")
|
||||||
|
private Integer chunks;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "分片大小(字节)", example = "1024")
|
||||||
|
private Integer chunkSize;
|
||||||
|
}
|
||||||
@ -1,6 +1,9 @@
|
|||||||
package com.evobms.project.ota.controller;
|
package com.evobms.project.ota.controller;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -12,10 +15,14 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import com.evobms.common.utils.StringUtils;
|
||||||
import com.evobms.framework.aspectj.lang.annotation.Log;
|
import com.evobms.framework.aspectj.lang.annotation.Log;
|
||||||
import com.evobms.framework.aspectj.lang.enums.BusinessType;
|
import com.evobms.framework.aspectj.lang.enums.BusinessType;
|
||||||
import com.evobms.project.ota.domain.OtaTasks;
|
import com.evobms.project.ota.domain.OtaTasks;
|
||||||
|
import com.evobms.project.ota.dto.OtaTaskQueryDTO;
|
||||||
import com.evobms.project.ota.service.IOtaTasksService;
|
import com.evobms.project.ota.service.IOtaTasksService;
|
||||||
|
import com.evobms.project.system.domain.BmsDevices;
|
||||||
|
import com.evobms.project.system.service.IBmsDevicesService;
|
||||||
import com.evobms.framework.web.controller.BaseController;
|
import com.evobms.framework.web.controller.BaseController;
|
||||||
import com.evobms.framework.web.domain.AjaxResult;
|
import com.evobms.framework.web.domain.AjaxResult;
|
||||||
import com.evobms.common.utils.poi.ExcelUtil;
|
import com.evobms.common.utils.poi.ExcelUtil;
|
||||||
@ -37,6 +44,9 @@ public class OtaTasksController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IOtaTasksService otaTasksService;
|
private IOtaTasksService otaTasksService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private IBmsDevicesService bmsDevicesService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询OTA任务列表
|
* 查询OTA任务列表
|
||||||
*/
|
*/
|
||||||
@ -50,6 +60,44 @@ public class OtaTasksController extends BaseController
|
|||||||
return getDataTable(list);
|
return getDataTable(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据SN查询设备OTA更新信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/sn/{sn}")
|
||||||
|
@ApiOperation(value = "根据SN查询OTA任务信息", notes = "返回设备对象、最新任务和任务列表,供前端展示设备升级信息")
|
||||||
|
public AjaxResult getBySn(@PathVariable("sn") String sn)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(sn))
|
||||||
|
{
|
||||||
|
return AjaxResult.error("sn不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
BmsDevices device = null;
|
||||||
|
String deviceId = sn;
|
||||||
|
if (bmsDevicesService != null)
|
||||||
|
{
|
||||||
|
device = bmsDevicesService.selectBmsDevicesBySn(sn);
|
||||||
|
if (device != null && StringUtils.isNotEmpty(device.getDeviceId()))
|
||||||
|
{
|
||||||
|
deviceId = device.getDeviceId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OtaTasks query = new OtaTasks();
|
||||||
|
query.setDeviceId(deviceId);
|
||||||
|
List<OtaTasks> taskList = otaTasksService.selectOtaTasksList(query);
|
||||||
|
List<OtaTasks> sortedTaskList = taskList == null ? new ArrayList<>() : new ArrayList<>(taskList);
|
||||||
|
sortedTaskList.sort(Comparator.comparing(this::resolveSortTime,
|
||||||
|
Comparator.nullsLast(Date::compareTo)).reversed());
|
||||||
|
|
||||||
|
OtaTaskQueryDTO dto = new OtaTaskQueryDTO();
|
||||||
|
dto.setSn(sn);
|
||||||
|
dto.setDevice(device);
|
||||||
|
dto.setTaskList(sortedTaskList);
|
||||||
|
dto.setLatestTask(sortedTaskList.isEmpty() ? null : sortedTaskList.get(0));
|
||||||
|
return success(dto);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出OTA任务列表
|
* 导出OTA任务列表
|
||||||
*/
|
*/
|
||||||
@ -109,4 +157,21 @@ public class OtaTasksController extends BaseController
|
|||||||
{
|
{
|
||||||
return toAjax(otaTasksService.deleteOtaTasksByIds(ids));
|
return toAjax(otaTasksService.deleteOtaTasksByIds(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Date resolveSortTime(OtaTasks task)
|
||||||
|
{
|
||||||
|
if (task == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (task.getUpdateTime() != null)
|
||||||
|
{
|
||||||
|
return task.getUpdateTime();
|
||||||
|
}
|
||||||
|
if (task.getCreateTime() != null)
|
||||||
|
{
|
||||||
|
return task.getCreateTime();
|
||||||
|
}
|
||||||
|
return task.getStartTime();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.evobms.project.ota.dto;
|
||||||
|
|
||||||
|
import com.evobms.project.ota.domain.OtaTasks;
|
||||||
|
import com.evobms.project.system.domain.BmsDevices;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "OtaTaskQueryDTO", description = "根据SN查询设备OTA任务响应")
|
||||||
|
public class OtaTaskQueryDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备SN", example = "137PBP1M11001AFCF0000007")
|
||||||
|
private String sn;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "设备对象")
|
||||||
|
private BmsDevices device;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "最新一条OTA任务")
|
||||||
|
private OtaTasks latestTask;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "该设备的OTA任务列表,按最近更新时间倒序")
|
||||||
|
private List<OtaTasks> taskList;
|
||||||
|
}
|
||||||
@ -1,29 +1,35 @@
|
|||||||
package com.evobms.project.system.controller;
|
package com.evobms.project.system.controller;
|
||||||
|
|
||||||
import java.util.List;
|
import java.math.BigDecimal;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Function;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.evobms.project.vehicledata.domain.VehicleLocation;
|
||||||
|
import com.evobms.project.vehicledata.service.IVehicleLocationService;
|
||||||
|
import io.swagger.annotations.ApiParam;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import com.evobms.framework.aspectj.lang.annotation.Log;
|
import com.evobms.framework.aspectj.lang.annotation.Log;
|
||||||
import com.evobms.framework.aspectj.lang.enums.BusinessType;
|
import com.evobms.framework.aspectj.lang.enums.BusinessType;
|
||||||
import com.evobms.project.system.domain.BmsDevices;
|
import com.evobms.project.system.domain.BmsDevices;
|
||||||
import com.evobms.project.system.service.IBmsDevicesService;
|
import com.evobms.project.system.service.IBmsDevicesService;
|
||||||
import com.evobms.framework.web.controller.BaseController;
|
import com.evobms.framework.web.controller.BaseController;
|
||||||
import com.evobms.framework.web.domain.AjaxResult;
|
import com.evobms.framework.web.domain.AjaxResult;
|
||||||
|
import com.evobms.framework.web.domain.R;
|
||||||
|
import com.evobms.common.constant.HttpStatus;
|
||||||
import com.evobms.common.utils.poi.ExcelUtil;
|
import com.evobms.common.utils.poi.ExcelUtil;
|
||||||
import com.evobms.framework.web.page.TableDataInfo;
|
import com.evobms.framework.web.page.TableDataInfo;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.evobms.project.vehicledata.dto.DeviceHistoryFlatRowDTO;
|
||||||
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO;
|
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO;
|
||||||
|
import com.evobms.project.vehicledata.dto.DeviceRealtimeAggregateDTO;
|
||||||
import com.evobms.project.vehicledata.dto.NameValueDTO;
|
import com.evobms.project.vehicledata.dto.NameValueDTO;
|
||||||
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.SystemStatusSection;
|
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.SystemStatusSection;
|
||||||
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.RunStatusSection;
|
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.RunStatusSection;
|
||||||
@ -31,15 +37,20 @@ import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.ChargeSection;
|
|||||||
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.StatisticsSection;
|
import com.evobms.project.vehicledata.dto.DeviceRealtimeDTO.StatisticsSection;
|
||||||
import com.evobms.project.vehicledata.dto.LineSeriesDTO;
|
import com.evobms.project.vehicledata.dto.LineSeriesDTO;
|
||||||
import com.evobms.project.vehicledata.dto.TimePointDTO;
|
import com.evobms.project.vehicledata.dto.TimePointDTO;
|
||||||
|
import com.evobms.project.vehicledata.dto.ChargeDischargeMonthDTO;
|
||||||
|
import com.evobms.project.vehicledata.dto.ChargeDischargeRecordDTO;
|
||||||
|
import com.evobms.project.vehicledata.dto.ChargeDischargeHistoryDTO;
|
||||||
|
import com.evobms.project.vehicledata.service.DeviceRealtimeHistoryAssembler;
|
||||||
import com.evobms.project.vehicledata.service.IVehicleDataService;
|
import com.evobms.project.vehicledata.service.IVehicleDataService;
|
||||||
import com.evobms.project.vehicledata.domain.VehicleData;
|
import com.evobms.project.vehicledata.domain.VehicleData;
|
||||||
import com.evobms.project.battery.service.IExtremeValuesService;
|
import com.evobms.project.battery.service.IExtremeValuesService;
|
||||||
|
import com.evobms.project.battery.service.ISubsystemVoltageService;
|
||||||
|
import com.evobms.project.battery.service.ISubsystemTemperatureService;
|
||||||
import com.evobms.project.battery.domain.ExtremeValues;
|
import com.evobms.project.battery.domain.ExtremeValues;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemVoltage;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemTemperature;
|
||||||
import com.evobms.project.bms.service.*;
|
import com.evobms.project.bms.service.*;
|
||||||
import com.evobms.project.bms.domain.*;
|
import com.evobms.project.bms.domain.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BBOX管理Controller
|
* BBOX管理Controller
|
||||||
@ -55,6 +66,18 @@ public class BmsDevicesController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IBmsDevicesService bmsDevicesService;
|
private IBmsDevicesService bmsDevicesService;
|
||||||
|
|
||||||
|
@Autowired private IVehicleDataService vehicleDataService;
|
||||||
|
@Autowired private IExtremeValuesService extremeValuesService;
|
||||||
|
@Autowired private IBmsSystemStatusService bmsSystemStatusService;
|
||||||
|
@Autowired private IBmsChargeDetailService bmsChargeDetailService;
|
||||||
|
@Autowired private IBmsChargeTemperatureService bmsChargeTemperatureService;
|
||||||
|
@Autowired private IBmsVoltageChannelDataService bmsVoltageChannelDataService;
|
||||||
|
@Autowired private IBmsSopDataService bmsSopDataService;
|
||||||
|
@Autowired private IVehicleLocationService vehicleLocationService;
|
||||||
|
@Autowired private IBmsRuntimeDataService bmsRuntimeDataService;
|
||||||
|
@Autowired private ISubsystemVoltageService subsystemVoltageService;
|
||||||
|
@Autowired private ISubsystemTemperatureService subsystemTemperatureService;
|
||||||
|
@Autowired private DeviceRealtimeHistoryAssembler deviceRealtimeHistoryAssembler;
|
||||||
/**
|
/**
|
||||||
* 查询BBOX管理列表
|
* 查询BBOX管理列表
|
||||||
*/
|
*/
|
||||||
@ -68,6 +91,124 @@ public class BmsDevicesController extends BaseController
|
|||||||
return getDataTable(list);
|
return getDataTable(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/history/{deviceId}")
|
||||||
|
@ApiOperation(value = "运行历史计算器(充/放电统计)", response = ChargeDischargeHistoryDTO.class)
|
||||||
|
public R<ChargeDischargeHistoryDTO> history(
|
||||||
|
@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId,
|
||||||
|
@ApiParam(value = "开始时间戳(毫秒)", required = false) @RequestParam(value = "start", required = false) Long startMillis,
|
||||||
|
@ApiParam(value = "结束时间戳(毫秒)", required = false) @RequestParam(value = "end", required = false) Long endMillis
|
||||||
|
) {
|
||||||
|
Date end = endMillis == null ? new Date() : new Date(endMillis);
|
||||||
|
Date start = startMillis == null ? new Date(end.getTime() - 30L * 86400_000L) : new Date(startMillis);
|
||||||
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
SimpleDateFormat mf = new SimpleDateFormat("yyyy-MM");
|
||||||
|
VehicleData q = new VehicleData();
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("beginTime", df.format(start));
|
||||||
|
params.put("endTime", df.format(end));
|
||||||
|
q.setDeviceId(deviceId);
|
||||||
|
q.setParams(params);
|
||||||
|
List<VehicleData> all = vehicleDataService.selectVehicleDataList(q);
|
||||||
|
all.removeIf(v -> v.getTimestamp() == null);
|
||||||
|
all.sort(Comparator.comparing(VehicleData::getTimestamp));
|
||||||
|
|
||||||
|
List<ChargeDischargeRecordDTO> records = new ArrayList<>();
|
||||||
|
String currentStatus = null;
|
||||||
|
int segmentStartIdx = -1;
|
||||||
|
Integer prevSoc = null;
|
||||||
|
for (int i = 0; i < all.size(); i++) {
|
||||||
|
VehicleData v = all.get(i);
|
||||||
|
Integer soc = v.getSoc();
|
||||||
|
BigDecimal cur = v.getTotalCurrent();
|
||||||
|
String status = null;
|
||||||
|
if (cur != null) {
|
||||||
|
int cmp = cur.compareTo(BigDecimal.ZERO);
|
||||||
|
if (cmp > 0) status = "充电";
|
||||||
|
else if (cmp < 0) status = "放电";
|
||||||
|
}
|
||||||
|
if (status == null && prevSoc != null && soc != null) {
|
||||||
|
if (soc > prevSoc) status = "充电";
|
||||||
|
else if (soc < prevSoc) status = "放电";
|
||||||
|
}
|
||||||
|
if (status == null && v.getChargingStatus() != null) {
|
||||||
|
Integer st = v.getChargingStatus();
|
||||||
|
if (st == 1 || st == 2) status = "充电";
|
||||||
|
else if (st == 3) status = "放电";
|
||||||
|
}
|
||||||
|
if (status == null) {
|
||||||
|
prevSoc = soc;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (currentStatus == null) {
|
||||||
|
currentStatus = status;
|
||||||
|
segmentStartIdx = i;
|
||||||
|
} else if (!currentStatus.equals(status)) {
|
||||||
|
// finalize previous segment
|
||||||
|
ChargeDischargeRecordDTO rec = new ChargeDischargeRecordDTO();
|
||||||
|
rec.setStatus(currentStatus);
|
||||||
|
com.evobms.project.vehicledata.domain.VehicleData sv = all.get(segmentStartIdx);
|
||||||
|
com.evobms.project.vehicledata.domain.VehicleData evd = all.get(i - 1);
|
||||||
|
rec.setStartTime(df.format(sv.getTimestamp()));
|
||||||
|
rec.setEndTime(df.format(evd.getTimestamp()));
|
||||||
|
rec.setStartSoc(sv.getSoc());
|
||||||
|
rec.setEndSoc(evd.getSoc());
|
||||||
|
long seconds = (evd.getTimestamp().getTime() - sv.getTimestamp().getTime()) / 1000L;
|
||||||
|
java.math.BigDecimal hours = new java.math.BigDecimal(seconds).divide(new java.math.BigDecimal(3600), 2, java.math.RoundingMode.HALF_UP);
|
||||||
|
rec.setDurationHours(hours);
|
||||||
|
// 忽略过短区段(小于5分钟)
|
||||||
|
if (seconds >= 300) {
|
||||||
|
records.add(rec);
|
||||||
|
}
|
||||||
|
currentStatus = status;
|
||||||
|
segmentStartIdx = i;
|
||||||
|
}
|
||||||
|
prevSoc = soc;
|
||||||
|
}
|
||||||
|
// finalize last
|
||||||
|
if (currentStatus != null && segmentStartIdx >= 0 && all.size() - 1 >= segmentStartIdx) {
|
||||||
|
ChargeDischargeRecordDTO rec = new ChargeDischargeRecordDTO();
|
||||||
|
rec.setStatus(currentStatus);
|
||||||
|
com.evobms.project.vehicledata.domain.VehicleData sv = all.get(segmentStartIdx);
|
||||||
|
com.evobms.project.vehicledata.domain.VehicleData evd = all.get(all.size() - 1);
|
||||||
|
rec.setStartTime(df.format(sv.getTimestamp()));
|
||||||
|
rec.setEndTime(df.format(evd.getTimestamp()));
|
||||||
|
rec.setStartSoc(sv.getSoc());
|
||||||
|
rec.setEndSoc(evd.getSoc());
|
||||||
|
long seconds = (evd.getTimestamp().getTime() - sv.getTimestamp().getTime()) / 1000L;
|
||||||
|
java.math.BigDecimal hours = new java.math.BigDecimal(seconds).divide(new java.math.BigDecimal(3600), 2, java.math.RoundingMode.HALF_UP);
|
||||||
|
rec.setDurationHours(hours);
|
||||||
|
if (seconds >= 300) {
|
||||||
|
records.add(rec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java.util.Map<String, java.math.BigDecimal[]> monthAgg = new java.util.HashMap<>();
|
||||||
|
for (ChargeDischargeRecordDTO rec : records) {
|
||||||
|
String m = rec.getStartTime().substring(0, 7);
|
||||||
|
java.math.BigDecimal[] pair = monthAgg.computeIfAbsent(m, k -> new java.math.BigDecimal[]{java.math.BigDecimal.ZERO, java.math.BigDecimal.ZERO});
|
||||||
|
if ("充电".equals(rec.getStatus())) {
|
||||||
|
pair[0] = pair[0].add(rec.getDurationHours());
|
||||||
|
} else {
|
||||||
|
pair[1] = pair[1].add(rec.getDurationHours());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
java.util.List<ChargeDischargeMonthDTO> months = new java.util.ArrayList<>();
|
||||||
|
java.util.List<String> keys = new java.util.ArrayList<>(monthAgg.keySet());
|
||||||
|
keys.sort(String::compareTo);
|
||||||
|
for (String k : keys) {
|
||||||
|
ChargeDischargeMonthDTO m = new ChargeDischargeMonthDTO();
|
||||||
|
m.setMonth(k);
|
||||||
|
java.math.BigDecimal[] pair = monthAgg.get(k);
|
||||||
|
m.setChargeHours(pair[0]);
|
||||||
|
m.setDischargeHours(pair[1]);
|
||||||
|
months.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChargeDischargeHistoryDTO out = new ChargeDischargeHistoryDTO();
|
||||||
|
out.setMonths(months);
|
||||||
|
out.setRecords(records);
|
||||||
|
return R.ok(out);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 导出BBOX管理列表
|
* 导出BBOX管理列表
|
||||||
*/
|
*/
|
||||||
@ -93,165 +234,118 @@ public class BmsDevicesController extends BaseController
|
|||||||
return success(bmsDevicesService.selectBmsDevicesById(id));
|
return success(bmsDevicesService.selectBmsDevicesById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired private IVehicleDataService vehicleDataService;
|
|
||||||
@Autowired private IExtremeValuesService extremeValuesService;
|
/** 子系统去重预取行数(按 timestamp 倒序,一般远小于子系统数×上报次数) */
|
||||||
@Autowired private IBmsSystemStatusService bmsSystemStatusService;
|
private static final int REALTIME_SUBSYSTEM_PREFETCH = 32;
|
||||||
@Autowired private IBmsChargeDetailService bmsChargeDetailService;
|
/** 24h 趋势曲线最多点数 */
|
||||||
@Autowired private IBmsChargeTemperatureService bmsChargeTemperatureService;
|
private static final int REALTIME_TREND_MAX_POINTS = 360;
|
||||||
@Autowired private IBmsVoltageChannelDataService bmsVoltageChannelDataService;
|
|
||||||
@Autowired private IBmsSopDataService bmsSopDataService;
|
|
||||||
|
|
||||||
@GetMapping("/realtime/{deviceId}")
|
@GetMapping("/realtime/{deviceId}")
|
||||||
@ApiOperation("设备实时数据")
|
@ApiOperation(value = "设备实时数据", notes = "默认不含 dashboard 趋势(24h),避免超时;趋势请调 includeTrends=true 或 GET .../trends", response = DeviceRealtimeAggregateDTO.class)
|
||||||
public AjaxResult realtime(@PathVariable("deviceId") String deviceId) {
|
public R<DeviceRealtimeAggregateDTO> realtime(@PathVariable("deviceId") String deviceId, @ApiParam("是否包含 dashboard 24h 趋势(较慢,易触发前端 10s 超时)") @RequestParam(value = "includeTrends", required = false, defaultValue = "true") boolean includeTrends) {
|
||||||
DeviceRealtimeDTO dto = new DeviceRealtimeDTO();
|
RealtimeSnapshot snap = loadRealtimeSnapshot(deviceId);
|
||||||
dto.setDeviceId(deviceId);
|
DeviceRealtimeAggregateDTO agg = new DeviceRealtimeAggregateDTO();
|
||||||
VehicleData vd = vehicleDataService.selectLatestByDeviceId(deviceId);
|
agg.setDeviceId(deviceId);
|
||||||
ExtremeValues ev = extremeValuesService.selectLatestByDeviceId(deviceId);
|
fillAggregateFromSnapshot(agg, snap);
|
||||||
SystemStatusSection sys = new SystemStatusSection();
|
if (includeTrends) {
|
||||||
if (vd != null) {
|
SimpleDateFormat tf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
sys.setTotalVoltage(vd.getTotalVoltage());
|
Date now = new Date();
|
||||||
sys.setTotalCurrent(vd.getTotalCurrent());
|
Date trendBegin = new Date(now.getTime() - 24L * 3600_000L);
|
||||||
sys.setSoc(vd.getSoc());
|
agg.setDashboard(buildRealtimeDashboard(deviceId, snap.vehicleData, snap.extremeValues, snap.systemStatus, snap.chargeDetail, snap.chargeTemperatures, snap.voltageChannels, trendBegin, now, tf));
|
||||||
}
|
}
|
||||||
if (ev != null) {
|
return R.ok(agg);
|
||||||
sys.setMaxCellVoltage(ev.getMaxVoltageValue());
|
}
|
||||||
sys.setMaxCellPos(ev.getMaxVoltageSubsystemNo() == null || ev.getMaxVoltageBatteryNo() == null ? null :
|
|
||||||
ev.getMaxVoltageSubsystemNo() + "-" + ev.getMaxVoltageBatteryNo());
|
|
||||||
sys.setMinCellVoltage(ev.getMinVoltageValue());
|
|
||||||
sys.setMinCellPos(ev.getMinVoltageSubsystemNo() == null || ev.getMinVoltageBatteryNo() == null ? null :
|
|
||||||
ev.getMinVoltageSubsystemNo() + "-" + ev.getMinVoltageBatteryNo());
|
|
||||||
sys.setMaxTemp(ev.getMaxTempValue());
|
|
||||||
sys.setMaxTempPos(ev.getMaxTempSubsystemNo() == null || ev.getMaxTempProbeNo() == null ? null :
|
|
||||||
ev.getMaxTempSubsystemNo() + "-" + ev.getMaxTempProbeNo());
|
|
||||||
sys.setMinTemp(ev.getMinTempValue());
|
|
||||||
sys.setMinTempPos(ev.getMinTempSubsystemNo() == null || ev.getMinTempProbeNo() == null ? null :
|
|
||||||
ev.getMinTempSubsystemNo() + "-" + ev.getMinTempProbeNo());
|
|
||||||
}
|
|
||||||
dto.setSystemStatus(sys);
|
|
||||||
|
|
||||||
RunStatusSection run = new RunStatusSection();
|
@GetMapping("/realtime/{deviceId}/trends")
|
||||||
if (vd != null) {
|
@ApiOperation(value = "设备实时 24h 趋势(dashboard)", notes = "与 realtime 拆分,避免与快照同请求导致 Axios 10s 超时", response = DeviceRealtimeDTO.class)
|
||||||
run.setChargingStatus(vd.getChargingStatus());
|
public R<DeviceRealtimeDTO> realtimeTrends(
|
||||||
}
|
@PathVariable("deviceId") String deviceId,
|
||||||
BmsSystemStatus ss = bmsSystemStatusService.lambdaQuery()
|
@ApiParam("向前小时数,默认 24") @RequestParam(value = "hours", required = false, defaultValue = "24") int hours) {
|
||||||
.eq(BmsSystemStatus::getDeviceId, deviceId)
|
RealtimeSnapshot snap = loadRealtimeSnapshot(deviceId);
|
||||||
.orderByDesc(BmsSystemStatus::getCreateTime).last("limit 1").one();
|
SimpleDateFormat tf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
if (ss != null) {
|
|
||||||
run.setRelayStatus(ss.getRelayStatus());
|
|
||||||
run.setOnaaccStatus(ss.getOnaaccStatus());
|
|
||||||
run.setResetCount(ss.getResetCount());
|
|
||||||
run.setCycleCount(ss.getCycleCount());
|
|
||||||
run.setSample25v(ss.getSample25v());
|
|
||||||
run.setSample5v(ss.getSample5v());
|
|
||||||
}
|
|
||||||
dto.setRunStatus(run);
|
|
||||||
|
|
||||||
ChargeSection chg = new ChargeSection();
|
|
||||||
BmsChargeDetail cd = bmsChargeDetailService.lambdaQuery()
|
|
||||||
.eq(BmsChargeDetail::getDeviceId, deviceId)
|
|
||||||
.orderByDesc(BmsChargeDetail::getCreateTime).last("limit 1").one();
|
|
||||||
if (cd != null) {
|
|
||||||
chg.setAcChargeCurrent(cd.getAcChargeCurrent());
|
|
||||||
chg.setAcChargeVoltage(cd.getAcChargeVoltage());
|
|
||||||
chg.setDcChargeCurrent(cd.getDcChargeCurrent());
|
|
||||||
chg.setDcChargeVoltage(cd.getDcChargeVoltage());
|
|
||||||
chg.setChargeTime(cd.getChargeTime());
|
|
||||||
chg.setAcStopCode(cd.getAcStopCode());
|
|
||||||
chg.setDcStopCode(cd.getDcStopCode());
|
|
||||||
chg.setAcCpValue(cd.getAcCpValue());
|
|
||||||
chg.setAcGunResistance(cd.getAcGunResistance());
|
|
||||||
chg.setChargingStage(cd.getChargingStage());
|
|
||||||
chg.setAcStage(cd.getAcStage());
|
|
||||||
chg.setDcStage(cd.getDcStage());
|
|
||||||
}
|
|
||||||
dto.setCharge(chg);
|
|
||||||
|
|
||||||
StatisticsSection stat = new StatisticsSection();
|
|
||||||
if (ss != null) {
|
|
||||||
stat.setCycleCount(ss.getCycleCount());
|
|
||||||
}
|
|
||||||
stat.setTotalRunHours(0L);
|
|
||||||
stat.setTotalChargeHours(0L);
|
|
||||||
stat.setTotalDischargeHours(0L);
|
|
||||||
dto.setStatistics(stat);
|
|
||||||
|
|
||||||
List<BmsChargeTemperature> temps = bmsChargeTemperatureService.lambdaQuery()
|
|
||||||
.eq(BmsChargeTemperature::getDeviceId, deviceId)
|
|
||||||
.orderByDesc(BmsChargeTemperature::getCreateTime).last("limit 8").list();
|
|
||||||
List<NameValueDTO> tempItems = new ArrayList<>();
|
|
||||||
if (temps != null) {
|
|
||||||
String ts = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
|
|
||||||
for (BmsChargeTemperature t : temps) {
|
|
||||||
NameValueDTO nv = new NameValueDTO();
|
|
||||||
nv.setName("T" + t.getTempIndex());
|
|
||||||
nv.setValue(t.getTemperature() == null ? 0 : t.getTemperature().longValue());
|
|
||||||
nv.setCreateTime(ts);
|
|
||||||
tempItems.add(nv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dto.setTemperatures(tempItems);
|
|
||||||
|
|
||||||
List<BmsVoltageChannelData> chs = bmsVoltageChannelDataService.lambdaQuery()
|
|
||||||
.eq(BmsVoltageChannelData::getDeviceId, deviceId)
|
|
||||||
.orderByDesc(BmsVoltageChannelData::getCreateTime).last("limit 16").list();
|
|
||||||
List<NameValueDTO> voltItems = new ArrayList<>();
|
|
||||||
if (chs != null) {
|
|
||||||
String ts = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
|
|
||||||
for (BmsVoltageChannelData c : chs) {
|
|
||||||
NameValueDTO nv = new NameValueDTO();
|
|
||||||
nv.setName((c.getChannelType() == null ? "CH" : c.getChannelType()) + "-" + c.getChannelIndex());
|
|
||||||
nv.setValue(c.getVoltage() == null ? 0 : c.getVoltage().longValue());
|
|
||||||
nv.setCreateTime(ts);
|
|
||||||
voltItems.add(nv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dto.setVoltages(voltItems);
|
|
||||||
|
|
||||||
dto.setFaults(new ArrayList<>());
|
|
||||||
// 折线图:最近24小时(按createTime过滤),默认RTP-1与T1
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date begin = new Date(now.getTime() - 24L * 3600_000L);
|
int h = hours <= 0 ? 24 : Math.min(hours, 168);
|
||||||
List<BmsVoltageChannelData> vtrend = bmsVoltageChannelDataService.lambdaQuery()
|
Date trendBegin = new Date(now.getTime() - (long) h * 3600_000L);
|
||||||
.eq(BmsVoltageChannelData::getDeviceId, deviceId)
|
return R.ok(buildRealtimeDashboard(deviceId, snap.vehicleData, snap.extremeValues, snap.systemStatus,
|
||||||
.eq(BmsVoltageChannelData::getChannelType, "RTP")
|
snap.chargeDetail, snap.chargeTemperatures, snap.voltageChannels, trendBegin, now, tf));
|
||||||
.eq(BmsVoltageChannelData::getChannelIndex, 1)
|
}
|
||||||
.ge(BmsVoltageChannelData::getCreateTime, begin)
|
|
||||||
.le(BmsVoltageChannelData::getCreateTime, now)
|
|
||||||
.orderByAsc(BmsVoltageChannelData::getCreateTime)
|
|
||||||
.list();
|
|
||||||
java.text.SimpleDateFormat f = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
||||||
List<TimePointDTO> vpoints = new ArrayList<>();
|
|
||||||
for (BmsVoltageChannelData it : vtrend) {
|
|
||||||
TimePointDTO p = new TimePointDTO();
|
|
||||||
p.setTime(it.getCreateTime() == null ? null : f.format(it.getCreateTime()));
|
|
||||||
p.setValue(it.getVoltage());
|
|
||||||
vpoints.add(p);
|
|
||||||
}
|
|
||||||
LineSeriesDTO vseries = new LineSeriesDTO();
|
|
||||||
vseries.setName("RTP-1");
|
|
||||||
vseries.setPoints(vpoints);
|
|
||||||
dto.setVoltageTrend(java.util.Collections.singletonList(vseries));
|
|
||||||
|
|
||||||
List<BmsChargeTemperature> ttrend = bmsChargeTemperatureService.lambdaQuery()
|
/**
|
||||||
.eq(BmsChargeTemperature::getDeviceId, deviceId)
|
* 与 {@link #realtime(String)} 同源字段的历史平铺列表:时间范围内全部 vehicle_data 在库内分页(pageNum/pageSize),
|
||||||
.eq(BmsChargeTemperature::getTempIndex, 1)
|
* 每页再与其它表按时间点合并;pageSize 仅支持 10、20、100、500。
|
||||||
.ge(BmsChargeTemperature::getCreateTime, begin)
|
*/
|
||||||
.le(BmsChargeTemperature::getCreateTime, now)
|
@PreAuthorize("@ss.hasPermi('system:devices:query')")
|
||||||
.orderByAsc(BmsChargeTemperature::getCreateTime)
|
@GetMapping("/realtime/history/{deviceId}")
|
||||||
.list();
|
@ApiOperation(value = "设备实时同源历史(平铺分页)", notes = "对 beginTime~endTime 内全部整车记录分页;参数 pageNum、pageSize(10|20|100|500);total 为符合条件的总条数", response = DeviceHistoryFlatRowDTO.class)
|
||||||
List<TimePointDTO> tpoints = new ArrayList<>();
|
public TableDataInfo realtimeHistoryFlat(
|
||||||
for (BmsChargeTemperature it : ttrend) {
|
@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId,
|
||||||
TimePointDTO p = new TimePointDTO();
|
@ApiParam(value = "开始时间戳(毫秒),与 beginTime 二选一") @RequestParam(value = "start", required = false) Long startMillis,
|
||||||
p.setTime(it.getCreateTime() == null ? null : f.format(it.getCreateTime()));
|
@ApiParam(value = "结束时间戳(毫秒),与 endTime 二选一") @RequestParam(value = "end", required = false) Long endMillis,
|
||||||
p.setValue(it.getTemperature());
|
@ApiParam(value = "开始时间 yyyy-MM-dd HH:mm:ss") @RequestParam(value = "beginTime", required = false) String beginTimeStr,
|
||||||
tpoints.add(p);
|
@ApiParam(value = "结束时间 yyyy-MM-dd HH:mm:ss") @RequestParam(value = "endTime", required = false) String endTimeStr,
|
||||||
|
@ApiParam(value = "排序:desc 默认最新在前;asc 时间正序") @RequestParam(value = "order", required = false, defaultValue = "desc") String order,
|
||||||
|
@ApiParam(value = "是否查询总条数 COUNT;大数据量可传 false 加快首屏") @RequestParam(value = "count", required = false, defaultValue = "true") boolean countTotal
|
||||||
|
) {
|
||||||
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
Date end;
|
||||||
|
Date start;
|
||||||
|
boolean userRange = startMillis != null || (beginTimeStr != null && !beginTimeStr.isEmpty());
|
||||||
|
try {
|
||||||
|
end = parseHistoryEnd(endMillis, endTimeStr, df);
|
||||||
|
start = parseHistoryStart(startMillis, beginTimeStr, df, end);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
TableDataInfo err = new TableDataInfo();
|
||||||
|
err.setCode(HttpStatus.ERROR);
|
||||||
|
err.setMsg("时间解析失败,请使用 yyyy-MM-dd HH:mm:ss 或毫秒时间戳");
|
||||||
|
err.setRows(Collections.emptyList());
|
||||||
|
err.setTotal(0);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
LineSeriesDTO tseries = new LineSeriesDTO();
|
boolean desc = !"asc".equalsIgnoreCase(order);
|
||||||
tseries.setName("T1");
|
if (!userRange) {
|
||||||
tseries.setPoints(tpoints);
|
start = clampHistoryStart(start, end);
|
||||||
dto.setTemperatureTrend(java.util.Collections.singletonList(tseries));
|
}
|
||||||
return success(dto);
|
int pageSize = com.evobms.common.utils.PageUtils.startHistoryPage(countTotal);
|
||||||
|
List<VehicleData> vehicles = vehicleDataService.selectVehicleDataListForHistory(deviceId, start, end, desc);
|
||||||
|
// total 必须来自 PageHelper 分页查询结果;不能用组装后的 rows(否则 total=当前页条数)
|
||||||
|
long total = new com.github.pagehelper.PageInfo<>(vehicles).getTotal();
|
||||||
|
SimpleDateFormat tf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
List<DeviceHistoryFlatRowDTO> rows = deviceRealtimeHistoryAssembler.build(deviceId, vehicles, tf, pageSize);
|
||||||
|
TableDataInfo table = getDataTable(rows);
|
||||||
|
table.setTotal(total);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date parseHistoryEnd(Long endMillis, String endTimeStr, SimpleDateFormat df) throws ParseException {
|
||||||
|
if (endMillis != null) {
|
||||||
|
return new Date(endMillis);
|
||||||
|
}
|
||||||
|
if (endTimeStr != null && !endTimeStr.isEmpty()) {
|
||||||
|
return df.parse(endTimeStr);
|
||||||
|
}
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date parseHistoryStart(Long startMillis, String beginTimeStr, SimpleDateFormat df, Date end) throws ParseException {
|
||||||
|
if (startMillis != null) {
|
||||||
|
return new Date(startMillis);
|
||||||
|
}
|
||||||
|
if (beginTimeStr != null && !beginTimeStr.isEmpty()) {
|
||||||
|
return df.parse(beginTimeStr);
|
||||||
|
}
|
||||||
|
return new Date(end.getTime() - 7L * 86400_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单次查询时间窗上限 7 天,避免一次扫过多行 */
|
||||||
|
private static Date clampHistoryStart(Date start, Date end) {
|
||||||
|
if (start == null || end == null) {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
long maxWindow = 7L * 86400_000L;
|
||||||
|
if (end.getTime() - start.getTime() > maxWindow) {
|
||||||
|
return new Date(end.getTime() - maxWindow);
|
||||||
|
}
|
||||||
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -289,4 +383,392 @@ public class BmsDevicesController extends BaseController
|
|||||||
{
|
{
|
||||||
return toAjax(bmsDevicesService.deleteBmsDevicesByIds(ids));
|
return toAjax(bmsDevicesService.deleteBmsDevicesByIds(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 扁平视图:与原始列表共用同一批查询结果,避免重复组装且趋势只在 dashboard 中暴露一份 */
|
||||||
|
private DeviceRealtimeDTO buildRealtimeDashboard(
|
||||||
|
String deviceId,
|
||||||
|
VehicleData vd,
|
||||||
|
ExtremeValues ev,
|
||||||
|
BmsSystemStatus ss,
|
||||||
|
BmsChargeDetail cd,
|
||||||
|
List<BmsChargeTemperature> chargeTemps,
|
||||||
|
List<BmsVoltageChannelData> voltageChs,
|
||||||
|
Date trendBegin,
|
||||||
|
Date trendEnd,
|
||||||
|
SimpleDateFormat tf) {
|
||||||
|
DeviceRealtimeDTO dto = new DeviceRealtimeDTO();
|
||||||
|
dto.setDeviceId(deviceId);
|
||||||
|
|
||||||
|
SystemStatusSection sys = new SystemStatusSection();
|
||||||
|
if (vd != null) {
|
||||||
|
sys.setTotalVoltage(vd.getTotalVoltage());
|
||||||
|
sys.setTotalCurrent(vd.getTotalCurrent());
|
||||||
|
sys.setSoc(vd.getSoc());
|
||||||
|
}
|
||||||
|
if (ev != null) {
|
||||||
|
sys.setMaxCellVoltage(ev.getMaxVoltageValue());
|
||||||
|
sys.setMaxCellPos(joinSubsysIndex(ev.getMaxVoltageSubsystemNo(), ev.getMaxVoltageBatteryNo()));
|
||||||
|
sys.setMinCellVoltage(ev.getMinVoltageValue());
|
||||||
|
sys.setMinCellPos(joinSubsysIndex(ev.getMinVoltageSubsystemNo(), ev.getMinVoltageBatteryNo()));
|
||||||
|
sys.setMaxTemp(ev.getMaxTempValue());
|
||||||
|
sys.setMaxTempPos(joinSubsysIndex(ev.getMaxTempSubsystemNo(), ev.getMaxTempProbeNo()));
|
||||||
|
sys.setMinTemp(ev.getMinTempValue());
|
||||||
|
sys.setMinTempPos(joinSubsysIndex(ev.getMinTempSubsystemNo(), ev.getMinTempProbeNo()));
|
||||||
|
}
|
||||||
|
dto.setSystemStatus(sys);
|
||||||
|
|
||||||
|
RunStatusSection run = new RunStatusSection();
|
||||||
|
if (vd != null) {
|
||||||
|
run.setChargingStatus(vd.getChargingStatus());
|
||||||
|
}
|
||||||
|
if (ss != null) {
|
||||||
|
run.setRelayStatus(ss.getRelayStatus());
|
||||||
|
run.setOnaaccStatus(ss.getOnaaccStatus());
|
||||||
|
run.setResetCount(ss.getResetCount());
|
||||||
|
run.setCycleCount(ss.getCycleCount());
|
||||||
|
run.setSample25v(ss.getSample25v());
|
||||||
|
run.setSample5v(ss.getSample5v());
|
||||||
|
}
|
||||||
|
dto.setRunStatus(run);
|
||||||
|
|
||||||
|
ChargeSection chg = new ChargeSection();
|
||||||
|
if (cd != null) {
|
||||||
|
chg.setAcChargeCurrent(cd.getAcChargeCurrent());
|
||||||
|
chg.setAcChargeVoltage(cd.getAcChargeVoltage());
|
||||||
|
chg.setDcChargeCurrent(cd.getDcChargeCurrent());
|
||||||
|
chg.setDcChargeVoltage(cd.getDcChargeVoltage());
|
||||||
|
chg.setChargeTime(cd.getChargeTime());
|
||||||
|
chg.setAcStopCode(cd.getAcStopCode());
|
||||||
|
chg.setDcStopCode(cd.getDcStopCode());
|
||||||
|
chg.setAcCpValue(cd.getAcCpValue());
|
||||||
|
chg.setAcGunResistance(cd.getAcGunResistance());
|
||||||
|
chg.setChargingStage(cd.getChargingStage());
|
||||||
|
chg.setAcStage(cd.getAcStage());
|
||||||
|
chg.setDcStage(cd.getDcStage());
|
||||||
|
}
|
||||||
|
dto.setCharge(chg);
|
||||||
|
|
||||||
|
StatisticsSection stat = new StatisticsSection();
|
||||||
|
if (ss != null) {
|
||||||
|
stat.setCycleCount(ss.getCycleCount());
|
||||||
|
}
|
||||||
|
stat.setTotalRunHours(0L);
|
||||||
|
stat.setTotalChargeHours(0L);
|
||||||
|
stat.setTotalDischargeHours(0L);
|
||||||
|
dto.setStatistics(stat);
|
||||||
|
|
||||||
|
dto.setTemperatures(toChargeTemperatureNameValues(chargeTemps, tf));
|
||||||
|
dto.setVoltages(toVoltageChannelNameValues(voltageChs, tf));
|
||||||
|
dto.setFaults(new ArrayList<>());
|
||||||
|
dto.setVoltageTrend(buildRtp1VoltageTrendSeries(deviceId, trendBegin, trendEnd, tf));
|
||||||
|
dto.setTemperatureTrend(buildT1TemperatureTrendSeries(deviceId, trendBegin, trendEnd, tf));
|
||||||
|
|
||||||
|
List<VehicleData> vehicleTrendRows = listVehicleDataForTrend(deviceId, trendBegin, trendEnd);
|
||||||
|
dto.setTotalVoltageTrend(Collections.singletonList(
|
||||||
|
buildVehicleNumericTrendSeries(vehicleTrendRows, "总电压(V)", trendBegin, trendEnd, tf, VehicleData::getTotalVoltage)));
|
||||||
|
dto.setTotalCurrentTrend(Collections.singletonList(
|
||||||
|
buildVehicleNumericTrendSeries(vehicleTrendRows, "总电流(A)", trendBegin, trendEnd, tf, VehicleData::getTotalCurrent)));
|
||||||
|
dto.setSocTrend(Collections.singletonList(
|
||||||
|
buildVehicleNumericTrendSeries(vehicleTrendRows, "SOC(%)", trendBegin, trendEnd, tf,
|
||||||
|
v -> v.getSoc() == null ? null : BigDecimal.valueOf(v.getSoc()))));
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 趋势图:DB 侧限量采样,避免 24h 全量拉取 */
|
||||||
|
private List<VehicleData> listVehicleDataForTrend(String deviceId, Date begin, Date end) {
|
||||||
|
return vehicleDataService.selectVehicleDataTrendInRange(deviceId, begin, end, REALTIME_TREND_MAX_POINTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date vehicleTrendPointTime(VehicleData v) {
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return v.getTimestamp() != null ? v.getTimestamp() : v.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LineSeriesDTO buildVehicleNumericTrendSeries(
|
||||||
|
List<VehicleData> rows,
|
||||||
|
String seriesName,
|
||||||
|
Date rangeBegin,
|
||||||
|
Date rangeEnd,
|
||||||
|
SimpleDateFormat tf,
|
||||||
|
Function<VehicleData, BigDecimal> valueFn) {
|
||||||
|
List<TimePointDTO> points = new ArrayList<>();
|
||||||
|
if (rows != null) {
|
||||||
|
for (VehicleData v : rows) {
|
||||||
|
Date t = vehicleTrendPointTime(v);
|
||||||
|
if (t == null || t.before(rangeBegin) || t.after(rangeEnd)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BigDecimal val = valueFn.apply(v);
|
||||||
|
if (val == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TimePointDTO p = new TimePointDTO();
|
||||||
|
p.setTime(tf.format(t));
|
||||||
|
p.setValue(val);
|
||||||
|
points.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LineSeriesDTO series = new LineSeriesDTO();
|
||||||
|
series.setName(seriesName);
|
||||||
|
series.setPoints(points);
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinSubsysIndex(Integer a, Integer b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return a + "-" + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<NameValueDTO> toChargeTemperatureNameValues(List<BmsChargeTemperature> temps, SimpleDateFormat tf) {
|
||||||
|
if (temps == null || temps.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<NameValueDTO> out = new ArrayList<>(temps.size());
|
||||||
|
for (BmsChargeTemperature t : temps) {
|
||||||
|
NameValueDTO nv = new NameValueDTO();
|
||||||
|
nv.setName("T" + t.getTempIndex());
|
||||||
|
nv.setValue(t.getTemperature() == null ? 0 : t.getTemperature().longValue());
|
||||||
|
nv.setCreateTime(formatTime(t.getCreateTime(), tf));
|
||||||
|
out.add(nv);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<NameValueDTO> toVoltageChannelNameValues(List<BmsVoltageChannelData> chs, SimpleDateFormat tf) {
|
||||||
|
if (chs == null || chs.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<NameValueDTO> out = new ArrayList<>(chs.size());
|
||||||
|
for (BmsVoltageChannelData c : chs) {
|
||||||
|
NameValueDTO nv = new NameValueDTO();
|
||||||
|
nv.setName((c.getChannelType() == null ? "CH" : c.getChannelType()) + "-" + c.getChannelIndex());
|
||||||
|
nv.setValue(c.getVoltage() == null ? 0 : c.getVoltage().longValue());
|
||||||
|
nv.setCreateTime(formatTime(c.getCreateTime(), tf));
|
||||||
|
out.add(nv);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatTime(Date d, SimpleDateFormat tf) {
|
||||||
|
return d == null ? null : tf.format(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LineSeriesDTO> buildRtp1VoltageTrendSeries(String deviceId, Date begin, Date end, SimpleDateFormat tf) {
|
||||||
|
List<BmsVoltageChannelData> rows = bmsVoltageChannelDataService.lambdaQuery()
|
||||||
|
.eq(BmsVoltageChannelData::getDeviceId, deviceId)
|
||||||
|
.eq(BmsVoltageChannelData::getChannelType, "RTP")
|
||||||
|
.eq(BmsVoltageChannelData::getChannelIndex, 1)
|
||||||
|
.isNotNull(BmsVoltageChannelData::getTimestamp)
|
||||||
|
.ge(BmsVoltageChannelData::getTimestamp, begin)
|
||||||
|
.le(BmsVoltageChannelData::getTimestamp, end)
|
||||||
|
.orderByDesc(BmsVoltageChannelData::getTimestamp)
|
||||||
|
.last("limit " + REALTIME_TREND_MAX_POINTS)
|
||||||
|
.list();
|
||||||
|
Collections.reverse(rows);
|
||||||
|
List<TimePointDTO> points = new ArrayList<>(rows.size());
|
||||||
|
for (BmsVoltageChannelData it : rows) {
|
||||||
|
TimePointDTO p = new TimePointDTO();
|
||||||
|
Date t = it.getTimestamp() != null ? it.getTimestamp() : it.getCreateTime();
|
||||||
|
p.setTime(formatTime(t, tf));
|
||||||
|
p.setValue(it.getVoltage());
|
||||||
|
points.add(p);
|
||||||
|
}
|
||||||
|
LineSeriesDTO series = new LineSeriesDTO();
|
||||||
|
series.setName("RTP-1");
|
||||||
|
series.setPoints(points);
|
||||||
|
return Collections.singletonList(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LineSeriesDTO> buildT1TemperatureTrendSeries(String deviceId, Date begin, Date end, SimpleDateFormat tf) {
|
||||||
|
List<BmsChargeTemperature> rows = bmsChargeTemperatureService.lambdaQuery()
|
||||||
|
.eq(BmsChargeTemperature::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsChargeTemperature::getTimestamp)
|
||||||
|
.ge(BmsChargeTemperature::getTimestamp, begin)
|
||||||
|
.le(BmsChargeTemperature::getTimestamp, end)
|
||||||
|
.eq(BmsChargeTemperature::getTempIndex, 1)
|
||||||
|
.orderByDesc(BmsChargeTemperature::getTimestamp)
|
||||||
|
.last("limit " + REALTIME_TREND_MAX_POINTS)
|
||||||
|
.list();
|
||||||
|
Collections.reverse(rows);
|
||||||
|
List<TimePointDTO> points = new ArrayList<>(rows.size());
|
||||||
|
for (BmsChargeTemperature it : rows) {
|
||||||
|
TimePointDTO p = new TimePointDTO();
|
||||||
|
Date t = it.getTimestamp() != null ? it.getTimestamp() : it.getCreateTime();
|
||||||
|
p.setTime(formatTime(t, tf));
|
||||||
|
p.setValue(it.getTemperature());
|
||||||
|
points.add(p);
|
||||||
|
}
|
||||||
|
LineSeriesDTO series = new LineSeriesDTO();
|
||||||
|
series.setName("T1");
|
||||||
|
series.setPoints(points);
|
||||||
|
return Collections.singletonList(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 并行加载实时快照各表最新数据(默认不含 24h 趋势 SQL) */
|
||||||
|
private RealtimeSnapshot loadRealtimeSnapshot(String deviceId) {
|
||||||
|
CompletableFuture<VehicleData> vehicleF = CompletableFuture.supplyAsync(
|
||||||
|
() -> vehicleDataService.selectLatestByDeviceId(deviceId));
|
||||||
|
CompletableFuture<ExtremeValues> extremeF = CompletableFuture.supplyAsync(
|
||||||
|
() -> extremeValuesService.selectLatestByDeviceId(deviceId));
|
||||||
|
CompletableFuture<VehicleLocation> locationF = CompletableFuture.supplyAsync(
|
||||||
|
() -> vehicleLocationService.selectLatestByDeviceId(deviceId));
|
||||||
|
CompletableFuture<BmsSystemStatus> systemF = CompletableFuture.supplyAsync(
|
||||||
|
() -> latestSystemStatus(deviceId));
|
||||||
|
CompletableFuture<BmsChargeDetail> chargeDetailF = CompletableFuture.supplyAsync(
|
||||||
|
() -> latestChargeDetail(deviceId));
|
||||||
|
CompletableFuture<BmsRuntimeData> runtimeF = CompletableFuture.supplyAsync(
|
||||||
|
() -> latestRuntimeData(deviceId));
|
||||||
|
CompletableFuture<BmsSopData> sopF = CompletableFuture.supplyAsync(
|
||||||
|
() -> latestSopData(deviceId));
|
||||||
|
CompletableFuture<List<SubsystemVoltage>> svF = CompletableFuture.supplyAsync(
|
||||||
|
() -> listLatestSubsystemVoltages(deviceId));
|
||||||
|
CompletableFuture<List<SubsystemTemperature>> stF = CompletableFuture.supplyAsync(
|
||||||
|
() -> listLatestSubsystemTemperatures(deviceId));
|
||||||
|
CompletableFuture<List<BmsChargeTemperature>> chargeTempF = CompletableFuture.supplyAsync(
|
||||||
|
() -> listLatestChargeTemperatures(deviceId));
|
||||||
|
CompletableFuture<List<BmsVoltageChannelData>> voltageChF = CompletableFuture.supplyAsync(
|
||||||
|
() -> listLatestVoltageChannels(deviceId));
|
||||||
|
|
||||||
|
CompletableFuture.allOf(vehicleF, extremeF, locationF, systemF, chargeDetailF, runtimeF, sopF,
|
||||||
|
svF, stF, chargeTempF, voltageChF).join();
|
||||||
|
|
||||||
|
RealtimeSnapshot snap = new RealtimeSnapshot();
|
||||||
|
snap.vehicleData = vehicleF.join();
|
||||||
|
snap.extremeValues = extremeF.join();
|
||||||
|
snap.latestLocation = locationF.join();
|
||||||
|
snap.systemStatus = systemF.join();
|
||||||
|
snap.chargeDetail = chargeDetailF.join();
|
||||||
|
snap.runtimeData = runtimeF.join();
|
||||||
|
snap.sopData = sopF.join();
|
||||||
|
snap.subsystemVoltageRows = svF.join();
|
||||||
|
snap.subsystemTemperatureRows = stF.join();
|
||||||
|
snap.chargeTemperatures = chargeTempF.join();
|
||||||
|
snap.voltageChannels = voltageChF.join();
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fillAggregateFromSnapshot(DeviceRealtimeAggregateDTO agg, RealtimeSnapshot snap) {
|
||||||
|
agg.setVehicleData(snap.vehicleData);
|
||||||
|
agg.setLatestLocation(snap.latestLocation);
|
||||||
|
agg.setExtremeValues(snap.extremeValues);
|
||||||
|
agg.setSystemStatus(snap.systemStatus);
|
||||||
|
agg.setChargeDetail(snap.chargeDetail);
|
||||||
|
agg.setRuntimeData(snap.runtimeData);
|
||||||
|
agg.setSopData(snap.sopData);
|
||||||
|
agg.setChargeTemperatures(snap.chargeTemperatures);
|
||||||
|
agg.setVoltageChannels(snap.voltageChannels);
|
||||||
|
List<SubsystemVoltage> svRows = snap.subsystemVoltageRows;
|
||||||
|
agg.setSubsystemVoltage(svRows == null || svRows.isEmpty() ? null : svRows.get(0));
|
||||||
|
agg.setSubsystemVoltages(latestOnePerSubsystem(svRows, SubsystemVoltage::getSubsystemNo));
|
||||||
|
List<SubsystemTemperature> stRows = snap.subsystemTemperatureRows;
|
||||||
|
agg.setSubsystemTemperature(stRows == null || stRows.isEmpty() ? null : stRows.get(0));
|
||||||
|
agg.setSubsystemTemperatures(latestOnePerSubsystem(stRows, SubsystemTemperature::getSubsystemNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsSystemStatus latestSystemStatus(String deviceId) {
|
||||||
|
return bmsSystemStatusService.lambdaQuery()
|
||||||
|
.eq(BmsSystemStatus::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsSystemStatus::getTimestamp)
|
||||||
|
.orderByDesc(BmsSystemStatus::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsChargeDetail latestChargeDetail(String deviceId) {
|
||||||
|
return bmsChargeDetailService.lambdaQuery()
|
||||||
|
.eq(BmsChargeDetail::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsChargeDetail::getTimestamp)
|
||||||
|
.orderByDesc(BmsChargeDetail::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsRuntimeData latestRuntimeData(String deviceId) {
|
||||||
|
return bmsRuntimeDataService.lambdaQuery()
|
||||||
|
.eq(BmsRuntimeData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsRuntimeData::getTimestamp)
|
||||||
|
.orderByDesc(BmsRuntimeData::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsSopData latestSopData(String deviceId) {
|
||||||
|
return bmsSopDataService.lambdaQuery()
|
||||||
|
.eq(BmsSopData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsSopData::getTimestamp)
|
||||||
|
.orderByDesc(BmsSopData::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SubsystemVoltage> listLatestSubsystemVoltages(String deviceId) {
|
||||||
|
return subsystemVoltageService.lambdaQuery()
|
||||||
|
.eq(SubsystemVoltage::getDeviceId, deviceId)
|
||||||
|
.isNotNull(SubsystemVoltage::getTimestamp)
|
||||||
|
.orderByDesc(SubsystemVoltage::getTimestamp)
|
||||||
|
.last("limit " + REALTIME_SUBSYSTEM_PREFETCH)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SubsystemTemperature> listLatestSubsystemTemperatures(String deviceId) {
|
||||||
|
return subsystemTemperatureService.lambdaQuery()
|
||||||
|
.eq(SubsystemTemperature::getDeviceId, deviceId)
|
||||||
|
.isNotNull(SubsystemTemperature::getTimestamp)
|
||||||
|
.orderByDesc(SubsystemTemperature::getTimestamp)
|
||||||
|
.last("limit " + REALTIME_SUBSYSTEM_PREFETCH)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsChargeTemperature> listLatestChargeTemperatures(String deviceId) {
|
||||||
|
return bmsChargeTemperatureService.lambdaQuery()
|
||||||
|
.eq(BmsChargeTemperature::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsChargeTemperature::getTimestamp)
|
||||||
|
.orderByDesc(BmsChargeTemperature::getTimestamp)
|
||||||
|
.last("limit 8")
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsVoltageChannelData> listLatestVoltageChannels(String deviceId) {
|
||||||
|
return bmsVoltageChannelDataService.lambdaQuery()
|
||||||
|
.eq(BmsVoltageChannelData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsVoltageChannelData::getTimestamp)
|
||||||
|
.orderByDesc(BmsVoltageChannelData::getTimestamp)
|
||||||
|
.last("limit 16")
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RealtimeSnapshot {
|
||||||
|
VehicleData vehicleData;
|
||||||
|
ExtremeValues extremeValues;
|
||||||
|
VehicleLocation latestLocation;
|
||||||
|
BmsSystemStatus systemStatus;
|
||||||
|
BmsChargeDetail chargeDetail;
|
||||||
|
BmsRuntimeData runtimeData;
|
||||||
|
BmsSopData sopData;
|
||||||
|
List<SubsystemVoltage> subsystemVoltageRows;
|
||||||
|
List<SubsystemTemperature> subsystemTemperatureRows;
|
||||||
|
List<BmsChargeTemperature> chargeTemperatures;
|
||||||
|
List<BmsVoltageChannelData> voltageChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从按时间倒序的结果中取每个子系统号的第一条(即该子系统最新数据) */
|
||||||
|
private static <T> List<T> latestOnePerSubsystem(List<T> orderedNewestFirst, Function<T, Integer> subsystemNoFn) {
|
||||||
|
if (orderedNewestFirst == null || orderedNewestFirst.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Map<Integer, T> seen = new LinkedHashMap<>();
|
||||||
|
for (T row : orderedNewestFirst) {
|
||||||
|
Integer no = subsystemNoFn.apply(row);
|
||||||
|
if (no != null && !seen.containsKey(no)) {
|
||||||
|
seen.put(no, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<T> out = new ArrayList<>(seen.values());
|
||||||
|
out.sort(Comparator.comparing(subsystemNoFn, Comparator.nullsLast(Integer::compareTo)));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,17 +42,17 @@ public class VehicleData extends BaseEntity
|
|||||||
|
|
||||||
/** 车辆状态 (1:启动 2:熄火 3:其他 254:异常 255:无效) */
|
/** 车辆状态 (1:启动 2:熄火 3:其他 254:异常 255:无效) */
|
||||||
@ApiModelProperty(value = "车辆状态 (1:启动 2:熄火 3:其他 254:异常 255:无效)")
|
@ApiModelProperty(value = "车辆状态 (1:启动 2:熄火 3:其他 254:异常 255:无效)")
|
||||||
@Excel(name = "车辆状态 (1:启动 2:熄火 3:其他 254:异常 255:无效)")
|
@Excel(name = "车辆状态 ")
|
||||||
private Integer vehicleStatus;
|
private Integer vehicleStatus;
|
||||||
|
|
||||||
/** 充电状态 (1:停车充电 2:行驶充电 3:未充电 4:充电完成 254:异常 255:无效) */
|
/** 充电状态 (1:停车充电 2:行驶充电 3:未充电 4:充电完成 254:异常 255:无效) */
|
||||||
@ApiModelProperty(value = "充电状态 (1:停车充电 2:行驶充电 3:未充电 4:充电完成 254:异常 255:无效)")
|
@ApiModelProperty(value = "充电状态 (1:停车充电 2:行驶充电 3:未充电 4:充电完成 254:异常 255:无效)")
|
||||||
@Excel(name = "充电状态 (1:停车充电 2:行驶充电 3:未充电 4:充电完成 254:异常 255:无效)")
|
@Excel(name = "充电状态 ")
|
||||||
private Integer chargingStatus;
|
private Integer chargingStatus;
|
||||||
|
|
||||||
/** 运行模式 (1:纯电 2:混动 3:燃油 254:异常 255:无效) */
|
/** 运行模式 (1:纯电 2:混动 3:燃油 254:异常 255:无效) */
|
||||||
@ApiModelProperty(value = "运行模式 (1:纯电 2:混动 3:燃油 254:异常 255:无效)")
|
@ApiModelProperty(value = "运行模式 (1:纯电 2:混动 3:燃油 254:异常 255:无效)")
|
||||||
@Excel(name = "运行模式 (1:纯电 2:混动 3:燃油 254:异常 255:无效)")
|
@Excel(name = "运行模式 ")
|
||||||
private Integer operationMode;
|
private Integer operationMode;
|
||||||
|
|
||||||
/** 车速(km/h) */
|
/** 车速(km/h) */
|
||||||
@ -87,7 +87,7 @@ public class VehicleData extends BaseEntity
|
|||||||
|
|
||||||
/** 档位 (0:空档 1-6:1-6档 13:倒档 14:自动D档 15:停车P档) */
|
/** 档位 (0:空档 1-6:1-6档 13:倒档 14:自动D档 15:停车P档) */
|
||||||
@ApiModelProperty(value = "档位 (0:空档 1-6:1-6档 13:倒档 14:自动D档 15:停车P档)")
|
@ApiModelProperty(value = "档位 (0:空档 1-6:1-6档 13:倒档 14:自动D档 15:停车P档)")
|
||||||
@Excel(name = "档位 (0:空档 1-6:1-6档 13:倒档 14:自动D档 15:停车P档)")
|
@Excel(name = "档位 ")
|
||||||
private Integer gearPosition;
|
private Integer gearPosition;
|
||||||
|
|
||||||
/** 绝缘电阻(kΩ) */
|
/** 绝缘电阻(kΩ) */
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "ChargeDischargeHistoryDTO", description = "充放电历史统计与明细")
|
||||||
|
public class ChargeDischargeHistoryDTO {
|
||||||
|
@ApiModelProperty(value = "月度充放电时长统计")
|
||||||
|
private List<ChargeDischargeMonthDTO> months;
|
||||||
|
@ApiModelProperty(value = "区段明细记录")
|
||||||
|
private List<ChargeDischargeRecordDTO> records;
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "ChargeDischargeMonthDTO", description = "月充放电时长统计")
|
||||||
|
public class ChargeDischargeMonthDTO {
|
||||||
|
@ApiModelProperty(value = "月份(yyyy-MM)", example = "2026-01")
|
||||||
|
private String month;
|
||||||
|
@ApiModelProperty(value = "充电总时长(小时)", example = "85.5")
|
||||||
|
private BigDecimal chargeHours;
|
||||||
|
@ApiModelProperty(value = "放电总时长(小时)", example = "92.0")
|
||||||
|
private BigDecimal dischargeHours;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "ChargeDischargeRecordDTO", description = "充放电区段记录")
|
||||||
|
public class ChargeDischargeRecordDTO {
|
||||||
|
@ApiModelProperty(value = "状态:充电/放电", example = "充电")
|
||||||
|
private String status;
|
||||||
|
@ApiModelProperty(value = "开始时间", example = "2026-01-10 15:30:00")
|
||||||
|
private String startTime;
|
||||||
|
@ApiModelProperty(value = "结束时间", example = "2026-01-10 23:30:00")
|
||||||
|
private String endTime;
|
||||||
|
@ApiModelProperty(value = "开始SOC(%)", example = "30")
|
||||||
|
private Integer startSoc;
|
||||||
|
@ApiModelProperty(value = "结束SOC(%)", example = "100")
|
||||||
|
private Integer endSoc;
|
||||||
|
@ApiModelProperty(value = "本次时长(小时)", example = "5")
|
||||||
|
private BigDecimal durationHours;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DashboardOverviewDTO", description = "首页概览聚合数据")
|
||||||
|
public class DashboardOverviewDTO {
|
||||||
|
@ApiModelProperty("基础统计(项目/设备/在线/故障)")
|
||||||
|
private DashboardSummaryDTO summary;
|
||||||
|
@ApiModelProperty("SOC分布(饼图)")
|
||||||
|
private List<NameValueDTO> socDistribution;
|
||||||
|
@ApiModelProperty("日充放电趋势(柱状图)")
|
||||||
|
private List<DashboardTrendItemDTO> trend;
|
||||||
|
@ApiModelProperty("运行时长累计与分布(左侧列表+右侧柱状图)")
|
||||||
|
private RuntimeSummaryDTO runtimeSummary;
|
||||||
|
@ApiModelProperty("运行时长分布(柱状图,单位小时)")
|
||||||
|
private List<NameValueDTO> runtimeDuration;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DashboardSocDistributionDTO", description = "设备SOC分布统计结果")
|
||||||
|
public class DashboardSocDistributionDTO {
|
||||||
|
@ApiModelProperty(value = "参与分桶的设备总数(不含unknown)", example = "865")
|
||||||
|
private long totalDevices;
|
||||||
|
@ApiModelProperty(value = "未取到有效SOC的设备数", example = "7")
|
||||||
|
private long unknownCount;
|
||||||
|
@ApiModelProperty(value = "分桶明细,按从低到高顺序返回")
|
||||||
|
private List<SocBinDTO> bins;
|
||||||
|
@ApiModelProperty(value = "生成时间(ISO8601)", example = "2026-03-18T10:22:00Z")
|
||||||
|
private String generatedAt;
|
||||||
|
@ApiModelProperty(value = "参数回显(bins/includeOffline/includeUnknown 等)")
|
||||||
|
private Map<String, Object> paramsEcho;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DashboardSummaryDTO", description = "首页概览卡片统计数据")
|
||||||
|
public class DashboardSummaryDTO {
|
||||||
|
@ApiModelProperty(value = "项目总数", example = "5")
|
||||||
|
private long projectCount;
|
||||||
|
@ApiModelProperty(value = "BBOX设备总数", example = "865")
|
||||||
|
private long deviceCount;
|
||||||
|
@ApiModelProperty(value = "在线设备总数", example = "789")
|
||||||
|
private long onlineCount;
|
||||||
|
@ApiModelProperty(value = "告警/故障数量", example = "12")
|
||||||
|
private long faultCount;
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DashboardTrendItemDTO", description = "日充放电时长项")
|
||||||
|
public class DashboardTrendItemDTO {
|
||||||
|
@ApiModelProperty(value = "日期", example = "2025-01-12")
|
||||||
|
private String date;
|
||||||
|
@ApiModelProperty(value = "充电时长(秒)", example = "252000")
|
||||||
|
private long chargeDuration;
|
||||||
|
@ApiModelProperty(value = "放电时长(秒)", example = "7200")
|
||||||
|
private long dischargeDuration;
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import com.evobms.framework.aspectj.lang.annotation.Excel;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DeviceDetailDTO", description = "设备详细信息聚合")
|
||||||
|
public class DeviceDetailDTO {
|
||||||
|
@ApiModelProperty("设备ID")
|
||||||
|
private String deviceId;
|
||||||
|
@ApiModelProperty("设备名称")
|
||||||
|
private String deviceName;
|
||||||
|
@ApiModelProperty("设备SN")
|
||||||
|
private String deviceSn;
|
||||||
|
@ApiModelProperty("设备类型")
|
||||||
|
private String deviceType;
|
||||||
|
@ApiModelProperty("设备状态")
|
||||||
|
private String status;
|
||||||
|
@ApiModelProperty("固件版本")
|
||||||
|
private String firmwareVersion;
|
||||||
|
@ApiModelProperty("最后在线时间")
|
||||||
|
private String lastOnline;
|
||||||
|
|
||||||
|
@ApiModelProperty("总电压(V)")
|
||||||
|
private BigDecimal totalVoltage;
|
||||||
|
@ApiModelProperty("总电流(A)")
|
||||||
|
private BigDecimal totalCurrent;
|
||||||
|
@ApiModelProperty("SOC(%)")
|
||||||
|
private Integer soc;
|
||||||
|
@ApiModelProperty("SOP(%)")
|
||||||
|
private Integer sop;
|
||||||
|
@ApiModelProperty(value = "电池单体电压最高值(V)")
|
||||||
|
private BigDecimal maxVoltageValue;
|
||||||
|
@ApiModelProperty(value = "电池单体电压最低值(V)")
|
||||||
|
private BigDecimal minVoltageValue;
|
||||||
|
@ApiModelProperty(value = "最高温度值(℃)")
|
||||||
|
private BigDecimal maxTempValue;
|
||||||
|
@ApiModelProperty(value = "最低温度值(℃)")
|
||||||
|
private BigDecimal minTempValue;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("车辆状态")
|
||||||
|
private Integer vehicleStatus;
|
||||||
|
@ApiModelProperty("充电状态")
|
||||||
|
private Integer chargingStatus;
|
||||||
|
@ApiModelProperty("运行模式")
|
||||||
|
private Integer operationMode;
|
||||||
|
@ApiModelProperty("DC-DC状态")
|
||||||
|
private Integer dcdcStatus;
|
||||||
|
@ApiModelProperty("档位")
|
||||||
|
private Integer gearPosition;
|
||||||
|
@ApiModelProperty("绝缘电阻(kΩ)")
|
||||||
|
private Integer insulationResistance;
|
||||||
|
|
||||||
|
@ApiModelProperty("经度")
|
||||||
|
private Double longitude;
|
||||||
|
@ApiModelProperty("纬度")
|
||||||
|
private Double latitude;
|
||||||
|
@ApiModelProperty("位置时间")
|
||||||
|
private String locationTime;
|
||||||
|
@ApiModelProperty("定位状态")
|
||||||
|
private Integer positioningStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty("云端维护-最近维护时间")
|
||||||
|
private String lastMaintenanceTime;
|
||||||
|
@ApiModelProperty("云端维护-最近升级时间")
|
||||||
|
private String lastUpgradeTime;
|
||||||
|
}
|
||||||
@ -0,0 +1,142 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.evobms.project.battery.domain.SubsystemTemperature;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemVoltage;
|
||||||
|
import com.evobms.project.bms.domain.BmsChargeTemperature;
|
||||||
|
import com.evobms.project.bms.domain.BmsVoltageChannelData;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One flattened history row aligned to vehicle_data time; other tables use latest snapshot at or before that time.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@ApiModel(value = "DeviceHistoryFlatRowDTO", description = "Device history flat row (same sources as realtime aggregate)")
|
||||||
|
public class DeviceHistoryFlatRowDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty("Device id")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@ApiModelProperty("vehicle_data primary key")
|
||||||
|
private Long vehicleDataId;
|
||||||
|
|
||||||
|
@ApiModelProperty("Point-in-time: business timestamp if present else create_time")
|
||||||
|
private String pointTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("vehicle_data create_time")
|
||||||
|
private String vehicleCreateTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("SOC percent")
|
||||||
|
private Integer soc;
|
||||||
|
@ApiModelProperty("Total voltage V")
|
||||||
|
private BigDecimal totalVoltage;
|
||||||
|
@ApiModelProperty("Total current A")
|
||||||
|
private BigDecimal totalCurrent;
|
||||||
|
@ApiModelProperty("Vehicle speed km/h")
|
||||||
|
private BigDecimal vehicleSpeed;
|
||||||
|
@ApiModelProperty("Total mileage km")
|
||||||
|
private BigDecimal totalMileage;
|
||||||
|
@ApiModelProperty("Charging status")
|
||||||
|
private Integer chargingStatus;
|
||||||
|
@ApiModelProperty("Vehicle status")
|
||||||
|
private Integer vehicleStatus;
|
||||||
|
@ApiModelProperty("Operation mode")
|
||||||
|
private Integer operationMode;
|
||||||
|
@ApiModelProperty("DC-DC status")
|
||||||
|
private Integer dcdcStatus;
|
||||||
|
@ApiModelProperty("Gear")
|
||||||
|
private Integer gearPosition;
|
||||||
|
@ApiModelProperty("Insulation resistance kOhm")
|
||||||
|
private Integer insulationResistance;
|
||||||
|
|
||||||
|
@ApiModelProperty("Max cell voltage V")
|
||||||
|
private BigDecimal maxCellVoltage;
|
||||||
|
@ApiModelProperty("Min cell voltage V")
|
||||||
|
private BigDecimal minCellVoltage;
|
||||||
|
@ApiModelProperty("Max temperature")
|
||||||
|
private BigDecimal maxTempValue;
|
||||||
|
@ApiModelProperty("Min temperature")
|
||||||
|
private BigDecimal minTempValue;
|
||||||
|
@ApiModelProperty("Max cell position subsystem-cell")
|
||||||
|
private String maxCellPos;
|
||||||
|
@ApiModelProperty("Min cell position")
|
||||||
|
private String minCellPos;
|
||||||
|
@ApiModelProperty("Max temp position subsystem-probe")
|
||||||
|
private String maxTempPos;
|
||||||
|
@ApiModelProperty("Min temp position")
|
||||||
|
private String minTempPos;
|
||||||
|
|
||||||
|
private Long relayStatus;
|
||||||
|
private Long onaaccStatus;
|
||||||
|
private Long resetCount;
|
||||||
|
private Long cycleCount;
|
||||||
|
private Long cellCount;
|
||||||
|
private BigDecimal sample25v;
|
||||||
|
private BigDecimal sample5v;
|
||||||
|
private Integer tempSensorCount;
|
||||||
|
|
||||||
|
private BigDecimal acChargeCurrent;
|
||||||
|
private BigDecimal acChargeVoltage;
|
||||||
|
private BigDecimal dcChargeCurrent;
|
||||||
|
private BigDecimal dcChargeVoltage;
|
||||||
|
private Long chargeTime;
|
||||||
|
private Integer acStopCode;
|
||||||
|
private Integer dcStopCode;
|
||||||
|
private Integer acCpValue;
|
||||||
|
private Integer acGunResistance;
|
||||||
|
private Integer chargingStage;
|
||||||
|
private Integer acStage;
|
||||||
|
private Integer dcStage;
|
||||||
|
|
||||||
|
private BigDecimal runtimeVehicleSpeed;
|
||||||
|
private Long runtimeMileage;
|
||||||
|
private BigDecimal systemVoltage;
|
||||||
|
private BigDecimal systemCurrent;
|
||||||
|
private Long runtimeInsulationResistance;
|
||||||
|
private BigDecimal socMax;
|
||||||
|
private BigDecimal socMin;
|
||||||
|
private BigDecimal gpvVoltage;
|
||||||
|
private Integer runStatus;
|
||||||
|
private Integer powerStatus;
|
||||||
|
|
||||||
|
private Long dischargeSop10s;
|
||||||
|
private Long dischargeSop30s;
|
||||||
|
private Long dischargeSop60s;
|
||||||
|
private Long dischargeSopContinuous;
|
||||||
|
private Long dischargeSopTotal;
|
||||||
|
private Long regenSop10s;
|
||||||
|
private Long regenSop30s;
|
||||||
|
private Long regenSop60s;
|
||||||
|
private Long regenSopContinuous;
|
||||||
|
private Long regenSopTotal;
|
||||||
|
private Long chargeSop;
|
||||||
|
private Long acChargeSop;
|
||||||
|
|
||||||
|
private BigDecimal longitude;
|
||||||
|
private BigDecimal latitude;
|
||||||
|
private Integer positioningStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty("Charge seat temperatures, latest per probe index 1-8 at point time")
|
||||||
|
private List<BmsChargeTemperature> chargeTemperaturesAtPoint;
|
||||||
|
|
||||||
|
@ApiModelProperty("Voltage channels, latest per type+index at point time")
|
||||||
|
private List<BmsVoltageChannelData> voltageChannelsAtPoint;
|
||||||
|
|
||||||
|
@ApiModelProperty("Per-subsystem voltage frame, latest per subsystem no at point time")
|
||||||
|
private List<SubsystemVoltage> subsystemVoltagesAtPoint;
|
||||||
|
|
||||||
|
@ApiModelProperty("Per-subsystem temperature frame, latest per subsystem no at point time")
|
||||||
|
private List<SubsystemTemperature> subsystemTemperaturesAtPoint;
|
||||||
|
|
||||||
|
@ApiModelProperty("Convenience: probe T1 deg C")
|
||||||
|
private BigDecimal chargeSeatTemp1;
|
||||||
|
|
||||||
|
@ApiModelProperty("Convenience: RTP channel 1 voltage")
|
||||||
|
private BigDecimal rtp1Voltage;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import com.evobms.project.system.domain.BmsDevices;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DeviceLatestLocationDTO", description = "设备与其最新位置")
|
||||||
|
public class DeviceLatestLocationDTO {
|
||||||
|
@ApiModelProperty(value = "设备对象")
|
||||||
|
private BmsDevices device;
|
||||||
|
@ApiModelProperty(value = "最新位置点,可能为空")
|
||||||
|
private TrackPointDTO location;
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
import com.evobms.project.vehicledata.domain.VehicleData;
|
||||||
|
import com.evobms.project.vehicledata.domain.VehicleLocation;
|
||||||
|
import com.evobms.project.battery.domain.ExtremeValues;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemTemperature;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemVoltage;
|
||||||
|
import com.evobms.project.bms.domain.*;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DeviceRealtimeAggregateDTO", description = "设备实时聚合:原始实体 + dashboard 扁平视图(含24h趋势)")
|
||||||
|
public class DeviceRealtimeAggregateDTO {
|
||||||
|
@ApiModelProperty(value = "设备ID", example = "EVO0001")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "基础:最新整车数据")
|
||||||
|
private VehicleData vehicleData;
|
||||||
|
@ApiModelProperty(value = "基础:最新位置数据")
|
||||||
|
private VehicleLocation latestLocation;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "极值:单体电压/温度极值")
|
||||||
|
private ExtremeValues extremeValues;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "BMS:系统状态")
|
||||||
|
private BmsSystemStatus systemStatus;
|
||||||
|
@ApiModelProperty(value = "BMS:实时运行")
|
||||||
|
private BmsRuntimeData runtimeData;
|
||||||
|
@ApiModelProperty(value = "BMS:充电详情")
|
||||||
|
private BmsChargeDetail chargeDetail;
|
||||||
|
@ApiModelProperty(value = "BMS:SOP功率能力")
|
||||||
|
private BmsSopData sopData;
|
||||||
|
@ApiModelProperty(value = "BMS:充电座温度最新")
|
||||||
|
private List<BmsChargeTemperature> chargeTemperatures;
|
||||||
|
@ApiModelProperty(value = "BMS:电压通道最新")
|
||||||
|
private List<BmsVoltageChannelData> voltageChannels;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "可充电储能子系统电压:入库时间最新的一条(任意子系统号,便于单独展示)")
|
||||||
|
private SubsystemVoltage subsystemVoltage;
|
||||||
|
@ApiModelProperty(value = "可充电储能子系统温度:入库时间最新的一条(任意子系统号,便于单独展示)")
|
||||||
|
private SubsystemTemperature subsystemTemperature;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "可充电储能子系统电压:各子系统号各保留入库最新一条")
|
||||||
|
private List<SubsystemVoltage> subsystemVoltages;
|
||||||
|
@ApiModelProperty(value = "可充电储能子系统温度:各子系统号各保留入库最新一条")
|
||||||
|
private List<SubsystemTemperature> subsystemTemperatures;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "前端扁平视图:系统状态分块、通道/探头列表、24h 趋势等(与上方原始实体互补,避免重复组装)")
|
||||||
|
private DeviceRealtimeDTO dashboard;
|
||||||
|
}
|
||||||
@ -0,0 +1,142 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DeviceRealtimeDTO", description = "设备实时详情聚合")
|
||||||
|
public class DeviceRealtimeDTO {
|
||||||
|
@ApiModelProperty(value = "设备ID", example = "EVO0001")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "系统状态(总电压/电流、SOC、单体/温度极值等)")
|
||||||
|
private SystemStatusSection systemStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "故障信息(名称+时间),暂无则为空数组")
|
||||||
|
private List<NameValueDTO> faults;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "运行状态(充放电状态、继电器、复位次数、采样电压等)")
|
||||||
|
private RunStatusSection runStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "充电信息(AC/DC电压电流、停止码、阶段等)")
|
||||||
|
private ChargeSection charge;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "统计信息(循环次数、累计时长等)")
|
||||||
|
private StatisticsSection statistics;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单体电压列表,name=通道名,value=电压值")
|
||||||
|
private List<NameValueDTO> voltages;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单体温度列表,name=T序号,value=温度值")
|
||||||
|
private List<NameValueDTO> temperatures;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单体电压折线图(默认RTP-1,最近24小时)")
|
||||||
|
private List<LineSeriesDTO> voltageTrend;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单体温度折线图(默认T1,最近24小时)")
|
||||||
|
private List<LineSeriesDTO> temperatureTrend;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "整车总电压折线图(vehicle_data,最近24小时)")
|
||||||
|
private List<LineSeriesDTO> totalVoltageTrend;
|
||||||
|
@ApiModelProperty(value = "整车总电流折线图(vehicle_data,最近24小时)")
|
||||||
|
private List<LineSeriesDTO> totalCurrentTrend;
|
||||||
|
@ApiModelProperty(value = "整车 SOC 折线图(vehicle_data,最近24小时)")
|
||||||
|
private List<LineSeriesDTO> socTrend;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SystemStatusSection {
|
||||||
|
@ApiModelProperty(value = "总电压(V)", example = "372.5")
|
||||||
|
private BigDecimal totalVoltage;
|
||||||
|
@ApiModelProperty(value = "总电流(A)", example = "-52.3")
|
||||||
|
private BigDecimal totalCurrent;
|
||||||
|
@ApiModelProperty(value = "SOC(%)", example = "95")
|
||||||
|
private Integer soc;
|
||||||
|
@ApiModelProperty(value = "SOH(%),暂无数据时为空", example = "86")
|
||||||
|
private Integer soh;
|
||||||
|
@ApiModelProperty(value = "最高单体电压(V)", example = "5.0")
|
||||||
|
private BigDecimal maxCellVoltage;
|
||||||
|
@ApiModelProperty(value = "最高单体位置(子系统-单体序号)", example = "10-5")
|
||||||
|
private String maxCellPos;
|
||||||
|
@ApiModelProperty(value = "最低单体电压(V)", example = "2.0")
|
||||||
|
private BigDecimal minCellVoltage;
|
||||||
|
@ApiModelProperty(value = "最低单体位置(子系统-单体序号)", example = "15-3")
|
||||||
|
private String minCellPos;
|
||||||
|
@ApiModelProperty(value = "平均单体电压(V)", example = "3.7")
|
||||||
|
private BigDecimal avgCellVoltage;
|
||||||
|
@ApiModelProperty(value = "压差单体(V) = 最高-最低", example = "0.005")
|
||||||
|
private BigDecimal voltageDiff;
|
||||||
|
@ApiModelProperty(value = "最高温度(℃)", example = "25")
|
||||||
|
private BigDecimal maxTemp;
|
||||||
|
@ApiModelProperty(value = "最高温度位置(子系统-探针序号)", example = "4-2")
|
||||||
|
private String maxTempPos;
|
||||||
|
@ApiModelProperty(value = "最低温度(℃)", example = "20")
|
||||||
|
private BigDecimal minTemp;
|
||||||
|
@ApiModelProperty(value = "最低温度位置(子系统-探针序号)", example = "5-1")
|
||||||
|
private String minTempPos;
|
||||||
|
@ApiModelProperty(value = "平均温度(℃)", example = "22")
|
||||||
|
private BigDecimal avgTemp;
|
||||||
|
@ApiModelProperty(value = "温差单体(℃) = 最高-最低", example = "2")
|
||||||
|
private BigDecimal tempDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class RunStatusSection {
|
||||||
|
@ApiModelProperty(value = "充放电状态:1停车充电/2行驶充电/3未充电/4充电完成/254异常/255无效", example = "1")
|
||||||
|
private Integer chargingStatus;
|
||||||
|
@ApiModelProperty(value = "继电器状态(按位标识)", example = "5")
|
||||||
|
private Long relayStatus;
|
||||||
|
@ApiModelProperty(value = "ON/A+/CC 状态(按位标识)", example = "4")
|
||||||
|
private Long onaaccStatus;
|
||||||
|
@ApiModelProperty(value = "系统复位次数", example = "12")
|
||||||
|
private Long resetCount;
|
||||||
|
@ApiModelProperty(value = "电池循环次数", example = "345")
|
||||||
|
private Long cycleCount;
|
||||||
|
@ApiModelProperty(value = "2.5V采样电压(V)", example = "2.5")
|
||||||
|
private BigDecimal sample25v;
|
||||||
|
@ApiModelProperty(value = "5V采样电压(V)", example = "5.0")
|
||||||
|
private BigDecimal sample5v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ChargeSection {
|
||||||
|
@ApiModelProperty(value = "慢充电流(A)", example = "7")
|
||||||
|
private BigDecimal acChargeCurrent;
|
||||||
|
@ApiModelProperty(value = "慢充电压(V)", example = "12")
|
||||||
|
private BigDecimal acChargeVoltage;
|
||||||
|
@ApiModelProperty(value = "快充电流(A)", example = "0")
|
||||||
|
private BigDecimal dcChargeCurrent;
|
||||||
|
@ApiModelProperty(value = "快充电压(V)", example = "52")
|
||||||
|
private BigDecimal dcChargeVoltage;
|
||||||
|
@ApiModelProperty(value = "本次充电时长(min)", example = "6")
|
||||||
|
private Long chargeTime;
|
||||||
|
@ApiModelProperty(value = "慢充停止码", example = "5")
|
||||||
|
private Integer acStopCode;
|
||||||
|
@ApiModelProperty(value = "快充停止码", example = "12")
|
||||||
|
private Integer dcStopCode;
|
||||||
|
@ApiModelProperty(value = "慢充CP值", example = "20")
|
||||||
|
private Integer acCpValue;
|
||||||
|
@ApiModelProperty(value = "慢充枪阻值", example = "5")
|
||||||
|
private Integer acGunResistance;
|
||||||
|
@ApiModelProperty(value = "充电阶段", example = "3")
|
||||||
|
private Integer chargingStage;
|
||||||
|
@ApiModelProperty(value = "慢充阶段", example = "2")
|
||||||
|
private Integer acStage;
|
||||||
|
@ApiModelProperty(value = "快充阶段", example = "1")
|
||||||
|
private Integer dcStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class StatisticsSection {
|
||||||
|
@ApiModelProperty(value = "循环次数", example = "345")
|
||||||
|
private Long cycleCount;
|
||||||
|
@ApiModelProperty(value = "累计运行时间(小时)", example = "1233")
|
||||||
|
private Long totalRunHours;
|
||||||
|
@ApiModelProperty(value = "累计充电时间(小时)", example = "456")
|
||||||
|
private Long totalChargeHours;
|
||||||
|
@ApiModelProperty(value = "累计放电时间(小时)", example = "321")
|
||||||
|
private Long totalDischargeHours;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "DeviceTrackDTO", description = "设备轨迹数据")
|
||||||
|
public class DeviceTrackDTO {
|
||||||
|
@ApiModelProperty(value = "设备ID", example = "BAT_001")
|
||||||
|
private String deviceId;
|
||||||
|
@ApiModelProperty(value = "设备名称", example = "设备A")
|
||||||
|
private String deviceName;
|
||||||
|
@ApiModelProperty(value = "设备SN", example = "SN001")
|
||||||
|
private String deviceSn;
|
||||||
|
@ApiModelProperty(value = "设备类型", example = "BBOX")
|
||||||
|
private String deviceType;
|
||||||
|
@ApiModelProperty(value = "设备状态", example = "online")
|
||||||
|
private String status;
|
||||||
|
@ApiModelProperty(value = "固件版本", example = "v1.0.0")
|
||||||
|
private String firmwareVersion;
|
||||||
|
@ApiModelProperty(value = "最后在线时间", example = "2026-03-18 10:55:00")
|
||||||
|
private String lastOnline;
|
||||||
|
@ApiModelProperty(value = "车速(km/h)", example = "35.2")
|
||||||
|
private java.math.BigDecimal vehicleSpeed;
|
||||||
|
@ApiModelProperty(value = "累计里程(km)", example = "12345.6")
|
||||||
|
private java.math.BigDecimal totalMileage;
|
||||||
|
@ApiModelProperty(value = "总电压(V)", example = "372.5")
|
||||||
|
private java.math.BigDecimal totalVoltage;
|
||||||
|
@ApiModelProperty(value = "总电流(A)", example = "-52.3")
|
||||||
|
private java.math.BigDecimal totalCurrent;
|
||||||
|
@ApiModelProperty(value = "SOC(%)", example = "78")
|
||||||
|
private Integer soc;
|
||||||
|
@ApiModelProperty(value = "轨迹点列表")
|
||||||
|
private List<TrackPointDTO> trackList;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "LineSeriesDTO", description = "折线图序列")
|
||||||
|
public class LineSeriesDTO {
|
||||||
|
@ApiModelProperty(value = "系列名称", example = "RTP-1")
|
||||||
|
private String name;
|
||||||
|
@ApiModelProperty(value = "时间点列表")
|
||||||
|
private List<TimePointDTO> points;
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "NameValueDTO", description = "名称-数值数据项")
|
||||||
|
public class NameValueDTO {
|
||||||
|
@ApiModelProperty(value = "名称", example = "0-20%")
|
||||||
|
private String name;
|
||||||
|
@ApiModelProperty(value = "数值", example = "15")
|
||||||
|
private long value;
|
||||||
|
@ApiModelProperty(value = "生成时间", example = "2026-03-18T10:22:00Z")
|
||||||
|
private String createTime;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "RuntimeSummaryDTO", description = "运行时长与能量容量累计统计")
|
||||||
|
public class RuntimeSummaryDTO {
|
||||||
|
@ApiModelProperty(value = "累计运行时间(小时)", example = "1233")
|
||||||
|
private long totalRunHours;
|
||||||
|
@ApiModelProperty(value = "累计充电时间(小时)", example = "545456")
|
||||||
|
private long totalChargeHours;
|
||||||
|
@ApiModelProperty(value = "累计放电时间(小时)", example = "565432")
|
||||||
|
private long totalDischargeHours;
|
||||||
|
@ApiModelProperty(value = "累计充电能量(kWh)", example = "71233")
|
||||||
|
private long totalChargeEnergyKWh;
|
||||||
|
@ApiModelProperty(value = "累计放电能量(kWh)", example = "556452")
|
||||||
|
private long totalDischargeEnergyKWh;
|
||||||
|
@ApiModelProperty(value = "累计充电容量(Ah)", example = "8233")
|
||||||
|
private long totalChargeCapacityAh;
|
||||||
|
@ApiModelProperty(value = "累计放电容量(Ah)", example = "8233")
|
||||||
|
private long totalDischargeCapacityAh;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "SocBinDTO", description = "SOC分布分桶信息")
|
||||||
|
public class SocBinDTO {
|
||||||
|
@ApiModelProperty(value = "标签,例如 20-40", example = "20-40")
|
||||||
|
private String label;
|
||||||
|
@ApiModelProperty(value = "桶下界(含)", example = "20")
|
||||||
|
private int min;
|
||||||
|
@ApiModelProperty(value = "桶上界(默认不含,最后一桶含)", example = "40")
|
||||||
|
private int max;
|
||||||
|
@ApiModelProperty(value = "该桶内设备数量", example = "96")
|
||||||
|
private long count;
|
||||||
|
@ApiModelProperty(value = "占比(四位小数)", example = "0.1159")
|
||||||
|
private double ratio;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "TimePointDTO", description = "折线图时间点")
|
||||||
|
public class TimePointDTO {
|
||||||
|
@ApiModelProperty(value = "时间", example = "2026-03-18 12:00:00")
|
||||||
|
private String time;
|
||||||
|
@ApiModelProperty(value = "数值", example = "4.95")
|
||||||
|
private BigDecimal value;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.evobms.project.vehicledata.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "TrackPointDTO", description = "轨迹点")
|
||||||
|
public class TrackPointDTO {
|
||||||
|
@ApiModelProperty(value = "时间", example = "2026-03-18 10:00:00")
|
||||||
|
private String timestamp;
|
||||||
|
@ApiModelProperty(value = "经度", example = "116.397128")
|
||||||
|
private Double longitude;
|
||||||
|
@ApiModelProperty(value = "纬度", example = "39.916527")
|
||||||
|
private Double latitude;
|
||||||
|
@ApiModelProperty(value = "定位状态(0无效1有效)", example = "1")
|
||||||
|
private Integer positioningStatus;
|
||||||
|
}
|
||||||
@ -65,4 +65,14 @@ public interface VehicleDataMapper extends BaseMapper<VehicleData>
|
|||||||
List<VehicleData> selectByDeviceAndStartTime(@Param("deviceId") String deviceId, @Param("startTime") Date startTime);
|
List<VehicleData> selectByDeviceAndStartTime(@Param("deviceId") String deviceId, @Param("startTime") Date startTime);
|
||||||
|
|
||||||
VehicleData selectLatestByDeviceId(String deviceId);
|
VehicleData selectLatestByDeviceId(String deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史分页:仅按 timestamp 过滤/排序,走 (device_id, timestamp) 索引,避免 COALESCE filesort。
|
||||||
|
* MQTT 入库均带 timestamp;timestamp 为空的旧数据不在此接口返回。
|
||||||
|
*/
|
||||||
|
List<VehicleData> selectVehicleDataListForHistory(
|
||||||
|
@Param("deviceId") String deviceId,
|
||||||
|
@Param("begin") Date begin,
|
||||||
|
@Param("end") Date end,
|
||||||
|
@Param("descending") boolean descending);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,753 @@
|
|||||||
|
package com.evobms.project.vehicledata.service;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
|
import com.evobms.project.battery.domain.ExtremeValues;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemTemperature;
|
||||||
|
import com.evobms.project.battery.domain.SubsystemVoltage;
|
||||||
|
import com.evobms.project.battery.mapper.ExtremeValuesMapper;
|
||||||
|
import com.evobms.project.battery.service.ISubsystemTemperatureService;
|
||||||
|
import com.evobms.project.battery.service.ISubsystemVoltageService;
|
||||||
|
import com.evobms.project.bms.domain.BmsChargeDetail;
|
||||||
|
import com.evobms.project.bms.domain.BmsChargeTemperature;
|
||||||
|
import com.evobms.project.bms.domain.BmsRuntimeData;
|
||||||
|
import com.evobms.project.bms.domain.BmsSopData;
|
||||||
|
import com.evobms.project.bms.domain.BmsSystemStatus;
|
||||||
|
import com.evobms.project.bms.domain.BmsVoltageChannelData;
|
||||||
|
import com.evobms.project.bms.service.IBmsChargeDetailService;
|
||||||
|
import com.evobms.project.bms.service.IBmsChargeTemperatureService;
|
||||||
|
import com.evobms.project.bms.service.IBmsRuntimeDataService;
|
||||||
|
import com.evobms.project.bms.service.IBmsSopDataService;
|
||||||
|
import com.evobms.project.bms.service.IBmsSystemStatusService;
|
||||||
|
import com.evobms.project.bms.service.IBmsVoltageChannelDataService;
|
||||||
|
import com.evobms.project.vehicledata.domain.VehicleData;
|
||||||
|
import com.evobms.project.vehicledata.domain.VehicleLocation;
|
||||||
|
import com.evobms.project.vehicledata.dto.DeviceHistoryFlatRowDTO;
|
||||||
|
import com.evobms.project.vehicledata.mapper.VehicleLocationMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/** Merges multi-table BMS/vehicle data into flat history rows by vehicle_data timeline. */
|
||||||
|
@Component
|
||||||
|
public class DeviceRealtimeHistoryAssembler {
|
||||||
|
|
||||||
|
private static final int PREFETCH_CAP_MAX = 20_000;
|
||||||
|
private static final int PREFETCH_PER_VEHICLE = 40;
|
||||||
|
private static final int PREFETCH_FLOOR = 128;
|
||||||
|
/** 多探针/多通道表单页预取上限(pageSize=500 时约 8000) */
|
||||||
|
private static final int MAP_PREFETCH_CAP_MAX = 20_000;
|
||||||
|
/** 每页整车行对应多探针表预取系数(约 8 路温度 × 余量) */
|
||||||
|
private static final int MAP_ROWS_PER_VEHICLE = 16;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ExtremeValuesMapper extremeValuesMapper;
|
||||||
|
@Autowired
|
||||||
|
private IBmsSystemStatusService bmsSystemStatusService;
|
||||||
|
@Autowired
|
||||||
|
private IBmsChargeDetailService bmsChargeDetailService;
|
||||||
|
@Autowired
|
||||||
|
private IBmsRuntimeDataService bmsRuntimeDataService;
|
||||||
|
@Autowired
|
||||||
|
private IBmsSopDataService bmsSopDataService;
|
||||||
|
@Autowired
|
||||||
|
private IBmsChargeTemperatureService bmsChargeTemperatureService;
|
||||||
|
@Autowired
|
||||||
|
private IBmsVoltageChannelDataService bmsVoltageChannelDataService;
|
||||||
|
@Autowired
|
||||||
|
private ISubsystemVoltageService subsystemVoltageService;
|
||||||
|
@Autowired
|
||||||
|
private ISubsystemTemperatureService subsystemTemperatureService;
|
||||||
|
@Autowired
|
||||||
|
private VehicleLocationMapper vehicleLocationMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param configuredPageSize 本页请求的 pageSize(10/20/100/500),用于放大预取上限;实际行数可能更少(末页)
|
||||||
|
*/
|
||||||
|
public List<DeviceHistoryFlatRowDTO> build(
|
||||||
|
String deviceId,
|
||||||
|
List<VehicleData> vehicleRows,
|
||||||
|
SimpleDateFormat tf,
|
||||||
|
int configuredPageSize) {
|
||||||
|
List<DeviceHistoryFlatRowDTO> out = new ArrayList<>();
|
||||||
|
if (vehicleRows == null || vehicleRows.isEmpty()) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
Date tMin = null;
|
||||||
|
Date tMax = null;
|
||||||
|
for (VehicleData v : vehicleRows) {
|
||||||
|
Date t = vehiclePointTime(v);
|
||||||
|
if (t == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (tMin == null || t.before(tMin)) {
|
||||||
|
tMin = t;
|
||||||
|
}
|
||||||
|
if (tMax == null || t.after(tMax)) {
|
||||||
|
tMax = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tMax == null) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
// 仅预取「当前页」时间窗内的辅助表数据,避免按整段 beginTime~endTime 拉数千行
|
||||||
|
Date lo = tMin;
|
||||||
|
Date hi = tMax;
|
||||||
|
Date bootstrapBefore = tMin;
|
||||||
|
int capRows = Math.max(vehicleRows.size(), Math.max(1, configuredPageSize));
|
||||||
|
int cap = prefetchCap(capRows);
|
||||||
|
int mapCap = mapPrefetchCap(capRows);
|
||||||
|
PrefetchContext ctx = loadPrefetchParallel(deviceId, bootstrapBefore, lo, hi, cap, mapCap);
|
||||||
|
|
||||||
|
ExtremeValues curEx = ctx.curEx;
|
||||||
|
BmsSystemStatus curSs = ctx.curSs;
|
||||||
|
BmsChargeDetail curCd = ctx.curCd;
|
||||||
|
BmsRuntimeData curRt = ctx.curRt;
|
||||||
|
BmsSopData curSop = ctx.curSop;
|
||||||
|
VehicleLocation curLoc = ctx.curLoc;
|
||||||
|
Map<Integer, BmsChargeTemperature> chargeByIdx = ctx.chargeByIdx;
|
||||||
|
Map<String, BmsVoltageChannelData> voltByKey = ctx.voltByKey;
|
||||||
|
Map<Integer, SubsystemVoltage> svByNo = ctx.svByNo;
|
||||||
|
Map<Integer, SubsystemTemperature> stByNo = ctx.stByNo;
|
||||||
|
|
||||||
|
List<ExtremeValues> extremeAsc = ctx.extremeAsc;
|
||||||
|
List<BmsSystemStatus> ssAsc = ctx.ssAsc;
|
||||||
|
List<BmsChargeDetail> cdAsc = ctx.cdAsc;
|
||||||
|
List<BmsRuntimeData> rtAsc = ctx.rtAsc;
|
||||||
|
List<BmsSopData> sopAsc = ctx.sopAsc;
|
||||||
|
List<VehicleLocation> locAsc = ctx.locAsc;
|
||||||
|
List<BmsChargeTemperature> chargeAsc = ctx.chargeAsc;
|
||||||
|
List<BmsVoltageChannelData> voltAsc = ctx.voltAsc;
|
||||||
|
List<SubsystemVoltage> svAsc = ctx.svAsc;
|
||||||
|
List<SubsystemTemperature> stAsc = ctx.stAsc;
|
||||||
|
|
||||||
|
List<Integer> orderAsc = sortedIndicesByPointTimeAsc(vehicleRows);
|
||||||
|
|
||||||
|
int pEx = 0, pSs = 0, pCd = 0, pRt = 0, pSop = 0, pLoc = 0;
|
||||||
|
int pCh = 0, pVc = 0, pSv = 0, pSt = 0;
|
||||||
|
|
||||||
|
Map<Integer, DeviceHistoryFlatRowDTO> slot = new HashMap<>();
|
||||||
|
|
||||||
|
for (int idx : orderAsc) {
|
||||||
|
VehicleData vd = vehicleRows.get(idx);
|
||||||
|
Date ti = vehiclePointTime(vd);
|
||||||
|
if (ti == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pEx < extremeAsc.size() && extremeEffectiveTime(extremeAsc.get(pEx)).compareTo(ti) <= 0) {
|
||||||
|
curEx = extremeAsc.get(pEx++);
|
||||||
|
}
|
||||||
|
while (pSs < ssAsc.size() && nullSafe(ssAsc.get(pSs).getCreateTime()).compareTo(ti) <= 0) {
|
||||||
|
curSs = ssAsc.get(pSs++);
|
||||||
|
}
|
||||||
|
while (pCd < cdAsc.size() && nullSafe(cdAsc.get(pCd).getCreateTime()).compareTo(ti) <= 0) {
|
||||||
|
curCd = cdAsc.get(pCd++);
|
||||||
|
}
|
||||||
|
while (pRt < rtAsc.size() && nullSafe(rtAsc.get(pRt).getCreateTime()).compareTo(ti) <= 0) {
|
||||||
|
curRt = rtAsc.get(pRt++);
|
||||||
|
}
|
||||||
|
while (pSop < sopAsc.size() && nullSafe(sopAsc.get(pSop).getCreateTime()).compareTo(ti) <= 0) {
|
||||||
|
curSop = sopAsc.get(pSop++);
|
||||||
|
}
|
||||||
|
while (pLoc < locAsc.size() && locationEffectiveTime(locAsc.get(pLoc)).compareTo(ti) <= 0) {
|
||||||
|
curLoc = locAsc.get(pLoc++);
|
||||||
|
}
|
||||||
|
while (pCh < chargeAsc.size() && chargeTempEffectiveTime(chargeAsc.get(pCh)).compareTo(ti) <= 0) {
|
||||||
|
BmsChargeTemperature row = chargeAsc.get(pCh++);
|
||||||
|
if (row.getTempIndex() != null) {
|
||||||
|
chargeByIdx.put(row.getTempIndex(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (pVc < voltAsc.size() && voltageChannelEffectiveTime(voltAsc.get(pVc)).compareTo(ti) <= 0) {
|
||||||
|
BmsVoltageChannelData row = voltAsc.get(pVc++);
|
||||||
|
voltByKey.put(voltChannelKey(row), row);
|
||||||
|
}
|
||||||
|
while (pSv < svAsc.size() && subsystemVoltageEffectiveTime(svAsc.get(pSv)).compareTo(ti) <= 0) {
|
||||||
|
SubsystemVoltage row = svAsc.get(pSv++);
|
||||||
|
if (row.getSubsystemNo() != null) {
|
||||||
|
svByNo.put(row.getSubsystemNo(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (pSt < stAsc.size() && subsystemTemperatureEffectiveTime(stAsc.get(pSt)).compareTo(ti) <= 0) {
|
||||||
|
SubsystemTemperature row = stAsc.get(pSt++);
|
||||||
|
if (row.getSubsystemNo() != null) {
|
||||||
|
stByNo.put(row.getSubsystemNo(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceHistoryFlatRowDTO dto = toFlat(deviceId, vd, tf, curEx, curSs, curCd, curRt, curSop, curLoc,
|
||||||
|
chargeByIdx, voltByKey, svByNo, stByNo);
|
||||||
|
slot.put(idx, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < vehicleRows.size(); i++) {
|
||||||
|
DeviceHistoryFlatRowDTO row = slot.get(i);
|
||||||
|
if (row != null) {
|
||||||
|
out.add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrefetchContext loadPrefetchParallel(
|
||||||
|
String deviceId, Date bootstrapBefore, Date lo, Date hi, int cap, int mapCap) {
|
||||||
|
CompletableFuture<ExtremeValues> curExF = CompletableFuture.supplyAsync(() -> bootstrapExtremeValues(deviceId, bootstrapBefore));
|
||||||
|
CompletableFuture<BmsSystemStatus> curSsF = CompletableFuture.supplyAsync(() -> bootstrapSystemStatus(deviceId, bootstrapBefore));
|
||||||
|
CompletableFuture<BmsChargeDetail> curCdF = CompletableFuture.supplyAsync(() -> bootstrapChargeDetail(deviceId, bootstrapBefore));
|
||||||
|
CompletableFuture<BmsRuntimeData> curRtF = CompletableFuture.supplyAsync(() -> bootstrapRuntimeData(deviceId, bootstrapBefore));
|
||||||
|
CompletableFuture<BmsSopData> curSopF = CompletableFuture.supplyAsync(() -> bootstrapSopData(deviceId, bootstrapBefore));
|
||||||
|
CompletableFuture<VehicleLocation> curLocF = CompletableFuture.supplyAsync(() -> bootstrapVehicleLocation(deviceId, bootstrapBefore));
|
||||||
|
CompletableFuture<Map<Integer, BmsChargeTemperature>> chargeMapF = CompletableFuture.supplyAsync(() -> bootstrapChargeTemps(deviceId, bootstrapBefore, mapCap));
|
||||||
|
CompletableFuture<Map<String, BmsVoltageChannelData>> voltMapF = CompletableFuture.supplyAsync(() -> bootstrapVoltageChannels(deviceId, bootstrapBefore, mapCap));
|
||||||
|
CompletableFuture<Map<Integer, SubsystemVoltage>> svMapF = CompletableFuture.supplyAsync(() -> bootstrapSubsystemVoltages(deviceId, bootstrapBefore, mapCap));
|
||||||
|
CompletableFuture<Map<Integer, SubsystemTemperature>> stMapF = CompletableFuture.supplyAsync(() -> bootstrapSubsystemTemperatures(deviceId, bootstrapBefore, mapCap));
|
||||||
|
|
||||||
|
CompletableFuture<List<ExtremeValues>> extremeAscF = CompletableFuture.supplyAsync(() -> loadExtremeValues(deviceId, lo, hi, cap));
|
||||||
|
CompletableFuture<List<BmsSystemStatus>> ssAscF = CompletableFuture.supplyAsync(() -> loadSystemStatus(deviceId, lo, hi, cap));
|
||||||
|
CompletableFuture<List<BmsChargeDetail>> cdAscF = CompletableFuture.supplyAsync(() -> loadChargeDetail(deviceId, lo, hi, cap));
|
||||||
|
CompletableFuture<List<BmsRuntimeData>> rtAscF = CompletableFuture.supplyAsync(() -> loadRuntimeData(deviceId, lo, hi, cap));
|
||||||
|
CompletableFuture<List<BmsSopData>> sopAscF = CompletableFuture.supplyAsync(() -> loadSopData(deviceId, lo, hi, cap));
|
||||||
|
CompletableFuture<List<VehicleLocation>> locAscF = CompletableFuture.supplyAsync(() -> loadVehicleLocations(deviceId, lo, hi, cap));
|
||||||
|
CompletableFuture<List<BmsChargeTemperature>> chargeAscF = CompletableFuture.supplyAsync(() -> loadChargeTemps(deviceId, lo, hi, mapCap));
|
||||||
|
CompletableFuture<List<BmsVoltageChannelData>> voltAscF = CompletableFuture.supplyAsync(() -> loadVoltageChannels(deviceId, lo, hi, mapCap));
|
||||||
|
CompletableFuture<List<SubsystemVoltage>> svAscF = CompletableFuture.supplyAsync(() -> loadSubsystemVoltages(deviceId, lo, hi, mapCap));
|
||||||
|
CompletableFuture<List<SubsystemTemperature>> stAscF = CompletableFuture.supplyAsync(() -> loadSubsystemTemperatures(deviceId, lo, hi, mapCap));
|
||||||
|
|
||||||
|
CompletableFuture.allOf(
|
||||||
|
curExF, curSsF, curCdF, curRtF, curSopF, curLocF, chargeMapF, voltMapF, svMapF, stMapF,
|
||||||
|
extremeAscF, ssAscF, cdAscF, rtAscF, sopAscF, locAscF, chargeAscF, voltAscF, svAscF, stAscF
|
||||||
|
).join();
|
||||||
|
|
||||||
|
PrefetchContext ctx = new PrefetchContext();
|
||||||
|
ctx.curEx = curExF.join();
|
||||||
|
ctx.curSs = curSsF.join();
|
||||||
|
ctx.curCd = curCdF.join();
|
||||||
|
ctx.curRt = curRtF.join();
|
||||||
|
ctx.curSop = curSopF.join();
|
||||||
|
ctx.curLoc = curLocF.join();
|
||||||
|
ctx.chargeByIdx = chargeMapF.join();
|
||||||
|
ctx.voltByKey = voltMapF.join();
|
||||||
|
ctx.svByNo = svMapF.join();
|
||||||
|
ctx.stByNo = stMapF.join();
|
||||||
|
ctx.extremeAsc = extremeAscF.join();
|
||||||
|
ctx.ssAsc = ssAscF.join();
|
||||||
|
ctx.cdAsc = cdAscF.join();
|
||||||
|
ctx.rtAsc = rtAscF.join();
|
||||||
|
ctx.sopAsc = sopAscF.join();
|
||||||
|
ctx.locAsc = locAscF.join();
|
||||||
|
ctx.chargeAsc = chargeAscF.join();
|
||||||
|
ctx.voltAsc = voltAscF.join();
|
||||||
|
ctx.svAsc = svAscF.join();
|
||||||
|
ctx.stAsc = stAscF.join();
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PrefetchContext {
|
||||||
|
ExtremeValues curEx;
|
||||||
|
BmsSystemStatus curSs;
|
||||||
|
BmsChargeDetail curCd;
|
||||||
|
BmsRuntimeData curRt;
|
||||||
|
BmsSopData curSop;
|
||||||
|
VehicleLocation curLoc;
|
||||||
|
Map<Integer, BmsChargeTemperature> chargeByIdx;
|
||||||
|
Map<String, BmsVoltageChannelData> voltByKey;
|
||||||
|
Map<Integer, SubsystemVoltage> svByNo;
|
||||||
|
Map<Integer, SubsystemTemperature> stByNo;
|
||||||
|
List<ExtremeValues> extremeAsc;
|
||||||
|
List<BmsSystemStatus> ssAsc;
|
||||||
|
List<BmsChargeDetail> cdAsc;
|
||||||
|
List<BmsRuntimeData> rtAsc;
|
||||||
|
List<BmsSopData> sopAsc;
|
||||||
|
List<VehicleLocation> locAsc;
|
||||||
|
List<BmsChargeTemperature> chargeAsc;
|
||||||
|
List<BmsVoltageChannelData> voltAsc;
|
||||||
|
List<SubsystemVoltage> svAsc;
|
||||||
|
List<SubsystemTemperature> stAsc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int prefetchCap(int vehicleCount) {
|
||||||
|
long cap = (long) vehicleCount * PREFETCH_PER_VEHICLE;
|
||||||
|
cap = Math.max(PREFETCH_FLOOR, cap);
|
||||||
|
cap = Math.min(PREFETCH_CAP_MAX, cap);
|
||||||
|
return (int) cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int mapPrefetchCap(int vehicleCount) {
|
||||||
|
long cap = (long) vehicleCount * MAP_ROWS_PER_VEHICLE;
|
||||||
|
cap = Math.max(64, cap);
|
||||||
|
cap = Math.min(MAP_PREFETCH_CAP_MAX, cap);
|
||||||
|
return (int) cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Integer> sortedIndicesByPointTimeAsc(List<VehicleData> vehicleRows) {
|
||||||
|
List<Integer> ix = new ArrayList<>(vehicleRows.size());
|
||||||
|
for (int i = 0; i < vehicleRows.size(); i++) {
|
||||||
|
ix.add(i);
|
||||||
|
}
|
||||||
|
ix.sort(Comparator.comparing(i -> vehiclePointTime(vehicleRows.get(i)), Comparator.nullsLast(Date::compareTo)));
|
||||||
|
return ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeviceHistoryFlatRowDTO toFlat(
|
||||||
|
String deviceId,
|
||||||
|
VehicleData vd,
|
||||||
|
SimpleDateFormat tf,
|
||||||
|
ExtremeValues ev,
|
||||||
|
BmsSystemStatus ss,
|
||||||
|
BmsChargeDetail cd,
|
||||||
|
BmsRuntimeData rt,
|
||||||
|
BmsSopData sop,
|
||||||
|
VehicleLocation loc,
|
||||||
|
Map<Integer, BmsChargeTemperature> chargeByIdx,
|
||||||
|
Map<String, BmsVoltageChannelData> voltByKey,
|
||||||
|
Map<Integer, SubsystemVoltage> svByNo,
|
||||||
|
Map<Integer, SubsystemTemperature> stByNo) {
|
||||||
|
|
||||||
|
DeviceHistoryFlatRowDTO dto = new DeviceHistoryFlatRowDTO();
|
||||||
|
dto.setDeviceId(deviceId);
|
||||||
|
dto.setVehicleDataId(vd.getId());
|
||||||
|
Date pt = vehiclePointTime(vd);
|
||||||
|
dto.setPointTime(pt == null ? null : tf.format(pt));
|
||||||
|
dto.setVehicleCreateTime(vd.getCreateTime() == null ? null : tf.format(vd.getCreateTime()));
|
||||||
|
|
||||||
|
dto.setSoc(vd.getSoc());
|
||||||
|
dto.setTotalVoltage(vd.getTotalVoltage());
|
||||||
|
dto.setTotalCurrent(vd.getTotalCurrent());
|
||||||
|
dto.setVehicleSpeed(vd.getVehicleSpeed());
|
||||||
|
dto.setTotalMileage(vd.getTotalMileage());
|
||||||
|
dto.setChargingStatus(vd.getChargingStatus());
|
||||||
|
dto.setVehicleStatus(vd.getVehicleStatus());
|
||||||
|
dto.setOperationMode(vd.getOperationMode());
|
||||||
|
dto.setDcdcStatus(vd.getDcdcStatus());
|
||||||
|
dto.setGearPosition(vd.getGearPosition());
|
||||||
|
dto.setInsulationResistance(vd.getInsulationResistance());
|
||||||
|
|
||||||
|
if (ev != null) {
|
||||||
|
dto.setMaxCellVoltage(ev.getMaxVoltageValue());
|
||||||
|
dto.setMinCellVoltage(ev.getMinVoltageValue());
|
||||||
|
dto.setMaxTempValue(ev.getMaxTempValue());
|
||||||
|
dto.setMinTempValue(ev.getMinTempValue());
|
||||||
|
dto.setMaxCellPos(joinPos(ev.getMaxVoltageSubsystemNo(), ev.getMaxVoltageBatteryNo()));
|
||||||
|
dto.setMinCellPos(joinPos(ev.getMinVoltageSubsystemNo(), ev.getMinVoltageBatteryNo()));
|
||||||
|
dto.setMaxTempPos(joinPos(ev.getMaxTempSubsystemNo(), ev.getMaxTempProbeNo()));
|
||||||
|
dto.setMinTempPos(joinPos(ev.getMinTempSubsystemNo(), ev.getMinTempProbeNo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ss != null) {
|
||||||
|
dto.setRelayStatus(ss.getRelayStatus());
|
||||||
|
dto.setOnaaccStatus(ss.getOnaaccStatus());
|
||||||
|
dto.setResetCount(ss.getResetCount());
|
||||||
|
dto.setCycleCount(ss.getCycleCount());
|
||||||
|
dto.setCellCount(ss.getCellCount());
|
||||||
|
dto.setSample25v(ss.getSample25v());
|
||||||
|
dto.setSample5v(ss.getSample5v());
|
||||||
|
dto.setTempSensorCount(ss.getTempSensorCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cd != null) {
|
||||||
|
dto.setAcChargeCurrent(cd.getAcChargeCurrent());
|
||||||
|
dto.setAcChargeVoltage(cd.getAcChargeVoltage());
|
||||||
|
dto.setDcChargeCurrent(cd.getDcChargeCurrent());
|
||||||
|
dto.setDcChargeVoltage(cd.getDcChargeVoltage());
|
||||||
|
dto.setChargeTime(cd.getChargeTime());
|
||||||
|
dto.setAcStopCode(cd.getAcStopCode());
|
||||||
|
dto.setDcStopCode(cd.getDcStopCode());
|
||||||
|
dto.setAcCpValue(cd.getAcCpValue());
|
||||||
|
dto.setAcGunResistance(cd.getAcGunResistance());
|
||||||
|
dto.setChargingStage(cd.getChargingStage());
|
||||||
|
dto.setAcStage(cd.getAcStage());
|
||||||
|
dto.setDcStage(cd.getDcStage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rt != null) {
|
||||||
|
dto.setRuntimeVehicleSpeed(rt.getVehicleSpeed());
|
||||||
|
dto.setRuntimeMileage(rt.getMileage());
|
||||||
|
dto.setSystemVoltage(rt.getSystemVoltage());
|
||||||
|
dto.setSystemCurrent(rt.getSystemCurrent());
|
||||||
|
dto.setRuntimeInsulationResistance(rt.getInsulationResistance());
|
||||||
|
dto.setSocMax(rt.getSocMax());
|
||||||
|
dto.setSocMin(rt.getSocMin());
|
||||||
|
dto.setGpvVoltage(rt.getGpvVoltage());
|
||||||
|
dto.setRunStatus(rt.getRunStatus());
|
||||||
|
dto.setPowerStatus(rt.getPowerStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sop != null) {
|
||||||
|
dto.setDischargeSop10s(sop.getDischargeSop10s());
|
||||||
|
dto.setDischargeSop30s(sop.getDischargeSop30s());
|
||||||
|
dto.setDischargeSop60s(sop.getDischargeSop60s());
|
||||||
|
dto.setDischargeSopContinuous(sop.getDischargeSopContinuous());
|
||||||
|
dto.setDischargeSopTotal(sop.getDischargeSopTotal());
|
||||||
|
dto.setRegenSop10s(sop.getRegenSop10s());
|
||||||
|
dto.setRegenSop30s(sop.getRegenSop30s());
|
||||||
|
dto.setRegenSop60s(sop.getRegenSop60s());
|
||||||
|
dto.setRegenSopContinuous(sop.getRegenSopContinuous());
|
||||||
|
dto.setRegenSopTotal(sop.getRegenSopTotal());
|
||||||
|
dto.setChargeSop(sop.getChargeSop());
|
||||||
|
dto.setAcChargeSop(sop.getAcChargeSop());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loc != null) {
|
||||||
|
dto.setLongitude(loc.getLongitude());
|
||||||
|
dto.setLatitude(loc.getLatitude());
|
||||||
|
dto.setPositioningStatus(loc.getPositioningStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BmsChargeTemperature> temps = new ArrayList<>(chargeByIdx.values());
|
||||||
|
temps.sort(Comparator.comparing(BmsChargeTemperature::getTempIndex, Comparator.nullsLast(Integer::compareTo)));
|
||||||
|
dto.setChargeTemperaturesAtPoint(temps.isEmpty() ? null : temps);
|
||||||
|
|
||||||
|
List<BmsVoltageChannelData> volts = new ArrayList<>(voltByKey.values());
|
||||||
|
volts.sort(Comparator
|
||||||
|
.comparing(BmsVoltageChannelData::getChannelType, Comparator.nullsFirst(String::compareTo))
|
||||||
|
.thenComparing(BmsVoltageChannelData::getChannelIndex, Comparator.nullsLast(Integer::compareTo)));
|
||||||
|
dto.setVoltageChannelsAtPoint(volts.isEmpty() ? null : volts);
|
||||||
|
|
||||||
|
List<SubsystemVoltage> svs = new ArrayList<>(svByNo.values());
|
||||||
|
svs.sort(Comparator.comparing(SubsystemVoltage::getSubsystemNo, Comparator.nullsLast(Integer::compareTo)));
|
||||||
|
dto.setSubsystemVoltagesAtPoint(svs.isEmpty() ? null : svs);
|
||||||
|
|
||||||
|
List<SubsystemTemperature> sts = new ArrayList<>(stByNo.values());
|
||||||
|
sts.sort(Comparator.comparing(SubsystemTemperature::getSubsystemNo, Comparator.nullsLast(Integer::compareTo)));
|
||||||
|
dto.setSubsystemTemperaturesAtPoint(sts.isEmpty() ? null : sts);
|
||||||
|
|
||||||
|
BmsChargeTemperature t1 = chargeByIdx.get(1);
|
||||||
|
dto.setChargeSeatTemp1(t1 == null ? null : t1.getTemperature());
|
||||||
|
|
||||||
|
BmsVoltageChannelData rtp1 = voltByKey.get("RTP|1");
|
||||||
|
dto.setRtp1Voltage(rtp1 == null ? null : rtp1.getVoltage());
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String voltChannelKey(BmsVoltageChannelData row) {
|
||||||
|
String t = row.getChannelType() == null ? "" : row.getChannelType();
|
||||||
|
Integer ix = row.getChannelIndex();
|
||||||
|
return t + "|" + (ix == null ? "" : ix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinPos(Integer a, Integer b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return a + "-" + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date nullSafe(Date d) {
|
||||||
|
return d == null ? new Date(0L) : d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date vehiclePointTime(VehicleData v) {
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return v.getTimestamp() != null ? v.getTimestamp() : v.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date extremeEffectiveTime(ExtremeValues e) {
|
||||||
|
return e.getTimestamp() != null ? e.getTimestamp() : e.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date locationEffectiveTime(VehicleLocation l) {
|
||||||
|
return l.getTimestamp() != null ? l.getTimestamp() : l.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date chargeTempEffectiveTime(BmsChargeTemperature r) {
|
||||||
|
return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date voltageChannelEffectiveTime(BmsVoltageChannelData r) {
|
||||||
|
return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date subsystemVoltageEffectiveTime(SubsystemVoltage r) {
|
||||||
|
return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date subsystemTemperatureEffectiveTime(SubsystemTemperature r) {
|
||||||
|
return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtremeValues bootstrapExtremeValues(String deviceId, Date before) {
|
||||||
|
LambdaQueryWrapper<ExtremeValues> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(ExtremeValues::getDeviceId, deviceId);
|
||||||
|
applyTimestampLt(qw, ExtremeValues::getTimestamp, before);
|
||||||
|
qw.orderByDesc(ExtremeValues::getTimestamp);
|
||||||
|
qw.last("LIMIT 1");
|
||||||
|
List<ExtremeValues> rows = extremeValuesMapper.selectList(qw);
|
||||||
|
return rows == null || rows.isEmpty() ? null : rows.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VehicleLocation bootstrapVehicleLocation(String deviceId, Date before) {
|
||||||
|
LambdaQueryWrapper<VehicleLocation> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(VehicleLocation::getDeviceId, deviceId);
|
||||||
|
applyTimestampLt(qw, VehicleLocation::getTimestamp, before);
|
||||||
|
qw.orderByDesc(VehicleLocation::getTimestamp);
|
||||||
|
qw.last("LIMIT 1");
|
||||||
|
List<VehicleLocation> rows = vehicleLocationMapper.selectList(qw);
|
||||||
|
return rows == null || rows.isEmpty() ? null : rows.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsSystemStatus bootstrapSystemStatus(String deviceId, Date before) {
|
||||||
|
return bmsSystemStatusService.lambdaQuery()
|
||||||
|
.eq(BmsSystemStatus::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsSystemStatus::getTimestamp)
|
||||||
|
.lt(BmsSystemStatus::getTimestamp, before)
|
||||||
|
.orderByDesc(BmsSystemStatus::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsChargeDetail bootstrapChargeDetail(String deviceId, Date before) {
|
||||||
|
return bmsChargeDetailService.lambdaQuery()
|
||||||
|
.eq(BmsChargeDetail::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsChargeDetail::getTimestamp)
|
||||||
|
.lt(BmsChargeDetail::getTimestamp, before)
|
||||||
|
.orderByDesc(BmsChargeDetail::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsRuntimeData bootstrapRuntimeData(String deviceId, Date before) {
|
||||||
|
return bmsRuntimeDataService.lambdaQuery()
|
||||||
|
.eq(BmsRuntimeData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsRuntimeData::getTimestamp)
|
||||||
|
.lt(BmsRuntimeData::getTimestamp, before)
|
||||||
|
.orderByDesc(BmsRuntimeData::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BmsSopData bootstrapSopData(String deviceId, Date before) {
|
||||||
|
return bmsSopDataService.lambdaQuery()
|
||||||
|
.eq(BmsSopData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsSopData::getTimestamp)
|
||||||
|
.lt(BmsSopData::getTimestamp, before)
|
||||||
|
.orderByDesc(BmsSopData::getTimestamp)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, BmsChargeTemperature> bootstrapChargeTemps(String deviceId, Date before, int limit) {
|
||||||
|
LambdaQueryWrapper<BmsChargeTemperature> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(BmsChargeTemperature::getDeviceId, deviceId);
|
||||||
|
applyTimestampLt(qw, BmsChargeTemperature::getTimestamp, before);
|
||||||
|
qw.orderByAsc(BmsChargeTemperature::getTimestamp);
|
||||||
|
qw.last("LIMIT " + limit);
|
||||||
|
return replayChargeTemps(bmsChargeTemperatureService.list(qw));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BmsVoltageChannelData> bootstrapVoltageChannels(String deviceId, Date before, int limit) {
|
||||||
|
LambdaQueryWrapper<BmsVoltageChannelData> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(BmsVoltageChannelData::getDeviceId, deviceId);
|
||||||
|
applyTimestampLt(qw, BmsVoltageChannelData::getTimestamp, before);
|
||||||
|
qw.orderByAsc(BmsVoltageChannelData::getTimestamp);
|
||||||
|
qw.last("LIMIT " + limit);
|
||||||
|
return replayVoltageChannels(bmsVoltageChannelDataService.list(qw));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, SubsystemVoltage> bootstrapSubsystemVoltages(String deviceId, Date before, int limit) {
|
||||||
|
LambdaQueryWrapper<SubsystemVoltage> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(SubsystemVoltage::getDeviceId, deviceId);
|
||||||
|
applyTimestampLt(qw, SubsystemVoltage::getTimestamp, before);
|
||||||
|
qw.orderByAsc(SubsystemVoltage::getTimestamp);
|
||||||
|
qw.last("LIMIT " + limit);
|
||||||
|
return replaySubsystemVoltages(subsystemVoltageService.list(qw));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, SubsystemTemperature> bootstrapSubsystemTemperatures(String deviceId, Date before, int limit) {
|
||||||
|
LambdaQueryWrapper<SubsystemTemperature> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(SubsystemTemperature::getDeviceId, deviceId);
|
||||||
|
applyTimestampLt(qw, SubsystemTemperature::getTimestamp, before);
|
||||||
|
qw.orderByAsc(SubsystemTemperature::getTimestamp);
|
||||||
|
qw.last("LIMIT " + limit);
|
||||||
|
return replaySubsystemTemperatures(subsystemTemperatureService.list(qw));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Integer, BmsChargeTemperature> replayChargeTemps(List<BmsChargeTemperature> rows) {
|
||||||
|
Map<Integer, BmsChargeTemperature> map = new HashMap<>();
|
||||||
|
if (rows == null) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (BmsChargeTemperature row : rows) {
|
||||||
|
if (row.getTempIndex() != null) {
|
||||||
|
map.put(row.getTempIndex(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, BmsVoltageChannelData> replayVoltageChannels(List<BmsVoltageChannelData> rows) {
|
||||||
|
Map<String, BmsVoltageChannelData> map = new HashMap<>();
|
||||||
|
if (rows == null) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (BmsVoltageChannelData row : rows) {
|
||||||
|
map.put(voltChannelKey(row), row);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Integer, SubsystemVoltage> replaySubsystemVoltages(List<SubsystemVoltage> rows) {
|
||||||
|
Map<Integer, SubsystemVoltage> map = new HashMap<>();
|
||||||
|
if (rows == null) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (SubsystemVoltage row : rows) {
|
||||||
|
if (row.getSubsystemNo() != null) {
|
||||||
|
map.put(row.getSubsystemNo(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Integer, SubsystemTemperature> replaySubsystemTemperatures(List<SubsystemTemperature> rows) {
|
||||||
|
Map<Integer, SubsystemTemperature> map = new HashMap<>();
|
||||||
|
if (rows == null) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (SubsystemTemperature row : rows) {
|
||||||
|
if (row.getSubsystemNo() != null) {
|
||||||
|
map.put(row.getSubsystemNo(), row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExtremeValues> loadExtremeValues(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
LambdaQueryWrapper<ExtremeValues> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(ExtremeValues::getDeviceId, deviceId);
|
||||||
|
applyTimestampRange(qw, ExtremeValues::getTimestamp, lo, hi);
|
||||||
|
qw.orderByAsc(ExtremeValues::getTimestamp);
|
||||||
|
qw.last("LIMIT " + cap);
|
||||||
|
List<ExtremeValues> rows = extremeValuesMapper.selectList(qw);
|
||||||
|
return rows == null ? new ArrayList<>() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VehicleLocation> loadVehicleLocations(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
LambdaQueryWrapper<VehicleLocation> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(VehicleLocation::getDeviceId, deviceId);
|
||||||
|
applyTimestampRange(qw, VehicleLocation::getTimestamp, lo, hi);
|
||||||
|
qw.orderByAsc(VehicleLocation::getTimestamp);
|
||||||
|
qw.last("LIMIT " + cap);
|
||||||
|
List<VehicleLocation> rows = vehicleLocationMapper.selectList(qw);
|
||||||
|
return rows == null ? new ArrayList<>() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsChargeTemperature> loadChargeTemps(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
LambdaQueryWrapper<BmsChargeTemperature> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(BmsChargeTemperature::getDeviceId, deviceId);
|
||||||
|
applyTimestampRange(qw, BmsChargeTemperature::getTimestamp, lo, hi);
|
||||||
|
qw.orderByAsc(BmsChargeTemperature::getTimestamp);
|
||||||
|
qw.last("LIMIT " + cap);
|
||||||
|
List<BmsChargeTemperature> rows = bmsChargeTemperatureService.list(qw);
|
||||||
|
return rows == null ? new ArrayList<>() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsVoltageChannelData> loadVoltageChannels(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
LambdaQueryWrapper<BmsVoltageChannelData> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(BmsVoltageChannelData::getDeviceId, deviceId);
|
||||||
|
applyTimestampRange(qw, BmsVoltageChannelData::getTimestamp, lo, hi);
|
||||||
|
qw.orderByAsc(BmsVoltageChannelData::getTimestamp);
|
||||||
|
qw.last("LIMIT " + cap);
|
||||||
|
List<BmsVoltageChannelData> rows = bmsVoltageChannelDataService.list(qw);
|
||||||
|
return rows == null ? new ArrayList<>() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SubsystemVoltage> loadSubsystemVoltages(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
LambdaQueryWrapper<SubsystemVoltage> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(SubsystemVoltage::getDeviceId, deviceId);
|
||||||
|
applyTimestampRange(qw, SubsystemVoltage::getTimestamp, lo, hi);
|
||||||
|
qw.orderByAsc(SubsystemVoltage::getTimestamp);
|
||||||
|
qw.last("LIMIT " + cap);
|
||||||
|
List<SubsystemVoltage> rows = subsystemVoltageService.list(qw);
|
||||||
|
return rows == null ? new ArrayList<>() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SubsystemTemperature> loadSubsystemTemperatures(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
LambdaQueryWrapper<SubsystemTemperature> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.eq(SubsystemTemperature::getDeviceId, deviceId);
|
||||||
|
applyTimestampRange(qw, SubsystemTemperature::getTimestamp, lo, hi);
|
||||||
|
qw.orderByAsc(SubsystemTemperature::getTimestamp);
|
||||||
|
qw.last("LIMIT " + cap);
|
||||||
|
List<SubsystemTemperature> rows = subsystemTemperatureService.list(qw);
|
||||||
|
return rows == null ? new ArrayList<>() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsSystemStatus> loadSystemStatus(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
return bmsSystemStatusService.lambdaQuery()
|
||||||
|
.eq(BmsSystemStatus::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsSystemStatus::getTimestamp)
|
||||||
|
.ge(BmsSystemStatus::getTimestamp, lo)
|
||||||
|
.le(BmsSystemStatus::getTimestamp, hi)
|
||||||
|
.orderByAsc(BmsSystemStatus::getTimestamp)
|
||||||
|
.last("limit " + cap)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsChargeDetail> loadChargeDetail(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
return bmsChargeDetailService.lambdaQuery()
|
||||||
|
.eq(BmsChargeDetail::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsChargeDetail::getTimestamp)
|
||||||
|
.ge(BmsChargeDetail::getTimestamp, lo)
|
||||||
|
.le(BmsChargeDetail::getTimestamp, hi)
|
||||||
|
.orderByAsc(BmsChargeDetail::getTimestamp)
|
||||||
|
.last("limit " + cap)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsRuntimeData> loadRuntimeData(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
return bmsRuntimeDataService.lambdaQuery()
|
||||||
|
.eq(BmsRuntimeData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsRuntimeData::getTimestamp)
|
||||||
|
.ge(BmsRuntimeData::getTimestamp, lo)
|
||||||
|
.le(BmsRuntimeData::getTimestamp, hi)
|
||||||
|
.orderByAsc(BmsRuntimeData::getTimestamp)
|
||||||
|
.last("limit " + cap)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsSopData> loadSopData(String deviceId, Date lo, Date hi, int cap) {
|
||||||
|
return bmsSopDataService.lambdaQuery()
|
||||||
|
.eq(BmsSopData::getDeviceId, deviceId)
|
||||||
|
.isNotNull(BmsSopData::getTimestamp)
|
||||||
|
.ge(BmsSopData::getTimestamp, lo)
|
||||||
|
.le(BmsSopData::getTimestamp, hi)
|
||||||
|
.orderByAsc(BmsSopData::getTimestamp)
|
||||||
|
.last("limit " + cap)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void applyTimestampLt(
|
||||||
|
LambdaQueryWrapper<T> qw,
|
||||||
|
SFunction<T, Date> timestampCol,
|
||||||
|
Date before) {
|
||||||
|
qw.isNotNull(timestampCol);
|
||||||
|
if (before != null) {
|
||||||
|
qw.lt(timestampCol, before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void applyTimestampRange(
|
||||||
|
LambdaQueryWrapper<T> qw,
|
||||||
|
SFunction<T, Date> timestampCol,
|
||||||
|
Date lo,
|
||||||
|
Date hi) {
|
||||||
|
qw.isNotNull(timestampCol);
|
||||||
|
if (lo != null) {
|
||||||
|
qw.ge(timestampCol, lo);
|
||||||
|
}
|
||||||
|
if (hi != null) {
|
||||||
|
qw.le(timestampCol, hi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -67,4 +67,14 @@ public interface IVehicleDataService
|
|||||||
VehicleData selectLatestByDeviceId(String deviceId);
|
VehicleData selectLatestByDeviceId(String deviceId);
|
||||||
|
|
||||||
HomeDashboardDTO getHomeDashboard();
|
HomeDashboardDTO getHomeDashboard();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 历史平铺分页:按业务时间优先(timestamp),否则 create_time;支持时间范围与排序。
|
||||||
|
*/
|
||||||
|
List<VehicleData> selectVehicleDataListForHistory(String deviceId, Date begin, Date end, boolean descending);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 趋势图采样:时间范围内按业务时间倒序取最多 maxPoints 条,返回结果按时间正序。
|
||||||
|
*/
|
||||||
|
List<VehicleData> selectVehicleDataTrendInRange(String deviceId, Date begin, Date end, int maxPoints);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.evobms.project.vehicledata.service.impl;
|
package com.evobms.project.vehicledata.service.impl;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -54,6 +56,11 @@ public class VehicleDataServiceImpl extends ServiceImpl<VehicleDataMapper, Vehic
|
|||||||
* @param vehicleData 整车数据
|
* @param vehicleData 整车数据
|
||||||
* @return 整车数据
|
* @return 整车数据
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
|
public List<VehicleData> selectVehicleDataListForHistory(String deviceId, Date begin, Date end, boolean descending) {
|
||||||
|
return vehicleDataMapper.selectVehicleDataListForHistory(deviceId, begin, end, descending);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<VehicleData> selectVehicleDataList(VehicleData vehicleData)
|
public List<VehicleData> selectVehicleDataList(VehicleData vehicleData)
|
||||||
{
|
{
|
||||||
@ -81,6 +88,37 @@ public class VehicleDataServiceImpl extends ServiceImpl<VehicleDataMapper, Vehic
|
|||||||
return vehicleDataMapper.selectList(qw);
|
return vehicleDataMapper.selectList(qw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<VehicleData> selectVehicleDataTrendInRange(String deviceId, Date begin, Date end, int maxPoints) {
|
||||||
|
int limit = maxPoints <= 0 ? 360 : Math.min(maxPoints, 2000);
|
||||||
|
LambdaQueryWrapper<VehicleData> qw = new LambdaQueryWrapper<>();
|
||||||
|
qw.select(
|
||||||
|
VehicleData::getId,
|
||||||
|
VehicleData::getDeviceId,
|
||||||
|
VehicleData::getTimestamp,
|
||||||
|
VehicleData::getTotalVoltage,
|
||||||
|
VehicleData::getTotalCurrent,
|
||||||
|
VehicleData::getSoc,
|
||||||
|
VehicleData::getCreateTime);
|
||||||
|
qw.eq(VehicleData::getDeviceId, deviceId);
|
||||||
|
qw.isNotNull(VehicleData::getTimestamp);
|
||||||
|
if (begin != null) {
|
||||||
|
qw.ge(VehicleData::getTimestamp, begin);
|
||||||
|
}
|
||||||
|
if (end != null) {
|
||||||
|
qw.le(VehicleData::getTimestamp, end);
|
||||||
|
}
|
||||||
|
qw.orderByDesc(VehicleData::getTimestamp);
|
||||||
|
qw.last("LIMIT " + limit);
|
||||||
|
List<VehicleData> rows = vehicleDataMapper.selectList(qw);
|
||||||
|
if (rows == null || rows.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<VehicleData> asc = new ArrayList<>(rows);
|
||||||
|
Collections.reverse(asc);
|
||||||
|
return asc;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增整车数据
|
* 新增整车数据
|
||||||
*
|
*
|
||||||
|
|||||||
@ -6,7 +6,8 @@ spring:
|
|||||||
druid:
|
druid:
|
||||||
# 主库数据源
|
# 主库数据源
|
||||||
master:
|
master:
|
||||||
url: jdbc:mysql://192.168.5.121:3306/evo_bms1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai
|
# tcpKeepAlive 减轻 NAT/防火墙长时间空闲断 TCP;配合 testOnBorrow 避免用到已被服务端关闭的池内连接
|
||||||
|
url: jdbc:mysql://192.168.5.121:3306/evo_bms1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&tcpKeepAlive=true
|
||||||
username: root
|
username: root
|
||||||
password: root
|
password: root
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
@ -34,10 +35,11 @@ spring:
|
|||||||
minEvictableIdleTimeMillis: 300000
|
minEvictableIdleTimeMillis: 300000
|
||||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||||
maxEvictableIdleTimeMillis: 900000
|
maxEvictableIdleTimeMillis: 900000
|
||||||
# 配置检测连接是否有效
|
# 配置检测连接是否有效(MySQL 可用 SELECT 1)
|
||||||
validationQuery: SELECT 1 FROM DUAL
|
validationQuery: SELECT 1
|
||||||
testWhileIdle: true
|
testWhileIdle: true
|
||||||
testOnBorrow: false
|
# 借连接前校验,避免 MySQL wait_timeout / 网络断开后仍使用池中死连接(略增开销)
|
||||||
|
testOnBorrow: true
|
||||||
testOnReturn: false
|
testOnReturn: false
|
||||||
webStatFilter:
|
webStatFilter:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@ -156,6 +156,13 @@ swagger:
|
|||||||
# 请求前缀
|
# 请求前缀
|
||||||
pathMapping: /prod-api
|
pathMapping: /prod-api
|
||||||
|
|
||||||
|
# OTA配置(设备固件上传/分片下载)
|
||||||
|
ota:
|
||||||
|
count: ${OTA_COUNT:001}
|
||||||
|
repo: ${OTA_REPO:./ota}
|
||||||
|
chunk-size: ${OTA_CHUNK_SIZE:1024}
|
||||||
|
header-format: ${OTA_HEADER_FORMAT:binary32}
|
||||||
|
|
||||||
# 防盗链配置
|
# 防盗链配置
|
||||||
referer:
|
referer:
|
||||||
# 防盗链开关
|
# 防盗链开关
|
||||||
@ -189,8 +196,12 @@ gen:
|
|||||||
mqtt:
|
mqtt:
|
||||||
# 是否启用MQTT功能(设置为false可在没有MQTT服务器时正常启动)
|
# 是否启用MQTT功能(设置为false可在没有MQTT服务器时正常启动)
|
||||||
enabled: true
|
enabled: true
|
||||||
|
legacyReceiver:
|
||||||
|
enabled: false
|
||||||
|
publicHost: ${MQTT_PUBLIC_HOST:61.182.73.218}
|
||||||
|
publicPort: ${MQTT_PUBLIC_PORT:11883}
|
||||||
# MQTT服务器地址
|
# MQTT服务器地址
|
||||||
host: ${MQTT_HOST_URI:tcp://61.182.73.218:1883}
|
host: ${MQTT_HOST_URI:tcp://61.182.73.218:11883}
|
||||||
# 客户端ID(避免冲突,随机后缀)
|
# 客户端ID(避免冲突,随机后缀)
|
||||||
clientId: evobms-client-${random.int}
|
clientId: evobms-client-${random.int}
|
||||||
# 用户名(如需认证请填写)
|
# 用户名(如需认证请填写)
|
||||||
@ -199,6 +210,8 @@ mqtt:
|
|||||||
password: ${MQTT_PASSWORD:741a03a10f3de6b2}
|
password: ${MQTT_PASSWORD:741a03a10f3de6b2}
|
||||||
# 连接超时时间(秒)
|
# 连接超时时间(秒)
|
||||||
connectionTimeout: 30
|
connectionTimeout: 30
|
||||||
|
# 订阅/连接完成等待时间(毫秒)
|
||||||
|
completionTimeout: 20000
|
||||||
# 心跳间隔(秒)
|
# 心跳间隔(秒)
|
||||||
keepAliveInterval: 60
|
keepAliveInterval: 60
|
||||||
# 是否自动重连
|
# 是否自动重连
|
||||||
|
|||||||
@ -7,7 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<resultMap type="BmsChargeTemperature" id="BmsChargeTemperatureResult">
|
<resultMap type="BmsChargeTemperature" id="BmsChargeTemperatureResult">
|
||||||
<result property="id" column="id" />
|
<result property="id" column="id" />
|
||||||
<result property="deviceId" column="device_id" />
|
<result property="deviceId" column="device_id" />
|
||||||
<result property="timestap" column="timestap" />
|
<result property="timestamp" column="timestamp" />
|
||||||
<result property="tempIndex" column="temp_index" />
|
<result property="tempIndex" column="temp_index" />
|
||||||
<result property="temperature" column="temperature" />
|
<result property="temperature" column="temperature" />
|
||||||
<result property="createBy" column="create_by" />
|
<result property="createBy" column="create_by" />
|
||||||
@ -17,14 +17,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectBmsChargeTemperatureVo">
|
<sql id="selectBmsChargeTemperatureVo">
|
||||||
select id, device_id, timestap, temp_index, temperature, create_by, update_by, create_time, update_time from bms_charge_temperature
|
select id, device_id, `timestamp`, temp_index, temperature, create_by, update_by, create_time, update_time from bms_charge_temperature
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectBmsChargeTemperatureList" parameterType="BmsChargeTemperature" resultMap="BmsChargeTemperatureResult">
|
<select id="selectBmsChargeTemperatureList" parameterType="BmsChargeTemperature" resultMap="BmsChargeTemperatureResult">
|
||||||
<include refid="selectBmsChargeTemperatureVo"/>
|
<include refid="selectBmsChargeTemperatureVo"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
||||||
<if test="timestap != null "> and timestap = #{timestap}</if>
|
<if test="timestamp != null "> and `timestamp` = #{timestamp}</if>
|
||||||
<if test="tempIndex != null "> and temp_index = #{tempIndex}</if>
|
<if test="tempIndex != null "> and temp_index = #{tempIndex}</if>
|
||||||
<if test="temperature != null "> and temperature = #{temperature}</if>
|
<if test="temperature != null "> and temperature = #{temperature}</if>
|
||||||
</where>
|
</where>
|
||||||
@ -39,7 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
insert into bms_charge_temperature
|
insert into bms_charge_temperature
|
||||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
||||||
<if test="timestap != null">timestap,</if>
|
<if test="timestamp != null">`timestamp`,</if>
|
||||||
<if test="tempIndex != null">temp_index,</if>
|
<if test="tempIndex != null">temp_index,</if>
|
||||||
<if test="temperature != null">temperature,</if>
|
<if test="temperature != null">temperature,</if>
|
||||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||||
@ -49,7 +49,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
|
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
|
||||||
<if test="timestap != null">#{timestap},</if>
|
<if test="timestamp != null">#{timestamp},</if>
|
||||||
<if test="tempIndex != null">#{tempIndex},</if>
|
<if test="tempIndex != null">#{tempIndex},</if>
|
||||||
<if test="temperature != null">#{temperature},</if>
|
<if test="temperature != null">#{temperature},</if>
|
||||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||||
@ -63,7 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
update bms_charge_temperature
|
update bms_charge_temperature
|
||||||
<trim prefix="SET" suffixOverrides=",">
|
<trim prefix="SET" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
|
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
|
||||||
<if test="timestap != null">timestap = #{timestap},</if>
|
<if test="timestamp != null">`timestamp` = #{timestamp},</if>
|
||||||
<if test="tempIndex != null">temp_index = #{tempIndex},</if>
|
<if test="tempIndex != null">temp_index = #{tempIndex},</if>
|
||||||
<if test="temperature != null">temperature = #{temperature},</if>
|
<if test="temperature != null">temperature = #{temperature},</if>
|
||||||
<if test="createBy != null and createBy != ''">create_by = #{createBy},</if>
|
<if test="createBy != null and createBy != ''">create_by = #{createBy},</if>
|
||||||
@ -84,4 +84,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
#{id}
|
#{id}
|
||||||
</foreach>
|
</foreach>
|
||||||
</delete>
|
</delete>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<resultMap type="BmsRuntimeData" id="BmsRuntimeDataResult">
|
<resultMap type="BmsRuntimeData" id="BmsRuntimeDataResult">
|
||||||
<result property="id" column="id" />
|
<result property="id" column="id" />
|
||||||
<result property="deviceId" column="device_id" />
|
<result property="deviceId" column="device_id" />
|
||||||
<result property="timestamp" column="timestap" />
|
<result property="timestamp" column="timestamp" />
|
||||||
<result property="vehicleSpeed" column="vehicle_speed" />
|
<result property="vehicleSpeed" column="vehicle_speed" />
|
||||||
<result property="mileage" column="mileage" />
|
<result property="mileage" column="mileage" />
|
||||||
<result property="systemVoltage" column="system_voltage" />
|
<result property="systemVoltage" column="system_voltage" />
|
||||||
@ -25,14 +25,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectBmsRuntimeDataVo">
|
<sql id="selectBmsRuntimeDataVo">
|
||||||
select id, device_id, timestap, vehicle_speed, mileage, system_voltage, system_current, insulation_resistance, soc_max, soc_min, gpv_voltage, run_status, power_status, create_by, update_by, create_time, update_time from bms_runtime_data
|
select id, device_id, `timestamp`, vehicle_speed, mileage, system_voltage, system_current, insulation_resistance, soc_max, soc_min, gpv_voltage, run_status, power_status, create_by, update_by, create_time, update_time from bms_runtime_data
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectBmsRuntimeDataList" parameterType="BmsRuntimeData" resultMap="BmsRuntimeDataResult">
|
<select id="selectBmsRuntimeDataList" parameterType="BmsRuntimeData" resultMap="BmsRuntimeDataResult">
|
||||||
<include refid="selectBmsRuntimeDataVo"/>
|
<include refid="selectBmsRuntimeDataVo"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
||||||
<if test="timestap != null "> and timestap = #{timestap}</if>
|
<if test="timestamp != null "> and `timestamp` = #{timestamp}</if>
|
||||||
<if test="vehicleSpeed != null "> and vehicle_speed = #{vehicleSpeed}</if>
|
<if test="vehicleSpeed != null "> and vehicle_speed = #{vehicleSpeed}</if>
|
||||||
<if test="mileage != null "> and mileage = #{mileage}</if>
|
<if test="mileage != null "> and mileage = #{mileage}</if>
|
||||||
<if test="systemVoltage != null "> and system_voltage = #{systemVoltage}</if>
|
<if test="systemVoltage != null "> and system_voltage = #{systemVoltage}</if>
|
||||||
@ -55,7 +55,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
insert into bms_runtime_data
|
insert into bms_runtime_data
|
||||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
||||||
<if test="timestap != null">timestap,</if>
|
<if test="timestamp != null">`timestamp`,</if>
|
||||||
<if test="vehicleSpeed != null">vehicle_speed,</if>
|
<if test="vehicleSpeed != null">vehicle_speed,</if>
|
||||||
<if test="mileage != null">mileage,</if>
|
<if test="mileage != null">mileage,</if>
|
||||||
<if test="systemVoltage != null">system_voltage,</if>
|
<if test="systemVoltage != null">system_voltage,</if>
|
||||||
@ -73,7 +73,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
|
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
|
||||||
<if test="timestap != null">#{timestap},</if>
|
<if test="timestamp != null">#{timestamp},</if>
|
||||||
<if test="vehicleSpeed != null">#{vehicleSpeed},</if>
|
<if test="vehicleSpeed != null">#{vehicleSpeed},</if>
|
||||||
<if test="mileage != null">#{mileage},</if>
|
<if test="mileage != null">#{mileage},</if>
|
||||||
<if test="systemVoltage != null">#{systemVoltage},</if>
|
<if test="systemVoltage != null">#{systemVoltage},</if>
|
||||||
@ -95,7 +95,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
update bms_runtime_data
|
update bms_runtime_data
|
||||||
<trim prefix="SET" suffixOverrides=",">
|
<trim prefix="SET" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
|
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
|
||||||
<if test="timestap != null">timestap = #{timestap},</if>
|
<if test="timestamp != null">`timestamp` = #{timestamp},</if>
|
||||||
<if test="vehicleSpeed != null">vehicle_speed = #{vehicleSpeed},</if>
|
<if test="vehicleSpeed != null">vehicle_speed = #{vehicleSpeed},</if>
|
||||||
<if test="mileage != null">mileage = #{mileage},</if>
|
<if test="mileage != null">mileage = #{mileage},</if>
|
||||||
<if test="systemVoltage != null">system_voltage = #{systemVoltage},</if>
|
<if test="systemVoltage != null">system_voltage = #{systemVoltage},</if>
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
insert into bms_system_status
|
insert into bms_system_status
|
||||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
||||||
<if test="timestamp != null">timestamp,</if>
|
<if test="timestamp != null">`timestamp`,</if>
|
||||||
<if test="relayStatus != null">relay_status,</if>
|
<if test="relayStatus != null">relay_status,</if>
|
||||||
<if test="onaaccStatus != null">onaacc_status,</if>
|
<if test="onaaccStatus != null">onaacc_status,</if>
|
||||||
<if test="resetCount != null">reset_count,</if>
|
<if test="resetCount != null">reset_count,</if>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<resultMap type="BmsVoltageChannelData" id="BmsVoltageChannelDataResult">
|
<resultMap type="BmsVoltageChannelData" id="BmsVoltageChannelDataResult">
|
||||||
<result property="id" column="id" />
|
<result property="id" column="id" />
|
||||||
<result property="deviceId" column="device_id" />
|
<result property="deviceId" column="device_id" />
|
||||||
<result property="timestap" column="timestap" />
|
<result property="timestamp" column="timestamp" />
|
||||||
<result property="channelType" column="channel_type" />
|
<result property="channelType" column="channel_type" />
|
||||||
<result property="channelIndex" column="channel_index" />
|
<result property="channelIndex" column="channel_index" />
|
||||||
<result property="voltage" column="voltage" />
|
<result property="voltage" column="voltage" />
|
||||||
@ -18,14 +18,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectBmsVoltageChannelDataVo">
|
<sql id="selectBmsVoltageChannelDataVo">
|
||||||
select id, device_id, timestap, channel_type, channel_index, voltage, create_by, update_by, create_time, update_time from bms_voltage_channel_data
|
select id, device_id, `timestamp`, channel_type, channel_index, voltage, create_by, update_by, create_time, update_time from bms_voltage_channel_data
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectBmsVoltageChannelDataList" parameterType="BmsVoltageChannelData" resultMap="BmsVoltageChannelDataResult">
|
<select id="selectBmsVoltageChannelDataList" parameterType="BmsVoltageChannelData" resultMap="BmsVoltageChannelDataResult">
|
||||||
<include refid="selectBmsVoltageChannelDataVo"/>
|
<include refid="selectBmsVoltageChannelDataVo"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
|
||||||
<if test="timestap != null "> and timestap = #{timestap}</if>
|
<if test="timestamp != null "> and `timestamp` = #{timestamp}</if>
|
||||||
<if test="channelType != null and channelType != ''"> and channel_type = #{channelType}</if>
|
<if test="channelType != null and channelType != ''"> and channel_type = #{channelType}</if>
|
||||||
<if test="channelIndex != null "> and channel_index = #{channelIndex}</if>
|
<if test="channelIndex != null "> and channel_index = #{channelIndex}</if>
|
||||||
<if test="voltage != null "> and voltage = #{voltage}</if>
|
<if test="voltage != null "> and voltage = #{voltage}</if>
|
||||||
@ -41,7 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
insert into bms_voltage_channel_data
|
insert into bms_voltage_channel_data
|
||||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
<if test="deviceId != null and deviceId != ''">device_id,</if>
|
||||||
<if test="timestap != null">timestap,</if>
|
<if test="timestamp != null">`timestamp`,</if>
|
||||||
<if test="channelType != null and channelType != ''">channel_type,</if>
|
<if test="channelType != null and channelType != ''">channel_type,</if>
|
||||||
<if test="channelIndex != null">channel_index,</if>
|
<if test="channelIndex != null">channel_index,</if>
|
||||||
<if test="voltage != null">voltage,</if>
|
<if test="voltage != null">voltage,</if>
|
||||||
@ -52,7 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
|
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
|
||||||
<if test="timestap != null">#{timestap},</if>
|
<if test="timestamp != null">#{timestamp},</if>
|
||||||
<if test="channelType != null and channelType != ''">#{channelType},</if>
|
<if test="channelType != null and channelType != ''">#{channelType},</if>
|
||||||
<if test="channelIndex != null">#{channelIndex},</if>
|
<if test="channelIndex != null">#{channelIndex},</if>
|
||||||
<if test="voltage != null">#{voltage},</if>
|
<if test="voltage != null">#{voltage},</if>
|
||||||
@ -67,7 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
update bms_voltage_channel_data
|
update bms_voltage_channel_data
|
||||||
<trim prefix="SET" suffixOverrides=",">
|
<trim prefix="SET" suffixOverrides=",">
|
||||||
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
|
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
|
||||||
<if test="timestap != null">timestap = #{timestap},</if>
|
<if test="timestamp != null">`timestamp` = #{timestamp},</if>
|
||||||
<if test="channelType != null and channelType != ''">channel_type = #{channelType},</if>
|
<if test="channelType != null and channelType != ''">channel_type = #{channelType},</if>
|
||||||
<if test="channelIndex != null">channel_index = #{channelIndex},</if>
|
<if test="channelIndex != null">channel_index = #{channelIndex},</if>
|
||||||
<if test="voltage != null">voltage = #{voltage},</if>
|
<if test="voltage != null">voltage = #{voltage},</if>
|
||||||
@ -89,4 +89,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
#{id}
|
#{id}
|
||||||
</foreach>
|
</foreach>
|
||||||
</delete>
|
</delete>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@ -50,6 +50,33 @@
|
|||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<sql id="historyListColumns">
|
||||||
|
id, device_id, timestamp, vehicle_status, charging_status, operation_mode, vehicle_speed,
|
||||||
|
total_mileage, total_voltage, total_current, soc, dcdc_status, gear_position,
|
||||||
|
insulation_resistance, create_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectVehicleDataListForHistory" resultMap="VehicleDataResult">
|
||||||
|
select <include refid="historyListColumns"/>
|
||||||
|
from vehicle_data
|
||||||
|
where device_id = #{deviceId}
|
||||||
|
and timestamp is not null
|
||||||
|
<if test="begin != null">
|
||||||
|
and timestamp >= #{begin}
|
||||||
|
</if>
|
||||||
|
<if test="end != null">
|
||||||
|
and timestamp <= #{end}
|
||||||
|
</if>
|
||||||
|
<choose>
|
||||||
|
<when test="descending">
|
||||||
|
order by timestamp desc
|
||||||
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
order by timestamp asc
|
||||||
|
</otherwise>
|
||||||
|
</choose>
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectLatestByDeviceId" resultMap="VehicleDataResult">
|
<select id="selectLatestByDeviceId" resultMap="VehicleDataResult">
|
||||||
<include refid="selectVehicleDataVo"/>
|
<include refid="selectVehicleDataVo"/>
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
|
|||||||
BIN
src/main/resources/ota/0.0.7.bin
Normal file
BIN
src/main/resources/ota/0.0.7.bin
Normal file
Binary file not shown.
BIN
src/main/resources/ota/0.0.8.bin
Normal file
BIN
src/main/resources/ota/0.0.8.bin
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user