Merge remote-tracking branch 'origin/master'

master
汤全昆 2025-09-04 10:45:35 +08:00
commit ce6d7acc92
7 changed files with 206 additions and 20 deletions

View File

@ -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);
}

View File

@ -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());

View File

@ -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());

View File

@ -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());
}
}));

View File

@ -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 {

View File

@ -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;
// 存储定时任务的Mapkey为任务标识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) {
// 查询虚拟机信息

View File

@ -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;
}
}