feat(image): 添加桌面镜像制作进度检查和自动处理功能
- 实现了定时任务检查桌面镜像制作进度的功能 - 在制作完成后自动更新镜像状态和信息 - 添加了文件大小获取和删除的接口 - 优化了终端开机和镜像列表获取的逻辑master
parent
32f6eae269
commit
4a034171bf
|
|
@ -18,6 +18,7 @@ package com.unisinsight.project.feign;
|
|||
import com.unisinsight.project.config.FeignConfig;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
|
|
@ -44,8 +45,15 @@ public interface ExternalTorrentClient {
|
|||
boolean startBt(@RequestParam("sourceFile") String sourceFile,
|
||||
@RequestParam("torrentFile") String torrentFile);
|
||||
|
||||
@GetMapping("/start")
|
||||
@GetMapping("/vdi/device/start")
|
||||
@ApiOperation(value = "终端开机")
|
||||
boolean deviceStart(@RequestParam("macAddr") String macAddr);
|
||||
|
||||
@GetMapping("/vdi/file/size")
|
||||
@ApiOperation(value = "获取物理机文件大小")
|
||||
Long getFileSize(@RequestParam("filePath") String filePath);
|
||||
@DeleteMapping("/file")
|
||||
@ApiOperation(value = "删除物理机文件")
|
||||
boolean deleteFile(@RequestParam("filePath") String filePath);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class ClientOperateServiceImpl implements ClientOperateService {
|
|||
private ExternalTorrentClient externalTorrentClient;
|
||||
@Override
|
||||
public Result<?> terminalStart(DeviceReq deviceReq) {
|
||||
if (StringUtil.isNotEmpty(deviceReq.getMacAddr())) {
|
||||
if (StringUtil.isEmpty(deviceReq.getMacAddr())) {
|
||||
return Result.errorResultMessage(BaseErrorCode.PARAMS_CHK_ERROR, "请输入正确的终端MAC地址");
|
||||
}
|
||||
boolean start = externalTorrentClient.deviceStart(deviceReq.getMacAddr());
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class ClientServiceImpl implements ClientService {
|
|||
|
||||
@Override
|
||||
public Result<?> getImageList(String deviceId, String token) {
|
||||
// todo 改为桌面镜像 需验证
|
||||
// 改为桌面镜像
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
List<DeviceImageMapping> deviceImageMappings = deviceImageMappingMapper.selectList(new LambdaQueryWrapper<DeviceImageMapping>().eq(DeviceImageMapping::getDeviceId, deviceId));
|
||||
if (CollectionUtil.isEmpty(deviceImageMappings)) {
|
||||
|
|
@ -58,12 +58,19 @@ public class ClientServiceImpl implements ClientService {
|
|||
if (CollectionUtil.isNotEmpty(imageIdList)) {
|
||||
List<ImageDesktop> images = imageDesktopMapper.selectList(new LambdaQueryWrapper<ImageDesktop>().in(ImageDesktop::getId, imageIdList));
|
||||
log.info("用户登录查询镜像结果:{}", JSONUtil.toJsonStr(images));
|
||||
List<ImageDesktopRes> imageRes = BeanUtil.copyToList(images, ImageDesktopRes.class);
|
||||
List<HashMap<String, Object>> collect = imageRes.stream().distinct().map(e -> {
|
||||
|
||||
List<HashMap<String, Object>> collect = images.stream().distinct().map(e -> {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("id",e.getId());
|
||||
if (StringUtils.isNotBlank(e.getDesktopName())) {
|
||||
map.put("name", e.getDesktopName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(e.getFileSize())) {
|
||||
map.put("file_size", e.getFileSize());
|
||||
}
|
||||
if (StringUtils.isNotBlank(e.getStoragePath())) {
|
||||
map.put("storage_path", e.getStoragePath());
|
||||
}
|
||||
if (StringUtils.isNotBlank(e.getBtPath())) {
|
||||
if (e.getBtPath().contains("http://") || e.getBtPath().contains("https://")) {
|
||||
map.put("torrent", e.getBtPath());
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.unisinsight.project.entity.dao.DeviceImageMapping;
|
||||
import com.unisinsight.project.entity.dao.Image;
|
||||
import com.unisinsight.project.entity.dao.ImageDesktop;
|
||||
import com.unisinsight.project.entity.dao.UserDeviceGroup;
|
||||
import com.unisinsight.project.entity.req.DeviceImageMappingReq;
|
||||
import com.unisinsight.project.entity.res.DeviceImageMappingRes;
|
||||
import com.unisinsight.project.exception.Result;
|
||||
import com.unisinsight.project.mapper.DeviceImageMappingMapper;
|
||||
import com.unisinsight.project.mapper.ImageDesktopMapper;
|
||||
import com.unisinsight.project.mapper.ImageMapper;
|
||||
import com.unisinsight.project.mapper.UserDeviceGroupMapper;
|
||||
import com.unisinsight.project.service.DeviceImageMappingService;
|
||||
|
|
@ -39,7 +41,7 @@ public class DeviceImageMappingServiceImpl extends ServiceImpl<DeviceImageMappin
|
|||
private DeviceImageMappingMapper deviceImageMappingMapper;
|
||||
|
||||
@Resource
|
||||
private ImageMapper imageMapper;
|
||||
private ImageDesktopMapper imageDesktopMapper;
|
||||
@Resource
|
||||
private UserDeviceGroupMapper groupMapper;
|
||||
|
||||
|
|
@ -60,13 +62,13 @@ public class DeviceImageMappingServiceImpl extends ServiceImpl<DeviceImageMappin
|
|||
List<DeviceImageMappingRes> deviceImageMappingRes = BeanUtil.copyToList(deviceUserMappings, DeviceImageMappingRes.class);
|
||||
List<Long> imageIds = deviceImageMappingRes.stream().map(DeviceImageMappingRes::getImageId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(imageIds)) {
|
||||
LambdaQueryWrapper<Image> imageLambdaQueryWrapper = new LambdaQueryWrapper<Image>().in(Image::getId, imageIds);
|
||||
List<Image> images = imageMapper.selectList(imageLambdaQueryWrapper);
|
||||
LambdaQueryWrapper<ImageDesktop> imageLambdaQueryWrapper = new LambdaQueryWrapper<ImageDesktop>().in(ImageDesktop::getId, imageIds);
|
||||
List<ImageDesktop> images = imageDesktopMapper.selectList(imageLambdaQueryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(images)) {
|
||||
deviceImageMappingRes.forEach(deviceImage -> images.forEach(image -> {
|
||||
if (ObjectUtil.isNotEmpty(deviceImage.getImageId()) && image.getId().equals(deviceImage.getImageId())) {
|
||||
deviceImage.setImageName(image.getImageName());
|
||||
deviceImage.setImageFileName(image.getImageFileName());
|
||||
deviceImage.setImageName(image.getDesktopName());
|
||||
deviceImage.setImageFileName(image.getDesktopName()+"."+image.getDesktopType());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
@ -77,7 +79,7 @@ public class DeviceImageMappingServiceImpl extends ServiceImpl<DeviceImageMappin
|
|||
List<UserDeviceGroup> userDeviceGroups = groupMapper.selectList(groupLambdaQueryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(userDeviceGroups)) {
|
||||
deviceImageMappingRes.forEach(deviceImage -> userDeviceGroups.forEach(userDeviceGroup -> {
|
||||
if (ObjectUtil.isNotEmpty(deviceImage.getImageId()) && userDeviceGroup.getId().equals(deviceImage.getImageId())) {
|
||||
if (ObjectUtil.isNotEmpty(deviceImage.getDeviceGroupId()) && userDeviceGroup.getId().equals(deviceImage.getDeviceGroupId())) {
|
||||
deviceImage.setDeviceGroupName(userDeviceGroup.getName());
|
||||
}
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.unisinsight.project.service.impl;
|
|||
import cn.hutool.core.lang.func.Func;
|
||||
import com.unisinsight.project.entity.dao.ImageVirtualMachines;
|
||||
import com.unisinsight.project.entity.res.ImageVirtualMachinesRes;
|
||||
import com.unisinsight.project.feign.ExternalTorrentClient;
|
||||
import com.unisinsight.project.mapper.ImageVirtualMachinesMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
|
|
@ -47,10 +48,13 @@ public class ImageDesktopServiceImpl extends ServiceImpl<ImageDesktopMapper, Ima
|
|||
@Resource
|
||||
private ImageVirtualMachinesMapper imageVirtualMachinesMapper;
|
||||
|
||||
@Resource
|
||||
private ExternalTorrentClient externalTorrentClient;
|
||||
@Override
|
||||
public Result<PageResult<ImageDesktopRes>> selectPage(ImageDesktopReq imageDesktopReq) {
|
||||
Page<ImageDesktop> page = new Page<>(imageDesktopReq.getPageNum(), imageDesktopReq.getPageSize());
|
||||
LambdaQueryWrapper<ImageDesktop> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.isNotBlank(imageDesktopReq.getDesktopName()), ImageDesktop::getDesktopName, imageDesktopReq.getDesktopName());
|
||||
queryWrapper.orderByAsc(ImageDesktop::getId);
|
||||
Page<ImageDesktop> imageDesktopPage = mapper.selectPage(page, queryWrapper);
|
||||
log.info("分页查询桌面镜像返回:{}", JSONUtil.toJsonStr(imageDesktopPage));
|
||||
|
|
@ -123,8 +127,13 @@ public class ImageDesktopServiceImpl extends ServiceImpl<ImageDesktopMapper, Ima
|
|||
|
||||
@Override
|
||||
public Result<?> delete(DeleteIdReq deleteIdReq) {
|
||||
ImageDesktop imageDesktop = mapper.selectById(deleteIdReq.getId());
|
||||
boolean b = externalTorrentClient.deleteFile(imageDesktop.getStoragePath());
|
||||
if (!b) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "删除文件失败");
|
||||
}
|
||||
int deleted = mapper.deleteById(deleteIdReq.getId());
|
||||
log.info("桌面镜像删除insert:{}", deleted);
|
||||
log.info("桌面镜像删除:{}", deleted);
|
||||
if (deleted == 1) {
|
||||
return Result.successResult();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package com.unisinsight.project.service.impl;
|
|||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.unisinsight.project.entity.dao.*;
|
||||
|
|
@ -25,10 +27,15 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -53,8 +60,21 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
private ImageToolService imageToolService;
|
||||
@Autowired
|
||||
private ImageDesktopService imageDesktopService;
|
||||
|
||||
|
||||
@Value("${external.torrent.url:http://localhost:8114}")
|
||||
private String torrentUrl;
|
||||
// 定时任务调度器
|
||||
@Autowired
|
||||
private StoragePoolService storagePoolService;
|
||||
private TaskScheduler taskScheduler;
|
||||
@Value("${external.torrent.timer:10}")
|
||||
private Integer timer;
|
||||
|
||||
// 存储定时任务的Map,key为任务标识,value为ScheduledFuture
|
||||
private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
|
||||
|
||||
// 存储任务开始时间的Map,用于超时检查
|
||||
private final Map<String, LocalDateTime> taskStartTimes = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -282,8 +302,10 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
|
||||
// 根据虚拟机信息调用远程虚拟机信息
|
||||
String fileName = imageVirtualMachines.getOsVersion() + "_desktop_" + System.currentTimeMillis();
|
||||
String detFile = "/vms/desktop/" + type + "/" + fileName + "." + type;
|
||||
|
||||
boolean response = externalTorrentClient.start(diskPath,
|
||||
"/vms/desktop/" + type + "/" + fileName + "." + type,
|
||||
detFile,
|
||||
imageVirtualMachines.getImageName() + ".json", type);
|
||||
|
||||
if (!response) {
|
||||
|
|
@ -293,12 +315,15 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
// 克隆成功后生成桌面镜像数据
|
||||
ImageDesktop imageDesktop = new ImageDesktop();
|
||||
imageDesktop.setDesktopName(fileName);
|
||||
imageDesktop.setDesktopType(type);
|
||||
//制作中
|
||||
imageDesktop.setPublishStatus("0");
|
||||
imageDesktop.setStoragePath(detFile);
|
||||
imageDesktop.setImageVirtualId(Math.toIntExact(req.getId()));
|
||||
imageDesktop.setOsVersion(imageVirtualMachines.getOsVersion());
|
||||
imageDesktop.setCreateUser("admin");
|
||||
imageDesktop.setUpdateUser("admin");
|
||||
imageDesktop.setDescription("从虚拟机克隆的桌面镜像");
|
||||
|
||||
// 保存桌面镜像数据
|
||||
boolean saved = imageDesktopService.save(imageDesktop);
|
||||
if (!saved) {
|
||||
|
|
@ -306,9 +331,123 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "保存桌面镜像数据失败");
|
||||
}
|
||||
|
||||
// 启动定时任务来检查进度
|
||||
startProgressCheckTask(imageVirtualMachines.getImageName() + ".json", detFile, fileName);
|
||||
|
||||
return Result.successResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动进度检查任务
|
||||
*
|
||||
* @param imageName 虚拟机名称
|
||||
* @param fileName 文件名称
|
||||
* @param name
|
||||
*/
|
||||
private void startProgressCheckTask(String imageName, String detFilePath, String fileName) {
|
||||
// 创建任务标识
|
||||
String taskKey = imageName + "_" + fileName;
|
||||
ScheduledFuture<?> existsScheduledFuture = scheduledTasks.get(taskKey);
|
||||
if (existsScheduledFuture != null) {
|
||||
// 中断正在执行的任务
|
||||
existsScheduledFuture.cancel(true);
|
||||
scheduledTasks.remove(taskKey);
|
||||
taskStartTimes.remove(taskKey);
|
||||
}
|
||||
// 记录任务开始时间
|
||||
taskStartTimes.put(taskKey, LocalDateTime.now());
|
||||
|
||||
// 启动定时任务,每30秒检查一次进度
|
||||
ScheduledFuture<?> scheduledFuture = taskScheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
checkProgressAndHandle(imageName, detFilePath, fileName, taskKey);
|
||||
} catch (Exception e) {
|
||||
log.error("检查进度任务执行异常: ", e);
|
||||
}
|
||||
}, timer * 60 * 1000);
|
||||
|
||||
// 将任务存入Map
|
||||
scheduledTasks.put(taskKey, scheduledFuture);
|
||||
log.info("已启动进度检查任务: {}", taskKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查进度并处理结果
|
||||
*
|
||||
* @param imageName 虚拟机名称
|
||||
* @param fileName 文件名称
|
||||
* @param taskKey 任务标识
|
||||
*/
|
||||
private void checkProgressAndHandle(String imageName, String detFilePath, String fileName, String taskKey) {
|
||||
// 检查是否超时(12小时)
|
||||
LocalDateTime startTime = taskStartTimes.get(taskKey);
|
||||
if (startTime != null && LocalDateTime.now().isAfter(startTime.plusHours(12))) {
|
||||
log.warn("任务{}已超时12小时,自动移除", taskKey);
|
||||
cancelAndRemoveTask(taskKey);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取进度
|
||||
Double progress = externalTorrentClient.progress(imageName);
|
||||
log.info("任务{}当前进度: {}", taskKey, progress);
|
||||
|
||||
// 如果进度达到100%或以上,执行特有操作
|
||||
if (progress != null && progress >= 100.0) {
|
||||
log.info("任务{}已完成,执行特有操作", taskKey);
|
||||
// 执行你的特有操作,防止执行出错 清除任务失败
|
||||
try {
|
||||
handleTaskCompletion(imageName, detFilePath, fileName);
|
||||
} finally {
|
||||
// 移除任务
|
||||
cancelAndRemoveTask(taskKey);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取进度失败: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理任务完成后的特有操作
|
||||
*
|
||||
* @param imageName 虚拟机名称
|
||||
* @param fileName 文件名称
|
||||
*/
|
||||
private void handleTaskCompletion(String imageName, String detFilePath, String fileName) {
|
||||
// 在这里添加你的特有操作
|
||||
log.info("执行任务完成后的特有操作,虚拟机名称: {}, det文件路径: {}, 文件名称: {}", imageName, detFilePath, fileName);
|
||||
LambdaUpdateWrapper<ImageDesktop> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(ImageDesktop::getDesktopName, fileName);
|
||||
Long fileSize = externalTorrentClient.getFileSize(detFilePath);
|
||||
log.info("det文件大小: {} 字节", fileSize);
|
||||
updateWrapper.set(ImageDesktop::getPublishStatus, "1");
|
||||
updateWrapper.set(ImageDesktop::getFileSize, fileSize);
|
||||
updateWrapper.set(ImageDesktop::getPublishTime, DateUtil.date());
|
||||
//转化bt
|
||||
String fileNamewithSuffixes = detFilePath.substring(detFilePath.lastIndexOf(fileName));
|
||||
externalTorrentClient.startBt(detFilePath, "/var/lib/vdi/test/" + fileNamewithSuffixes + ".torrent");
|
||||
updateWrapper.set(ImageDesktop::getBtPath, torrentUrl + "/api/vdi/file/down/" + fileNamewithSuffixes + ".torrent");
|
||||
imageDesktopService.update(updateWrapper);
|
||||
System.out.println("任务已完成,执行特有操作...");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消并移除任务
|
||||
*
|
||||
* @param taskKey 任务标识
|
||||
*/
|
||||
private void cancelAndRemoveTask(String taskKey) {
|
||||
ScheduledFuture<?> scheduledFuture = scheduledTasks.get(taskKey);
|
||||
if (scheduledFuture != null) {
|
||||
scheduledFuture.cancel(false);
|
||||
scheduledTasks.remove(taskKey);
|
||||
}
|
||||
taskStartTimes.remove(taskKey);
|
||||
log.info("已取消并移除任务: {}", taskKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> start(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ import com.unisinsight.torrent.util.ImageVirtualUtils;
|
|||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -71,4 +68,28 @@ public class DeskImageController {
|
|||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/file/size")
|
||||
@ApiOperation(value = "获取物理机文件大小")
|
||||
Long getFileSize(@RequestParam("filePath") String filePath) {
|
||||
// 根据det文件路径获取文件
|
||||
java.io.File detFile = new java.io.File(filePath);
|
||||
// 检查文件是否存在
|
||||
if (detFile.exists()) {
|
||||
return detFile.length();
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
@DeleteMapping("/file")
|
||||
@ApiOperation(value = "删除物理机文件")
|
||||
boolean deleteFile(@RequestParam("filePath") String filePath) {
|
||||
// 根据det文件路径获取文件
|
||||
java.io.File detFile = new java.io.File(filePath);
|
||||
// 检查文件是否存在
|
||||
if (detFile.exists()) {
|
||||
return detFile.delete();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue