feat(image): 新增镜像虚拟机相关功能

- 新增镜像虚拟机实体类、控制器、服务接口和 mapper
- 实现镜像虚拟机的分页查询、新增、修改、删除等功能- 添加镜像虚拟机克隆为模板的功能
- 优化 FileChunkController 中的异步处理逻辑
- 在 DeviceRes 中添加设备状态字段
master
chenhao 2025-08-25 16:38:45 +08:00
parent 587cdb0e0d
commit d54cb15b91
15 changed files with 1151 additions and 31 deletions

View File

@ -177,35 +177,35 @@ public class FileChunkController {
log.info("镜像新增insert:{}", save);
if (save) {
// 异步执行创建和做种操作
CompletableFuture.runAsync(() -> {
try {
String url = btUrl + "/vdi/start?sourceFile=%s&torrentFile=%s";
url = String.format(url, finalFilePath, finalFilePath + ".torrent");
log.info("请求bt创建接口参数: {}", url);
ResponseEntity<Boolean> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, Boolean.class);
log.info("请求bt创建接口返回: {}", JSONUtil.toJsonStr(responseEntity));
HttpStatus statusCode = responseEntity.getStatusCode();
if (statusCode == HttpStatus.OK) {
boolean result = Boolean.TRUE.equals(responseEntity.getBody());
if (result) {
log.info("请求bt创建接口成功");
LambdaQueryWrapper<Image> imageQueryWrapper = new LambdaQueryWrapper<>();
imageQueryWrapper.eq(Image::getImageFileName, fileName);
Image imageBt = imageService.getOne(imageQueryWrapper);
if (ObjectUtils.isNotEmpty(imageBt)) {
imageBt.setBtPath(finalFilePath + ".torrent");
boolean update = imageService.updateById(imageBt);
log.info("镜像bt更新insert:{}", update);
} else {
log.info("镜像bt更新查询失败:{}", fileName);
}
}
}
} catch (Exception e) {
log.error("请求bt创建接口失败: {}", e.getMessage(), e);
}
});
//DEPRECATED bt种子移动到镜像虚拟机克隆 异步执行创建和做种操作
// CompletableFuture.runAsync(() -> {
// try {
// String url = btUrl + "/vdi/start?sourceFile=%s&torrentFile=%s";
// url = String.format(url, finalFilePath, finalFilePath + ".torrent");
// log.info("请求bt创建接口参数: {}", url);
// ResponseEntity<Boolean> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, Boolean.class);
// log.info("请求bt创建接口返回: {}", JSONUtil.toJsonStr(responseEntity));
// HttpStatus statusCode = responseEntity.getStatusCode();
// if (statusCode == HttpStatus.OK) {
// boolean result = Boolean.TRUE.equals(responseEntity.getBody());
// if (result) {
// log.info("请求bt创建接口成功");
// LambdaQueryWrapper<Image> imageQueryWrapper = new LambdaQueryWrapper<>();
// imageQueryWrapper.eq(Image::getImageFileName, fileName);
// Image imageBt = imageService.getOne(imageQueryWrapper);
// if (ObjectUtils.isNotEmpty(imageBt)) {
// imageBt.setBtPath(finalFilePath + ".torrent");
// boolean update = imageService.updateById(imageBt);
// log.info("镜像bt更新insert:{}", update);
// } else {
// log.info("镜像bt更新查询失败:{}", fileName);
// }
// }
// }
// } catch (Exception e) {
// log.error("请求bt创建接口失败: {}", e.getMessage(), e);
// }
// });
response.put("status", "completed");
response.put("message", "文件上传并合并完成");
response.put("filePath", finalFilePath.toString());

View File

@ -0,0 +1,104 @@
package com.unisinsight.project.controller;
import cn.hutool.json.JSONUtil;
import com.unisinsight.project.entity.req.DeleteIdReq;
import com.unisinsight.project.entity.req.ImageVirtualMachinesReq;
import com.unisinsight.project.entity.res.ImageVirtualMachinesRes;
import com.unisinsight.project.entity.res.PageResult;
import com.unisinsight.project.exception.BaseErrorCode;
import com.unisinsight.project.exception.Result;
import com.unisinsight.project.service.ImageVirtualMachinesService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
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 java.util.Objects;
/**
* (ImageVirtualMachines)
*
* @author ch
* @since 2025-08-22 09:42:41
*/
@RestController
@RequestMapping("/api/nex/v1/image/virtual/machines")
@Slf4j
@Api(tags = "镜像虚拟机类")
public class ImageVirtualMachinesController {
/**
*
*/
@Resource
private ImageVirtualMachinesService service;
/**
*
*
*
* @param imageVirtualMachinesReq
* @return
*/
@ApiOperation(value = "分页查询镜像")
@PostMapping("/select/page")
public Result<PageResult<ImageVirtualMachinesRes>> selectPage(@RequestBody ImageVirtualMachinesReq imageVirtualMachinesReq) {
if (Objects.isNull(imageVirtualMachinesReq)) {
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
}
log.info("分页查询镜像请求参数为:{}", JSONUtil.toJsonStr(imageVirtualMachinesReq));
return service.selectPage(imageVirtualMachinesReq);
}
@ApiOperation(value = "新增")
@PostMapping("/add")
public Result<?> insertImageVirtualMachines(@RequestBody ImageVirtualMachinesReq imageVirtualMachinesReq) {
if (Objects.isNull(imageVirtualMachinesReq)) {
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
}
log.info("镜像虚拟机新增请求参数为:{}", JSONUtil.toJsonStr(imageVirtualMachinesReq));
return service.insert(imageVirtualMachinesReq);
}
@ApiOperation(value = "镜像虚拟机修改")
@PostMapping("/update")
public Result<?> updateUser(@RequestBody ImageVirtualMachinesReq imageVirtualMachinesReq) {
if (Objects.isNull(imageVirtualMachinesReq)) {
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
}
log.info("镜像虚拟机修改请求参数为:{}", JSONUtil.toJsonStr(imageVirtualMachinesReq));
return service.update(imageVirtualMachinesReq);
}
@ApiOperation(value = "镜像虚拟机查询")
@PostMapping("/query")
public Result<?> queryUser(@RequestBody ImageVirtualMachinesReq imageVirtualMachinesReq) {
if (Objects.isNull(imageVirtualMachinesReq)) {
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
}
log.info("镜像虚拟机查询请求参数为:{}", JSONUtil.toJsonStr(imageVirtualMachinesReq));
return service.query(imageVirtualMachinesReq);
}
@ApiOperation(value = "终端删除")
@PostMapping("/delete")
public Result<?> delete(@RequestBody DeleteIdReq deleteIdReq) {
if (Objects.isNull(deleteIdReq)) {
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
}
log.info("镜像虚拟机删除请求参数为:{}", JSONUtil.toJsonStr(deleteIdReq));
return service.delete(deleteIdReq);
}
@ApiOperation(value = "终端删除")
@PostMapping("/clone/template")
public Result<?> cloneTemplate(@RequestBody ImageVirtualMachinesReq req) {
if (Objects.isNull(req)) {
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
}
log.info("镜像虚拟机克隆为模板请求参数为:{}", JSONUtil.toJsonStr(req));
return service.cloneTemplate(req);
}
}

View File

@ -0,0 +1,126 @@
package com.unisinsight.project.entity.dao;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* (ImageVirtualMachines)
*
* @author ch
* @since 2025-08-25 11:42:50
*/
@TableName(value = "image_virtual_machines")
@Data
@ApiModel("镜像虚拟机")
public class ImageVirtualMachines extends Model<ImageVirtualMachines> {
/**
* ID
**/
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty("ID")
private Long id;
/**
*
**/
@TableField(value = "image_name")
@ApiModelProperty("镜像名称")
private String imageName;
/**
* :
**/
@TableField(value = "image_status")
@ApiModelProperty("镜像状态")
private String imageStatus;
/**
* Id
**/
@TableField(value = "image_system_id")
@ApiModelProperty("系统镜像Id")
private Long imageSystemId;
/**
*
**/
@TableField(value = "os_version")
@ApiModelProperty("操作系统")
private String osVersion;
/**
*
**/
@TableField(value = "storage_path")
@ApiModelProperty("镜像存储路径")
private String storagePath;
/**
*
**/
@TableField(value = "create_time", fill = FieldFill.INSERT)
@ApiModelProperty("创建时间")
private String createTime;
/**
*
**/
@TableField(value = "create_user")
@ApiModelProperty("创建人")
private String createUser;
/**
*
**/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
@ApiModelProperty("更新时间")
private String updateTime;
/**
*
**/
@TableField(value = "update_user")
@ApiModelProperty("更新人")
private String updateUser;
/**
* 0-1-
**/
@TableLogic
@TableField(value = "deleted")
@ApiModelProperty("是否删除0-否1-删除")
private Integer deleted;
/**
*
**/
@TableField(value = "description")
@ApiModelProperty("描述")
private String description;
/**
* cpu
**/
@TableField(value = "cpu_total")
@ApiModelProperty("cpu数量")
private Integer cpuTotal;
/**
* cpu
**/
@TableField(value = "cpu_core_total")
@ApiModelProperty("cpu核数量")
private Integer cpuCoreTotal;
/**
* (GB)
**/
@TableField(value = "memory_total")
@ApiModelProperty("内存大小(GB)")
private Integer memoryTotal;
/**
* (GB)
**/
@TableField(value = "system_total")
@ApiModelProperty("系统盘大小(GB)")
private Integer systemTotal;
/**
*
**/
@TableField(value = "network_module")
@ApiModelProperty("网络模块")
private String networkModule;
}

View File

@ -0,0 +1,54 @@
package com.unisinsight.project.entity.req;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : ch
* @version : 1.0
* @ClassName : ImageCreateReq
* @Description :
* @DATE : Created in 15:49 2025/8/25
* <pre> Copyright: Copyright(c) 2025 </pre>
* <pre> Company : </pre>
* Modification History:
* Date Author Version Discription
* --------------------------------------------------------------------------
* 2025/08/25 ch 1.0 Why & What is modified: <> *
*/
@Data
@Builder
public class ImageCreateReq {
@JsonProperty("name")
private String name;
@JsonProperty("os_type")
private String osType;
@JsonProperty("os_variant")
private String osVariant;
@JsonProperty("vcpus")
private Integer vcpus;
@JsonProperty("memory")
private Integer memory;
@JsonProperty("disk_size")
private Integer diskSize;
@JsonProperty("disk_format")
private String diskFormat;
@JsonProperty("storage_pool")
private String storagePool;
@JsonProperty("network_name")
private String networkName;
@JsonProperty("iso_path")
private String isoPath;
@JsonProperty("auto_mount_virtio")
private Boolean autoMountVirtio;
@JsonProperty("virtio_win_path")
private String virtioWinPath;
@JsonProperty("autostart")
private Boolean autostart;
@JsonProperty("description")
private String description;
}

View File

@ -0,0 +1,43 @@
package com.unisinsight.project.entity.req;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : ch
* @version : 1.0
* @ClassName : ImageUpdateReq
* @Description :
* @DATE : Created in 16:14 2025/8/25
* <pre> Copyright: Copyright(c) 2025 </pre>
* <pre> Company : </pre>
* Modification History:
* Date Author Version Discription
* --------------------------------------------------------------------------
* 2025/08/25 ch 1.0 Why & What is modified: <> *
*/
@Data
@Builder
public class ImageUpdateReq {
@JsonProperty("vm_name")
private String vmName;
@JsonProperty("vcpus")
private Integer vcpus;
@JsonProperty("memory")
private Integer memory;
@JsonProperty("network_name")
private String networkName;
@JsonProperty("autostart")
private Boolean autostart;
@JsonProperty("auto_mount_virtio")
private Boolean autoMountVirtio;
@JsonProperty("virtio_win_path")
private String virtioWinPath;
@JsonProperty("description")
private String description;
}

View File

@ -0,0 +1,135 @@
package com.unisinsight.project.entity.req;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* (ImageVirtualMachines)
*
* @author ch
* @since 2025-08-25 11:42:52
*/
@Data
@ApiModel("镜像虚拟机")
public class ImageVirtualMachinesReq {
/**
* ID
**/
@JsonProperty("id")
@ApiModelProperty("ID")
private Long id;
/**
*
**/
@JsonProperty("image_name")
@ApiModelProperty("镜像名称")
private String imageName;
/**
* : 1-2-
**/
@JsonProperty("image_status")
@ApiModelProperty("镜像状态")
private String imageStatus;
/**
* Id
**/
@JsonProperty("image_system_id")
@ApiModelProperty("系统镜像Id")
private Long imageSystemId;
/**
*
**/
@JsonProperty("os_version")
@ApiModelProperty("操作系统")
private String osVersion;
/**
*
**/
@JsonProperty("storage_path")
@ApiModelProperty("镜像存储路径")
private String storagePath;
/**
*
**/
@JsonProperty("create_time")
@ApiModelProperty("创建时间")
private String createTime;
/**
*
**/
@JsonProperty("create_user")
@ApiModelProperty("创建人")
private String createUser;
/**
*
**/
@JsonProperty("update_time")
@ApiModelProperty("更新时间")
private String updateTime;
/**
*
**/
@JsonProperty("update_user")
@ApiModelProperty("更新人")
private String updateUser;
/**
* 0-1-
**/
@JsonProperty("deleted")
@ApiModelProperty("是否删除0-否1-删除")
private Integer deleted;
/**
*
**/
@JsonProperty("description")
@ApiModelProperty("描述")
private String description;
/**
* cpu
**/
@JsonProperty("cpu_total")
@ApiModelProperty("cpu数量")
private Integer cpuTotal;
/**
* cpu
**/
@JsonProperty("cpu_core_total")
@ApiModelProperty("cpu核数量")
private Integer cpuCoreTotal;
/**
* (GB)
**/
@JsonProperty("memory_total")
@ApiModelProperty("内存大小(GB)")
private Integer memoryTotal;
/**
* (GB)
**/
@JsonProperty("system_total")
@ApiModelProperty("系统盘大小(GB)")
private Integer systemTotal;
/**
*
**/
@JsonProperty("network_module")
@ApiModelProperty("网络模块")
private String networkModule;
/**
*
*/
@ApiModelProperty(value = "查询页", notes = "分页查询时再传")
@JsonProperty("page_num")
private Integer pageNum;
/**
*
*/
@ApiModelProperty(value = "每页数量", notes = "分页查询时再传")
@JsonProperty("page_size")
private Integer pageSize;
}

View File

@ -1,6 +1,7 @@
package com.unisinsight.project.entity.res;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -84,4 +85,10 @@ public class DeviceRes implements Serializable {
@ApiModelProperty("描述")
@JsonProperty("description")
private String description;
/**
* 0:线 1:线
*/
@ApiModelProperty("设备状态")
@JsonProperty("device_status")
private Integer deviceStatus;
}

View File

@ -0,0 +1,128 @@
package com.unisinsight.project.entity.res;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author : ch
* @version : 1.0
* @ClassName : ImageStatusRes
* @Description :
* @DATE : Created in 14:34 2025/8/25
* <pre> Copyright: Copyright(c) 2025 </pre>
* <pre> Company : </pre>
* Modification History:
* Date Author Version Discription
* --------------------------------------------------------------------------
* 2025/08/25 ch 1.0 Why & What is modified: <> *
*/
@NoArgsConstructor
@Data
public class ImageStatusRes {
@JsonProperty("code")
private String code;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private DataDTO data;
@NoArgsConstructor
@Data
public static class DataDTO {
@JsonProperty("items")
private List<ItemsDTO> items;
@JsonProperty("total")
private Integer total;
@JsonProperty("page")
private Integer page;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("total_pages")
private Integer totalPages;
@JsonProperty("not_found")
private List<?> notFound;
@NoArgsConstructor
@Data
public static class ItemsDTO {
@JsonProperty("name")
private String name;
@JsonProperty("uuid")
private String uuid;
@JsonProperty("id")
private Integer id;
@JsonProperty("state")
private String state;
@JsonProperty("vcpus")
private Integer vcpus;
@JsonProperty("memory")
private Integer memory;
@JsonProperty("memory_mb")
private Integer memoryMb;
@JsonProperty("autostart")
private Integer autostart;
@JsonProperty("persistent")
private Integer persistent;
@JsonProperty("os_type")
private String osType;
@JsonProperty("os_variant")
private String osVariant;
@JsonProperty("created_at")
private String createdAt;
@JsonProperty("disks")
private List<DisksDTO> disks;
@JsonProperty("disk_size")
private Double diskSize;
@JsonProperty("network_interfaces")
private List<NetworkInterfacesDTO> networkInterfaces;
@JsonProperty("vnc")
private VncDTO vnc;
@JsonProperty("auto_mount_virtio")
private Boolean autoMountVirtio;
@JsonProperty("virtio_win_path")
private String virtioWinPath;
@JsonProperty("iso_path")
private String isoPath;
@JsonProperty("interfaces")
private List<?> interfaces;
@JsonProperty("ip_address")
private Object ipAddress;
@NoArgsConstructor
@Data
public static class VncDTO {
@JsonProperty("port")
private String port;
@JsonProperty("autoport")
private Boolean autoport;
@JsonProperty("listen")
private String listen;
}
@NoArgsConstructor
@Data
public static class DisksDTO {
@JsonProperty("device")
private String device;
@JsonProperty("file")
private String file;
@JsonProperty("bus")
private String bus;
}
@NoArgsConstructor
@Data
public static class NetworkInterfacesDTO {
@JsonProperty("mac")
private String mac;
@JsonProperty("network")
private String network;
}
}
}
}

View File

@ -0,0 +1,133 @@
package com.unisinsight.project.entity.res;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* (ImageVirtualMachines)
*
* @author ch
* @since 2025-08-25 11:42:57
*/
@Data
@ApiModel("镜像虚拟机")
public class ImageVirtualMachinesRes implements Serializable {
/**
* ID
**/
@JsonProperty("id")
@ApiModelProperty("ID")
private Long id;
/**
*
**/
@JsonProperty("image_name")
@ApiModelProperty("镜像名称")
private String imageName;
/**
* :
**/
@JsonProperty("image_status")
@ApiModelProperty("镜像状态: 根据接口返回")
private String imageStatus;
/**
* Id
**/
@JsonProperty("image_system_id")
@ApiModelProperty("系统镜像Id")
private Long imageSystemId;
/**
* Id
**/
@JsonProperty("image_system_name")
@ApiModelProperty("系统镜像名称")
private String imageSystemName;
/**
*
**/
@JsonProperty("os_version")
@ApiModelProperty("操作系统")
private String osVersion;
/**
*
**/
@JsonProperty("storage_path")
@ApiModelProperty("镜像存储路径")
private String storagePath;
/**
*
**/
@JsonProperty("create_time")
@ApiModelProperty("创建时间")
private String createTime;
/**
*
**/
@JsonProperty("create_user")
@ApiModelProperty("创建人")
private String createUser;
/**
*
**/
@JsonProperty("update_time")
@ApiModelProperty("更新时间")
private String updateTime;
/**
*
**/
@JsonProperty("update_user")
@ApiModelProperty("更新人")
private String updateUser;
/**
* 0-1-
**/
@JsonProperty("deleted")
@ApiModelProperty("是否删除0-否1-删除")
private Integer deleted;
/**
*
**/
@JsonProperty("description")
@ApiModelProperty("描述")
private String description;
/**
* cpu
**/
@JsonProperty("cpu_total")
@ApiModelProperty("cpu数量")
private Integer cpuTotal;
/**
* cpu
**/
@JsonProperty("cpu_core_total")
@ApiModelProperty("cpu核数量")
private Integer cpuCoreTotal;
/**
* (GB)
**/
@JsonProperty("memory_total")
@ApiModelProperty("内存大小(GB)")
private Integer memoryTotal;
/**
* (GB)
**/
@JsonProperty("system_total")
@ApiModelProperty("系统盘大小(GB)")
private Integer systemTotal;
/**
*
**/
@JsonProperty("network_module")
@ApiModelProperty("网络模块")
private String networkModule;
@JsonProperty("vnc_data")
@ApiModelProperty("远程连接信息")
private ImageStatusRes.DataDTO.ItemsDTO.VncDTO vncData;
}

View File

@ -0,0 +1,18 @@
package com.unisinsight.project.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import com.unisinsight.project.entity.dao.ImageVirtualMachines;
/**
* (ImageVirtualMachines)访
*
* @author ch
* @since 2025-08-25 11:42:53
*/
public interface ImageVirtualMachinesMapper extends BaseMapper<ImageVirtualMachines> {
}

View File

@ -0,0 +1,68 @@
package com.unisinsight.project.properties;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author : ch
* @version : 1.0
* @ClassName : ImageConfigProperties
* @Description :
* @DATE : Created in 15:52 2025/8/25
* <pre> Copyright: Copyright(c) 2025 </pre>
* <pre> Company : </pre>
* Modification History:
* Date Author Version Discription
* --------------------------------------------------------------------------
* 2025/08/25 ch 1.0 Why & What is modified: <> *
*/
@Component
@ConfigurationProperties(prefix = "image")
@Data
public class ImageConfigProperties {
/**
* URL
*/
private String baseUrl;
/**
* URL
*/
@Getter(value=AccessLevel.NONE)
private String statusUrl;
@Getter(value=AccessLevel.NONE)
private String createUrl;
@Getter(value=AccessLevel.NONE)
private String deleteUrl;
@Getter(value=AccessLevel.NONE)
private String updateUrl;
@Getter(value=AccessLevel.NONE)
private String startUrl;
public String getStatusUrl() {
return baseUrl+statusUrl;
}
public String getCreateUrl() {
return baseUrl+createUrl;
}
public String getDeleteUrl() {
return baseUrl+deleteUrl;
}
public String getUpdateUrl() {
return baseUrl+updateUrl;
}
public String getStartUrl() {
return baseUrl+startUrl;
}
}

View File

@ -0,0 +1,31 @@
package com.unisinsight.project.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.unisinsight.project.entity.dao.ImageVirtualMachines;
import com.unisinsight.project.entity.req.DeleteIdReq;
import com.unisinsight.project.entity.req.ImageVirtualMachinesReq;
import com.unisinsight.project.entity.res.ImageVirtualMachinesRes;
import com.unisinsight.project.entity.res.PageResult;
import com.unisinsight.project.exception.Result;
/**
* (ImageVirtualMachines)
*
* @author ch
* @since 2025-08-22 09:42:41
*/
public interface ImageVirtualMachinesService extends IService<ImageVirtualMachines> {
Result<PageResult<ImageVirtualMachinesRes>> selectPage(ImageVirtualMachinesReq imageVirtualMachinesReq);
Result<?> insert(ImageVirtualMachinesReq imageVirtualMachinesReq);
Result<?> update(ImageVirtualMachinesReq imageVirtualMachinesReq);
Result<?> query(ImageVirtualMachinesReq imageVirtualMachinesReq);
Result<?> delete(DeleteIdReq deleteIdReq);
Result<?> cloneTemplate(ImageVirtualMachinesReq req);
}

View File

@ -0,0 +1,237 @@
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.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.unisinsight.project.entity.dao.Image;
import com.unisinsight.project.entity.dao.ImageVirtualMachines;
import com.unisinsight.project.entity.req.DeleteIdReq;
import com.unisinsight.project.entity.req.ImageCreateReq;
import com.unisinsight.project.entity.req.ImageUpdateReq;
import com.unisinsight.project.entity.req.ImageVirtualMachinesReq;
import com.unisinsight.project.entity.res.ImageStatusRes;
import com.unisinsight.project.entity.res.ImageVirtualMachinesRes;
import com.unisinsight.project.entity.res.PageResult;
import com.unisinsight.project.exception.BaseErrorCode;
import com.unisinsight.project.exception.Result;
import com.unisinsight.project.mapper.ImageVirtualMachinesMapper;
import com.unisinsight.project.properties.ImageConfigProperties;
import com.unisinsight.project.service.ImageService;
import com.unisinsight.project.service.ImageVirtualMachinesService;
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.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* (ImageVirtualMachines)
*
* @author ch
* @since 2025-08-22 09:42:41
*/
@Service("imageVirtualMachinesService")
@Slf4j
public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMachinesMapper, ImageVirtualMachines> implements ImageVirtualMachinesService {
@Resource
private ImageVirtualMachinesMapper machinesMapper;
@Autowired
private ImageService imageService;
@Resource
private RestTemplate restTemplate;
@Autowired
private ImageConfigProperties imageConfigProperties;
@Override
public Result<PageResult<ImageVirtualMachinesRes>> selectPage(ImageVirtualMachinesReq imageVirtualMachinesReq) {
Page<ImageVirtualMachines> page = new Page<>(imageVirtualMachinesReq.getPageNum(), imageVirtualMachinesReq.getPageSize());
LambdaQueryWrapper<ImageVirtualMachines> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(imageVirtualMachinesReq.getImageName()), ImageVirtualMachines::getImageName, imageVirtualMachinesReq.getImageName());
queryWrapper.orderByAsc(ImageVirtualMachines::getId);
Page<ImageVirtualMachines> imageVirtualMachinesPage = machinesMapper.selectPage(page, queryWrapper);
log.info("分页查询镜像返回:{}", JSONUtil.toJsonStr(imageVirtualMachinesPage));
if (CollectionUtil.isEmpty(imageVirtualMachinesPage.getRecords())) {
log.info("分页查询镜像返回为空");
return Result.successResult();
} else {
PageResult<ImageVirtualMachinesRes> convert = PageResult.convertIPage(imageVirtualMachinesPage, ImageVirtualMachinesRes.class);
List<ImageVirtualMachinesRes> data = convert.getData();
List<Long> imageIdList = imageVirtualMachinesPage.getRecords().stream().map(ImageVirtualMachines::getImageSystemId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
Map<Long, String> imageMap = Collections.emptyMap();
if (CollectionUtil.isNotEmpty(imageIdList)) {
LambdaQueryWrapper<Image> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
imageLambdaQueryWrapper.in(Image::getId, imageIdList);
List<Image> imageList = imageService.list(imageLambdaQueryWrapper);
imageMap = imageList.stream().collect(Collectors.toMap(Image::getId, Image::getImageName));
}
//查询虚拟机数据
List<String> nameList = data.stream().map(ImageVirtualMachinesRes::getImageName).filter(Objects::nonNull).distinct().collect(Collectors.toList());
// 添加请求参数
Map<String, ImageStatusRes.DataDTO.ItemsDTO> imageStatusData = getImageStatusData(nameList);
for (ImageVirtualMachinesRes res : data) {
if (res.getImageSystemId() != null && StringUtils.isNotEmpty(imageMap.get(res.getImageSystemId()))) {
res.setImageSystemName(imageMap.get(res.getImageSystemId()));
}
if (imageStatusData.containsKey(res.getImageName())) {
ImageStatusRes.DataDTO.ItemsDTO itemsDTO = imageStatusData.get(res.getImageName());
res.setImageStatus(itemsDTO.getState());
res.setVncData(itemsDTO.getVnc());
}
}
return Result.successResult(convert);
}
}
private Map<String, ImageStatusRes.DataDTO.ItemsDTO> getImageStatusData(List<String> nameList) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("page", 1);
requestBody.put("page_size", 100);
requestBody.put("vm_names", nameList);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(imageConfigProperties.getStatusUrl(), HttpMethod.POST, requestEntity, ImageStatusRes.class);
if (exchange.getBody() != null && "200".equals(exchange.getBody().getCode()) && exchange.getBody().getData() != null) {
List<ImageStatusRes.DataDTO.ItemsDTO> items = exchange.getBody().getData().getItems();
if (CollUtil.isNotEmpty(items)) {
return items.stream().collect(Collectors.toMap(ImageStatusRes.DataDTO.ItemsDTO::getName, Function.identity(), (k1, k2) -> k1));
}
}
log.error("根据名称[{}]请求接口,返回数据为空:{}", nameList, exchange.getBody());
return Collections.emptyMap();
}
@Override
public Result<?> insert(ImageVirtualMachinesReq imageVirtualMachinesReq) {
// 调用镜像生成服务
LambdaQueryWrapper<Image> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
imageLambdaQueryWrapper.in(Image::getId, imageVirtualMachinesReq.getImageSystemId());
Image systemImage = imageService.getOne(imageLambdaQueryWrapper);
ImageCreateReq createReq = ImageCreateReq.builder()
.name(imageVirtualMachinesReq.getImageName())
.osType(imageVirtualMachinesReq.getOsVersion())
.osVariant((systemImage!=null && StringUtils.isNotEmpty(systemImage.getImageVersion()))?systemImage.getImageVersion():"generic")
.vcpus(imageVirtualMachinesReq.getCpuTotal())
.memory(imageVirtualMachinesReq.getMemoryTotal())
.diskSize(imageVirtualMachinesReq.getSystemTotal())
//存储池
// .storagePool(imageVirtualMachinesReq.getStoragePath())
.networkName(imageVirtualMachinesReq.getNetworkModule())
.isoPath(imageVirtualMachinesReq.getStoragePath())
//驱动
// .virtioWinPath(imageVirtualMachinesReq.getStoragePath())
.description(imageVirtualMachinesReq.getDescription())
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ImageCreateReq> requestEntity = new HttpEntity<>(createReq, headers);
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(imageConfigProperties.getCreateUrl(), HttpMethod.POST, requestEntity, ImageStatusRes.class);
if (!exchange.getStatusCode().equals(HttpStatus.OK)){
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "创建虚拟机失败");
}
ImageVirtualMachines imageVirtualMachines = BeanUtil.copyProperties(imageVirtualMachinesReq, ImageVirtualMachines.class);
QueryWrapper<ImageVirtualMachines> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(ImageVirtualMachines::getImageName, imageVirtualMachines.getImageName());
ImageVirtualMachines selectOne = machinesMapper.selectOne(queryWrapper);
if (ObjectUtils.isNotEmpty(selectOne)) {
return new Result<>("200", "名称重复");
}
imageVirtualMachines.setCreateUser("admin");
imageVirtualMachines.setUpdateUser("admin");
int insert = machinesMapper.insert(imageVirtualMachines);
log.info("终端新增insert:{}", insert);
if (insert == 1) {
return Result.successResult();
} else {
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
}
}
@Override
public Result<?> update(ImageVirtualMachinesReq imageVirtualMachinesReq) {
// todo 调用镜像修改服务
ImageUpdateReq updateReq = ImageUpdateReq.builder()
.vmName(imageVirtualMachinesReq.getImageName())
.vcpus(imageVirtualMachinesReq.getCpuTotal())
.memory(imageVirtualMachinesReq.getMemoryTotal())
.networkName(imageVirtualMachinesReq.getNetworkModule())
//驱动
// .virtioWinPath(imageVirtualMachinesReq.getStoragePath())
.description(imageVirtualMachinesReq.getDescription())
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ImageUpdateReq> requestEntity = new HttpEntity<>(updateReq, headers);
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(imageConfigProperties.getUpdateUrl(), HttpMethod.POST, requestEntity, ImageStatusRes.class);
if (!exchange.getStatusCode().equals(HttpStatus.OK)){
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "修改虚拟机失败");
}
ImageVirtualMachines imageVirtualMachines = BeanUtil.copyProperties(imageVirtualMachinesReq, ImageVirtualMachines.class);
imageVirtualMachines.setUpdateUser("admin");
int updated = machinesMapper.updateById(imageVirtualMachines);
log.info("镜像虚拟机修改updated:{}", updated);
if (updated == 1) {
return Result.successResult();
} else {
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
}
}
@Override
public Result<?> query(ImageVirtualMachinesReq imageVirtualMachinesReq) {
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(imageVirtualMachinesReq.getId());
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
log.info("查询镜像虚拟机返回为空");
return Result.successResult();
}
ImageVirtualMachinesRes res = BeanUtil.copyProperties(imageVirtualMachines, ImageVirtualMachinesRes.class);
log.info("查询镜像虚拟机返回:{}", JSONUtil.toJsonStr(res));
return Result.successResult(res);
}
@Override
public Result<?> delete(DeleteIdReq deleteIdReq) {
int deleted = machinesMapper.deleteById(deleteIdReq.getId());
log.info("终端删除insert:{}", deleted);
if (deleted == 1) {
return Result.successResult();
} else {
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
}
}
@Override
public Result<?> cloneTemplate(ImageVirtualMachinesReq req) {
//todo 调用镜像生成服务 在生成完毕后生成桌面镜像数据
return null;
}
}

View File

@ -6,7 +6,13 @@ file:
temp-dir: /var/lib/vdi/tmp/chunked-uploads
dir: /var/lib/vdi/test
bt-url: http://10.100.51.86:8114
image:
base-url: http://10.100.51.118:5173
status-url: /api/v1/vm/batch-status
create-url: /api/v1/vm/create
delete-url: /api/v1/vm/delete
update-url: /api/v1/vm/update
start-url: /api/v1/vm/start
spring:
servlet:
multipart:
@ -47,4 +53,4 @@ mybatis-plus:
grpc:
server:
# 指定Grpc暴露的端口后续客户端通过这个端口访问
port: 50051
port: 51051

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.unisinsight.project.mapper.ImageVirtualMachinesMapper">
<resultMap id="BaseResultMap" type="com.unisinsight.project.entity.dao.Image">
<id property="id" column="id" />
<result property="imageName" column="image_name" />
<result property="imageFileName" column="image_file_name" />
<result property="imageStatus" column="image_status" />
<result property="imageVersion" column="image_version" />
<result property="osVersion" column="os_version" />
<result property="btPath" column="bt_path" />
<result property="storagePath" column="storage_path" />
<result property="createTime" column="create_time" />
<result property="createUser" column="create_user" />
<result property="updateTime" column="update_time" />
<result property="updateUser" column="update_user" />
<result property="deleted" column="deleted" />
<result property="description" column="description" />
</resultMap>
<sql id="Base_Column_List">
id,image_name,image_file_name,image_status,image_version,os_version,
bt_path,storage_path,create_time,create_user,update_time,
update_user,deleted,description
</sql>
</mapper>