feat(image): 添加获取 VNC信息功能并优化镜像相关服务

- 在 DeviceImageMappingRes 中添加父镜像名称和创建时间字段
- 优化 DeviceImageMappingService 中镜像信息查询逻辑- 在 ExternalApiClient 中添加获取VNC 信息的接口
- 在 ImageVirtualMachinesController 中添加获取 VNC 信息的控制器方法- 在 ImageVirtualMachinesService接口中添加获取 VNC 信息的方法
- 实现 ImageVirtualMachinesServiceImpl 中获取 VNC 信息的逻辑
- 新增 VncData 类用于存储 VNC 信息
master
chenhao 2025-09-08 09:51:52 +08:00
parent 65365b2a86
commit 3c8d1b833b
8 changed files with 93 additions and 27 deletions

View File

@ -13,10 +13,7 @@ import com.unisinsight.project.service.ImageVirtualMachinesService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Objects; import java.util.Objects;
@ -154,6 +151,13 @@ public class ImageVirtualMachinesController {
log.info("终端卸载文件请求参数为:{}", JSONUtil.toJsonStr(req)); log.info("终端卸载文件请求参数为:{}", JSONUtil.toJsonStr(req));
return service.removeIso(req); return service.removeIso(req);
} }
@ApiOperation(value = "获取VNC信息")
@GetMapping("/vnc")
public Result<?> getVncData(@RequestParam("vmName") String vmName) {
log.info("获取VNC信息请求参数为{}", vmName);
return service.getVncData(vmName);
}
} }

View File

@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
/** /**
* @TableName device_image_mapping * @TableName device_image_mapping
@ -59,6 +60,19 @@ public class DeviceImageMappingRes implements Serializable {
@JsonProperty(value = "image_file_name") @JsonProperty(value = "image_file_name")
@ApiModelProperty("镜像源文件名称") @ApiModelProperty("镜像源文件名称")
private String imageFileName; private String imageFileName;
/**
*
*/
@JsonProperty(value = "parent_image_name")
@ApiModelProperty("父镜像名称")
private String parentImageName;
/**
*
*/
@JsonProperty(value = "create_time")
@ApiModelProperty("创建时间")
private Date createTime;
/** /**
* *

View File

@ -0,0 +1,33 @@
package com.unisinsight.project.entity.res;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : ch
* @version : 1.0
* @ClassName : VncData
* @Description :
* @DATE : Created in 9:41 2025/9/8
* <pre> Copyright: Copyright(c) 2025 </pre>
* <pre> Company : </pre>
* Modification History:
* Date Author Version Discription
* --------------------------------------------------------------------------
* 2025/09/08 ch 1.0 Why & What is modified: <> *
*/
@NoArgsConstructor
@Data
public class VncData {
@JsonProperty("host")
private String host;
@JsonProperty("port")
private Integer port;
@JsonProperty("url")
private String url;
@JsonProperty("websocket_url")
private String websocketUrl;
}

View File

@ -4,6 +4,7 @@ import com.unisinsight.project.config.FeignConfig;
import com.unisinsight.project.entity.dto.*; import com.unisinsight.project.entity.dto.*;
import com.unisinsight.project.entity.req.*; import com.unisinsight.project.entity.req.*;
import com.unisinsight.project.entity.res.ImageStatusRes; import com.unisinsight.project.entity.res.ImageStatusRes;
import com.unisinsight.project.entity.res.VncData;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -90,5 +91,7 @@ public interface ExternalApiClient {
@GetMapping("/api/v1/network/bridge-interfaces") @GetMapping("/api/v1/network/bridge-interfaces")
ApiResponse<List<BridgeInterfaceDTO>> getBridgeInterfaces(); ApiResponse<List<BridgeInterfaceDTO>> getBridgeInterfaces();
@GetMapping("/api/v1/vm/{vmName}/vnc")
ApiResponse<VncData> getVncData(@PathVariable("vmName") String vmName);
} }

View File

@ -41,7 +41,7 @@ public class ClientNotificationServiceImpl extends NotificationServiceGrpc.Notif
private final ConcurrentHashMap<String, StreamObserver<NotificationMessage>> clientStreamMap = private final ConcurrentHashMap<String, StreamObserver<NotificationMessage>> clientStreamMap =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
private final static int MAX_CLIENT_COUNT = 1000; private final static int MAX_CLIENT_COUNT = 1000;
public static final String DEFAULT_USER = "NexOS";
// 生成客户端连接key的工具方法 // 生成客户端连接key的工具方法
private String generateClientKey(String clientId, String clientUser) { private String generateClientKey(String clientId, String clientUser) {
return clientId + "#" + clientUser; return clientId + "#" + clientUser;
@ -70,15 +70,7 @@ public class ClientNotificationServiceImpl extends NotificationServiceGrpc.Notif
return; // 避免继续执行后续逻辑 return; // 避免继续执行后续逻辑
} }
if (StringUtil.isEmpty(clientUser)) { clientUser= StringUtil.isEmpty(clientUser) ? DEFAULT_USER : clientUser;
NotificationMessage errMsg = NotificationMessage.newBuilder()
.setCode(HttpStatus.BAD_REQUEST.value())
.setMsg("client_user不能为空")
.build();
responseObserver.onNext(errMsg);
responseObserver.onCompleted();
return; // 避免继续执行后续逻辑
}
log.info("客户端连接client_sn:[{}],client_user: [{}]", clientSn, clientUser); log.info("客户端连接client_sn:[{}],client_user: [{}]", clientSn, clientUser);
@ -93,11 +85,12 @@ public class ClientNotificationServiceImpl extends NotificationServiceGrpc.Notif
final io.grpc.Context context = io.grpc.Context.current(); final io.grpc.Context context = io.grpc.Context.current();
// 注册一个取消监听器来监听客户端断开连接 // 注册一个取消监听器来监听客户端断开连接
String finalClientUser = clientUser;
context.addListener(context1 -> { context.addListener(context1 -> {
// 客户端断开连接时的处理逻辑 // 客户端断开连接时的处理逻辑
log.info("通过Context监听到客户端断开连接: {}, 用户: {}", clientSn, clientUser); log.info("通过Context监听到客户端断开连接: {}, 用户: {}", clientSn, finalClientUser);
removeClientConnection(clientSn, clientUser); removeClientConnection(clientSn, finalClientUser);
log.info("清理客户端连接: {}#{}, 剩余客户端数: {}", clientSn, clientUser, clientStreamMap.size()); log.info("清理客户端连接: {}#{}, 剩余客户端数: {}", clientSn, finalClientUser, clientStreamMap.size());
}, executorService); }, executorService);
} }

View File

@ -61,5 +61,7 @@ public interface ImageVirtualMachinesService extends IService<ImageVirtualMachin
Result<?> removeIso(ImageVirtualMachinesReq req); Result<?> removeIso(ImageVirtualMachinesReq req);
Result<?> attachIso(ImageVirtualMachinesReq req); Result<?> attachIso(ImageVirtualMachinesReq req);
Result<?> getVncData(String vmName);
} }

View File

@ -7,23 +7,18 @@ import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.unisinsight.project.entity.dao.DeviceImageMapping; import com.unisinsight.project.entity.dao.*;
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.req.DeviceImageMappingReq;
import com.unisinsight.project.entity.res.DeviceImageMappingRes; import com.unisinsight.project.entity.res.DeviceImageMappingRes;
import com.unisinsight.project.exception.Result; import com.unisinsight.project.exception.Result;
import com.unisinsight.project.mapper.DeviceImageMappingMapper; import com.unisinsight.project.mapper.*;
import com.unisinsight.project.mapper.ImageDesktopMapper;
import com.unisinsight.project.mapper.ImageMapper;
import com.unisinsight.project.mapper.UserDeviceGroupMapper;
import com.unisinsight.project.service.DeviceImageMappingService; import com.unisinsight.project.service.DeviceImageMappingService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -43,6 +38,8 @@ public class DeviceImageMappingServiceImpl extends ServiceImpl<DeviceImageMappin
@Resource @Resource
private ImageDesktopMapper imageDesktopMapper; private ImageDesktopMapper imageDesktopMapper;
@Resource @Resource
private ImageVirtualMachinesMapper imageVirtualMachinesMapper;
@Resource
private UserDeviceGroupMapper groupMapper; private UserDeviceGroupMapper groupMapper;
@ -64,11 +61,21 @@ public class DeviceImageMappingServiceImpl extends ServiceImpl<DeviceImageMappin
if (CollectionUtil.isNotEmpty(imageIds)) { if (CollectionUtil.isNotEmpty(imageIds)) {
LambdaQueryWrapper<ImageDesktop> imageLambdaQueryWrapper = new LambdaQueryWrapper<ImageDesktop>().in(ImageDesktop::getId, imageIds); LambdaQueryWrapper<ImageDesktop> imageLambdaQueryWrapper = new LambdaQueryWrapper<ImageDesktop>().in(ImageDesktop::getId, imageIds);
List<ImageDesktop> images = imageDesktopMapper.selectList(imageLambdaQueryWrapper); List<ImageDesktop> images = imageDesktopMapper.selectList(imageLambdaQueryWrapper);
List<Integer> imageVirtualIdList = images.stream().map(ImageDesktop::getImageVirtualId).filter(Objects::nonNull).collect(Collectors.toList());
LambdaQueryWrapper<ImageVirtualMachines> imageVirtualMachinesLambdaQueryWrapper = new LambdaQueryWrapper<ImageVirtualMachines>().in(ImageVirtualMachines::getId, imageVirtualIdList);
List<ImageVirtualMachines> imageVirtualMachines = imageVirtualMachinesMapper.selectList(imageVirtualMachinesLambdaQueryWrapper);
Map<Long, String> imageVirtualMachinesMap = imageVirtualMachines.stream().collect(Collectors.toMap(ImageVirtualMachines::getId, ImageVirtualMachines::getImageName));
if (CollectionUtil.isNotEmpty(images)) { if (CollectionUtil.isNotEmpty(images)) {
deviceImageMappingRes.forEach(deviceImage -> images.forEach(image -> { deviceImageMappingRes.forEach(deviceImage -> images.forEach(image -> {
if (ObjectUtil.isNotEmpty(deviceImage.getImageId()) && image.getId().equals(deviceImage.getImageId())) { if (ObjectUtil.isNotEmpty(deviceImage.getImageId()) && image.getId().equals(deviceImage.getImageId())) {
deviceImage.setImageName(image.getDesktopName()); deviceImage.setImageName(image.getDesktopName());
deviceImage.setImageFileName(image.getDesktopName()+"."+image.getDesktopType()); deviceImage.setImageFileName(image.getDesktopName()+"."+image.getDesktopType());
deviceImage.setCreateTime(image.getCreateTime());
// 设置父镜像名称
if (image.getImageVirtualId() != null) {
deviceImage.setParentImageName(imageVirtualMachinesMap.get(image.getImageVirtualId().longValue()));
}
} }
})); }));
} }

View File

@ -17,6 +17,7 @@ import com.unisinsight.project.entity.req.*;
import com.unisinsight.project.entity.res.ImageStatusRes; import com.unisinsight.project.entity.res.ImageStatusRes;
import com.unisinsight.project.entity.res.ImageVirtualMachinesRes; import com.unisinsight.project.entity.res.ImageVirtualMachinesRes;
import com.unisinsight.project.entity.res.PageResult; import com.unisinsight.project.entity.res.PageResult;
import com.unisinsight.project.entity.res.VncData;
import com.unisinsight.project.exception.BaseErrorCode; import com.unisinsight.project.exception.BaseErrorCode;
import com.unisinsight.project.exception.Result; import com.unisinsight.project.exception.Result;
import com.unisinsight.project.feign.ExternalApiClient; import com.unisinsight.project.feign.ExternalApiClient;
@ -323,7 +324,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
imageDesktop.setDesktopName(fileName); imageDesktop.setDesktopName(fileName);
imageDesktop.setDesktopType(type); imageDesktop.setDesktopType(type);
//制作中 //制作中
imageDesktop.setPublishStatus("0"); imageDesktop.setPublishStatus(0);
imageDesktop.setStoragePath(detFile); imageDesktop.setStoragePath(detFile);
imageDesktop.setImageVirtualId(Math.toIntExact(req.getId())); imageDesktop.setImageVirtualId(Math.toIntExact(req.getId()));
imageDesktop.setOsVersion(imageVirtualMachines.getOsVersion()); imageDesktop.setOsVersion(imageVirtualMachines.getOsVersion());
@ -428,7 +429,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
updateWrapper.eq(ImageDesktop::getDesktopName, fileName); updateWrapper.eq(ImageDesktop::getDesktopName, fileName);
Long fileSize = externalTorrentClient.getFileSize(detFilePath); Long fileSize = externalTorrentClient.getFileSize(detFilePath);
log.info("det文件大小: {} 字节", fileSize); log.info("det文件大小: {} 字节", fileSize);
updateWrapper.set(ImageDesktop::getPublishStatus, "1"); updateWrapper.set(ImageDesktop::getPublishStatus, 1);
updateWrapper.set(ImageDesktop::getFileSize, fileSize); updateWrapper.set(ImageDesktop::getFileSize, fileSize);
updateWrapper.set(ImageDesktop::getPublishTime, DateUtil.date()); updateWrapper.set(ImageDesktop::getPublishTime, DateUtil.date());
//转化bt //转化bt
@ -578,5 +579,14 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
return Result.successResult(); return Result.successResult();
} }
@Override
public Result<?> getVncData(String vmName) {
ApiResponse<VncData> vncData = externalApiClient.getVncData(vmName);
if ("200".equals(vncData.getCode())){
return Result.successResult(vncData.getData());
}
return Result.errorResultMessage(BaseErrorCode.HTTP_ERROR_CODE_500, vncData.getMessage());
}
} }