feat(file-upload): 实现分片上传功能并添加 USB 控制接口
- 新增分片上传相关类和接口,包括 ChunkedUploadService、ChunkedUploadResult等 - 实现分片上传、合并、取消上传等功能 - 添加 USB 开启和关闭控制接口 - 新增 ImageTool 相关实体和接口,用于管理工具文件master
parent
70f6ade5db
commit
4b7772b38d
|
|
@ -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,141 @@
|
|||
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.web.bind.annotation.*;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
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 {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@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("file_version") String fileVersion,
|
||||
@RequestParam(value = "description", required = false) String description
|
||||
) {
|
||||
|
||||
return service.uploadChunk(chunk, chunkSize, chunkMd5, fileId, chunkNumber, totalChunks,
|
||||
fileName, totalSize,description);
|
||||
}
|
||||
@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());
|
||||
}
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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> {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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,32 @@
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
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) {
|
||||
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.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,6 +9,7 @@ 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.req.DeleteIdReq;
|
||||
import com.unisinsight.project.entity.req.ImageCreateReq;
|
||||
|
|
@ -22,6 +23,7 @@ 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.ImageToolService;
|
||||
import com.unisinsight.project.service.ImageVirtualMachinesService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
|
@ -54,7 +56,8 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
private RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private ImageConfigProperties imageConfigProperties;
|
||||
|
||||
@Resource
|
||||
private ImageToolService imageToolService;
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -124,11 +127,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,7 +155,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
.memory(imageVirtualMachinesReq.getMemoryTotal())
|
||||
.diskSize(imageVirtualMachinesReq.getSystemTotal())
|
||||
//存储池
|
||||
// .storagePool(imageVirtualMachinesReq.getStoragePath())
|
||||
.storagePoolName(imageVirtualMachinesReq.getStoragePoolName())
|
||||
.networkName(imageVirtualMachinesReq.getNetworkModule())
|
||||
.isoPath(imageVirtualMachinesReq.getStoragePath())
|
||||
//驱动
|
||||
|
|
@ -154,12 +172,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 +180,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,"新增失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ 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
|
||||
|
|
|
|||
|
|
@ -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