BugImageDownloadService.java:package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import com.example.model.BugApiResponse;
import com.example.model.BugResponseData;
import com.example.model.BugImageInfo;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.net.InetAddress;
import java.util.stream.Collectors;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
@Service
public class BugImageDownloadService {
private static final ZoneId SERVER_ZONE = ZoneId.of("Asia/Shanghai");
private static final String API_URL = "http://nyzbwlw.com/situation/http/device/bug/getBugData";
private static final DateTimeFormatter API_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter FILE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter FILE_TIME_FORMAT = DateTimeFormatter.ofPattern("HH_mm_ss");
private final ConcurrentHashMap<String, LocalDateTime> deviceLastUpdateMap = new ConcurrentHashMap<>();
@Value("${bug.download.deviceCodes:GK2025040001,GK2025040002,GK2025040003,GK2025040004,GK2025040005,GK2025040006,GK2025040007,GK2025040008}")
private List<String> deviceCodes;
private final Map<String, String> cookies = new ConcurrentHashMap<>();
// 存储每个设备的下一个处理日期
private final ConcurrentHashMap<String, LocalDate> deviceNextDateMap = new ConcurrentHashMap<>();
@Value("${bug.image.save.path:F:/GKD/}")
private String savePath;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public BugImageDownloadService(RestTemplate restTemplate, ObjectMapper objectMapper) {
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
}
@PostConstruct
public void init() {
// 所有设备从固定起始日期开始
LocalDate startDate = LocalDate.of(2025, 7, 1);
for (String deviceCode : deviceCodes) {
deviceNextDateMap.put(deviceCode, startDate);
}
}
/**
* 持续下载所有设备的新图片
*/
public void downloadNewImagesContinuously() {
System.out.println("[TASK] ====== CONTINUOUS DOWNLOAD STARTED ======");
// System.out.println("[INFO] Devices: " + deviceCodes.size());
while (true) {
try {
boolean allDevicesUpToDate = true;
// 处理每个设备的历史数据
for (String deviceCode : deviceCodes) {
LocalDate currentDate = deviceNextDateMap.get(deviceCode);
LocalDate today = LocalDate.now(SERVER_ZONE);
if (currentDate.isBefore(today)) {
allDevicesUpToDate = false;
// 处理该设备当前日期的数据
LocalDateTime startOfDay = currentDate.atStartOfDay();
LocalDateTime endOfDay = currentDate.atTime(23, 59, 59);
System.out.printf("[PROCESS] Device: %s | Date: %s%n",
deviceCode, currentDate);
// 下载该日期的图片
int[] counts = downloadImagesForDevice(
deviceCode,
startOfDay,
endOfDay,
false // 标记为历史数据请求
);
// 更新设备的下一个处理日期(明天)
deviceNextDateMap.put(deviceCode, currentDate.plusDays(1));
// System.out.printf("[RESULT] Device: %s | Date: %s | Found: %d | Downloaded: %d | Failed: %d%n",
// deviceCode, currentDate, counts[0], counts[1], counts[2]);
// 设备间处理间隔
Thread.sleep(2000);
}
}
// 所有设备都处理到最新日期
if (allDevicesUpToDate) {
System.out.println("[STATUS] All devices up-to-date. Starting real-time updates...");
// ===== 优化后的实时更新逻辑 =====
for (String deviceCode : deviceCodes) {
// 获取上次更新时间(默认当天0点)
LocalDateTime lastUpdate = deviceLastUpdateMap.getOrDefault(
deviceCode,
LocalDate.now(SERVER_ZONE).atStartOfDay() // 使用服务器时区
);
LocalDateTime now = LocalDateTime.now();
// System.out.printf("[REALTIME] Device: %s | Time range: %s to %s%n",
// deviceCode, lastUpdate, now);
// 修复点1:添加第四个参数 true 表示实时请求
int[] counts = downloadImagesForDevice(
deviceCode,
lastUpdate, // 从上次更新时间开始
now,
true // 标记为实时请求
);
// 更新最后记录时间(当前轮询结束时间)
deviceLastUpdateMap.put(deviceCode, now);
System.out.printf("[REALTIME-RESULT] Device: %s | Downloaded: %d | Failed: %d%n",
deviceCode, counts[1], counts[2]);
// 设备间处理间隔
Thread.sleep(2000);
}
// ========================
// 等待4h后再次检查
System.out.println("[STATUS] Real-time round completed. Waiting 4 hours...");
Thread.sleep(14_400_000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("[ERROR] Task interrupted: " + e.getMessage());
break;
} catch (Exception e) {
System.err.println("[ERROR] Critical error: " + e.getMessage());
try {
Thread.sleep(60_000); // 错误后等待1分钟
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
/**
* 下载指定设备在指定时间范围内的图片
*/
private int[] downloadImagesForDevice(String deviceCode, LocalDateTime startTime, LocalDateTime endTime, boolean isRealtime) {
int maxRetries = 3;
int retryCount = 0;
int waitTime = 2000; // 初始等待时间 2秒
int total = 0;
int success = 0;
int failed = 0;
while (retryCount < maxRetries) {
try {
// ====== 网络诊断 ======
// ... [网络诊断代码保持不变] ...
// ========== 统一时区转换逻辑 ==========
ZonedDateTime serverStart = startTime.atZone(ZoneId.systemDefault())
.withZoneSameInstant(SERVER_ZONE);
ZonedDateTime serverEnd = endTime.atZone(ZoneId.systemDefault())
.withZoneSameInstant(SERVER_ZONE);
String startStr = serverStart.format(API_DATE_FORMAT);
String endStr = serverEnd.format(API_DATE_FORMAT);
// 调试日志增强
// System.out.println("[DEBUG] Time Parameters:");
// System.out.println(" Original Local: " + startTime + " - " + endTime);
// System.out.println(" Converted Server: " +
// serverStart.format(API_DATE_FORMAT) + " - " +
// serverEnd.format(API_DATE_FORMAT));
// System.out.println(" Raw Time: " + startStr + " & " + endStr);
// 使用Spring的URI构建器确保正确编码
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_URL)
.queryParam("deviceCode", deviceCode)
.queryParam("startTime", startStr)
.queryParam("endTime", endStr)
.queryParam("size", 1000)
.queryParam("t", System.currentTimeMillis());
String apiUrl = builder.build().toUriString();
// ============================
// System.out.println("[DEBUG] Final request URL: " + apiUrl);
// ====== 创建带Cookie的请求头 ======
HttpHeaders headers = new HttpHeaders();
// 添加浏览器级 User-Agent
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0");
// 必须添加的请求头
headers.set("Accept", "application/json, text/plain, */*");
headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
headers.set("Connection", "keep-alive");
headers.set("Referer", "http://nyzbwlw.com/situation/"); // 关键修复:添加 Referer
headers.set("Origin", "http://nyzbwlw.com");
headers.set("Sec-Fetch-Dest", "empty");
headers.set("Sec-Fetch-Mode", "cors");
headers.set("Sec-Fetch-Site", "same-origin");
// 添加 Cookie(关键修复:使用持久化 Cookie)
if (!cookies.isEmpty()) {
String cookieHeader = cookies.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("; "));
headers.add("Cookie", cookieHeader);
// System.out.println("[DEBUG] Sending cookies: " + cookieHeader);
}
// 发送请求
ResponseEntity<String> response = restTemplate.exchange(
apiUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class);
// 保存新Cookie
List<String> setCookieHeaders = response.getHeaders().get("Set-Cookie");
if (setCookieHeaders != null) {
for (String cookie : setCookieHeaders) {
// 提取关键 Cookie(排除过期/路径信息)
String[] cookieParts = cookie.split(";")[0].split("=");
if (cookieParts.length >= 2) {
String cookieName = cookieParts[0].trim();
String cookieValue = cookieParts[1].trim();
cookies.put(cookieName, cookieValue);
// System.out.println("[COOKIE] Saved session cookie: " + cookieName);
}
}
}
// 响应状态日志
// System.out.println("[DEBUG] Response Status: " +
// response.getStatusCodeValue() + " " +
// response.getStatusCode().getReasonPhrase());
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
String responseBody = response.getBody();
System.out.println("[RESPONSE-DATA] " + responseBody);
// System.out.println("[DEBUG] Raw API response snippet: "
// + responseBody.substring(0, Math.min(300, responseBody.length()))
// + (responseBody.length() > 300 ? "..." : ""));
// 使用POJO解析响应
BugApiResponse apiResponse = objectMapper.readValue(responseBody, BugApiResponse.class);
// System.out.printf("[DEBUG] Parsed API response code: %d | message: %s%n",
// apiResponse.getCode(), apiResponse.getMsg());
if (apiResponse.getCode() == 100) {
BugResponseData data = apiResponse.getData();
List<BugImageInfo> bugList = (data != null && data.getList() != null) ?
data.getList() : Collections.emptyList();
total = bugList.size();
// System.out.printf("[DEBUG] Found %d valid image entries%n", total);
// 如果有数据,处理并返回
if (total > 0) {
for (BugImageInfo bug : bugList) {
String pictureUrl = bug.getPictureUrl();
String collectionTime = bug.getCollectionTime();
if (pictureUrl != null && !pictureUrl.isEmpty() &&
collectionTime != null && !collectionTime.isEmpty()) {
System.out.printf("[PROCESSUrl] Downloading: %s | %s%n",
collectionTime, pictureUrl);
if (downloadImage(deviceCode, pictureUrl, collectionTime)) {
success++;
} else {
failed++;
}
} else {
// System.err.printf("[WARN] Invalid data: URL=%s | Time=%s%n",
// pictureUrl, collectionTime);
failed++;
}
}
return new int[]{total, success, failed};
}
} else {
System.err.println("[API-ERROR] Device: " + deviceCode
+ " | Code: " + apiResponse.getCode());
}
}
} catch (Exception e) {
System.err.println("[DOWNLOAD-ERROR] Device: " + deviceCode);
// e.printStackTrace();
}
// 如果没有数据或出错,等待后重试
retryCount++;
if (retryCount < maxRetries) {
try {
System.out.printf("[RETRY] Retrying in %d ms (attempt %d/%d)%n",
waitTime, retryCount + 1, maxRetries);
Thread.sleep(waitTime);
waitTime *= 2; // 指数退避
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
System.out.printf("[STATS] Device: %s | Total: %d | Success: %d | Failed: %d%n",
deviceCode, total, success, failed);
return new int[]{total, success, failed};
}
private boolean downloadImage(String deviceCode, String imageUrl, String collectionTime) {
try {
LocalDateTime dateTime = LocalDateTime.parse(collectionTime, API_DATE_FORMAT);
// 创建保存路径
Path saveDir = Paths.get(savePath,
deviceCode,
dateTime.format(FILE_DATE_FORMAT));
if (!Files.exists(saveDir)) {
Files.createDirectories(saveDir);
}
// 生成文件名
String fileName = dateTime.format(FILE_TIME_FORMAT) + ".png";
Path filePath = saveDir.resolve(fileName);
// 跳过已存在文件
if (Files.exists(filePath)) {
return true;
}
// 下载图片
byte[] imageBytes = restTemplate.getForObject(imageUrl, byte[].class);
if (imageBytes != null && imageBytes.length > 0) {
Files.write(filePath, imageBytes);
return true;
}
} catch (Exception e) {
System.err.println("[IMAGE-ERROR] URL: " + imageUrl + " | Error: " + e.getMessage());
}
return false;
}
} application.properties:spring.datasource.url=jdbc:mysql://117.190.40.178:3306/efwulianwang?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=efwlw123
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
logging.level.root=WARN
# logging.file.name=./logs/application.log
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
logging.level.org.hibernate.type.descriptor.sql=OFF
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
logging.level.org.springframework.web=WARN
logging.level.org.hibernate.SQL=WARN
server.address=0.0.0.0
server.port=8080
bug.download.deviceCodes=GK2025040001,GK2025040002,GK2025040003,GK2025040004,GK2025040005,GK2025040006,GK2025040007,GK2025040008
bug.download.auto-startup=true
bug.download.initial-delay=5000
bug.image.save.path=F:/GKD/
# logging.level.org.springframework.web.client=DEBUG
# logging.level.com.example.service=TRACE
# spring.web.resources.static-locations=file:F:/shuhaitupian/,file:F:/shuhaishipin/
# spring.mvc.static-path-pattern=/**
# 关闭Hibernate自动DDL操作
# spring.jpa.hibernate.ddl-auto=none
# 禁用方言校验(减少启动时元数据查询)
# spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false。命令行输出:[PROCESS] Device: GK2025040001 | Date: 2025-07-01
[RESPONSE-DATA] {"msg":"操作成功!","code":100,"data":{"count":7,"list":[{"eqName":"GK-1","collectionTime":"2025-07-01 05:26:13","monitorId":250701052649169,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701052649996593.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 05:17:28","monitorId":250701052651629,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701052651307594.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:57:27","monitorId":250701050608211,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701050608335522.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:37:27","monitorId":250701044639810,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701044639876462.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:17:27","monitorId":250701042609162,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701042609277377.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 03:57:27","monitorId":250701040609527,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701040609250286.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 03:37:27","monitorId":250701034610703,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701034610530207.jpg","pictureAnalysis":{}}]}}
[DOWNLOAD-ERROR] Device: GK2025040001
[RETRY] Retrying in 2000 ms (attempt 2/3)
[RESPONSE-DATA] {"msg":"操作成功!","code":100,"data":{"count":7,"list":[{"eqName":"GK-1","collectionTime":"2025-07-01 05:26:13","monitorId":250701052649169,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701052649996593.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 05:17:28","monitorId":250701052651629,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701052651307594.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:57:27","monitorId":250701050608211,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701050608335522.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:37:27","monitorId":250701044639810,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701044639876462.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:17:27","monitorId":250701042609162,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701042609277377.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 03:57:27","monitorId":250701040609527,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701040609250286.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 03:37:27","monitorId":250701034610703,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701034610530207.jpg","pictureAnalysis":{}}]}}
[DOWNLOAD-ERROR] Device: GK2025040001
[RETRY] Retrying in 4000 ms (attempt 3/3)
[RESPONSE-DATA] {"msg":"操作成功!","code":100,"data":{"count":7,"list":[{"eqName":"GK-1","collectionTime":"2025-07-01 05:26:13","monitorId":250701052649169,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701052649996593.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 05:17:28","monitorId":250701052651629,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701052651307594.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:57:27","monitorId":250701050608211,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701050608335522.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:37:27","monitorId":250701044639810,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701044639876462.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 04:17:27","monitorId":250701042609162,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701042609277377.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 03:57:27","monitorId":250701040609527,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701040609250286.jpg","pictureAnalysis":{}},{"eqName":"GK-1","collectionTime":"2025-07-01 03:37:27","monitorId":250701034610703,"exhibitionPath":"","num":"0","pictureUrl":"https://zzoukeqi.oss-cn-beijing.aliyuncs.com/situations/bugImgs/GK2025040001/2025-07-01/250701034610530207.jpg","pictureAnalysis":{}}]}}
[DOWNLOAD-ERROR] Device: GK2025040001
[STATS] Device: GK2025040001 | Total: 0 | Success: 0 | Failed: 0。依据上述部分springboot代码和命令行输出解决当前代码可以请求到有效"pictureUrl",但是程序运行后没有往F:/GKD/目录下存储各个设备的图片是为什么。
最新发布