视频录制

This commit is contained in:
cwchen 2026-01-19 15:01:40 +08:00
parent e75a917116
commit 818af4c7a7
25 changed files with 1151 additions and 39 deletions

View File

@ -49,4 +49,20 @@ public class LineController {
public AjaxResult getVideoStreamAPI(ParamsDto dto) {
return lineService.getVideoStreamAPI(dto);
}
@ApiOperation(notes = "设备操作",value = "设备操作")
@RequiresPermissions("data:device:oper")
@PostMapping("/operDevice")
@SysLog(title = "设备管理", businessType = OperaType.UPDATE, logType = 1, module = "设备管理->视频管理", details = "设备操作")
public AjaxResult operDevice(@RequestBody ParamsDto dto) {
return lineService.operDevice(dto);
}
@ApiOperation(notes = "录制视频",value = "录制视频")
@PostMapping("/recordingVideo")
@SysLog(title = "设备管理", businessType = OperaType.UPDATE, logType = 1, module = "设备管理->视频管理", details = "录制视频")
public AjaxResult recordingVideo(@RequestBody ParamsDto dto) {
return lineService.recordingVideo(dto);
}
}

View File

@ -1,8 +1,12 @@
package com.bonus.web.service.data;
import com.bonus.algorithm.service.DeviceOperService;
import com.bonus.algorithm.service.DrawLinesService;
import com.bonus.algorithm.service.EndVideoService;
import com.bonus.algorithm.service.StartVideoService;
import com.bonus.common.core.domain.AjaxResult;
import com.bonus.common.domain.algorithm.dto.DrawLinesRequest;
import com.bonus.common.domain.algorithm.dto.OperDeviceRequest;
import com.bonus.common.domain.algorithm.vo.DrawLinesResponse;
import com.bonus.common.domain.data.dto.LineDto;
import com.bonus.common.domain.data.dto.ParamsDto;
@ -18,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Objects;
/**
@ -40,6 +45,15 @@ public class LineService {
@Resource(name = "DrawLinesService")
private DrawLinesService drawLinesService;
@Resource(name = "DeviceOperService")
private DeviceOperService deviceOperService;
@Resource(name = "StartVideoService")
private StartVideoService startVideoService;
@Resource(name = "EndVideoService")
private EndVideoService endVideoService;
/**
* 查询初始线的位置
* @param dto
@ -121,4 +135,68 @@ public class LineService {
}
return AjaxResult.success(vo);
}
/**
* 设备操作
* @param dto
* @return AjaxResult
* @author cwchen
* @date 2026/1/19 9:28
*/
public AjaxResult operDevice(ParamsDto dto) {
try {
OperDeviceRequest request = new OperDeviceRequest();
if (StringUtils.isNotBlank(dto.getMode())) {
request.setMode(Long.parseLong(dto.getMode()));
request.setAlarm(true);
} else {
request.setMode(1L);
request.setAlarm(dto.isAlarm());
}
DrawLinesResponse drawLinesResponse = deviceOperService.callOperDeviceService(request);
if (Objects.nonNull(drawLinesResponse)) {
if(drawLinesResponse.isSuccess()){
return AjaxResult.success();
}else{
return AjaxResult.error(drawLinesResponse.getMessage());
}
}else{
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
} catch (IOException e) {
log.error(e.toString(),e);
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
}
/**
* 开始录制视频 结束录制视频
* @param dto
* @return AjaxResult
* @author cwchen
* @date 2026/1/19 14:48
*/
public AjaxResult recordingVideo(ParamsDto dto) {
try {
DrawLinesResponse drawLinesResponse = null;
if(Objects.equals(dto.getRecordingType(),"1")){
drawLinesResponse = startVideoService.callStartVideoService();
}else{
drawLinesResponse = endVideoService.callEndVideoService();
}
if (Objects.nonNull(drawLinesResponse)) {
if(drawLinesResponse.isSuccess()){
return AjaxResult.success();
}else{
return AjaxResult.error(drawLinesResponse.getMessage());
}
}else{
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
} catch (IOException e) {
log.error(e.toString(),e);
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
}
}

View File

@ -1,7 +1,6 @@
package com.bonus.web.service.data;
import com.bonus.common.core.domain.AjaxResult;
import com.bonus.common.domain.data.dto.LineDto;
import com.bonus.common.domain.data.dto.NetworkEthernetDto;
import com.bonus.common.domain.data.dto.NetworkSimDto;
import com.bonus.common.domain.data.dto.ParamsDto;
@ -77,7 +76,7 @@ public class NetworkService {
return AjaxResult.error(validResult);
}
if(Objects.equals(dto.getEnableDhcp(),"0")){
String validResult2 = validatorsUtils.valid(dto, NetworkEthernetDto.ENABLE.class);
String validResult2 = validatorsUtils.valid(dto, NetworkEthernetDto.NOENABLE.class);
if (StringUtils.isNotBlank(validResult2)) {
return AjaxResult.error(validResult2);
}
@ -88,7 +87,7 @@ public class NetworkService {
return AjaxResult.error(validResult);
}
if(Objects.equals(dto.getEnableDhcp(),"0")){
String validResult2 = validatorsUtils.valid(dto, NetworkEthernetDto.ENABLE.class);
String validResult2 = validatorsUtils.valid(dto, NetworkEthernetDto.NOENABLE.class);
if (StringUtils.isNotBlank(validResult2)) {
return AjaxResult.error(validResult2);
}

View File

@ -1,15 +1,22 @@
package com.bonus.web.service.data;
import com.bonus.algorithm.service.DeviceOperService;
import com.bonus.common.core.domain.AjaxResult;
import com.bonus.common.domain.algorithm.dto.OperDeviceRequest;
import com.bonus.common.domain.algorithm.vo.DrawLinesResponse;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.SystemInfoVo;
import com.bonus.common.enums.ResultCode;
import com.bonus.data.service.DISystemInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Objects;
/**
* @className:SystemInfoService
@ -25,8 +32,12 @@ public class SystemInfoService {
@Resource(name = "DISystemInfoService")
private DISystemInfoService systemInfoService;
@Resource(name = "DeviceOperService")
private DeviceOperService deviceOperService;
/**
* 查询系统信息
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -38,7 +49,7 @@ public class SystemInfoService {
vo = systemInfoService.getDeviceInfo(dto);
return AjaxResult.success(vo == null ? new SystemInfoVo() : vo);
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
vo = new SystemInfoVo();
}
return AjaxResult.success(vo);
@ -46,6 +57,7 @@ public class SystemInfoService {
/**
* 修改设备信息
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -57,7 +69,7 @@ public class SystemInfoService {
systemInfoService.updateDevice(dto);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
@ -65,6 +77,7 @@ public class SystemInfoService {
/**
* 修改告警状态
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -72,13 +85,23 @@ public class SystemInfoService {
*/
public AjaxResult updateAlarmStatus(ParamsDto dto) {
try {
// 1.是否调用第三方接口
systemInfoService.updateAlarmStatus(dto);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
OperDeviceRequest request = new OperDeviceRequest();
request.setMode(1L);
request.setAlarm(Objects.equals(dto.getAlarmStatus(), "1"));
DrawLinesResponse drawLinesResponse = deviceOperService.callOperDeviceService(request);
if (Objects.nonNull(drawLinesResponse)) {
if (drawLinesResponse.isSuccess()) {
systemInfoService.updateAlarmStatus(dto);
return AjaxResult.success();
} else {
return AjaxResult.error(drawLinesResponse.getMessage());
}
} else {
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
} catch (IOException e) {
log.error(e.toString(), e);
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
}
}

View File

@ -1,5 +1,9 @@
algorithm:
service:
url: http://192.168.0.108:8080/api/v1/video/setTripwire # 画线算法接口请求地址
operUrl: http://192.168.0.108:8080/api/v1/battery/control # 设备操作接口请求地址
detailUrl: http://192.168.0.108:8080/api/v1/battery/status # 设备状态接口请求地址
startVideoUrl: http://192.168.0.108:8080/api/v1/record/start # 开始录制接口请求地址
endVideoUrl: http://192.168.0.108:8080/api/v1/record/stop # 结束录制接口请求地址
timeout: 30000 # 画线算法请求超时时间
max-connections: 100

View File

@ -8,6 +8,7 @@ bonus:
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/bonus/uploadPathLinux配置 /home/bonus/uploadPath
profile: D:/bonus/uploadPath
lsFile: /home/jar/smart-car-dev
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证

View File

@ -6,12 +6,12 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.0.14:2009/smart-car-dev?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Bonus@admin123!
# url: jdbc:mysql://127.0.0.1:3306/smart_bid_dev?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# url: jdbc:mysql://192.168.0.14:2009/smart-car-dev?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root
# password: ccw1998@yyt1999
# password: Bonus@admin123!
url: jdbc:mysql://192.168.0.108:3306/smart-car-dev?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: RemoteDev
password: forlinx
# 从库数据源
slave:
# 从数据源开关/默认关闭

View File

@ -7,7 +7,8 @@ bonus:
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/bonus/uploadPathLinux配置 /home/bonus/uploadPath
profile: /home/bonus/uploadPath
profile: /home/forlinx/Vehicle_Road_Counter
lsFile: /home/jar/smart-car-dev
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
@ -20,7 +21,7 @@ sql:
# 开发环境配置
server:
# 服务器的HTTP端口默认为8091
port: 8091
port: 8099
servlet:
# 应用的访问路径
context-path: /smartCar
@ -70,13 +71,13 @@ spring:
# redis 配置
redis:
# 地址
host: 192.168.0.14
host: 192.168.0.108
# 端口默认为6379
port: 2004
port: 6379
# 数据库索引
database: 6
# 密码
password: Plzbns@Redis123!
password: forlinx
# 连接超时时间
timeout: 10s
lettuce:

View File

@ -1,6 +1,6 @@
spring:
profiles:
active: @profiles.active@,druid,ocr,file,rabbitmq
active: @profiles.active@,druid,ocr,file,rabbitmq,algorithm
# 解决SpringBoot 2.6+与SpringFox兼容性问题
mvc:
pathmatch:

View File

@ -0,0 +1,198 @@
package com.bonus.algorithm.service;
import com.bonus.common.domain.algorithm.dto.OperDeviceRequest;
import com.bonus.common.domain.algorithm.vo.DeviceDetailResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URI;
/**
* @className: DeviceOperService
* @author: cwchen
* @date: 2026-01-08-9:26
* @version: 1.0
* @description: 设备详情详情接口调用-业务逻辑层
*/
@Service(value = "DeviceDetailService")
@Slf4j
public class DeviceDetailService {
private static final String UTF_8 = "UTF-8";
@Value("${algorithm.service.detailUrl}")
private String algorithmServiceUrl;
@Value("${algorithm.service.timeout}")
private int timeout;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public DeviceDetailService() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
this.objectMapper = new ObjectMapper();
}
/**
* 调用设备详情接口
*
* @return 设备详情响应结果
* @throws IOException 当设备详情服务调用失败时抛出
*/
public DeviceDetailResponse callDeviceDetailService() throws IOException {
HttpGet httpGet = null;
try {
httpGet = createHttpGet();
return executeOcrRequest(httpGet);
} catch (IOException e) {
log.error("调用设备详情服务失败", e);
return null;
} finally {
cleanupResources(httpGet);
}
}
/**
* 验证设备详情请求参数
*/
private void validateOperDeviceRequest(OperDeviceRequest operDeviceRequest) {
if (operDeviceRequest == null) {
throw new IllegalArgumentException("设备详情请求参数不能为空");
}
}
/**
* 创建HTTP GET请求
*/
private HttpGet createHttpGet() {
// 声明 HttpGet 对象
HttpGet httpGet = null;
try {
// 1. 使用 URIBuilder 构建带参数的 URL
URIBuilder uriBuilder = new URIBuilder(algorithmServiceUrl);
// 2. 构建 URI 并创建 HttpGet
URI uri = uriBuilder.build();
httpGet = new HttpGet(uri);
} catch (Exception e) {
throw new RuntimeException("创建HTTP GET请求失败", e);
}
return httpGet;
}
/**
* 将请求对象转换为JSON字符串
*/
private String convertToJson(OperDeviceRequest request) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(request);
}
/**
* 执行设备详情请求
*/
private DeviceDetailResponse executeOcrRequest(HttpGet httpGet) throws IOException {
log.info("开始调用设备详情服务识别");
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
return processHttpResponse(response);
}
}
/**
* 处理HTTP响应
*/
private DeviceDetailResponse processHttpResponse(CloseableHttpResponse response) throws IOException {
log.info("响应内容:{}",response);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = getResponseBody(response);
log.info("设备详情服务响应状态: {}", statusCode);
log.debug("设备详情服务响应内容: {}", responseBody); // 改为debug级别避免日志过大
// 检查HTTP状态码
if (statusCode != 200) {
log.error("设备详情服务HTTP请求失败状态码: {}, 响应: {}", statusCode, responseBody);
return null;
}
DeviceDetailResponse DeviceDetailResponse = parseResponseBody(responseBody);
return DeviceDetailResponse;
}
/**
* 获取响应体
*/
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, UTF_8);
}
/**
* 解析响应体
*/
private DeviceDetailResponse parseResponseBody(String responseBody) throws IOException {
try {
return objectMapper.readValue(responseBody, DeviceDetailResponse.class);
} catch (IOException e) {
log.error("解析设备详情请求响应失败,响应内容: {}", responseBody, e);
return null;
}
}
/**
* 清理资源
*/
private void cleanupResources(HttpGet httpGet) {
// 清理HTTP连接
if (httpGet != null) {
httpGet.releaseConnection();
}
}
/**
* 关闭HTTP客户端
*/
public void close() {
try {
if (httpClient != null) {
httpClient.close();
log.info("设备详情服务HTTP客户端已关闭");
}
} catch (IOException e) {
log.error("关闭HTTP客户端失败", e);
}
}
/**
* 销毁方法用于Spring容器关闭时调用
*/
public void destroy() {
close();
}
}

View File

@ -0,0 +1,206 @@
package com.bonus.algorithm.service;
import com.bonus.common.domain.algorithm.dto.DrawLinesRequest;
import com.bonus.common.domain.algorithm.dto.OperDeviceRequest;
import com.bonus.common.domain.algorithm.vo.DrawLinesResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @className: DeviceOperService
* @author: cwchen
* @date: 2026-01-08-9:26
* @version: 1.0
* @description: 设备操作接口调用-业务逻辑层
*/
@Service(value = "DeviceOperService")
@Slf4j
public class DeviceOperService {
private static final String UTF_8 = "UTF-8";
@Value("${algorithm.service.operUrl}")
private String algorithmServiceUrl;
@Value("${algorithm.service.timeout}")
private int timeout;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public DeviceOperService() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
this.objectMapper = new ObjectMapper();
}
/**
* 调用设备操作接口
*
* @param operDeviceRequest 设备操作请求参数
* @return 设备操作响应结果
* @throws IOException 当设备操作服务调用失败时抛出
*/
public DrawLinesResponse callOperDeviceService(OperDeviceRequest operDeviceRequest) throws IOException {
validateOperDeviceRequest(operDeviceRequest);
HttpPost httpPost = null;
try {
httpPost = createHttpPost(operDeviceRequest);
return executeOcrRequest(httpPost);
} catch (IOException e) {
log.error("调用设备操作服务失败", e);
return null;
} finally {
cleanupResources(httpPost);
}
}
/**
* 验证设备操作请求参数
*/
private void validateOperDeviceRequest(OperDeviceRequest operDeviceRequest) {
if (operDeviceRequest == null) {
throw new IllegalArgumentException("设备操作请求参数不能为空");
}
}
/**
* 创建HTTP POST请求
*/
private HttpPost createHttpPost(OperDeviceRequest operDeviceRequest) {
HttpPost httpPost = new HttpPost(algorithmServiceUrl);
try {
// 将请求对象转换为JSON字符串
String jsonRequest = convertToJson(operDeviceRequest);
// 设置请求体为JSON
StringEntity entity = new StringEntity(jsonRequest, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 设置请求头
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
} catch (Exception e) {
log.error("创建HTTP POST请求失败请求参数: {}", operDeviceRequest, e);
throw new RuntimeException("创建HTTP POST请求失败", e);
}
return httpPost;
}
/**
* 将请求对象转换为JSON字符串
*/
private String convertToJson(OperDeviceRequest request) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(request);
}
/**
* 执行设备操作请求
*/
private DrawLinesResponse executeOcrRequest(HttpPost httpPost) throws IOException {
log.info("开始调用设备操作服务识别");
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return processHttpResponse(response);
}
}
/**
* 处理HTTP响应
*/
private DrawLinesResponse processHttpResponse(CloseableHttpResponse response) throws IOException {
log.info("响应内容:{}",response);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = getResponseBody(response);
log.info("设备操作服务响应状态: {}", statusCode);
log.debug("设备操作服务响应内容: {}", responseBody); // 改为debug级别避免日志过大
// 检查HTTP状态码
if (statusCode != 200) {
log.error("设备操作服务HTTP请求失败状态码: {}, 响应: {}", statusCode, responseBody);
return null;
}
DrawLinesResponse drawLinesResponse = parseResponseBody(responseBody);
return drawLinesResponse;
}
/**
* 获取响应体
*/
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, UTF_8);
}
/**
* 解析响应体
*/
private DrawLinesResponse parseResponseBody(String responseBody) throws IOException {
try {
return objectMapper.readValue(responseBody, DrawLinesResponse.class);
} catch (IOException e) {
log.error("解析设备操作请求响应失败,响应内容: {}", responseBody, e);
return null;
}
}
/**
* 清理资源
*/
private void cleanupResources(HttpPost httpPost) {
// 清理HTTP连接
if (httpPost != null) {
httpPost.releaseConnection();
}
}
/**
* 关闭HTTP客户端
*/
public void close() {
try {
if (httpClient != null) {
httpClient.close();
log.info("设备操作服务HTTP客户端已关闭");
}
} catch (IOException e) {
log.error("关闭HTTP客户端失败", e);
}
}
/**
* 销毁方法用于Spring容器关闭时调用
*/
public void destroy() {
close();
}
}

View File

@ -0,0 +1,186 @@
package com.bonus.algorithm.service;
import com.bonus.common.domain.algorithm.dto.OperDeviceRequest;
import com.bonus.common.domain.algorithm.vo.DrawLinesResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @className: EndVideoService
* @author: cwchen
* @date: 2026-01-08-9:26
* @version: 1.0
* @description: 结束录制视频接口调用-业务逻辑层
*/
@Service(value = "EndVideoService")
@Slf4j
public class EndVideoService {
private static final String UTF_8 = "UTF-8";
@Value("${algorithm.service.endVideoUrl}")
private String algorithmServiceUrl;
@Value("${algorithm.service.timeout}")
private int timeout;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public EndVideoService() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
this.objectMapper = new ObjectMapper();
}
/**
* 调用结束录制视频接口
*
* @return 结束录制视频响应结果
* @throws IOException 当结束录制视频服务调用失败时抛出
*/
public DrawLinesResponse callEndVideoService() throws IOException {
HttpPost httpPost = null;
try {
httpPost = createHttpPost();
return executeOcrRequest(httpPost);
} catch (IOException e) {
log.error("调用结束录制视频服务失败", e);
return null;
} finally {
cleanupResources(httpPost);
}
}
/**
* 创建HTTP POST请求
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(algorithmServiceUrl);
try {
// 设置请求头
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
} catch (Exception e) {
log.error("创建HTTP POST请求失败", e);
throw new RuntimeException("创建HTTP POST请求失败", e);
}
return httpPost;
}
/**
* 将请求对象转换为JSON字符串
*/
private String convertToJson(OperDeviceRequest request) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(request);
}
/**
* 执行结束录制视频请求
*/
private DrawLinesResponse executeOcrRequest(HttpPost httpPost) throws IOException {
log.info("开始调用结束录制视频服务识别");
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return processHttpResponse(response);
}
}
/**
* 处理HTTP响应
*/
private DrawLinesResponse processHttpResponse(CloseableHttpResponse response) throws IOException {
log.info("响应内容:{}",response);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = getResponseBody(response);
log.info("结束录制视频服务响应状态: {}", statusCode);
log.debug("结束录制视频服务响应内容: {}", responseBody); // 改为debug级别避免日志过大
// 检查HTTP状态码
if (statusCode != 200) {
log.error("结束录制视频服务HTTP请求失败状态码: {}, 响应: {}", statusCode, responseBody);
return null;
}
DrawLinesResponse drawLinesResponse = parseResponseBody(responseBody);
return drawLinesResponse;
}
/**
* 获取响应体
*/
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, UTF_8);
}
/**
* 解析响应体
*/
private DrawLinesResponse parseResponseBody(String responseBody) throws IOException {
try {
return objectMapper.readValue(responseBody, DrawLinesResponse.class);
} catch (IOException e) {
log.error("解析结束录制视频请求响应失败,响应内容: {}", responseBody, e);
return null;
}
}
/**
* 清理资源
*/
private void cleanupResources(HttpPost httpPost) {
// 清理HTTP连接
if (httpPost != null) {
httpPost.releaseConnection();
}
}
/**
* 关闭HTTP客户端
*/
public void close() {
try {
if (httpClient != null) {
httpClient.close();
log.info("结束录制视频服务HTTP客户端已关闭");
}
} catch (IOException e) {
log.error("关闭HTTP客户端失败", e);
}
}
/**
* 销毁方法用于Spring容器关闭时调用
*/
public void destroy() {
close();
}
}

View File

@ -0,0 +1,188 @@
package com.bonus.algorithm.service;
import com.bonus.common.domain.algorithm.dto.OperDeviceRequest;
import com.bonus.common.domain.algorithm.vo.DrawLinesResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @className: StartVideoService
* @author: cwchen
* @date: 2026-01-08-9:26
* @version: 1.0
* @description: 开始录制视频接口调用-业务逻辑层
*/
@Service(value = "StartVideoService")
@Slf4j
public class StartVideoService {
private static final String UTF_8 = "UTF-8";
@Value("${algorithm.service.startVideoUrl}")
private String algorithmServiceUrl;
@Value("${algorithm.service.timeout}")
private int timeout;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public StartVideoService() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
this.objectMapper = new ObjectMapper();
}
/**
* 调用录制视频接口
*
* @return 录制视频响应结果
* @throws IOException 当录制视频服务调用失败时抛出
*/
public DrawLinesResponse callStartVideoService() throws IOException {
HttpPost httpPost = null;
try {
httpPost = createHttpPost();
return executeOcrRequest(httpPost);
} catch (IOException e) {
log.error("调用录制视频服务失败", e);
return null;
} finally {
cleanupResources(httpPost);
}
}
/**
* 创建HTTP POST请求
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(algorithmServiceUrl);
try {
// 设置请求头
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
} catch (Exception e) {
log.error("创建HTTP POST请求失败", e);
throw new RuntimeException("创建HTTP POST请求失败", e);
}
return httpPost;
}
/**
* 将请求对象转换为JSON字符串
*/
private String convertToJson(OperDeviceRequest request) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(request);
}
/**
* 执行录制视频请求
*/
private DrawLinesResponse executeOcrRequest(HttpPost httpPost) throws IOException {
log.info("开始调用录制视频服务识别");
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return processHttpResponse(response);
}
}
/**
* 处理HTTP响应
*/
private DrawLinesResponse processHttpResponse(CloseableHttpResponse response) throws IOException {
log.info("响应内容:{}",response);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = getResponseBody(response);
log.info("录制视频服务响应状态: {}", statusCode);
log.debug("录制视频服务响应内容: {}", responseBody); // 改为debug级别避免日志过大
// 检查HTTP状态码
if (statusCode != 200) {
log.error("录制视频服务HTTP请求失败状态码: {}, 响应: {}", statusCode, responseBody);
return null;
}
DrawLinesResponse drawLinesResponse = parseResponseBody(responseBody);
return drawLinesResponse;
}
/**
* 获取响应体
*/
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, UTF_8);
}
/**
* 解析响应体
*/
private DrawLinesResponse parseResponseBody(String responseBody) throws IOException {
try {
return objectMapper.readValue(responseBody, DrawLinesResponse.class);
} catch (IOException e) {
log.error("解析录制视频请求响应失败,响应内容: {}", responseBody, e);
return null;
}
}
/**
* 清理资源
*/
private void cleanupResources(HttpPost httpPost) {
// 清理HTTP连接
if (httpPost != null) {
httpPost.releaseConnection();
}
}
/**
* 关闭HTTP客户端
*/
public void close() {
try {
if (httpClient != null) {
httpClient.close();
log.info("录制视频服务HTTP客户端已关闭");
}
} catch (IOException e) {
log.error("关闭HTTP客户端失败", e);
}
}
/**
* 销毁方法用于Spring容器关闭时调用
*/
public void destroy() {
close();
}
}

View File

@ -0,0 +1,23 @@
package com.bonus.common.domain.algorithm.dto;
import lombok.Data;
/**
* @className:OperDeviceRequest
* @author:cwchen
* @date:2026-01-19-9:29
* @version:1.0
* @description:设备操作请求实体
*/
@Data
public class OperDeviceRequest {
/**
* 为1工作模式或者2休眠模式
*/
private Long mode;
/**
* 为true开启报警或者false关闭报警
*/
private boolean alarm;
}

View File

@ -0,0 +1,61 @@
package com.bonus.common.domain.algorithm.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Objects;
/**
* @className: DeviceDetailResponse
* @author: cwchen
* @date: 2026-01-08-9:35
* @version: 1.0
* @description: 设备详情响应-实体
*/
@Data
public class DeviceDetailResponse {
@JsonProperty("code")
private Integer code; // 状态码
@JsonProperty("status")
private String status;
/**
* 对应 JSON 中的 "data" 对象
*/
@JsonProperty("data")
private DeviceInfo data;
public boolean isSuccess() {
return Objects.equals(status, "success") && code != null && code == 200;
}
/**
* 内部类用于映射 data 字段
* 使用静态内部类 (static class) 方便实例化
*/
@Data
public static class DeviceInfo {
@JsonProperty("battery_cap")
private Integer batteryCap; // 电量百分比 (0-100)
@JsonProperty("time_to_full_min")
private Integer timeToFullMin; // 预计充满剩余分钟数
@JsonProperty("time_to_empty_min")
private Integer timeToEmptyMin; // 预计放空剩余分钟数
@JsonProperty("status_code")
private Integer statusCode; // 电池内部状态码
@JsonProperty("temperature_c")
private Double temperatureC; // 温度 (摄氏度)注意用 Double 接收小数
@JsonProperty("system_mode")
private Integer systemMode; // 当前模式 (1: WORK, 2: SLEEP)
@JsonProperty("alarm_flag")
private Integer alarmFlag; // 报警标志 (0: , 1: 报警)
}
}

View File

@ -28,6 +28,6 @@ public class DrawLinesResponse {
private String status;
public boolean isSuccess() {
return Objects.equals(status, "success") && code != null && code == 200;
return code == 200;
}
}

View File

@ -48,8 +48,11 @@ public class NetworkEthernetDto {
*/
@NotBlank(message = "IP地址不能为空", groups = {NOENABLE.class})
@Length(max = 32, message = "IP地址字符长度不能超过32", groups = {NOENABLE.class})
@Pattern(regexp = "^(?=.{1,255}$)(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$|^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
message = "IP地址格式不正确需为有效IP或域名", groups = {NOENABLE.class})
@Pattern(
regexp = "^(?:(?=.{1,255}$)(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}))|(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$",
message = "IP地址格式不正确需为有效IPv4地址或域名",
groups = {NOENABLE.class}
)
private String ipAddress;
/**
@ -58,19 +61,18 @@ public class NetworkEthernetDto {
@NotBlank(message = "子网掩码不能为空", groups = {NOENABLE.class})
@Length(max = 15, message = "子网掩码长度不正确", groups = {NOENABLE.class})
@Pattern(
regexp = "^(254|252|248|240|224|192|128|0)\\.0\\.0\\.0$|^(255\\.(254|252|248|240|224|192|128|0)\\.0\\.0)$|^(255\\.255\\.(254|252|248|240|224|192|128|0)\\.0)$|^(255\\.255\\.255\\.(255|254|252|248|240|224|192|128|0))$",
regexp = "^(?:(?:254|252|248|240|224|192|128|0)\\.0\\.0\\.0)|(?:255\\.(?:254|252|248|240|224|192|128|0)\\.0\\.0)|(?:255\\.255\\.(?:254|252|248|240|224|192|128|0)\\.0)|(?:255\\.255\\.255\\.(?:255|254|252|248|240|224|192|128|0))$",
message = "子网掩码格式不正确",
groups = {NOENABLE.class}
)
private String subnetMask;
/**
* 默认网关
* 默认网关 (允许为空有内容则校验格式)
*/
@NotBlank(message = "默认网关不能为空", groups = {NOENABLE.class})
@Length(max = 15, message = "默认网关长度不正确", groups = {NOENABLE.class})
@Pattern(
regexp = "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
// 将原正则包裹在 (?:原正则)? 或者利用 @Pattern 默认不校验 null 的特性
regexp = "^$|^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
message = "默认网关格式不正确",
groups = {NOENABLE.class}
)
@ -79,7 +81,6 @@ public class NetworkEthernetDto {
/**
* DNS
*/
@NotBlank(message = "DNS不能为空", groups = {NOENABLE.class})
@Length(max = 512, message = "DNS字符长度不能超过512", groups = {NOENABLE.class})
private String dns;

View File

@ -16,7 +16,9 @@ import java.util.Date;
@Data
public class ParamsDto {
/**车辆类型 1.油车 2.新能源*/
/**
* 车辆类型 1.油车 2.新能源
*/
private String carType;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@ -27,16 +29,24 @@ public class ParamsDto {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**设备名称*/
/**
* 设备名称
*/
private String equipmentName;
/**设备ID*/
/**
* 设备ID
*/
private Long systemId = 1L;
/**告警状态*/
/**
* 告警状态
*/
private String alarmStatus;
/**系统设置ID*/
/**
* 系统设置ID
*/
private Long systemSettingId;
/**
@ -48,4 +58,17 @@ public class ParamsDto {
* SIM_ID
*/
private Long simId;
/**
* 为1工作模式或者2休眠模式
*/
private String mode;
/**
* 为true开启报警或者false关闭报警
*/
private boolean alarm;
/**录制状态 1.开始录制 2.结束录制*/
private String recordingType;
}

View File

@ -1,5 +1,6 @@
package com.bonus.data.mapper;
import com.bonus.common.domain.algorithm.vo.DeviceDetailResponse;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.StorageVo;
import com.bonus.common.domain.data.vo.SystemInfoVo;
@ -49,4 +50,13 @@ public interface DISystemInfoMapper {
* @date 2025/12/24 15:07
*/
void updateSystemStorage(StorageVo vo);
/**
* 更新设备详情
* @param data
* @return void
* @author cwchen
* @date 2026/1/19 13:48
*/
void updateSystemInfo(DeviceDetailResponse.DeviceInfo data);
}

View File

@ -1,5 +1,6 @@
package com.bonus.data.service;
import com.bonus.common.domain.algorithm.vo.DeviceDetailResponse;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.StorageVo;
import com.bonus.common.domain.data.vo.SystemInfoVo;
@ -47,4 +48,13 @@ public interface DISystemInfoService {
* @date 2025/12/24 15:06
*/
void updateSystemStorage(StorageVo vo);
/**
* 更新设备详情
* @param data
* @return void
* @author cwchen
* @date 2026/1/19 13:48
*/
void updateSystemInfo(DeviceDetailResponse.DeviceInfo data);
}

View File

@ -1,5 +1,6 @@
package com.bonus.data.service.impl;
import com.bonus.common.domain.algorithm.vo.DeviceDetailResponse;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.StorageVo;
import com.bonus.common.domain.data.vo.SystemInfoVo;
@ -41,4 +42,9 @@ public class DSystemInfoServiceImpl implements DISystemInfoService {
public void updateSystemStorage(StorageVo vo) {
diSystemInfoMapper.updateSystemStorage(vo);
}
@Override
public void updateSystemInfo(DeviceDetailResponse.DeviceInfo data) {
diSystemInfoMapper.updateSystemInfo(data);
}
}

View File

@ -42,4 +42,14 @@
<update id="updateSystemStorage">
UPDATE tb_system_info SET tf_storage = #{capacity} WHERE system_id = #{systemId}
</update>
<!--更新设备详情-->
<update id="updateSystemInfo">
UPDATE tb_system_info
SET
equipment_status = #{systemMode},
equipment_operating_temperature = #{temperatureC},
alarm_status = #{alarmFlag},
battery_power = #{batteryCap}
WHERE system_id = 1
</update>
</mapper>

View File

@ -34,7 +34,16 @@
<groupId>com.bonus</groupId>
<artifactId>bonus-common</artifactId>
</dependency>
<dependency>
<groupId>com.bonus</groupId>
<artifactId>bonus-algorithm</artifactId>
<version>3.9.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.bonus</groupId>
<artifactId>bonus-data</artifactId>
</dependency>
</dependencies>

View File

@ -0,0 +1,59 @@
package com.bonus.quartz.task;
import com.bonus.algorithm.service.DeviceDetailService;
import com.bonus.common.domain.algorithm.vo.DeviceDetailResponse;
import com.bonus.data.service.DISystemInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
/**
* @className:DeviceDetailTask
* @author:cwchen
* @date:2025-12-30-17:07
* @version:1.0
* @description:设备详情任务类
*/
@Slf4j
@Component(value = "DeviceDetailTask")
public class DeviceDetailTask {
@Resource(name = "DeviceDetailService")
private DeviceDetailService detailService;
@Resource(name = "DISystemInfoService")
private DISystemInfoService diSystemInfoService;
/**
* 每天十分钟执行一次清理任务
* cron 表达式:
*/
public void getDeviceDetail() {
try {
DeviceDetailResponse deviceDetailResponse = detailService.callDeviceDetailService();
if (Objects.nonNull(deviceDetailResponse)) {
if(deviceDetailResponse.isSuccess()){
log.info("设备详情:{}",deviceDetailResponse);
DeviceDetailResponse.DeviceInfo data = deviceDetailResponse.getData();
// 处理温度
BigDecimal bd = new BigDecimal(Double.toString(data.getTemperatureC()));
bd = bd.setScale(2, RoundingMode.HALF_UP);
double result = bd.doubleValue();
data.setTemperatureC(result);
// 处理设备状态
if(Objects.equals(data.getSystemMode(),"2")){
data.setSystemMode(0);
}
diSystemInfoService.updateSystemInfo(data);
}
}
} catch (IOException e) {
log.error("设备详情更新失败",e);
}
}
}

View File

@ -10,7 +10,7 @@
<name>bonus</name>
<url>http://www.bonus.vip</url>
<description>智能投标系统</description>
<description>车辆道路计数仪</description>
<properties>
<bonus.version>3.9.0</bonus.version>