refactor: 移除图片保存相关逻辑,直接更新任务状态为完成
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add:*)"
|
||||
"Bash(git add:*)",
|
||||
"Bash(mvn clean:*)",
|
||||
"Bash(mvn test:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package com.gameplatform.server.controller.image;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 图片访问控制器
|
||||
* 提供本地保存的图片访问接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/images")
|
||||
public class ImageController {
|
||||
private static final Logger log = LoggerFactory.getLogger(ImageController.class);
|
||||
|
||||
private final String imageSavePath;
|
||||
|
||||
public ImageController(@Value("${app.image-save-path:./images}") String imageSavePath) {
|
||||
this.imageSavePath = imageSavePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存的图片
|
||||
* @param imagePath 图片相对路径,如 task_123_device_f1_completed_20231201_143000/homepage_1701234567890.png
|
||||
* @return 图片资源
|
||||
*/
|
||||
@GetMapping("/{imagePath:.+}")
|
||||
public ResponseEntity<Resource> getImage(@PathVariable String imagePath) {
|
||||
try {
|
||||
// 构建完整的文件路径
|
||||
Path fullPath = Paths.get(imageSavePath).resolve(imagePath).normalize();
|
||||
|
||||
// 安全检查:确保请求的文件在指定目录内
|
||||
Path basePath = Paths.get(imageSavePath).normalize();
|
||||
if (!fullPath.startsWith(basePath)) {
|
||||
log.warn("非法的图片路径访问尝试: {}", imagePath);
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!Files.exists(fullPath) || !Files.isRegularFile(fullPath)) {
|
||||
log.debug("图片文件不存在: {}", fullPath);
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 创建资源
|
||||
Resource resource = new FileSystemResource(fullPath);
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
String fileName = fullPath.getFileName().toString();
|
||||
String contentType = getContentType(fileName);
|
||||
headers.setContentType(MediaType.parseMediaType(contentType));
|
||||
headers.setCacheControl("max-age=3600"); // 缓存1小时
|
||||
|
||||
log.debug("返回图片: {}", fullPath);
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(resource);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取图片失败: {}", imagePath, e);
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件扩展名获取MIME类型
|
||||
*/
|
||||
private String getContentType(String fileName) {
|
||||
String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
|
||||
switch (extension) {
|
||||
case "png":
|
||||
return "image/png";
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
return "image/jpeg";
|
||||
case "gif":
|
||||
return "image/gif";
|
||||
case "bmp":
|
||||
return "image/bmp";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -46,28 +39,17 @@ public class QrProxyController {
|
||||
private final String appBaseUrl;
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
private final String imageSavePath;
|
||||
|
||||
public QrProxyController(ScriptClient scriptClient,
|
||||
LinkStatusService linkStatusService,
|
||||
@Value("${app.base-url}") String appBaseUrl,
|
||||
@Value("${app.image-save-path:./images}") String imageSavePath,
|
||||
LinkTaskMapper linkTaskMapper,
|
||||
LinkBatchMapper linkBatchMapper) {
|
||||
this.scriptClient = scriptClient;
|
||||
this.linkStatusService = linkStatusService;
|
||||
this.appBaseUrl = appBaseUrl;
|
||||
this.imageSavePath = imageSavePath;
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
|
||||
// 确保图片保存目录存在
|
||||
try {
|
||||
Files.createDirectories(Paths.get(imageSavePath));
|
||||
log.info("图片保存目录已创建或已存在: {}", imageSavePath);
|
||||
} catch (IOException e) {
|
||||
log.error("创建图片保存目录失败: {}", imageSavePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "/image/{codeNo}/qr.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
@@ -102,18 +84,6 @@ public class QrProxyController {
|
||||
public Mono<ResponseEntity<byte[]>> homepage(@PathVariable("codeNo") String codeNo) {
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "homepage.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
@@ -122,21 +92,14 @@ public class QrProxyController {
|
||||
|
||||
String path = "/" + machineId + "/首次主页.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "homepage.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=homepage.png")
|
||||
.body(bytes))
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "homepage.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
log.warn("图片不存在: path={}", path);
|
||||
return createNotFoundResponseMono();
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
@@ -154,18 +117,6 @@ public class QrProxyController {
|
||||
public Mono<ResponseEntity<byte[]>> firstReward(@PathVariable("codeNo") String codeNo) {
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "firstReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
@@ -174,21 +125,14 @@ public class QrProxyController {
|
||||
|
||||
String path = "/" + machineId + "/首次赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "firstReward.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=first-reward.png")
|
||||
.body(bytes))
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "firstReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
log.warn("图片不存在: path={}", path);
|
||||
return createNotFoundResponseMono();
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
@@ -206,18 +150,6 @@ public class QrProxyController {
|
||||
public Mono<ResponseEntity<byte[]>> midReward(@PathVariable("codeNo") String codeNo) {
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "midReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
@@ -226,21 +158,14 @@ public class QrProxyController {
|
||||
|
||||
String path = "/" + machineId + "/中途赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "midReward.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=mid-reward.png")
|
||||
.body(bytes))
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "midReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
log.warn("图片不存在: path={}", path);
|
||||
return createNotFoundResponseMono();
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
@@ -258,18 +183,6 @@ public class QrProxyController {
|
||||
public Mono<ResponseEntity<byte[]>> endReward(@PathVariable("codeNo") String codeNo) {
|
||||
return linkStatusService.getLinkStatus(codeNo)
|
||||
.flatMap(linkStatus -> {
|
||||
// 如果状态是COMPLETED,直接读取本地文件
|
||||
if ("COMPLETED".equals(linkStatus.getStatus())) {
|
||||
log.info("链接已完成,尝试读取本地图片: codeNo={}", codeNo);
|
||||
return readLocalImageFile(codeNo, "endReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件不存在,返回404: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
}
|
||||
|
||||
// 状态不是COMPLETED,使用scriptClient获取图片
|
||||
String machineId = linkStatus.getMachineId();
|
||||
if (machineId == null) {
|
||||
log.warn("无法找到codeNo对应的machineId: {}", codeNo);
|
||||
@@ -278,21 +191,14 @@ public class QrProxyController {
|
||||
|
||||
String path = "/" + machineId + "/结束赏金.png";
|
||||
return scriptClient.getImagePng(path)
|
||||
.flatMap(bytes -> {
|
||||
// 异步保存图片到本地
|
||||
return saveImageToLocalAsync(codeNo, bytes, "endReward.png")
|
||||
.then(Mono.just(bytes));
|
||||
})
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.map(bytes -> ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS).cachePublic())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=end-reward.png")
|
||||
.body(bytes))
|
||||
.onErrorResume(WebClientResponseException.NotFound.class, ex -> {
|
||||
log.warn("scriptClient图片不存在,尝试读取本地文件: path={}", path);
|
||||
// 如果scriptClient返回404,尝试读取本地文件
|
||||
return readLocalImageFile(codeNo, "endReward.png")
|
||||
.flatMap(this::createImageResponseMono)
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
log.warn("本地图片文件也不存在: codeNo={}", codeNo);
|
||||
return createNotFoundResponseMono();
|
||||
}));
|
||||
log.warn("图片不存在: path={}", path);
|
||||
return createNotFoundResponseMono();
|
||||
})
|
||||
.onErrorResume(WebClientResponseException.class, ex -> {
|
||||
log.warn("获取图片失败: path={}, status={}, error={}", path, ex.getStatusCode(), ex.getMessage());
|
||||
@@ -392,96 +298,12 @@ public class QrProxyController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存图片到本地文件系统
|
||||
* @param codeNo 任务编号
|
||||
* @param imageData 图片数据
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
private void saveImageToLocal(String codeNo, byte[] imageData, String fileName) {
|
||||
if (imageData == null || imageData.length == 0) {
|
||||
log.debug("图片数据为空,跳过保存: codeNo={}, fileName={}", codeNo, fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建任务目录
|
||||
Path taskDirPath = Paths.get(imageSavePath, codeNo);
|
||||
Files.createDirectories(taskDirPath);
|
||||
|
||||
// 保存文件
|
||||
Path filePath = taskDirPath.resolve(fileName);
|
||||
Files.write(filePath, imageData, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||
|
||||
log.debug("图片保存成功: {} ({}字节)", filePath, imageData.length);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.warn("保存图片失败: codeNo={}, fileName={}, error={}", codeNo, fileName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步保存图片到本地
|
||||
* @param codeNo 任务编号
|
||||
* @param imageData 图片数据
|
||||
* @param fileName 文件名
|
||||
* @return Mono<Void>
|
||||
*/
|
||||
private Mono<Void> saveImageToLocalAsync(String codeNo, byte[] imageData, String fileName) {
|
||||
return Mono.fromRunnable(() -> saveImageToLocal(codeNo, imageData, fileName))
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取本地保存的图片文件
|
||||
* @param codeNo 任务编号
|
||||
* @param fileName 文件名
|
||||
* @return Mono<byte[]> 图片数据,如果文件不存在则返回empty
|
||||
*/
|
||||
private Mono<byte[]> readLocalImageFile(String codeNo, String fileName) {
|
||||
return Mono.fromCallable(() -> {
|
||||
Path filePath = Paths.get(imageSavePath, codeNo, fileName);
|
||||
if (Files.exists(filePath)) {
|
||||
byte[] imageBytes = Files.readAllBytes(filePath);
|
||||
log.info("读取本地图片成功: {} ({}字节)", filePath, imageBytes.length);
|
||||
return imageBytes;
|
||||
} else {
|
||||
log.debug("本地图片文件不存在: {}", filePath);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.filter(bytes -> bytes != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图片响应
|
||||
* @param bytes 图片字节数据
|
||||
* @return ResponseEntity<byte[]>
|
||||
*/
|
||||
private ResponseEntity<byte[]> createImageResponse(byte[] bytes) {
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图片响应的Mono包装
|
||||
* @param bytes 图片字节数据
|
||||
* @return Mono<ResponseEntity<byte[]>>
|
||||
*/
|
||||
private Mono<ResponseEntity<byte[]>> createImageResponseMono(byte[] bytes) {
|
||||
return Mono.just(createImageResponse(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建404响应的Mono包装
|
||||
* @return Mono<ResponseEntity<byte[]>>
|
||||
*/
|
||||
private Mono<ResponseEntity<byte[]>> createNotFoundResponseMono() {
|
||||
return createNotFoundResponseMono();
|
||||
return Mono.just(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,7 +311,7 @@ public class QrProxyController {
|
||||
* @return Mono<ResponseEntity<byte[]>>
|
||||
*/
|
||||
private Mono<ResponseEntity<byte[]>> createInternalServerErrorResponseMono() {
|
||||
return createInternalServerErrorResponseMono();
|
||||
return Mono.just(ResponseEntity.internalServerError().build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package com.gameplatform.server.service.image;
|
||||
|
||||
import com.gameplatform.server.service.external.ScriptClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.Duration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 图片下载保存服务
|
||||
* 负责从脚本服务器下载图片并保存到本地
|
||||
*/
|
||||
@Service
|
||||
public class ImageSaveService {
|
||||
private static final Logger log = LoggerFactory.getLogger(ImageSaveService.class);
|
||||
|
||||
private final ScriptClient scriptClient;
|
||||
private final String imageSavePath;
|
||||
private final String serverBaseUrl;
|
||||
|
||||
// 使用信号量限制并发下载数量,防止服务器资源不足
|
||||
private final Semaphore downloadSemaphore = new Semaphore(3);
|
||||
|
||||
|
||||
public ImageSaveService(
|
||||
ScriptClient scriptClient,
|
||||
@Value("${app.image-save-path:./images}") String imageSavePath,
|
||||
@Value("${app.base-url}") String serverBaseUrl) {
|
||||
this.scriptClient = scriptClient;
|
||||
this.imageSavePath = imageSavePath;
|
||||
this.serverBaseUrl = serverBaseUrl;
|
||||
|
||||
// 确保图片保存目录存在
|
||||
try {
|
||||
Files.createDirectories(Paths.get(imageSavePath));
|
||||
log.info("图片保存目录已创建或已存在: {}", imageSavePath);
|
||||
} catch (IOException e) {
|
||||
log.error("创建图片保存目录失败: {}", imageSavePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载并保存完成时的所有图片
|
||||
* @param deviceId 设备ID
|
||||
* @param codeNo 任务编号
|
||||
* @return 保存后的本地图片URL映射
|
||||
*/
|
||||
public Mono<Map<String, String>> downloadAndSaveCompletionImages(String deviceId, String codeNo) {
|
||||
log.info("开始为任务 {} 设备 {} 下载完成图片", codeNo, deviceId);
|
||||
|
||||
Map<String, String> imageTypes = Map.of(
|
||||
"homepage", "首次主页.png",
|
||||
"firstReward", "首次赏金.png",
|
||||
"midReward", "中途赏金.png",
|
||||
"endReward", "结束赏金.png"
|
||||
);
|
||||
|
||||
return downloadAndSaveImages(deviceId, codeNo, imageTypes)
|
||||
.doOnSuccess(result -> log.info("任务 {} 设备 {} 完成图片下载保存完成,共保存{}张图片",
|
||||
codeNo, deviceId, result.size()))
|
||||
.doOnError(error -> log.error("任务 {} 设备 {} 完成图片下载保存失败", codeNo, deviceId, error));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载并保存进行中的任务快照图片
|
||||
* @param deviceId 设备ID
|
||||
* @param codeNo 任务编号
|
||||
* @return 保存后的本地图片URL映射
|
||||
*/
|
||||
public Mono<Map<String, String>> downloadAndSaveProgressImages(String deviceId, String codeNo) {
|
||||
log.debug("开始为任务 {} 设备 {} 下载进度图片", codeNo, deviceId);
|
||||
|
||||
// 进行中只保存可能有的图片
|
||||
Map<String, String> imageTypes = Map.of(
|
||||
"homepage", "首次主页.png",
|
||||
"firstReward", "首次赏金.png",
|
||||
"midReward", "中途赏金.png"
|
||||
);
|
||||
|
||||
return downloadAndSaveImages(deviceId, codeNo, imageTypes)
|
||||
.doOnSuccess(result -> log.debug("任务 {} 设备 {} 进度图片下载保存完成,共保存{}张图片",
|
||||
codeNo, deviceId, result.size()))
|
||||
.doOnError(error -> log.warn("任务 {} 设备 {} 进度图片下载保存失败", codeNo, deviceId, error));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的图片下载保存方法
|
||||
*/
|
||||
private Mono<Map<String, String>> downloadAndSaveImages(String deviceId, String codeNo, Map<String, String> imageTypes) {
|
||||
Map<String, String> savedImageUrls = new HashMap<>();
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
// 使用codeNo作为目录名
|
||||
Path taskDirPath = Paths.get(imageSavePath, codeNo);
|
||||
Files.createDirectories(taskDirPath);
|
||||
return taskDirPath;
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(taskDirPath -> {
|
||||
// 并行下载所有图片,但使用信号量限制并发数
|
||||
return Flux.fromIterable(imageTypes.entrySet())
|
||||
.flatMap(entry -> {
|
||||
String imageType = entry.getKey();
|
||||
String imageName = entry.getValue();
|
||||
|
||||
return downloadSingleImageWithDelay(deviceId, imageName, taskDirPath, imageType)
|
||||
.doOnSuccess(url -> {
|
||||
if (url != null) {
|
||||
savedImageUrls.put(imageType, url);
|
||||
}
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
log.warn("下载图片 {} 失败: {}", imageName, error.getMessage());
|
||||
return Mono.empty();
|
||||
});
|
||||
}, 2) // 最多同时下载2张图片
|
||||
.then(Mono.just(savedImageUrls));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载单张图片(带延迟和重试机制)
|
||||
*/
|
||||
private Mono<String> downloadSingleImageWithDelay(String deviceId, String imageName, Path taskDirPath, String imageType) {
|
||||
// 随机延迟0-2秒,避免同时请求
|
||||
int delayMs = ThreadLocalRandom.current().nextInt(0, 2000);
|
||||
|
||||
return Mono.delay(Duration.ofMillis(delayMs))
|
||||
.then(Mono.fromCallable(() -> {
|
||||
// 获取信号量许可
|
||||
boolean acquired = downloadSemaphore.tryAcquire(10, TimeUnit.SECONDS);
|
||||
if (!acquired) {
|
||||
throw new RuntimeException("获取下载许可超时");
|
||||
}
|
||||
return true;
|
||||
}))
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(acquired -> {
|
||||
String imagePath = String.format("/%s/%s", deviceId, imageName);
|
||||
|
||||
return scriptClient.getImagePng(imagePath)
|
||||
.timeout(Duration.ofSeconds(15))
|
||||
.retry(2) // 重试2次
|
||||
.flatMap(imageData -> saveImageToLocal(imageData, taskDirPath, imageName, imageType))
|
||||
.doFinally(signal -> downloadSemaphore.release()); // 释放信号量
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片数据保存到本地文件
|
||||
*/
|
||||
private Mono<String> saveImageToLocal(byte[] imageData, Path taskDirPath, String originalName, String imageType) {
|
||||
return Mono.fromCallable(() -> {
|
||||
if (imageData == null || imageData.length == 0) {
|
||||
log.warn("图片数据为空: {}", originalName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 直接使用图片类型作为文件名,覆盖同名文件
|
||||
String extension = getFileExtension(originalName);
|
||||
String localFileName = imageType + extension;
|
||||
Path localFilePath = taskDirPath.resolve(localFileName);
|
||||
|
||||
// 保存文件
|
||||
Files.write(localFilePath, imageData, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||
|
||||
// 生成访问URL
|
||||
String relativePath = taskDirPath.getFileName() + "/" + localFileName;
|
||||
String imageUrl = serverBaseUrl + "/api/images/" + relativePath;
|
||||
|
||||
log.debug("图片保存成功: {} -> {} ({}字节)", originalName, localFilePath, imageData.length);
|
||||
return imageUrl;
|
||||
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
private String getFileExtension(String fileName) {
|
||||
int lastDotIndex = fileName.lastIndexOf('.');
|
||||
return lastDotIndex > 0 ? fileName.substring(lastDotIndex) : ".png";
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.gameplatform.server.service.link;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import com.gameplatform.server.event.DeviceStatusUpdatedEvent;
|
||||
import com.gameplatform.server.service.image.ImageSaveService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -16,7 +14,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备任务更新服务
|
||||
@@ -28,15 +25,11 @@ public class DeviceTaskUpdateService {
|
||||
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final ImageSaveService imageSaveService;
|
||||
|
||||
public DeviceTaskUpdateService(LinkTaskMapper linkTaskMapper,
|
||||
ObjectMapper objectMapper,
|
||||
ImageSaveService imageSaveService) {
|
||||
ObjectMapper objectMapper) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.objectMapper = objectMapper;
|
||||
this.imageSaveService = imageSaveService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,59 +77,19 @@ public class DeviceTaskUpdateService {
|
||||
|
||||
for (LinkTask task : tasks) {
|
||||
try {
|
||||
// 异步下载并保存完成图片
|
||||
imageSaveService.downloadAndSaveCompletionImages(deviceId, task.getCodeNo())
|
||||
.subscribe(
|
||||
savedImages -> {
|
||||
// 保存成功后更新任务
|
||||
updateTaskWithCompletionImages(task, savedImages);
|
||||
},
|
||||
error -> {
|
||||
// 即使图片保存失败,也要标记任务为完成,但不保存图片信息
|
||||
log.error("保存完成图片失败,任务 {} 仍将标记为完成", task.getId(), error);
|
||||
updateTaskWithoutImages(task);
|
||||
}
|
||||
);
|
||||
// 直接更新任务状态为完成
|
||||
updateTaskAsCompleted(task);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理已完成任务 {} 时发生异常", task.getId(), e);
|
||||
// 出现异常也要尝试标记为完成
|
||||
updateTaskWithoutImages(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务状态并保存图片信息
|
||||
* 更新任务状态为完成
|
||||
*/
|
||||
private void updateTaskWithCompletionImages(LinkTask task, Map<String, String> savedImages) {
|
||||
try {
|
||||
task.setStatus("COMPLETED");
|
||||
task.setCompletionImages(convertToJson(savedImages));
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
// 如果之前有点数,保持不变;如果没有,设为0
|
||||
if (task.getCompletedPoints() == null) {
|
||||
task.setCompletedPoints(0);
|
||||
}
|
||||
|
||||
int updated = linkTaskMapper.update(task);
|
||||
if (updated > 0) {
|
||||
log.info("链接任务 {} (代码: {}) 已标记为完成,完成点数: {}, 成功保存了{}张完成图片",
|
||||
task.getId(), task.getCodeNo(), task.getCompletedPoints(), savedImages.size());
|
||||
} else {
|
||||
log.warn("更新链接任务 {} 失败", task.getId());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("更新链接任务 {} 时发生异常", task.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务状态(无图片信息)
|
||||
*/
|
||||
private void updateTaskWithoutImages(LinkTask task) {
|
||||
private void updateTaskAsCompleted(LinkTask task) {
|
||||
try {
|
||||
task.setStatus("COMPLETED");
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
@@ -148,7 +101,7 @@ public class DeviceTaskUpdateService {
|
||||
|
||||
int updated = linkTaskMapper.update(task);
|
||||
if (updated > 0) {
|
||||
log.info("链接任务 {} (代码: {}) 已标记为完成,完成点数: {} (图片保存失败)",
|
||||
log.info("链接任务 {} (代码: {}) 已标记为完成,完成点数: {}",
|
||||
task.getId(), task.getCodeNo(), task.getCompletedPoints());
|
||||
} else {
|
||||
log.warn("更新链接任务 {} 失败", task.getId());
|
||||
@@ -169,24 +122,11 @@ public class DeviceTaskUpdateService {
|
||||
|
||||
for (LinkTask task : tasks) {
|
||||
try {
|
||||
// 异步下载并保存完成图片
|
||||
imageSaveService.downloadAndSaveCompletionImages(deviceId, task.getCodeNo())
|
||||
.subscribe(
|
||||
savedImages -> {
|
||||
// 保存成功后更新任务
|
||||
updateTaskWithCompletionImages(task, savedImages);
|
||||
},
|
||||
error -> {
|
||||
// 即使图片保存失败,也要标记任务为完成
|
||||
log.error("保存完成图片失败,任务 {} 仍将标记为完成", task.getId(), error);
|
||||
updateTaskWithoutImages(task);
|
||||
}
|
||||
);
|
||||
// 直接更新任务状态为完成
|
||||
updateTaskAsCompleted(task);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理空闲状态任务 {} 时发生异常", task.getId(), e);
|
||||
// 出现异常也要尝试标记为完成
|
||||
updateTaskWithoutImages(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,54 +155,6 @@ public class DeviceTaskUpdateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定期保存进行中任务的图片快照
|
||||
* @param deviceId 设备ID
|
||||
* @param codeNo 任务编号
|
||||
*/
|
||||
public void saveProgressImagesForTask(String deviceId, String codeNo) {
|
||||
log.debug("开始为任务 {} 设备 {} 保存进度图片", codeNo, deviceId);
|
||||
|
||||
imageSaveService.downloadAndSaveProgressImages(deviceId, codeNo)
|
||||
.subscribe(
|
||||
savedImages -> {
|
||||
if (!savedImages.isEmpty()) {
|
||||
log.info("任务 {} 设备 {} 进度图片保存成功,共保存{}张图片",
|
||||
codeNo, deviceId, savedImages.size());
|
||||
// 可选:更新任务记录中的进度图片信息
|
||||
updateTaskProgressImagesByCodeNo(codeNo, savedImages);
|
||||
}
|
||||
},
|
||||
error -> log.warn("任务 {} 设备 {} 进度图片保存失败: {}",
|
||||
codeNo, deviceId, error.getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务的进度图片信息(可选)
|
||||
*/
|
||||
private void updateTaskProgressImagesByCodeNo(String codeNo, Map<String, String> savedImages) {
|
||||
try {
|
||||
// 这里可以选择是否要将进度图片也保存到数据库中
|
||||
// 目前只记录日志,不修改数据库记录
|
||||
log.debug("任务 {} 进度图片已保存到本地: {}", codeNo, savedImages.keySet());
|
||||
} catch (Exception e) {
|
||||
log.warn("更新任务 {} 进度图片记录失败", codeNo, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片URL映射转换为JSON字符串
|
||||
*/
|
||||
private String convertToJson(Map<String, String> images) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(images);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("转换完成图片URL为JSON失败", e);
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量处理设备状态更新
|
||||
* @param deviceStatus 设备状态响应
|
||||
|
||||
@@ -669,15 +669,15 @@ public class LinkStatusService {
|
||||
}
|
||||
|
||||
// 7. 调用保存总次数接口
|
||||
log.info("步骤7: 开始保存总次数到脚本端");
|
||||
try {
|
||||
log.info("保存总次数: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
||||
scriptClient.saveTotalTimes(selectedDeviceId, linkBatch.getTimes()).block();
|
||||
log.info("总次数保存成功: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
||||
} catch (Exception e) {
|
||||
log.warn("总次数保存失败: 设备={}, 次数={}, 错误={}, 继续后续流程", selectedDeviceId, linkBatch.getTimes(), e.getMessage());
|
||||
// 不影响后续流程,只记录警告日志
|
||||
}
|
||||
// log.info("步骤7: 开始保存总次数到脚本端");
|
||||
// try {
|
||||
// log.info("保存总次数: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
||||
// scriptClient.saveTotalTimes(selectedDeviceId, linkBatch.getTimes()).block();
|
||||
// log.info("总次数保存成功: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
||||
// } catch (Exception e) {
|
||||
// log.warn("总次数保存失败: 设备={}, 次数={}, 错误={}, 继续后续流程", selectedDeviceId, linkBatch.getTimes(), e.getMessage());
|
||||
// // 不影响后续流程,只记录警告日志
|
||||
// }
|
||||
|
||||
// 8. 调用脚本端选区,使用选中的设备
|
||||
log.info("步骤8: 开始调用脚本端选区");
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package com.gameplatform.server.task;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import com.gameplatform.server.service.link.DeviceTaskUpdateService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 图片保存定时任务
|
||||
* 每3分钟为进行中的任务保存图片快照
|
||||
*/
|
||||
@Component
|
||||
public class ImageSaveScheduleTask {
|
||||
private static final Logger log = LoggerFactory.getLogger(ImageSaveScheduleTask.class);
|
||||
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final DeviceTaskUpdateService deviceTaskUpdateService;
|
||||
|
||||
|
||||
// 使用单独的线程池执行图片保存任务,避免阻塞主线程
|
||||
private final Executor imageTaskExecutor = Executors.newFixedThreadPool(2);
|
||||
|
||||
public ImageSaveScheduleTask(
|
||||
LinkTaskMapper linkTaskMapper,
|
||||
DeviceTaskUpdateService deviceTaskUpdateService) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.deviceTaskUpdateService = deviceTaskUpdateService;
|
||||
|
||||
log.info("图片保存定时任务已初始化");
|
||||
}
|
||||
|
||||
/**
|
||||
* 每3分钟执行一次的图片保存任务
|
||||
* 使用fixedDelay确保上一次执行完成后再开始下一次
|
||||
*/
|
||||
@Scheduled(fixedDelayString = "${image.save-interval-minutes:3}", timeUnit = TimeUnit.MINUTES)
|
||||
public void saveProgressImages() {
|
||||
try {
|
||||
log.debug("开始执行进度图片保存定时任务");
|
||||
|
||||
// 查找所有LOGGED_IN状态的任务
|
||||
QueryWrapper<LinkTask> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("status", "LOGGED_IN");
|
||||
List<LinkTask> activeTasks = linkTaskMapper.selectList(queryWrapper);
|
||||
|
||||
if (activeTasks.isEmpty()) {
|
||||
log.debug("当前没有进行中的任务,跳过图片保存");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("发现 {} 个进行中的任务,开始保存进度图片", activeTasks.size());
|
||||
|
||||
// 分批处理任务,避免同时处理过多任务
|
||||
processBatchTasks(activeTasks);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("执行进度图片保存定时任务时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分批处理任务列表
|
||||
*/
|
||||
private void processBatchTasks(List<LinkTask> tasks) {
|
||||
final int batchSize = 5; // 每批处理5个任务
|
||||
|
||||
for (int i = 0; i < tasks.size(); i += batchSize) {
|
||||
final int startIndex = i;
|
||||
final int endIndex = Math.min(i + batchSize, tasks.size());
|
||||
final List<LinkTask> batch = tasks.subList(startIndex, endIndex);
|
||||
|
||||
// 异步处理每一批任务
|
||||
imageTaskExecutor.execute(() -> processSingleBatch(batch, startIndex / batchSize + 1));
|
||||
|
||||
// 批次之间稍微延迟,避免过度并发
|
||||
if (endIndex < tasks.size()) {
|
||||
try {
|
||||
Thread.sleep(1000); // 延迟1秒
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("批次间延迟被中断");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个批次的任务
|
||||
*/
|
||||
private void processSingleBatch(List<LinkTask> batch, int batchNumber) {
|
||||
log.debug("开始处理第 {} 批任务,共 {} 个任务", batchNumber, batch.size());
|
||||
|
||||
for (LinkTask task : batch) {
|
||||
try {
|
||||
// 为每个任务随机延迟0-10秒,避免同时请求
|
||||
int delaySeconds = ThreadLocalRandom.current().nextInt(0, 10);
|
||||
Thread.sleep(delaySeconds * 1000L);
|
||||
|
||||
// 保存任务的进度图片
|
||||
deviceTaskUpdateService.saveProgressImagesForTask(task.getMachineId(), task.getCodeNo());
|
||||
|
||||
log.debug("任务 {} (设备: {}) 进度图片保存请求已提交", task.getId(), task.getMachineId());
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("任务 {} 处理被中断", task.getId());
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.error("处理任务 {} 进度图片保存时发生异常", task.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("第 {} 批任务处理完成", batchNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发保存所有进行中任务的图片(用于调试或紧急情况)
|
||||
*/
|
||||
public void saveAllProgressImages() {
|
||||
log.info("手动触发进度图片保存");
|
||||
saveProgressImages();
|
||||
}
|
||||
|
||||
/**
|
||||
* 为特定设备的任务保存进度图片
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
public void saveProgressImagesForDevice(String deviceId) {
|
||||
try {
|
||||
List<LinkTask> deviceTasks = linkTaskMapper.findByMachineIdAndStatus(deviceId, "LOGGED_IN");
|
||||
|
||||
if (deviceTasks.isEmpty()) {
|
||||
log.debug("设备 {} 没有进行中的任务", deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("为设备 {} 的 {} 个任务保存进度图片", deviceId, deviceTasks.size());
|
||||
|
||||
imageTaskExecutor.execute(() -> {
|
||||
for (LinkTask task : deviceTasks) {
|
||||
try {
|
||||
deviceTaskUpdateService.saveProgressImagesForTask(deviceId, task.getCodeNo());
|
||||
Thread.sleep(2000); // 每个任务间延迟2秒
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.error("为设备 {} 任务 {} 保存进度图片时发生异常", deviceId, task.getId(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("为设备 {} 保存进度图片时发生异常", deviceId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,13 +72,6 @@ script:
|
||||
app:
|
||||
base-url: "https://2.uzi0.cc" # 生产环境需要配置为实际域名
|
||||
# base-url: "http://localhost:18080" # 本地测试环境
|
||||
image-save-path: "./images" # 图片保存路径
|
||||
|
||||
link:
|
||||
expire-hours: 2
|
||||
|
||||
# 图片保存配置
|
||||
image:
|
||||
save-interval-minutes: 3 # 进行中任务图片保存间隔(分钟)
|
||||
max-concurrent-downloads: 3 # 最大并发下载数
|
||||
download-timeout-seconds: 15 # 下载超时时间(秒)
|
||||
|
||||
@@ -4,18 +4,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import com.gameplatform.server.service.image.ImageSaveService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -31,16 +27,13 @@ public class DeviceTaskUpdateServiceTest {
|
||||
@Mock
|
||||
private LinkTaskMapper linkTaskMapper;
|
||||
|
||||
@Mock
|
||||
private ImageSaveService imageSaveService;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
objectMapper = new ObjectMapper();
|
||||
deviceTaskUpdateService = new DeviceTaskUpdateService(linkTaskMapper, objectMapper, imageSaveService);
|
||||
deviceTaskUpdateService = new DeviceTaskUpdateService(linkTaskMapper, objectMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,13 +78,6 @@ public class DeviceTaskUpdateServiceTest {
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(tasks);
|
||||
when(linkTaskMapper.update(any(LinkTask.class))).thenReturn(1);
|
||||
Map<String, String> mockImages = new HashMap<>();
|
||||
mockImages.put("homepage", "首次主页.png");
|
||||
mockImages.put("firstReward", "首次赏金.png");
|
||||
mockImages.put("midReward", "中途赏金.png");
|
||||
mockImages.put("endReward", "结束赏金.png");
|
||||
when(imageSaveService.downloadAndSaveCompletionImages(anyString(), anyString()))
|
||||
.thenReturn(Mono.just(mockImages));
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
@@ -103,11 +89,6 @@ public class DeviceTaskUpdateServiceTest {
|
||||
// 验证任务状态已更新为COMPLETED
|
||||
assertEquals("COMPLETED", task.getStatus());
|
||||
assertEquals(Integer.valueOf(0), task.getCompletedPoints());
|
||||
assertNotNull(task.getCompletionImages());
|
||||
assertTrue(task.getCompletionImages().contains("首次主页.png"));
|
||||
assertTrue(task.getCompletionImages().contains("首次赏金.png"));
|
||||
assertTrue(task.getCompletionImages().contains("中途赏金.png"));
|
||||
assertTrue(task.getCompletionImages().contains("结束赏金.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,13 +105,6 @@ public class DeviceTaskUpdateServiceTest {
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(tasks);
|
||||
when(linkTaskMapper.update(any(LinkTask.class))).thenReturn(1);
|
||||
Map<String, String> mockImages = new HashMap<>();
|
||||
mockImages.put("homepage", "首次主页.png");
|
||||
mockImages.put("firstReward", "首次赏金.png");
|
||||
mockImages.put("midReward", "中途赏金.png");
|
||||
mockImages.put("endReward", "结束赏金.png");
|
||||
when(imageSaveService.downloadAndSaveCompletionImages(anyString(), anyString()))
|
||||
.thenReturn(Mono.just(mockImages));
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
@@ -142,7 +116,6 @@ public class DeviceTaskUpdateServiceTest {
|
||||
// 验证任务状态已更新为COMPLETED,点数保持不变
|
||||
assertEquals("COMPLETED", task.getStatus());
|
||||
assertEquals(Integer.valueOf(2350), task.getCompletedPoints());
|
||||
assertNotNull(task.getCompletionImages());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user