画线算法调用

This commit is contained in:
cwchen 2026-01-09 10:13:15 +08:00
parent 5886398a56
commit e75a917116
20 changed files with 677 additions and 57 deletions

View File

@ -77,6 +77,19 @@
<groupId>com.bonus</groupId>
<artifactId>bonus-data</artifactId>
</dependency>
<!--算法调用模块-->
<dependency>
<groupId>com.bonus</groupId>
<artifactId>bonus-algorithm</artifactId>
</dependency>
<dependency>
<groupId>com.bonus</groupId>
<artifactId>bonus-algorithm</artifactId>
<version>3.9.0</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -2,16 +2,19 @@ package com.bonus.web.service.data;
import com.bonus.common.core.domain.AjaxResult;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.DataRecognitionExportVO;
import com.bonus.common.domain.data.vo.DeviceIdentificationDataCountVo;
import com.bonus.common.domain.data.vo.DeviceIdentificationDataVo;
import com.bonus.common.domain.data.vo.ImageRecord;
import com.bonus.data.service.DIDeviceIdentificationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
@ -35,18 +38,7 @@ public class DeviceIdentificationService {
public List<DeviceIdentificationDataVo> getDeviceIdentificationList(ParamsDto dto) {
try {
if (dto.getStartTime() == null || dto.getEndTime() == null) {
// 获取今天的日期
LocalDateTime now = LocalDateTime.now();
// 设置开始时间为 00:00:00
LocalDateTime startOfDay = now.with(LocalTime.MIN);
// 设置结束时间为 23:59:59 (也可以使用 LocalTime.MAX)
LocalDateTime endOfDay = now.with(LocalTime.MAX);
// 转换为 Date 赋值给 dto (如果你的字段类型是 Date)
ZoneId zone = ZoneId.systemDefault();
dto.setStartTime(Date.from(startOfDay.atZone(zone).toInstant()));
dto.setEndTime(Date.from(endOfDay.atZone(zone).toInstant()));
}
dto = setTime(dto);
List<DeviceIdentificationDataVo> result = deviceIdentificationService.getDeviceIdentificationList(dto);
// 处理图片-待实现
return result != null ? result : Collections.emptyList();
@ -67,6 +59,7 @@ public class DeviceIdentificationService {
public AjaxResult getDeviceIdentificationByDataDetail(ParamsDto dto) {
DeviceIdentificationDataCountVo vo = null;
try {
dto = setTime(dto);
List<DeviceIdentificationDataVo> result = deviceIdentificationService.getDeviceIdentificationList(dto);
vo = calculateStatistics(result, dto.getStartTime(), dto.getEndTime());
@ -117,6 +110,7 @@ public class DeviceIdentificationService {
/**
* 查询要下载的识别图片
*
* @param dto
* @return List<ImageRecord>
* @author cwchen
@ -124,19 +118,101 @@ public class DeviceIdentificationService {
*/
public List<ImageRecord> getImageData(ParamsDto dto) {
try {
if (dto.getStartTime() == null || dto.getEndTime() == null) {
// 获取今天的日期
LocalDateTime now = LocalDateTime.now();
// 设置开始时间为 00:00:00
LocalDateTime startOfDay = now.with(LocalTime.MIN);
// 设置结束时间为 23:59:59 (也可以使用 LocalTime.MAX)
LocalDateTime endOfDay = now.with(LocalTime.MAX);
// 转换为 Date 赋值给 dto (如果你的字段类型是 Date)
ZoneId zone = ZoneId.systemDefault();
dto.setStartTime(Date.from(startOfDay.atZone(zone).toInstant()));
dto.setEndTime(Date.from(endOfDay.atZone(zone).toInstant()));
}
dto = setTime(dto);
List<ImageRecord> result = deviceIdentificationService.getImageData(dto);
if(CollectionUtils.isEmpty(result)){
return Collections.emptyList();
}else{
for (ImageRecord vo : result) {
// 1. 获取原始路径并提取最后一个 "_" 之后的部分
String photoPath = vo.getUrl();
String suffix = "";
if (photoPath != null && photoPath.contains("_")) {
suffix = photoPath.substring(photoPath.lastIndexOf("_") + 1);
}
// 2. 格式化日期如果是 Date 类型需要格式化如果是 String 则直接拼接
// 假设 recognitionTime Date 类型建议格式化为 yyyyMMddHHmm
String timeStr = "";
if (vo.getCreateTime() != null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
timeStr = sdf.format(vo.getCreateTime());
}
// 3. 拼接最终文件名地点 + 时间 + 类型 + 路径后缀
// 使用 Optional StringUtils 处理 null 以防出现 "null" 字符串
String fileName = String.format("%s_%s_%s_%s",
vo.getLocation() != null ? vo.getLocation() : "",
timeStr,
vo.getCarType() != null ? vo.getCarType() : "",
suffix);
vo.setFileName(fileName);
}
return result;
}
} catch (Exception e) {
log.error(e.toString(), e);
return Collections.emptyList();
}
}
/**
* 设置日期时间
*
* @param dto
* @return ParamsDto
* @author cwchen
* @date 2026/1/8 14:21
*/
public ParamsDto setTime(ParamsDto dto) {
if (dto.getStartTime() == null || dto.getEndTime() == null) {
// 获取今天的日期
LocalDateTime now = LocalDateTime.now();
// 设置开始时间为 00:00:00
LocalDateTime startOfDay = now.with(LocalTime.MIN);
// 设置结束时间为 23:59:59 (也可以使用 LocalTime.MAX)
LocalDateTime endOfDay = now.with(LocalTime.MAX);
// 转换为 Date 赋值给 dto (如果你的字段类型是 Date)
ZoneId zone = ZoneId.systemDefault();
dto.setStartTime(Date.from(startOfDay.atZone(zone).toInstant()));
dto.setEndTime(Date.from(endOfDay.atZone(zone).toInstant()));
}
return dto;
}
/**
* 获取导出数据的数量
*
* @param date
* @param endDate
* @return long
* @author cwchen
* @date 2026/1/8 15:20
*/
public long getDataCount(Date date, Date endDate) {
try {
Long result = deviceIdentificationService.getDataCount(date, endDate);
return result != null ? result : 0L;
} catch (Exception e) {
log.error(e.toString(), e);
return 0L;
}
}
/**
* 获取导出数据
*
* @param date
* @param endDate
* @return long
* @author cwchen
* @date 2026/1/8 15:20
*/
public List<DataRecognitionExportVO> getExportDataList(Date date, Date endDate, int pageNo, int pageSize) {
try {
List<DataRecognitionExportVO> result = deviceIdentificationService.getExportDataList(date, endDate,pageNo,pageSize);
return result != null ? result : Collections.emptyList();
} catch (Exception e) {
log.error(e.toString(), e);

View File

@ -6,6 +6,7 @@ import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.bonus.common.config.BonusConfig;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.DataRecognitionExportVO;
import com.bonus.common.domain.data.vo.ExportTask;
@ -19,12 +20,16 @@ import org.springframework.scheduling.annotation.Async;
import javax.annotation.Resource;
import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 识别数据导出-业务逻辑层 (多线程+防OOM版本)
* 识别数据导出-业务逻辑层
*/
@Service(value = "ExportExcelService")
@Slf4j
@ -33,6 +38,9 @@ public class ExportExcelService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource(name = "DeviceIdentificationService")
private DeviceIdentificationService deviceIdentificationService;
// 自定义线程池限制核心并发数为2最大为3防止大量图片IO导致内存溢出
private static final ExecutorService exportThreadPool = new ThreadPoolExecutor(
2, 3, 60L, TimeUnit.SECONDS,
@ -41,7 +49,8 @@ public class ExportExcelService {
);
private static final String REDIS_KEY_PREFIX = "export:task:";
private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator;
// private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator;
private static final String TEMP_PATH = BonusConfig.getLsFile() + File.separator + "export_task" + File.separator;
public String createExportTask(ParamsDto dto) {
String taskId = UUID.randomUUID().toString();
@ -59,6 +68,8 @@ public class ExportExcelService {
try {
Date start = dto.getStartTime();
Date end = dto.getEndTime();
LocalDate startLD = toLocalDate(start);
LocalDate endLD = toLocalDate(end);
long dayDiff = DateUtil.between(start, end, DateUnit.DAY);
File tempDir = new File(TEMP_PATH);
@ -71,7 +82,7 @@ public class ExportExcelService {
// 单天任务直接处理
downloadFileName = "数据识别_" + DateUtil.formatDate(start) + ".xlsx";
finalResultFile = new File(TEMP_PATH + taskId + "_" + downloadFileName);
generateExcel(finalResultFile, start, taskId, 0, 100, true);
generateExcel(finalResultFile, start, end, taskId, 0, 100, true);
} else {
// 多天任务多线程处理
List<Date> dateRange = getDatesBetween(start, end);
@ -86,7 +97,26 @@ public class ExportExcelService {
String subFileName = taskId + "_数据识别_" + DateUtil.formatDate(currentDate) + ".xlsx";
File subFile = new File(TEMP_PATH + subFileName);
// 传入 false表示子线程不直接更新 Redis 整体进度由主线程负责更新
generateExcel(subFile, currentDate, taskId, -1, -1, false);
LocalDate currentLD = toLocalDate(end);
// 进行逻辑判断
boolean isSameAsStart = currentLD.equals(startLD);
boolean isSameAsEnd = currentLD.equals(endLD);
if (isSameAsStart && !isSameAsEnd) {
// 情况 1仅与开始日期相同
LocalDateTime endOfDay = currentLD.atTime(LocalTime.MAX);
Date endDate = Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
generateExcel(subFile, start, endDate, taskId, -1, -1, false);
} else if (!isSameAsStart && isSameAsEnd) {
// 情况 2仅与结束日期相同
Date startDate = Date.from(currentLD.atStartOfDay(ZoneId.systemDefault()).toInstant());
generateExcel(subFile, startDate, end, taskId, -1, -1, false);
} else if (!isSameAsStart && !isSameAsEnd) {
// 情况 3都不同
Date startDate = Date.from(currentLD.atStartOfDay(ZoneId.systemDefault()).toInstant());
LocalDateTime endOfDay = currentLD.atTime(LocalTime.MAX);
Date endDate = Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
generateExcel(subFile, startDate, endDate, taskId, -1, -1, false);
}
subFiles.add(subFile);
} catch (Exception e) {
log.error("子任务执行失败: {}", currentDate, e);
@ -119,7 +149,7 @@ public class ExportExcelService {
}
}
private void generateExcel(File file, Date date, String taskId, int basePct, int maxPct, boolean updateRedis) {
private void generateExcel(File file, Date date, Date endDate, String taskId, int basePct, int maxPct, boolean updateRedis) {
SheetWriteHandler freezePaneHandler = new SheetWriteHandler() {
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
@ -138,7 +168,7 @@ public class ExportExcelService {
int pageNo = 1;
int pageSize = 150; // 多线程下进一步减小 pageSize 降低内存压力
long totalCount = mockDbCount(date);
long totalCount = mockDbCount(date, endDate);
int totalPages = (int) Math.ceil((double) totalCount / pageSize);
if (totalCount == 0) {
@ -147,7 +177,7 @@ public class ExportExcelService {
}
while (pageNo <= totalPages) {
List<DataRecognitionExportVO> dbDataList = mockDbQuery(date, pageNo, pageSize);
List<DataRecognitionExportVO> dbDataList = mockDbQuery(date,endDate, pageNo, pageSize);
List<DataRecognitionExportVO> voList = new ArrayList<>();
for (int i = 0; i < dbDataList.size(); i++) {
@ -162,10 +192,11 @@ public class ExportExcelService {
try {
if (entity.getPhotoPath() != null) {
File img = new File(entity.getPhotoPath());
File img = new File(BonusConfig.getProfile() + entity.getPhotoPath());
if (img.exists() && img.canRead()) vo.setPhotoFile(img);
}
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
voList.add(vo);
}
@ -183,7 +214,11 @@ public class ExportExcelService {
}
} finally {
if (excelWriter != null) {
try { excelWriter.finish(); } catch (Exception e) { log.error("IO Close Error", e); }
try {
excelWriter.finish();
} catch (Exception e) {
log.error("IO Close Error", e);
}
}
}
}
@ -238,16 +273,12 @@ public class ExportExcelService {
return list;
}
/*private long mockDbCount(Date date) { return 500; }
private List<DataRecognitionExportVO> mockDbQuery(Date date, int p, int s) {
return new ArrayList<>(); // 替换为实际查询
}*/
private long mockDbCount(Date date) {
return 1000; // 模拟少量数据测试
private long mockDbCount(Date date, Date endDate) {
return deviceIdentificationService.getDataCount(date,endDate);
}
private List<DataRecognitionExportVO> mockDbQuery(Date date, int pageNo, int pageSize) {
List<DataRecognitionExportVO> list = new ArrayList<>();
private List<DataRecognitionExportVO> mockDbQuery(Date date,Date endDate, int pageNo, int pageSize) {
/* List<DataRecognitionExportVO> list = new ArrayList<>();
for (int i = 0; i < pageSize; i++) {
DataRecognitionExportVO vo = new DataRecognitionExportVO();
vo.setIndex((pageNo - 1) * pageSize + i + 1);
@ -258,7 +289,16 @@ public class ExportExcelService {
// 生产环境下确保此路径存在
vo.setPhotoPath("C:\\Users\\10488\\Desktop\\test_image.png");
list.add(vo);
}*/
List<DataRecognitionExportVO> exportDataList = deviceIdentificationService.getExportDataList(date, endDate, pageNo, pageSize);
for (int i = 0; i < exportDataList.size(); i++) {
DataRecognitionExportVO dataRecognitionExportVO = exportDataList.get(i);
dataRecognitionExportVO.setIndex((pageNo - 1) * pageSize + i + 1);
}
return list;
return exportDataList;
}
public static LocalDate toLocalDate(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
}

View File

@ -1,5 +1,6 @@
package com.bonus.web.service.data;
import com.bonus.common.config.BonusConfig;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.ExportTask;
import com.bonus.common.domain.data.vo.ImageRecord;
@ -40,7 +41,8 @@ public class ExportService {
private static final long EXPIRE_TIME = 24;
// 临时文件存放目录
private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/export_task/";
// private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/export_task/";
private static final String TEMP_DIR = BonusConfig.getLsFile() + "/export_task/";
/**
* 创建任务并立即返回 TaskID
@ -211,7 +213,7 @@ public class ExportService {
for (ImageRecord img : images) {
// 这里假设你的 ImageRecord.getUrl() 存储的是本地绝对路径例如 /home/data/images/1.jpg
// 如果字段名不同比如叫 getFilePath请自行替换
String localPath = img.getUrl();
String localPath = BonusConfig.getProfile() + img.getUrl();
if (localPath == null || localPath.isEmpty()) {
continue;
@ -229,8 +231,10 @@ public class ExportService {
try {
// 1. 确定在压缩包内的文件名 (防止重名)
String originalFileName = imageFile.getName();
String zipEntryName = UUID.randomUUID().toString().substring(0, 8) + "_" + originalFileName;
// String originalFileName = imageFile.getName();
String originalFileName = img.getFileName();
// String zipEntryName = UUID.randomUUID().toString().substring(0, 8) + "_" + originalFileName;
String zipEntryName = originalFileName;
// 2. 创建 Zip 条目
ZipEntry entry = new ZipEntry(zipEntryName);

View File

@ -1,10 +1,14 @@
package com.bonus.web.service.data;
import com.bonus.algorithm.service.DrawLinesService;
import com.bonus.common.core.domain.AjaxResult;
import com.bonus.common.domain.algorithm.dto.DrawLinesRequest;
import com.bonus.common.domain.algorithm.vo.DrawLinesResponse;
import com.bonus.common.domain.data.dto.LineDto;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.LineVo;
import com.bonus.common.domain.data.vo.StreamVo;
import com.bonus.common.enums.ResultCode;
import com.bonus.common.utils.ValidatorsUtils;
import com.bonus.data.service.DILineService;
import lombok.extern.slf4j.Slf4j;
@ -14,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
import java.util.Objects;
/**
* @className:LineService
@ -32,6 +37,9 @@ public class LineService {
@Resource(name = "ValidatorsUtils")
private ValidatorsUtils validatorsUtils;
@Resource(name = "DrawLinesService")
private DrawLinesService drawLinesService;
/**
* 查询初始线的位置
* @param dto
@ -43,6 +51,13 @@ public class LineService {
LineVo vo = null;
try {
vo = lineService.getLineDetail(dto);
DrawLinesRequest request = new DrawLinesRequest();
request.setX1(Double.parseDouble(vo.getStartX()));
request.setY1(Double.parseDouble(vo.getStartY()));
request.setX2(Double.parseDouble(vo.getEndX()));
request.setY2(Double.parseDouble(vo.getEndY()));
log.info("视频流初始加载时设置线的坐标:{}",request);
DrawLinesResponse drawLinesResponse = drawLinesService.callDrawLinesService(request);
} catch (Exception e) {
log.error(e.toString(),e);
}
@ -64,14 +79,30 @@ public class LineService {
return AjaxResult.error(validResult);
}
try {
// 1.添加线的位置
lineService.addLineData(dto);
DrawLinesRequest request = new DrawLinesRequest();
request.setX1(Double.parseDouble(dto.getStartX()));
request.setY1(Double.parseDouble(dto.getStartY()));
request.setX2(Double.parseDouble(dto.getEndX()));
request.setY2(Double.parseDouble(dto.getEndY()));
log.info("设置线的坐标:{}",request);
DrawLinesResponse drawLinesResponse = drawLinesService.callDrawLinesService(request);
if (Objects.nonNull(drawLinesResponse)) {
if(drawLinesResponse.isSuccess()){
// 画线接口调用成功添加线的位置
lineService.addLineData(dto);
return AjaxResult.success();
}else{
return AjaxResult.error(drawLinesResponse.getMessage());
}
}else{
return AjaxResult.error(ResultCode.INTERFACE_CALL_FAILED.getMessage());
}
} catch (Exception e) {
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
return AjaxResult.success();
}
/**

View File

@ -0,0 +1,5 @@
algorithm:
service:
url: http://192.168.0.108:8080/api/v1/video/setTripwire # 画线算法接口请求地址
timeout: 30000 # 画线算法请求超时时间
max-connections: 100

27
bonus-algorithm/pom.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bonus</groupId>
<artifactId>bonus</artifactId>
<version>3.9.0</version>
</parent>
<artifactId>bonus-algorithm</artifactId>
<description>
算法调用模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.bonus</groupId>
<artifactId>bonus-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,205 @@
package com.bonus.algorithm.service;
import com.bonus.common.domain.algorithm.dto.DrawLinesRequest;
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: DrawLinesService
* @author: cwchen
* @date: 2026-01-08-9:26
* @version: 1.0
* @description: 画线接口调用-业务逻辑层
*/
@Service(value = "DrawLinesService")
@Slf4j
public class DrawLinesService {
private static final String UTF_8 = "UTF-8";
@Value("${algorithm.service.url}")
private String algorithmServiceUrl;
@Value("${algorithm.service.timeout}")
private int timeout;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public DrawLinesService() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.build();
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
this.objectMapper = new ObjectMapper();
}
/**
* 调用画线接口
*
* @param drawLinesRequest 画线请求参数
* @return 画线响应结果
* @throws IOException 当画线服务调用失败时抛出
*/
public DrawLinesResponse callDrawLinesService(DrawLinesRequest drawLinesRequest) throws IOException {
validateDrawLinesRequest(drawLinesRequest);
HttpPost httpPost = null;
try {
httpPost = createHttpPost(drawLinesRequest);
return executeOcrRequest(httpPost);
} catch (IOException e) {
log.error("调用画线服务失败", e);
return null;
} finally {
cleanupResources(httpPost);
}
}
/**
* 验证画线请求参数
*/
private void validateDrawLinesRequest(DrawLinesRequest drawLinesRequest) {
if (drawLinesRequest == null) {
throw new IllegalArgumentException("画线请求参数不能为空");
}
}
/**
* 创建HTTP POST请求
*/
private HttpPost createHttpPost(DrawLinesRequest drawLinesRequest) {
HttpPost httpPost = new HttpPost(algorithmServiceUrl);
try {
// 将请求对象转换为JSON字符串
String jsonRequest = convertToJson(drawLinesRequest);
// 设置请求体为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请求失败请求参数: {}", drawLinesRequest, e);
throw new RuntimeException("创建HTTP POST请求失败", e);
}
return httpPost;
}
/**
* 将请求对象转换为JSON字符串
*/
private String convertToJson(DrawLinesRequest 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

@ -24,6 +24,9 @@ public class BonusConfig
/** 上传路径 */
private static String profile;
/** 临时文件路径 */
private static String lsFile;
/** 获取地址开关 */
private static boolean addressEnabled;
@ -119,4 +122,12 @@ public class BonusConfig
{
return getProfile() + "/upload";
}
public static String getLsFile() {
return lsFile;
}
public static void setLsFile(String lsFile) {
BonusConfig.lsFile = lsFile;
}
}

View File

@ -0,0 +1,34 @@
package com.bonus.common.domain.algorithm.dto;
import lombok.Data;
/**
* @className:DrawLinesRequest
* @author:cwchen
* @date:2026-01-08-9:35
* @version:1.0
* @description:画线调用请求-实体
*/
@Data
public class DrawLinesRequest {
/**起点坐标x*/
private double x1;
/**起点坐标y*/
private double y1;
/**终点坐标x*/
private double x2;
/**终点坐标Y*/
private double y2;
public DrawLinesRequest(double x1, double y1, double x2, double y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
public DrawLinesRequest() {
}
}

View File

@ -0,0 +1,33 @@
package com.bonus.common.domain.algorithm.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @className: DrawLinesResponse
* @author: cwchen
* @date: 2026-01-08-9:35
* @version: 1.0
* @description: 画线调用响应-实体
*/
@Data
public class DrawLinesResponse {
@JsonProperty("code")
private Integer code; // 状态码
@JsonProperty("message")
private String message; // 消息
@JsonProperty("status")
private String status;
public boolean isSuccess() {
return Objects.equals(status, "success") && code != null && code == 200;
}
}

View File

@ -17,6 +17,10 @@ import java.util.Date;
public class ImageRecord {
private String url;
private String location;
private String identificationTime;
private String carType;
private String fileName;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date createTime;

View File

@ -0,0 +1,34 @@
package com.bonus.common.enums;
/**
* @className:ResultCode
* @author:cwchen
* @date:2026-01-08-9:59
* @version:1.0
* @description:异常枚举
*/
public enum ResultCode {
SUCCESS(200, "操作成功"),
INTERFACE_CALL_FAILED(10001, "接口调用失败"),
SYSTEM_ERROR(500, "系统繁忙,请稍后再试");
private final int code;
private final String message;
// 构造方法
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
// 获取错误码
public int getCode() {
return code;
}
// 获取错误信息
public String getMessage() {
return message;
}
}

View File

@ -1,10 +1,13 @@
package com.bonus.data.mapper;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.DataRecognitionExportVO;
import com.bonus.common.domain.data.vo.DeviceIdentificationDataVo;
import com.bonus.common.domain.data.vo.ImageRecord;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.List;
/**
@ -19,6 +22,7 @@ public interface DIDeviceIdentificationMapper {
/**
* 查询识别图片
*
* @param dto
* @return List<DeviceIdentificationDataVo>
* @author cwchen
@ -28,10 +32,34 @@ public interface DIDeviceIdentificationMapper {
/**
* 查询要下载的图片
*
* @param dto
* @return List<ImageRecord>
* @author cwchen
* @date 2025/12/26 10:41
*/
List<ImageRecord> getImageData(ParamsDto dto);
/**
* 获取导出数据的数量
*
* @param date
* @param endDate
* @return Long
* @author cwchen
* @date 2026/1/8 15:24
*/
Long getDataCount(@Param("startDate") Date date,@Param("endDate") Date endDate);
/**
* 获取导出数据
* @param date
* @param endDate
* @param pageNo
* @param pageSize
* @return List<DataRecognitionExportVO>
* @author cwchen
* @date 2026/1/8 15:24
*/
List<DataRecognitionExportVO> getExportDataList(@Param("startDate") Date date,@Param("endDate") Date endDate,@Param("pageNo") int pageNo,@Param("pageSize") int pageSize);
}

View File

@ -2,10 +2,12 @@ package com.bonus.data.service;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.AlarmVo;
import com.bonus.common.domain.data.vo.DataRecognitionExportVO;
import com.bonus.common.domain.data.vo.DeviceIdentificationDataVo;
import com.bonus.common.domain.data.vo.ImageRecord;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
@ -34,4 +36,24 @@ public interface DIDeviceIdentificationService {
* @date 2025/12/26 10:38
*/
List<ImageRecord> getImageData(ParamsDto dto);
/**
* 获取导出数据的数量
* @param date
* @param endDate
* @return Long
* @author cwchen
* @date 2026/1/8 15:22
*/
Long getDataCount(Date date, Date endDate);
/**
* 获取导出数据
* @param date
* @param endDate
* @return List<DataRecognitionExportVO>
* @author cwchen
* @date 2026/1/8 15:22
*/
List<DataRecognitionExportVO> getExportDataList(Date date, Date endDate,int pageNo, int pageSize);
}

View File

@ -2,6 +2,7 @@ package com.bonus.data.service.impl;
import com.bonus.common.domain.data.dto.ParamsDto;
import com.bonus.common.domain.data.vo.AlarmVo;
import com.bonus.common.domain.data.vo.DataRecognitionExportVO;
import com.bonus.common.domain.data.vo.DeviceIdentificationDataVo;
import com.bonus.common.domain.data.vo.ImageRecord;
import com.bonus.data.mapper.DIDeviceIdentificationMapper;
@ -10,6 +11,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
@ -34,4 +36,14 @@ public class DDeviceIdentificationServiceImpl implements DIDeviceIdentificationS
public List<ImageRecord> getImageData(ParamsDto dto) {
return diDeviceIdentificationMapper.getImageData(dto);
}
@Override
public Long getDataCount(Date date, Date endDate) {
return diDeviceIdentificationMapper.getDataCount(date,endDate);
}
@Override
public List<DataRecognitionExportVO> getExportDataList(Date date, Date endDate,int pageNo, int pageSize) {
return diDeviceIdentificationMapper.getExportDataList(date,endDate,pageNo,pageSize);
}
}

View File

@ -35,12 +35,14 @@
<select id="getImageData" resultType="com.bonus.common.domain.data.vo.ImageRecord">
SELECT
tdid.identification_time AS createTime,
srf.file_path AS url
srf.file_path AS url,
tdid.identification_location AS location,
CASE tdid.car_type WHEN '1' THEN '油车' WHEN '2' THEN '新能源' END AS carType
FROM tb_device_identification_data tdid
LEFT JOIN sys_resource_file srf
ON srf.business_id = tdid.identification_data_id
AND srf.source_table = 'tb_device_identification_data'
AND srf.file_type = '2'
AND srf.file_type = '1'
<where>
<if test="startTime != null">
AND tdid.identification_time &gt;= #{startTime}
@ -54,4 +56,44 @@
</where>
ORDER BY tdid.identification_time DESC
</select>
<!--获取导出数据的数量-->
<select id="getDataCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM tb_device_identification_data tdid
<where>
<if test="startDate != null">
AND tdid.identification_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND tdid.identification_time &lt;= #{endDate}
</if>
</where>
</select>
<!--获取导出数据-->
<select id="getExportDataList" resultType="com.bonus.common.domain.data.vo.DataRecognitionExportVO">
<bind name="offset" value="(pageNo - 1) * pageSize" />
SELECT
tdid.identification_time AS recognitionTime,
srf.file_path AS photoPath,
CASE tdid.car_color WHEN '1' THEN '蓝色' WHEN '2' THEN '绿色' END AS plateColor,
CASE tdid.car_type WHEN '1' THEN '油车' WHEN '2' THEN '新能源' END AS vehicleType,
tdid.identification_location AS location
FROM tb_device_identification_data tdid
LEFT JOIN sys_resource_file srf
ON srf.business_id = tdid.identification_data_id
AND srf.source_table = 'tb_device_identification_data'
AND srf.file_type = '1'
<where>
<if test="startDate != null">
AND tdid.identification_time &gt;= #{startDate}
</if>
<if test="endDate != null">
AND tdid.identification_time &lt;= #{endDate}
</if>
</where>
ORDER BY tdid.identification_time DESC
LIMIT #{offset}, #{pageSize}
</select>
</mapper>

View File

@ -59,10 +59,6 @@
<artifactId>bonus-system</artifactId>
</dependency>
<dependency>
<groupId>com.bonus</groupId>
<artifactId>bonus-file</artifactId>
</dependency>
</dependencies>

View File

@ -1,4 +1,5 @@
package com.bonus.quartz.task;
import com.bonus.common.config.BonusConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@ -22,7 +23,8 @@ import java.util.concurrent.TimeUnit;
public class FileCleanupTask {
// 使用之前定义的临时目录路径
private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator;
// private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator;
private static final String TEMP_PATH = BonusConfig.getLsFile() + File.separator + "export_task" + File.separator;
/**
* 每天凌晨 2 点执行清理任务

View File

@ -251,6 +251,7 @@
<module>bonus-common</module>
<module>bonus-ocr</module>
<module>bonus-data</module>
<module>bonus-algorithm</module>
</modules>
<packaging>pom</packaging>