Merge remote-tracking branch 'origin/master'
commit
f2b298f9c7
|
|
@ -106,5 +106,25 @@ public class DeviceController {
|
|||
return clientOperateService.terminalEnd(deviceReq);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "usb开启")
|
||||
@PostMapping("/terminal/usb/enabled")
|
||||
public Result<?> usbEnabled(@RequestBody DeviceReq deviceReq) {
|
||||
if (Objects.isNull(deviceReq)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("终端usb开启请求参数为:{}", JSONUtil.toJsonStr(deviceReq));
|
||||
return clientOperateService.usbEnabled(deviceReq);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "usb关闭")
|
||||
@PostMapping("/terminal/usb/disabled")
|
||||
public Result<?> usbDisabled(@RequestBody DeviceReq deviceReq) {
|
||||
if (Objects.isNull(deviceReq)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("终端usb关闭请求参数为:{}", JSONUtil.toJsonStr(deviceReq));
|
||||
return clientOperateService.usbDisabled(deviceReq);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
package com.unisinsight.project.controller;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.unisinsight.project.entity.dao.ImageTool;
|
||||
import com.unisinsight.project.entity.req.ImageToolReq;
|
||||
import com.unisinsight.project.entity.res.ImageToolRes;
|
||||
import com.unisinsight.project.service.ChunkedUploadService;
|
||||
import com.unisinsight.project.service.ImageToolService;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import com.unisinsight.project.exception.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.unisinsight.project.exception.BaseErrorCode;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.unisinsight.project.entity.req.DeleteIdReq;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* (ImageTool)表控制层
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 14:58:39
|
||||
*/
|
||||
@RestController
|
||||
@Api(tags = "工具管理接口")
|
||||
@Slf4j
|
||||
@RequestMapping("/api/nex/v1/image_tool")
|
||||
public class ImageToolController {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@javax.annotation.Resource
|
||||
private ImageToolService service;
|
||||
@Autowired
|
||||
private ChunkedUploadService chunkedUploadService;
|
||||
|
||||
/**
|
||||
* 分页查询所有数据
|
||||
*
|
||||
* @param imageToolReq 查询实体
|
||||
* @return 所有数据
|
||||
*/
|
||||
@PostMapping("/select/page")
|
||||
@ApiOperation(value = "分页查询")
|
||||
public Result selectPage(@RequestBody ImageToolReq imageToolReq) {
|
||||
if (Objects.isNull(imageToolReq)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("分页查询请求参数为:{}", JSONUtil.toJsonStr(imageToolReq));
|
||||
return service.selectPage(imageToolReq);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/add")
|
||||
@ApiOperation(value = "上传ImageTool文件分片", notes = "上传单个ImageTool文件分片,当所有分片上传完成后自动合并文件并保存到数据库")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "chunk", value = "文件分片", required = true, dataType = "__File", paramType = "form"),
|
||||
@ApiImplicitParam(name = "chunk_size", value = "文件分片大小", required = true, dataType = "int", paramType = "query"),
|
||||
@ApiImplicitParam(name = "chunk_md5", value = "文件分片md5", required = true, dataType = "String", paramType = "query"),
|
||||
@ApiImplicitParam(name = "file_id", value = "文件唯一标识符", required = true, dataType = "String", paramType = "query"),
|
||||
@ApiImplicitParam(name = "file_type", value = "文件类型", required = true, dataType = "String", paramType = "query"),
|
||||
@ApiImplicitParam(name = "shard_index", value = "当前分片编号(从1开始)", required = true, dataType = "int", paramType = "query"),
|
||||
@ApiImplicitParam(name = "shard_total", value = "总分片数", required = true, dataType = "int", paramType = "query"),
|
||||
@ApiImplicitParam(name = "file_name", value = "原始文件名", required = true, dataType = "String", paramType = "query"),
|
||||
@ApiImplicitParam(name = "file_size", value = "文件总大小", required = true, dataType = "long", paramType = "query"),
|
||||
@ApiImplicitParam(name = "file_version", value = "文件版本", required = true, dataType = "String", paramType = "query"),
|
||||
@ApiImplicitParam(name = "description", value = "描述", dataType = "String", paramType = "query")
|
||||
})
|
||||
public Result<?> uploadChunk(
|
||||
@RequestParam("chunk") MultipartFile chunk,
|
||||
@RequestParam("chunk_size") int chunkSize,
|
||||
@RequestParam("chunk_md5") String chunkMd5,
|
||||
@RequestParam("file_id") String fileId,
|
||||
@RequestParam("file_type") String fileType,
|
||||
@RequestParam("shard_index") int chunkNumber,
|
||||
@RequestParam("shard_total") int totalChunks,
|
||||
@RequestParam("file_name") String fileName,
|
||||
@RequestParam("file_size") long totalSize,
|
||||
@RequestParam(value = "file_version",required = false) String fileVersion,
|
||||
@RequestParam(value = "description", required = false) String description
|
||||
) {
|
||||
|
||||
return service.uploadChunk(chunk, chunkSize, chunkMd5, fileId, chunkNumber, totalChunks,
|
||||
fileName, totalSize,description,fileType,fileVersion);
|
||||
}
|
||||
@PostMapping("/cancel/upload")
|
||||
@ApiOperation(value = "取消上传ImageTool文件")
|
||||
public Result<?> cancelUpload(@RequestParam("file_id") String fileId) {
|
||||
try {
|
||||
chunkedUploadService.cancelUpload(fileId);
|
||||
return Result.successResult();
|
||||
} catch (Exception e) {
|
||||
log.error("取消上传失败,fileId:{},error:{}", fileId, e.getMessage(), e);
|
||||
return Result.errorResultMessage(BaseErrorCode.HTTP_ERROR_CODE_500, "取消上传失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/download/{id}")
|
||||
@ApiOperation(value = "下载ImageTool文件", notes = "通过文件ID下载对应的ImageTool文件")
|
||||
public ResponseEntity<Resource> downloadFile(@PathVariable Integer id, HttpServletRequest request) {
|
||||
try {
|
||||
// 1. 查询文件信息
|
||||
ImageTool imageTool = service.getById(id);
|
||||
if (imageTool == null || imageTool.getStorePath() == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 2. 获取文件路径
|
||||
String filePath = imageTool.getStorePath();
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 3. 构建响应
|
||||
FileSystemResource resource = new FileSystemResource(file);
|
||||
String contentType = request.getServletContext().getMimeType(file.getAbsolutePath());
|
||||
if(contentType == null) {
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + imageTool.getFileName() + "\"")
|
||||
.body(resource);
|
||||
} catch (Exception e) {
|
||||
log.error("下载文件失败,id:{},error:{}", id, e.getMessage(), e);
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation(value = "修改")
|
||||
@PostMapping("/update")
|
||||
public Result<?> updateUser(@RequestBody ImageToolReq imageToolReq) {
|
||||
if (Objects.isNull(imageToolReq)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("修改请求参数为:{}", JSONUtil.toJsonStr(imageToolReq));
|
||||
return service.update(imageToolReq);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "查询")
|
||||
@PostMapping("/query")
|
||||
public Result<?> queryUser(@RequestBody ImageToolReq imageToolReq) {
|
||||
if (Objects.isNull(imageToolReq)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("查询请求参数为:{}", JSONUtil.toJsonStr(imageToolReq));
|
||||
return service.query(imageToolReq);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,8 +106,26 @@ public class ImageVirtualMachinesController {
|
|||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("终端启动请求参数为:{}", JSONUtil.toJsonStr(req));
|
||||
return service.cloneTemplate(req);
|
||||
return service.start(req);
|
||||
}
|
||||
@ApiOperation(value = "终端关闭")
|
||||
@PostMapping("/shutdown")
|
||||
public Result<?> shutdown(@RequestBody ImageVirtualMachinesReq req) {
|
||||
if (Objects.isNull(req)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("终端关闭请求参数为:{}", JSONUtil.toJsonStr(req));
|
||||
return service.shutdown(req);
|
||||
}
|
||||
// @ApiOperation(value = "终端强制关闭")
|
||||
// @PostMapping("/destroy")
|
||||
// public Result<?> destroy(@RequestBody ImageVirtualMachinesReq req) {
|
||||
// if (Objects.isNull(req)) {
|
||||
// return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
// }
|
||||
// log.info("终端强制关闭请求参数为:{}", JSONUtil.toJsonStr(req));
|
||||
// return service.destroy(req);
|
||||
// }
|
||||
@ApiOperation(value = "终端重启")
|
||||
@PostMapping("/reboot")
|
||||
public Result<?> reboot(@RequestBody ImageVirtualMachinesReq req) {
|
||||
|
|
@ -115,16 +133,7 @@ public class ImageVirtualMachinesController {
|
|||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("终端重启请求参数为:{}", JSONUtil.toJsonStr(req));
|
||||
return service.cloneTemplate(req);
|
||||
}
|
||||
@ApiOperation(value = "终端快照")
|
||||
@PostMapping("/snapshot")
|
||||
public Result<?> snapshot(@RequestBody ImageVirtualMachinesReq req) {
|
||||
if (Objects.isNull(req)) {
|
||||
return Result.errorResult(BaseErrorCode.PARAMS_CHK_ERROR);
|
||||
}
|
||||
log.info("终端快照请求参数为:{}", JSONUtil.toJsonStr(req));
|
||||
return service.cloneTemplate(req);
|
||||
return service.reboot(req);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
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;
|
||||
|
||||
|
||||
/**
|
||||
* (ImageTool)表实体类
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 16:10:59
|
||||
*/
|
||||
@TableName(value = "image_tool")
|
||||
@Data
|
||||
@ApiModel("")
|
||||
public class ImageTool extends Model<ImageTool> {
|
||||
/**
|
||||
* ${column.comment}
|
||||
**/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
@ApiModelProperty("${column.comment}")
|
||||
private Integer id;
|
||||
/**
|
||||
* 文件名称
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "file_name")
|
||||
@ApiModelProperty("文件名称")
|
||||
private String fileName;
|
||||
/**
|
||||
* 文件类型
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "file_type")
|
||||
@ApiModelProperty("文件类型")
|
||||
private String fileType;
|
||||
/**
|
||||
* 文件大小
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "file_size")
|
||||
@ApiModelProperty("文件大小")
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 文件版本
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "file_version")
|
||||
@ApiModelProperty("文件版本")
|
||||
private String fileVersion;
|
||||
/**
|
||||
* 创建时间
|
||||
**/
|
||||
|
||||
|
||||
@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;
|
||||
/**
|
||||
* 上传时间
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "upload_time")
|
||||
@ApiModelProperty("上传时间")
|
||||
private String uploadTime;
|
||||
/**
|
||||
* 存储路径
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "store_path")
|
||||
@ApiModelProperty("存储路径")
|
||||
private String storePath;
|
||||
/**
|
||||
* 备注
|
||||
**/
|
||||
|
||||
|
||||
@TableField(value = "description")
|
||||
@ApiModelProperty("备注")
|
||||
private String description;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.unisinsight.project.entity.enums;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author : ch
|
||||
* @version : 1.0
|
||||
* @ClassName : GrpcTypeEnum
|
||||
* @Description :
|
||||
* @DATE : Created in 9:20 2025/9/1
|
||||
* <pre> Copyright: Copyright(c) 2025 </pre>
|
||||
* <pre> Company : 紫光汇智信息技术有限公司 </pre>
|
||||
* Modification History:
|
||||
* Date Author Version Discription
|
||||
* --------------------------------------------------------------------------
|
||||
* 2025/09/01 ch 1.0 Why & What is modified: <修改原因描述> *
|
||||
*/
|
||||
@Getter
|
||||
public enum GrpcTypeEnum {
|
||||
SHUTDOWN("1", "关机"),
|
||||
IMAGE_UPDATE("2", "镜像列表更新"),
|
||||
USB_ENABLED("3", "USB开启"),
|
||||
USB_DISABLED("4", "USB关闭"),
|
||||
;
|
||||
|
||||
|
||||
private final String type;
|
||||
private final String desc;
|
||||
|
||||
GrpcTypeEnum(String type, String desc) {
|
||||
this.type = type;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,8 +37,8 @@ public class ImageCreateReq {
|
|||
private Integer diskSize;
|
||||
@JsonProperty("disk_format")
|
||||
private String diskFormat;
|
||||
@JsonProperty("storage_pool")
|
||||
private String storagePool;
|
||||
@JsonProperty("storage_pool_name")
|
||||
private String storagePoolName;
|
||||
@JsonProperty("network_name")
|
||||
private String networkName;
|
||||
@JsonProperty("iso_path")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* (ImageTool)表实体类
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 16:10:59
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("")
|
||||
public class ImageToolReq {
|
||||
/**
|
||||
* ${column.comment}
|
||||
**/
|
||||
@JsonProperty("id")
|
||||
@ApiModelProperty("${column.comment}")
|
||||
private Integer id;
|
||||
/**
|
||||
* 文件名称
|
||||
**/
|
||||
@JsonProperty("file_name")
|
||||
@ApiModelProperty("文件名称")
|
||||
private String fileName;
|
||||
/**
|
||||
* 文件类型
|
||||
**/
|
||||
@JsonProperty("file_type")
|
||||
@ApiModelProperty("文件类型")
|
||||
private String fileType;
|
||||
/**
|
||||
* 文件大小
|
||||
**/
|
||||
@JsonProperty("file_size")
|
||||
@ApiModelProperty("文件大小")
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 文件版本
|
||||
**/
|
||||
@JsonProperty("file_version")
|
||||
@ApiModelProperty("文件版本")
|
||||
private String fileVersion;
|
||||
/**
|
||||
* 创建时间
|
||||
**/
|
||||
@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;
|
||||
/**
|
||||
* 上传时间
|
||||
**/
|
||||
@JsonProperty("upload_time")
|
||||
@ApiModelProperty("上传时间")
|
||||
private String uploadTime;
|
||||
/**
|
||||
* 存储路径
|
||||
**/
|
||||
@JsonProperty("store_path")
|
||||
@ApiModelProperty("存储路径")
|
||||
private String storePath;
|
||||
/**
|
||||
* 备注
|
||||
**/
|
||||
@JsonProperty("description")
|
||||
@ApiModelProperty("备注")
|
||||
private String description;
|
||||
/**
|
||||
* 查询页
|
||||
*/
|
||||
@ApiModelProperty(value = "查询页", notes = "分页查询时再传")
|
||||
@JsonProperty("page_num")
|
||||
private Integer pageNum;
|
||||
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
@ApiModelProperty(value = "每页数量", notes = "分页查询时再传")
|
||||
@JsonProperty("page_size")
|
||||
private Integer pageSize;
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +51,21 @@ public class ImageVirtualMachinesReq {
|
|||
@JsonProperty("storage_path")
|
||||
@ApiModelProperty("镜像存储路径")
|
||||
private String storagePath;
|
||||
|
||||
@JsonProperty("storage_pool_name")
|
||||
@ApiModelProperty("存储卷名称")
|
||||
private String storagePoolName;
|
||||
|
||||
|
||||
/**
|
||||
* 驱动名称
|
||||
**/
|
||||
@JsonProperty("image_tool_name")
|
||||
@ApiModelProperty("驱动名称")
|
||||
private String imageToolName;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
**/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* (ImageTool)表实体类
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 16:10:59
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("")
|
||||
public class ImageToolRes implements Serializable {
|
||||
/**
|
||||
* ${column.comment}
|
||||
**/
|
||||
@JsonProperty("id")
|
||||
@ApiModelProperty("${column.comment}")
|
||||
private Integer id;
|
||||
/**
|
||||
* 文件名称
|
||||
**/
|
||||
@JsonProperty("file_name")
|
||||
@ApiModelProperty("文件名称")
|
||||
private String fileName;
|
||||
/**
|
||||
* 文件类型
|
||||
**/
|
||||
@JsonProperty("file_type")
|
||||
@ApiModelProperty("文件类型")
|
||||
private String fileType;
|
||||
/**
|
||||
* 文件大小
|
||||
**/
|
||||
@JsonProperty("file_size")
|
||||
@ApiModelProperty("文件大小")
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 文件版本
|
||||
**/
|
||||
@JsonProperty("file_version")
|
||||
@ApiModelProperty("文件版本")
|
||||
private String fileVersion;
|
||||
/**
|
||||
* 创建时间
|
||||
**/
|
||||
@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;
|
||||
/**
|
||||
* 上传时间
|
||||
**/
|
||||
@JsonProperty("upload_time")
|
||||
@ApiModelProperty("上传时间")
|
||||
private String uploadTime;
|
||||
/**
|
||||
* 存储路径
|
||||
**/
|
||||
@JsonProperty("store_path")
|
||||
@ApiModelProperty("存储路径")
|
||||
private String storePath;
|
||||
/**
|
||||
* 备注
|
||||
**/
|
||||
@JsonProperty("description")
|
||||
@ApiModelProperty("备注")
|
||||
private String description;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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.ImageTool;
|
||||
|
||||
/**
|
||||
* (ImageTool)表数据库访问层
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 15:24:09
|
||||
*/
|
||||
public interface ImageToolMapper extends BaseMapper<ImageTool> {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +43,19 @@ public class ImageConfigProperties {
|
|||
private String updateUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String startUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String shutdownUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String destroyUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String rebootUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String pauseUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String resumeUrl;
|
||||
@Getter(value=AccessLevel.NONE)
|
||||
private String cloneToDesktopUrl;
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -65,4 +78,29 @@ public class ImageConfigProperties {
|
|||
public String getStartUrl() {
|
||||
return baseUrl+startUrl;
|
||||
}
|
||||
|
||||
public String getShutdownUrl() {
|
||||
return baseUrl+shutdownUrl;
|
||||
}
|
||||
|
||||
public String getDestroyUrl() {
|
||||
return baseUrl+destroyUrl;
|
||||
}
|
||||
|
||||
public String getRebootUrl() {
|
||||
return baseUrl+rebootUrl;
|
||||
}
|
||||
|
||||
public String getPauseUrl() {
|
||||
return baseUrl+pauseUrl;
|
||||
}
|
||||
|
||||
public String getResumeUrl() {
|
||||
return baseUrl+resumeUrl;
|
||||
}
|
||||
|
||||
public String getCloneToDesktopUrl() {
|
||||
return baseUrl+cloneToDesktopUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.unisinsight.project.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分片上传完成事件数据
|
||||
*/
|
||||
@Data
|
||||
public class ChunkedUploadCompletion {
|
||||
private String fileId;
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private long fileSize;
|
||||
private String md5; // 文件整体MD5,如果需要的话
|
||||
|
||||
public ChunkedUploadCompletion(String fileId, String fileName, String filePath, long fileSize) {
|
||||
this.fileId = fileId;
|
||||
this.fileName = fileName;
|
||||
this.filePath = filePath;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.unisinsight.project.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 分片上传结果类
|
||||
*/
|
||||
@Data
|
||||
public class ChunkedUploadResult {
|
||||
private boolean success;
|
||||
private String status;
|
||||
private String message;
|
||||
private String filePath;
|
||||
private Integer uploadedChunks;
|
||||
private Integer totalChunks;
|
||||
private Map<String, Object> additionalData = new HashMap<>();
|
||||
|
||||
public static ChunkedUploadResult success(String status, String message) {
|
||||
ChunkedUploadResult result = new ChunkedUploadResult();
|
||||
result.setSuccess(true);
|
||||
result.setStatus(status);
|
||||
result.setMessage(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ChunkedUploadResult success(String status, String message, String filePath) {
|
||||
ChunkedUploadResult result = new ChunkedUploadResult();
|
||||
result.setSuccess(true);
|
||||
result.setStatus(status);
|
||||
result.setMessage(message);
|
||||
result.setFilePath(filePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ChunkedUploadResult uploading(String message, int uploadedChunks, int totalChunks) {
|
||||
ChunkedUploadResult result = new ChunkedUploadResult();
|
||||
result.setSuccess(true);
|
||||
result.setStatus("uploading");
|
||||
result.setMessage(message);
|
||||
result.setUploadedChunks(uploadedChunks);
|
||||
result.setTotalChunks(totalChunks);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ChunkedUploadResult error(String message) {
|
||||
ChunkedUploadResult result = new ChunkedUploadResult();
|
||||
result.setSuccess(false);
|
||||
result.setStatus("error");
|
||||
result.setMessage(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
public ChunkedUploadResult addData(String key, Object value) {
|
||||
this.additionalData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.unisinsight.project.service;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 通用分片上传服务接口
|
||||
*/
|
||||
public interface ChunkedUploadService {
|
||||
|
||||
/**
|
||||
* 上传文件分片
|
||||
*
|
||||
* @param chunk 分片文件
|
||||
* @param chunkSize 分片大小
|
||||
* @param chunkMd5 分片MD5
|
||||
* @param fileId 文件唯一标识符
|
||||
* @param chunkNumber 当前分片编号(从1开始)
|
||||
* @param totalChunks 总分片数
|
||||
* @param fileName 原始文件名
|
||||
* @param totalSize 文件总大小
|
||||
* @param onComplete 上传完成后的回调函数
|
||||
* @return 上传结果
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
ChunkedUploadResult uploadChunk(
|
||||
MultipartFile chunk,
|
||||
int chunkSize,
|
||||
String chunkMd5,
|
||||
String fileId,
|
||||
int chunkNumber,
|
||||
int totalChunks,
|
||||
String fileName,
|
||||
long totalSize,
|
||||
String fileSavePath,
|
||||
Consumer<ChunkedUploadCompletion> onComplete
|
||||
) throws IOException;
|
||||
|
||||
/**
|
||||
* 上传文件分片(无回调)
|
||||
*
|
||||
* @param chunk 分片文件
|
||||
* @param chunkSize 分片大小
|
||||
* @param chunkMd5 分片MD5
|
||||
* @param fileId 文件唯一标识符
|
||||
* @param chunkNumber 当前分片编号(从1开始)
|
||||
* @param totalChunks 总分片数
|
||||
* @param fileName 原始文件名
|
||||
* @param totalSize 文件总大小
|
||||
* @return 上传结果
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
ChunkedUploadResult uploadChunk(
|
||||
MultipartFile chunk,
|
||||
int chunkSize,
|
||||
String chunkMd5,
|
||||
String fileId,
|
||||
int chunkNumber,
|
||||
int totalChunks,
|
||||
String fileName,
|
||||
long totalSize
|
||||
) throws IOException;
|
||||
|
||||
/**
|
||||
* 取消上传文件
|
||||
*
|
||||
* @param fileId 文件唯一标识符
|
||||
*/
|
||||
void cancelUpload(String fileId);
|
||||
|
||||
/**
|
||||
* 查询文件上传状态
|
||||
*
|
||||
* @param fileId 文件唯一标识符
|
||||
* @return 上传状态信息
|
||||
*/
|
||||
ChunkedUploadStatus getUploadStatus(String fileId);
|
||||
|
||||
/**
|
||||
* 合并所有分片文件
|
||||
*
|
||||
* @param fileId 文件唯一标识符
|
||||
* @param outputPath 合并后的文件路径
|
||||
* @param totalChunks 总分片数
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void mergeChunks(String fileId, String outputPath, int totalChunks) throws IOException;
|
||||
|
||||
/**
|
||||
* 清理临时分片文件
|
||||
*
|
||||
* @param fileId 文件唯一标识符
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void cleanupTempFiles(String fileId) throws IOException;
|
||||
|
||||
/**
|
||||
* 根据文件名删除已上传的文件
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void cleanUploadFile(String fileName) throws IOException;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.unisinsight.project.service;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分片上传状态类
|
||||
*/
|
||||
@Data
|
||||
public class ChunkedUploadStatus {
|
||||
private boolean success;
|
||||
private String status;
|
||||
private String message;
|
||||
private String filePath;
|
||||
private Integer uploadedChunks;
|
||||
private Integer totalChunks;
|
||||
private Double progress;
|
||||
|
||||
public static ChunkedUploadStatus completed(String filePath) {
|
||||
ChunkedUploadStatus status = new ChunkedUploadStatus();
|
||||
status.setSuccess(true);
|
||||
status.setStatus("completed");
|
||||
status.setMessage("文件上传已完成");
|
||||
status.setFilePath(filePath);
|
||||
return status;
|
||||
}
|
||||
|
||||
public static ChunkedUploadStatus uploading(int uploadedChunks, int totalChunks) {
|
||||
ChunkedUploadStatus status = new ChunkedUploadStatus();
|
||||
status.setSuccess(true);
|
||||
status.setStatus("uploading");
|
||||
status.setUploadedChunks(uploadedChunks);
|
||||
status.setTotalChunks(totalChunks);
|
||||
status.setProgress((double) uploadedChunks / totalChunks);
|
||||
return status;
|
||||
}
|
||||
|
||||
public static ChunkedUploadStatus notFound() {
|
||||
ChunkedUploadStatus status = new ChunkedUploadStatus();
|
||||
status.setSuccess(true);
|
||||
status.setStatus("not_found");
|
||||
status.setMessage("文件上传信息不存在");
|
||||
return status;
|
||||
}
|
||||
|
||||
public static ChunkedUploadStatus error(String message) {
|
||||
ChunkedUploadStatus status = new ChunkedUploadStatus();
|
||||
status.setSuccess(false);
|
||||
status.setStatus("error");
|
||||
status.setMessage(message);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,4 +22,8 @@ public interface ClientOperateService {
|
|||
Result<?> terminalStart(DeviceReq deviceReq);
|
||||
|
||||
Result<?> terminalEnd(DeviceReq deviceReq);
|
||||
|
||||
Result<?> usbEnabled(DeviceReq deviceReq);
|
||||
|
||||
Result<?> usbDisabled(DeviceReq deviceReq);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.unisinsight.project.service;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.unisinsight.project.entity.dao.ImageTool;
|
||||
import com.unisinsight.project.entity.req.DeleteIdReq;
|
||||
import com.unisinsight.project.entity.req.ImageToolReq;
|
||||
import com.unisinsight.project.entity.res.ImageToolRes;
|
||||
import com.unisinsight.project.entity.res.PageResult;
|
||||
import com.unisinsight.project.exception.Result;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* (ImageTool)表服务接口
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 14:58:39
|
||||
*/
|
||||
public interface ImageToolService extends IService<ImageTool> {
|
||||
Result<PageResult<ImageToolRes>> selectPage(ImageToolReq imageToolReq);
|
||||
|
||||
Result<?> insert(ImageToolReq imageToolReq);
|
||||
|
||||
Result<?> update(ImageToolReq imageToolReq);
|
||||
|
||||
Result<?> query(ImageToolReq imageToolReq);
|
||||
|
||||
Result<?> delete(DeleteIdReq deleteIdReq);
|
||||
|
||||
Result<?> uploadChunk(MultipartFile chunk, int chunkSize, String chunkMd5,
|
||||
String fileId, int chunkNumber,
|
||||
int totalChunks, String fileName,
|
||||
long totalSize, String description
|
||||
, String fileType,String fileVersion);
|
||||
}
|
||||
|
||||
|
|
@ -27,5 +27,35 @@ public interface ImageVirtualMachinesService extends IService<ImageVirtualMachin
|
|||
Result<?> delete(DeleteIdReq deleteIdReq);
|
||||
|
||||
Result<?> cloneTemplate(ImageVirtualMachinesReq req);
|
||||
|
||||
/**
|
||||
* 启动虚拟机
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
Result<?> start(ImageVirtualMachinesReq req);
|
||||
|
||||
/**
|
||||
* 关闭虚拟机
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
Result<?> shutdown(ImageVirtualMachinesReq req);
|
||||
|
||||
/**
|
||||
* 强制关闭虚拟机
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
Result<?> destroy(ImageVirtualMachinesReq req);
|
||||
|
||||
/**
|
||||
* 重启虚拟机
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
Result<?> reboot(ImageVirtualMachinesReq req);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,262 @@
|
|||
package com.unisinsight.project.service.impl;
|
||||
|
||||
import com.unisinsight.project.service.*;
|
||||
import com.unisinsight.project.util.DigestUtil;
|
||||
import com.unisinsight.project.util.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 通用分片上传服务实现类
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ChunkedUploadServiceImpl implements ChunkedUploadService {
|
||||
|
||||
// 临时目录,用于存储上传的分片
|
||||
@Value("${file.upload.temp-dir:${java.io.tmpdir}/chunked-uploads}")
|
||||
private String tempDir;
|
||||
|
||||
// 最终文件存储目录
|
||||
@Value("${file.upload.dir:${user.home}/uploads}")
|
||||
private String uploadDir;
|
||||
|
||||
// 存储每个文件的分片信息
|
||||
private final ConcurrentMap<String, FileUploadInfo> fileUploadMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public ChunkedUploadResult uploadChunk(
|
||||
MultipartFile chunk,
|
||||
int chunkSize,
|
||||
String chunkMd5,
|
||||
String fileId,
|
||||
int chunkNumber,
|
||||
int totalChunks,
|
||||
String fileName,
|
||||
long totalSize,
|
||||
String fileSavePath,
|
||||
Consumer<ChunkedUploadCompletion> onComplete
|
||||
) throws IOException {
|
||||
try {
|
||||
// MD5校验
|
||||
String md5 = DigestUtil.encryptMd5(chunk.getBytes());
|
||||
if (!chunkMd5.equals(md5)) {
|
||||
log.info("分片文件md5校验失败,chunkMd5:{},md5:{}", chunkMd5, md5);
|
||||
throw new RuntimeException("分片文件md5校验失败");
|
||||
}
|
||||
|
||||
// 创建临时目录
|
||||
Path fileTempDir = Paths.get(tempDir, fileId);
|
||||
if (!Files.exists(fileTempDir)) {
|
||||
Files.createDirectories(fileTempDir);
|
||||
}
|
||||
|
||||
// 保存分片文件
|
||||
String chunkFileName = String.format("%05d.part", chunkNumber);
|
||||
log.info("保存分片文件: {}", chunkFileName);
|
||||
Path chunkFilePath = fileTempDir.resolve(chunkFileName);
|
||||
chunk.transferTo(chunkFilePath);
|
||||
|
||||
// 更新文件上传信息
|
||||
FileUploadInfo uploadInfo = fileUploadMap.computeIfAbsent(fileId,
|
||||
id -> new FileUploadInfo(id, fileName, totalChunks, totalSize));
|
||||
uploadInfo.addUploadedChunk(chunkNumber);
|
||||
|
||||
// 检查是否所有分片都已上传
|
||||
if (uploadInfo.isUploadComplete()) {
|
||||
// 合并文件
|
||||
Path finalDir = Paths.get(StringUtil.isNotEmpty(fileSavePath) ? fileSavePath : uploadDir);
|
||||
if (!Files.exists(finalDir)) {
|
||||
Files.createDirectories(finalDir);
|
||||
}
|
||||
Path finalFilePath = finalDir.resolve(fileName);
|
||||
log.info("合并所有分片文件: {}", finalFilePath);
|
||||
mergeChunks(fileId, finalFilePath.toString(), totalChunks);
|
||||
|
||||
// 清理临时文件
|
||||
log.info("清理临时文件: {}", fileId);
|
||||
cleanupTempFiles(fileId);
|
||||
|
||||
// 从上传映射中移除
|
||||
fileUploadMap.remove(fileId);
|
||||
|
||||
// 调用完成回调
|
||||
if (onComplete != null) {
|
||||
ChunkedUploadCompletion completion = new ChunkedUploadCompletion(
|
||||
fileId, fileName, finalFilePath.toString(), totalSize);
|
||||
onComplete.accept(completion);
|
||||
}
|
||||
|
||||
return ChunkedUploadResult.success("completed", "文件上传并合并完成", finalFilePath.toString());
|
||||
} else {
|
||||
return ChunkedUploadResult.uploading("分片上传成功", uploadInfo.getUploadedChunks().size(), totalChunks);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("上传失败,fileId:{},error:{}", fileId, e.getMessage(), e);
|
||||
try {
|
||||
cleanupTempFiles(fileId);
|
||||
cleanUploadFile(fileName);
|
||||
} catch (IOException ex) {
|
||||
log.error("清理临时文件失败,fileId:{},error:{}", fileId, ex.getMessage(), ex);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkedUploadResult uploadChunk(
|
||||
MultipartFile chunk,
|
||||
int chunkSize,
|
||||
String chunkMd5,
|
||||
String fileId,
|
||||
int chunkNumber,
|
||||
int totalChunks,
|
||||
String fileName,
|
||||
long totalSize
|
||||
) throws IOException {
|
||||
return uploadChunk(chunk, chunkSize, chunkMd5, fileId, chunkNumber, totalChunks, fileName, totalSize, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelUpload(String fileId) {
|
||||
if (fileId == null || fileId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
log.info("取消上传,清理临时文件: {}", fileId);
|
||||
try {
|
||||
// 从上传映射中移除
|
||||
fileUploadMap.remove(fileId);
|
||||
cleanupTempFiles(fileId);
|
||||
} catch (IOException ex) {
|
||||
log.error("清理临时文件失败,fileId:{},error:{}", fileId, ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkedUploadStatus getUploadStatus(String fileId) {
|
||||
FileUploadInfo uploadInfo = fileUploadMap.get(fileId);
|
||||
if (uploadInfo == null) {
|
||||
// 检查文件是否已经完成上传并合并
|
||||
try {
|
||||
Path finalFilePath = Paths.get(uploadDir, fileId);
|
||||
if (Files.exists(finalFilePath)) {
|
||||
return ChunkedUploadStatus.completed(finalFilePath.toString());
|
||||
} else {
|
||||
return ChunkedUploadStatus.notFound();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return ChunkedUploadStatus.error("查询状态失败: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
return ChunkedUploadStatus.uploading(uploadInfo.getUploadedChunks().size(), uploadInfo.getTotalChunks());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeChunks(String fileId, String outputPath, int totalChunks) throws IOException {
|
||||
Path finalPath = Paths.get(outputPath);
|
||||
Path tempOutputPath = Paths.get(outputPath + ".tmp");
|
||||
|
||||
try {
|
||||
// 先写入临时文件
|
||||
try (OutputStream outputStream = Files.newOutputStream(tempOutputPath)) {
|
||||
Path fileTempDir = Paths.get(tempDir, fileId);
|
||||
|
||||
// 按顺序合并分片
|
||||
for (int i = 1; i <= totalChunks; i++) {
|
||||
String chunkFileName = String.format("%05d.part", i);
|
||||
Path chunkPath = fileTempDir.resolve(chunkFileName);
|
||||
|
||||
if (!Files.exists(chunkPath)) {
|
||||
throw new IOException("缺少分片文件: " + chunkFileName);
|
||||
}
|
||||
|
||||
// 将分片内容追加到输出文件
|
||||
Files.copy(chunkPath, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
// 原子性地替换目标文件
|
||||
Files.move(tempOutputPath, finalPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} finally {
|
||||
// 确保临时文件被删除
|
||||
if (Files.exists(tempOutputPath)) {
|
||||
Files.delete(tempOutputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupTempFiles(String fileId) throws IOException {
|
||||
Path fileTempDir = Paths.get(tempDir, fileId);
|
||||
if (Files.exists(fileTempDir)) {
|
||||
// 递归删除临时目录及其内容
|
||||
Files.walk(fileTempDir)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUploadFile(String fileName) throws IOException {
|
||||
Path filePath = Paths.get(uploadDir, fileName);
|
||||
if (Files.exists(filePath)) {
|
||||
// 删除文件
|
||||
Files.delete(filePath);
|
||||
log.info("已删除文件: {}", filePath);
|
||||
} else {
|
||||
log.warn("文件不存在,无需删除: {}", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传信息类
|
||||
*/
|
||||
private static class FileUploadInfo {
|
||||
private final String fileId;
|
||||
private final String fileName;
|
||||
private final int totalChunks;
|
||||
private final long totalSize;
|
||||
private final Set<Integer> uploadedChunks;
|
||||
|
||||
public FileUploadInfo(String fileId, String fileName, int totalChunks, long totalSize) {
|
||||
this.fileId = fileId;
|
||||
this.fileName = fileName;
|
||||
this.totalChunks = totalChunks;
|
||||
this.totalSize = totalSize;
|
||||
this.uploadedChunks = ConcurrentHashMap.newKeySet();
|
||||
}
|
||||
|
||||
public void addUploadedChunk(int chunkNumber) {
|
||||
uploadedChunks.add(chunkNumber);
|
||||
}
|
||||
|
||||
public boolean isUploadComplete() {
|
||||
return uploadedChunks.size() == totalChunks;
|
||||
}
|
||||
|
||||
public Set<Integer> getUploadedChunks() {
|
||||
return uploadedChunks;
|
||||
}
|
||||
|
||||
public int getTotalChunks() {
|
||||
return totalChunks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.unisinsight.project.service.impl;
|
||||
|
||||
|
||||
import com.unisinsight.project.entity.enums.GrpcTypeEnum;
|
||||
import com.unisinsight.project.entity.req.DeviceReq;
|
||||
import com.unisinsight.project.exception.Result;
|
||||
import com.unisinsight.project.grpc.generate.NotificationMessage;
|
||||
|
|
@ -35,7 +36,22 @@ public class ClientOperateServiceImpl implements ClientOperateService {
|
|||
|
||||
@Override
|
||||
public Result<?> terminalEnd(DeviceReq deviceReq) {
|
||||
//todo 待客户端确认消息内容后完善
|
||||
return notificationService.sendNotification(deviceReq.getDeviceId(), NotificationMessage.newBuilder().setContent("终端关机").build());
|
||||
return notificationService.sendNotification(deviceReq.getDeviceId(), NotificationMessage.newBuilder()
|
||||
.setType(GrpcTypeEnum.SHUTDOWN.getType())
|
||||
.setContent(GrpcTypeEnum.SHUTDOWN.getDesc()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> usbEnabled(DeviceReq deviceReq) {
|
||||
return notificationService.sendNotification(deviceReq.getDeviceId(), NotificationMessage.newBuilder()
|
||||
.setType(GrpcTypeEnum.USB_ENABLED.getType())
|
||||
.setContent(GrpcTypeEnum.USB_ENABLED.getDesc()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> usbDisabled(DeviceReq deviceReq) {
|
||||
return notificationService.sendNotification(deviceReq.getDeviceId(), NotificationMessage.newBuilder()
|
||||
.setType(GrpcTypeEnum.USB_DISABLED.getType())
|
||||
.setContent(GrpcTypeEnum.USB_DISABLED.getDesc()).build());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ import cn.hutool.json.JSONUtil;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
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.res.ImageDesktopRes;
|
||||
import com.unisinsight.project.entity.res.ImageRes;
|
||||
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.service.ClientService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -33,7 +36,7 @@ import java.util.stream.Collectors;
|
|||
public class ClientServiceImpl implements ClientService {
|
||||
|
||||
@Resource
|
||||
private ImageMapper imageMapper;
|
||||
private ImageDesktopMapper imageDesktopMapper;
|
||||
|
||||
@Resource
|
||||
private DeviceImageMappingMapper deviceImageMappingMapper;
|
||||
|
|
@ -44,6 +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)) {
|
||||
|
|
@ -52,13 +56,13 @@ public class ClientServiceImpl implements ClientService {
|
|||
}
|
||||
List<Long> imageIdList = deviceImageMappings.stream().map(DeviceImageMapping::getImageId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(imageIdList)) {
|
||||
List<Image> images = imageMapper.selectList(new LambdaQueryWrapper<Image>().in(Image::getId, imageIdList));
|
||||
List<ImageDesktop> images = imageDesktopMapper.selectList(new LambdaQueryWrapper<ImageDesktop>().in(ImageDesktop::getId, imageIdList));
|
||||
log.info("用户登录查询镜像结果:{}", JSONUtil.toJsonStr(images));
|
||||
List<ImageRes> imageRes = BeanUtil.copyToList(images, ImageRes.class);
|
||||
List<ImageDesktopRes> imageRes = BeanUtil.copyToList(images, ImageDesktopRes.class);
|
||||
List<HashMap<String, Object>> collect = imageRes.stream().distinct().map(e -> {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
if (StringUtils.isNotBlank(e.getImageFileName())) {
|
||||
map.put("name", e.getImageFileName());
|
||||
if (StringUtils.isNotBlank(e.getDesktopName())) {
|
||||
map.put("name", e.getDesktopName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(e.getBtPath())) {
|
||||
if (e.getBtPath().contains("http://") || e.getBtPath().contains("https://")) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,219 @@
|
|||
package com.unisinsight.project.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.unisinsight.project.entity.dao.ImageTool;
|
||||
import com.unisinsight.project.entity.req.DeleteIdReq;
|
||||
import com.unisinsight.project.entity.req.ImageToolReq;
|
||||
import com.unisinsight.project.entity.res.ImageToolRes;
|
||||
import com.unisinsight.project.entity.res.PageResult;
|
||||
import com.unisinsight.project.exception.BaseErrorCode;
|
||||
import com.unisinsight.project.exception.Result;
|
||||
import com.unisinsight.project.mapper.ImageToolMapper;
|
||||
import com.unisinsight.project.service.ChunkedUploadCompletion;
|
||||
import com.unisinsight.project.service.ChunkedUploadResult;
|
||||
import com.unisinsight.project.service.ChunkedUploadService;
|
||||
import com.unisinsight.project.service.ImageToolService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Slf4j
|
||||
/**
|
||||
* (ImageTool)表服务实现类
|
||||
*
|
||||
* @author ch
|
||||
* @since 2025-08-29 14:58:39
|
||||
*/
|
||||
@Service("imageToolService")
|
||||
public class ImageToolServiceImpl extends ServiceImpl<ImageToolMapper, ImageTool> implements ImageToolService {
|
||||
@Resource
|
||||
private ImageToolMapper mapper;
|
||||
|
||||
@Resource
|
||||
private ChunkedUploadService chunkedUploadService;
|
||||
|
||||
@Value("${file.upload.tool.dir:${user.home}/uploads/tool}")
|
||||
private String uploadDir;
|
||||
|
||||
@Override
|
||||
public Result<PageResult<ImageToolRes>> selectPage(ImageToolReq imageToolReq) {
|
||||
Page<ImageTool> page = new Page<>(imageToolReq.getPageNum(), imageToolReq.getPageSize());
|
||||
LambdaQueryWrapper<ImageTool> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.orderByAsc(ImageTool::getId);
|
||||
Page<ImageTool> imageToolPage = mapper.selectPage(page, queryWrapper);
|
||||
log.info("分页查询返回:{}", JSONUtil.toJsonStr(imageToolPage));
|
||||
if (CollectionUtil.isEmpty(imageToolPage.getRecords())) {
|
||||
log.info("分页查询返回为空");
|
||||
return Result.successResult();
|
||||
} else {
|
||||
PageResult<ImageToolRes> convert = PageResult.convertIPage(imageToolPage, ImageToolRes.class);
|
||||
List<ImageToolRes> data = convert.getData();
|
||||
convert.setData(data);
|
||||
return Result.successResult(convert);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> insert(ImageToolReq imageToolReq) {
|
||||
ImageTool imageTool = BeanUtil.copyProperties(imageToolReq, ImageTool.class);
|
||||
imageTool.setCreateUser("admin");
|
||||
imageTool.setUpdateUser("admin");
|
||||
int insert = mapper.insert(imageTool);
|
||||
log.info("新增insert:{}", insert);
|
||||
|
||||
if (insert == 1) {
|
||||
return Result.successResult();
|
||||
} else {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> update(ImageToolReq imageToolReq) {
|
||||
ImageTool imageTool = BeanUtil.copyProperties(imageToolReq, ImageTool.class);
|
||||
imageTool.setUpdateUser("admin");
|
||||
int updated = mapper.updateById(imageTool);
|
||||
log.info("修改updated:{}", updated);
|
||||
if (updated == 1) {
|
||||
return Result.successResult();
|
||||
} else {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> query(ImageToolReq imageToolReq) {
|
||||
ImageTool imageTool = mapper.selectById(imageToolReq.getId());
|
||||
|
||||
if (ObjectUtils.isEmpty(imageTool)) {
|
||||
log.info("查询返回为空");
|
||||
return Result.successResult();
|
||||
}
|
||||
ImageToolRes res = BeanUtil.copyProperties(imageTool, ImageToolRes.class);
|
||||
log.info("查询返回:{}", JSONUtil.toJsonStr(res));
|
||||
return Result.successResult(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> delete(DeleteIdReq deleteIdReq) {
|
||||
int deleted = mapper.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<?> uploadChunk(MultipartFile chunk, int chunkSize, String chunkMd5, String fileId, int chunkNumber,
|
||||
int totalChunks, String fileName, long totalSize, String description
|
||||
, String fileType, String fileVersion) {
|
||||
LambdaQueryWrapper<ImageTool> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImageTool::getFileName, fileName);
|
||||
ImageTool exists = mapper.selectOne(queryWrapper);
|
||||
if (exists!=null){
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500,"文件名重复");
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
try {
|
||||
ChunkedUploadResult result = chunkedUploadService.uploadChunk(
|
||||
chunk, chunkSize, chunkMd5, fileId, chunkNumber, totalChunks,
|
||||
fileName, totalSize, uploadDir, completion -> {
|
||||
try {
|
||||
// 文件上传完成,保存到数据库
|
||||
ImageTool imageTool = new ImageTool();
|
||||
imageTool.setFileName(completion.getFileName());
|
||||
imageTool.setFileType(getFileType(completion.getFileName()));
|
||||
imageTool.setFileSize(completion.getFileSize());
|
||||
imageTool.setFileVersion(fileVersion);
|
||||
imageTool.setFileType(fileType);
|
||||
imageTool.setStorePath(completion.getFilePath());
|
||||
imageTool.setUploadTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
imageTool.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
imageTool.setCreateUser("admin");
|
||||
imageTool.setUpdateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
imageTool.setUpdateUser("admin");
|
||||
if (description != null && !description.isEmpty()) {
|
||||
imageTool.setDescription(description);
|
||||
}
|
||||
|
||||
boolean save = this.save(imageTool);
|
||||
log.info("ImageTool新增结果: {}", save);
|
||||
|
||||
if (!save) {
|
||||
// 如果保存数据库失败,删除已上传的文件
|
||||
try {
|
||||
chunkedUploadService.cleanUploadFile(completion.getFileName());
|
||||
} catch (Exception e) {
|
||||
log.error("清理上传文件失败,fileName:{},error:{}", completion.getFileName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("保存ImageTool失败,fileId:{},error:{}", completion.getFileId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
);
|
||||
if (result.isSuccess()) {
|
||||
if ("completed".equals(result.getStatus())) {
|
||||
response.put("status", "completed");
|
||||
response.put("message", "文件上传并合并完成");
|
||||
response.put("filePath", result.getFilePath());
|
||||
} else {
|
||||
response.put("status", "uploading");
|
||||
response.put("message", "分片上传成功");
|
||||
response.put("uploadedChunks", result.getUploadedChunks());
|
||||
response.put("totalChunks", totalChunks);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
response.put("success", false);
|
||||
response.put("status", "error");
|
||||
response.put("message", "上传失败");
|
||||
log.info("上次失败清理临时文件: {},error:{}", fileId, e.getMessage(), e);
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
|
||||
}
|
||||
|
||||
response.put("success", true);
|
||||
return Result.successResult(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据文件名获取文件类型
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 文件类型
|
||||
*/
|
||||
private String getFileType(String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
int lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
|
||||
return fileName.substring(lastDotIndex + 1).toLowerCase();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9,9 +9,14 @@ 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.ImageTool;
|
||||
import com.unisinsight.project.entity.dao.ImageVirtualMachines;
|
||||
import com.unisinsight.project.entity.dao.ImageDesktop;
|
||||
import com.unisinsight.project.entity.req.DeleteIdReq;
|
||||
import com.unisinsight.project.entity.req.ImageCloneToDesktopReq;
|
||||
import com.unisinsight.project.entity.req.ImageCreateReq;
|
||||
import com.unisinsight.project.entity.req.ImageDeleteReq;
|
||||
import com.unisinsight.project.entity.req.ImageOperationReq;
|
||||
import com.unisinsight.project.entity.req.ImageUpdateReq;
|
||||
import com.unisinsight.project.entity.req.ImageVirtualMachinesReq;
|
||||
import com.unisinsight.project.entity.res.ImageStatusRes;
|
||||
|
|
@ -21,7 +26,9 @@ 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.ImageDesktopService;
|
||||
import com.unisinsight.project.service.ImageService;
|
||||
import com.unisinsight.project.service.ImageToolService;
|
||||
import com.unisinsight.project.service.ImageVirtualMachinesService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
|
@ -54,7 +61,10 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
private RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private ImageConfigProperties imageConfigProperties;
|
||||
|
||||
@Resource
|
||||
private ImageToolService imageToolService;
|
||||
@Autowired
|
||||
private ImageDesktopService imageDesktopService;
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -124,11 +134,26 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
|
||||
@Override
|
||||
public Result<?> insert(ImageVirtualMachinesReq imageVirtualMachinesReq) {
|
||||
QueryWrapper<ImageVirtualMachines> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda().eq(ImageVirtualMachines::getImageName, imageVirtualMachinesReq.getImageName());
|
||||
ImageVirtualMachines selectOne = machinesMapper.selectOne(queryWrapper);
|
||||
if (ObjectUtils.isNotEmpty(selectOne)) {
|
||||
return Result.errorResultMessage(BaseErrorCode.HTTP_ERROR_CODE_500, "名称重复");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 调用镜像生成服务
|
||||
LambdaQueryWrapper<Image> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
imageLambdaQueryWrapper.in(Image::getId, imageVirtualMachinesReq.getImageSystemId());
|
||||
Image systemImage = imageService.getOne(imageLambdaQueryWrapper);
|
||||
//查询驱动信息
|
||||
LambdaQueryWrapper<ImageTool> imageToolLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
imageToolLambdaQueryWrapper.eq(ImageTool::getFileName, imageVirtualMachinesReq.getImageName());
|
||||
ImageTool imageTool = imageToolService.getOne(imageToolLambdaQueryWrapper);
|
||||
|
||||
|
||||
|
||||
ImageCreateReq createReq = ImageCreateReq.builder()
|
||||
.name(imageVirtualMachinesReq.getImageName())
|
||||
.osType(imageVirtualMachinesReq.getOsVersion())
|
||||
|
|
@ -137,11 +162,11 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
.memory(imageVirtualMachinesReq.getMemoryTotal())
|
||||
.diskSize(imageVirtualMachinesReq.getSystemTotal())
|
||||
//存储池
|
||||
// .storagePool(imageVirtualMachinesReq.getStoragePath())
|
||||
.storagePoolName(imageVirtualMachinesReq.getStoragePoolName())
|
||||
.networkName(imageVirtualMachinesReq.getNetworkModule())
|
||||
.isoPath(imageVirtualMachinesReq.getStoragePath())
|
||||
//驱动
|
||||
// .virtioWinPath(imageVirtualMachinesReq.getStoragePath())
|
||||
.virtioWinPath(imageTool.getStorePath())
|
||||
.description(imageVirtualMachinesReq.getDescription())
|
||||
|
||||
.build();
|
||||
|
|
@ -154,12 +179,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
@ -167,7 +187,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
if (insert == 1) {
|
||||
return Result.successResult();
|
||||
} else {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500);
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500,"新增失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +239,27 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
|
||||
@Override
|
||||
public Result<?> delete(DeleteIdReq deleteIdReq) {
|
||||
// 先查询要删除的虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(deleteIdReq.getId());
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用镜像删除服务
|
||||
ImageDeleteReq deleteReq = ImageDeleteReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.deleteStorage(true)
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ImageDeleteReq> requestEntity = new HttpEntity<>(deleteReq, headers);
|
||||
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(imageConfigProperties.getDeleteUrl(), HttpMethod.POST, requestEntity, ImageStatusRes.class);
|
||||
if (!exchange.getStatusCode().equals(HttpStatus.OK)) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "删除虚拟机失败");
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
int deleted = machinesMapper.deleteById(deleteIdReq.getId());
|
||||
log.info("终端删除delete:{}", deleted);
|
||||
if (deleted == 1) {
|
||||
|
|
@ -230,8 +271,150 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
|
||||
@Override
|
||||
public Result<?> cloneTemplate(ImageVirtualMachinesReq req) {
|
||||
//todo 调用镜像生成服务 在生成完毕后生成桌面镜像数据 桌面镜像加BT
|
||||
return null;
|
||||
}
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用克隆虚拟机到桌面镜像服务
|
||||
ImageCloneToDesktopReq cloneReq = ImageCloneToDesktopReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.desktopName(req.getImageName() + "_desktop") // 使用虚拟机名称加desktop作为桌面镜像名称
|
||||
.storagePath("/vms/iso") // 默认存储路径
|
||||
.description("从虚拟机克隆的桌面镜像")
|
||||
.build();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ImageCloneToDesktopReq> requestEntity = new HttpEntity<>(cloneReq, headers);
|
||||
String cloneToDesktopUrl = imageConfigProperties.getCloneToDesktopUrl();
|
||||
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(cloneToDesktopUrl, HttpMethod.POST, requestEntity, ImageStatusRes.class);
|
||||
if (!exchange.getStatusCode().equals(HttpStatus.OK)) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "克隆虚拟机到桌面镜像失败");
|
||||
}
|
||||
|
||||
// 克隆成功后生成桌面镜像数据
|
||||
ImageDesktop imageDesktop = new ImageDesktop();
|
||||
imageDesktop.setDesktopName(req.getImageName() + "_desktop");
|
||||
imageDesktop.setImageVirtualId(Math.toIntExact(req.getId()));
|
||||
imageDesktop.setOsVersion(imageVirtualMachines.getOsVersion());
|
||||
// imageDesktop.setStoragePath("/vms/iso/" + req.getImageName() + "_desktop.qcow2"); // 假设是qcow2格式
|
||||
// imageDesktop.setDesktopType(3); // QCOW2格式
|
||||
// imageDesktop.setPublishStatus("unpublished"); // 默认未发布状态
|
||||
imageDesktop.setCreateUser("admin");
|
||||
imageDesktop.setUpdateUser("admin");
|
||||
imageDesktop.setDescription("从虚拟机克隆的桌面镜像");
|
||||
|
||||
// 保存桌面镜像数据
|
||||
boolean saved = imageDesktopService.save(imageDesktop);
|
||||
if (!saved) {
|
||||
log.error("保存桌面镜像数据失败");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "保存桌面镜像数据失败");
|
||||
}
|
||||
|
||||
return Result.successResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> start(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用启动虚拟机服务
|
||||
ImageOperationReq operationReq = ImageOperationReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ImageOperationReq> requestEntity = new HttpEntity<>(operationReq, headers);
|
||||
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(imageConfigProperties.getStartUrl(), HttpMethod.POST, requestEntity, ImageStatusRes.class);
|
||||
if (!exchange.getStatusCode().equals(HttpStatus.OK)) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "启动虚拟机失败");
|
||||
}
|
||||
|
||||
return Result.successResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> shutdown(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用关闭虚拟机服务
|
||||
ImageOperationReq operationReq = ImageOperationReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ImageOperationReq> requestEntity = new HttpEntity<>(operationReq, headers);
|
||||
String shutdownUrl = imageConfigProperties.getShutdownUrl();
|
||||
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(shutdownUrl, HttpMethod.POST, requestEntity, ImageStatusRes.class);
|
||||
if (!exchange.getStatusCode().equals(HttpStatus.OK)) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "关闭虚拟机失败");
|
||||
}
|
||||
|
||||
return Result.successResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> destroy(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用强制关闭虚拟机服务
|
||||
ImageOperationReq operationReq = ImageOperationReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ImageOperationReq> requestEntity = new HttpEntity<>(operationReq, headers);
|
||||
String destroyUrl = imageConfigProperties.getDestroyUrl();
|
||||
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(destroyUrl, HttpMethod.POST, requestEntity, ImageStatusRes.class);
|
||||
if (!exchange.getStatusCode().equals(HttpStatus.OK)) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "强制关闭虚拟机失败");
|
||||
}
|
||||
|
||||
return Result.successResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> reboot(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用重启虚拟机服务
|
||||
ImageOperationReq operationReq = ImageOperationReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<ImageOperationReq> requestEntity = new HttpEntity<>(operationReq, headers);
|
||||
String rebootUrl = imageConfigProperties.getRebootUrl();
|
||||
ResponseEntity<ImageStatusRes> exchange = restTemplate.exchange(rebootUrl, HttpMethod.POST, requestEntity, ImageStatusRes.class);
|
||||
if (!exchange.getStatusCode().equals(HttpStatus.OK)) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "重启虚拟机失败");
|
||||
}
|
||||
|
||||
return Result.successResult();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,21 @@ file:
|
|||
temp-dir: /var/lib/vdi/tmp/chunked-uploads
|
||||
dir: /var/lib/vdi/test
|
||||
bt-url: http://10.100.51.86:8114
|
||||
tool:
|
||||
dir: /vms/tool/iso
|
||||
image:
|
||||
base-url: http://10.100.51.118:5173
|
||||
base-url: http://10.100.51.178: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
|
||||
shutdown-url: /api/v1/vm/shutdown
|
||||
destroy-url: /api/v1/vm/destroy
|
||||
reboot-url: /api/v1/vm/reboot
|
||||
pause-url: /api/v1/vm/pause
|
||||
resume-url: /api/v1/vm/resume
|
||||
clone-to-desktop-url: /api/v1/vm/clone-to-desktop
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?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.ImageToolMapper">
|
||||
|
||||
<resultMap type="com.unisinsight.project.entity.dao.ImageTool" id="ImageToolMap">
|
||||
<result property="id" column="id" jdbcType="INTEGER"/>
|
||||
<result property="fileName" column="file_name" jdbcType="VARCHAR"/>
|
||||
<result property="fileType" column="file_type" jdbcType="VARCHAR"/>
|
||||
<result property="fileSize" column="file_size" jdbcType="INTEGER"/>
|
||||
<result property="fileVersion" column="file_version" jdbcType="VARCHAR"/>
|
||||
<result property="createTime" column="create_time" jdbcType="VARCHAR"/>
|
||||
<result property="createUser" column="create_user" jdbcType="VARCHAR"/>
|
||||
<result property="updateTime" column="update_time" jdbcType="VARCHAR"/>
|
||||
<result property="updateUser" column="update_user" jdbcType="VARCHAR"/>
|
||||
<result property="uploadTime" column="upload_time" jdbcType="VARCHAR"/>
|
||||
<result property="storePath" column="store_path" jdbcType="VARCHAR"/>
|
||||
<result property="description" column="description" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id, file_name, file_type, file_size, file_version, create_time, create_user, update_time, update_user, upload_time, store_path, description
|
||||
</sql>
|
||||
|
||||
|
||||
</mapper>
|
||||
|
||||
Loading…
Reference in New Issue