批量上传优化.

This commit is contained in:
lijiahangmax
2024-05-11 00:16:42 +08:00
parent 0a43e5db45
commit 978d94dddf
21 changed files with 274 additions and 111 deletions

View File

@@ -11,6 +11,7 @@ import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest; import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest; import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest;
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO; import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskStatusVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskVO; import com.orion.ops.module.asset.entity.vo.UploadTaskVO;
import com.orion.ops.module.asset.service.UploadTaskService; import com.orion.ops.module.asset.service.UploadTaskService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -40,8 +41,6 @@ import java.util.List;
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"}) @SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class UploadTaskController { public class UploadTaskController {
// TODO 前端日志 测试删除慢吗
@Resource @Resource
private UploadTaskService uploadTaskService; private UploadTaskService uploadTaskService;
@@ -92,7 +91,7 @@ public class UploadTaskController {
@Operation(summary = "查询上传状态") @Operation(summary = "查询上传状态")
@Parameter(name = "id", description = "id", required = true) @Parameter(name = "id", description = "id", required = true)
@PreAuthorize("@ss.hasPermission('asset:upload-task:query')") @PreAuthorize("@ss.hasPermission('asset:upload-task:query')")
public List<UploadTaskVO> getUploadTaskStatus(@RequestParam("idList") List<Long> idList, @RequestParam("queryFiles") Boolean queryFiles) { public List<UploadTaskStatusVO> getUploadTaskStatus(@RequestParam("idList") List<Long> idList, @RequestParam("queryFiles") Boolean queryFiles) {
return uploadTaskService.getUploadTaskStatus(idList, queryFiles); return uploadTaskService.getUploadTaskStatus(idList, queryFiles);
} }

View File

@@ -3,6 +3,7 @@ package com.orion.ops.module.asset.convert;
import com.orion.ops.module.asset.entity.domain.UploadTaskDO; import com.orion.ops.module.asset.entity.domain.UploadTaskDO;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest; import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest; import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
import com.orion.ops.module.asset.entity.vo.UploadTaskStatusVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskVO; import com.orion.ops.module.asset.entity.vo.UploadTaskVO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@@ -27,6 +28,8 @@ public interface UploadTaskConvert {
UploadTaskVO to(UploadTaskDO domain); UploadTaskVO to(UploadTaskDO domain);
List<UploadTaskVO> to(List<UploadTaskDO> list); List<UploadTaskVO> toList(List<UploadTaskDO> list);
UploadTaskStatusVO toStatus(UploadTaskDO domain);
} }

View File

@@ -56,6 +56,14 @@ public class UploadTaskDO extends BaseDO {
@TableField("extra_info") @TableField("extra_info")
private String extraInfo; private String extraInfo;
@Schema(description = "文件数量")
@TableField("file_count")
private Integer fileCount;
@Schema(description = "主机数量")
@TableField("host_count")
private Integer hostCount;
@Schema(description = "开始时间") @Schema(description = "开始时间")
@TableField("start_time") @TableField("start_time")
private Date startTime; private Date startTime;

View File

@@ -38,6 +38,9 @@ public class HostTerminalConnectDTO {
@Schema(description = "主机地址") @Schema(description = "主机地址")
private String hostAddress; private String hostAddress;
@Schema(description = "系统类型")
private String osType;
@Schema(description = "端口") @Schema(description = "端口")
private Integer port; private Integer port;

View File

@@ -0,0 +1,44 @@
package com.orion.ops.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 上传任务状态 视图响应对象
*
* @author Jiahang Li
* @version 1.0.7
* @since 2024-5-7 22:15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "UploadTaskStatusVO", description = "上传任务状态 视图响应对象")
public class UploadTaskStatusVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "状态")
private String status;
@Schema(description = "开始时间")
private Date startTime;
@Schema(description = "结束时间")
private Date endTime;
@Schema(description = "上传文件")
private List<UploadTaskFileVO> files;
}

View File

@@ -47,6 +47,12 @@ public class UploadTaskVO implements Serializable {
@Schema(description = "额外信息") @Schema(description = "额外信息")
private String extraInfo; private String extraInfo;
@Schema(description = "文件数量")
private Integer fileCount;
@Schema(description = "主机数量")
private Integer hostCount;
@Schema(description = "开始时间") @Schema(description = "开始时间")
private Date startTime; private Date startTime;

View File

@@ -1,6 +1,7 @@
package com.orion.ops.module.asset.handler.host.upload.task; package com.orion.ops.module.asset.handler.host.upload.task;
import com.orion.lang.utils.Threads; import com.orion.lang.utils.Threads;
import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.io.Streams; import com.orion.lang.utils.io.Streams;
import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.module.asset.dao.UploadTaskDAO; import com.orion.ops.module.asset.dao.UploadTaskDAO;
@@ -136,7 +137,7 @@ public class FileUploadTask implements IFileUploadTask {
.map(s -> FileUploadFileItemDTO.builder() .map(s -> FileUploadFileItemDTO.builder()
.id(s.getId()) .id(s.getId())
.fileId(s.getFileId()) .fileId(s.getFileId())
.remotePath(record.getRemotePath() + Const.SLASH + s.getFilePath()) .remotePath(Files1.getPath(Const.SLASH + record.getRemotePath() + Const.SLASH + s.getFilePath()))
.status(UploadTaskFileStatusEnum.WAITING.name()) .status(UploadTaskFileStatusEnum.WAITING.name())
.current(0L) .current(0L)
.build()) .build())

View File

@@ -1,15 +1,19 @@
package com.orion.ops.module.asset.handler.host.upload.uploader; package com.orion.ops.module.asset.handler.host.upload.uploader;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Maps;
import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.io.Streams; import com.orion.lang.utils.io.Streams;
import com.orion.net.host.SessionStore; import com.orion.net.host.SessionStore;
import com.orion.net.host.sftp.SftpExecutor; import com.orion.net.host.sftp.SftpExecutor;
import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.file.FileClient; import com.orion.ops.framework.common.file.FileClient;
import com.orion.ops.framework.common.utils.PathUtils;
import com.orion.ops.module.asset.dao.UploadTaskFileDAO; import com.orion.ops.module.asset.dao.UploadTaskFileDAO;
import com.orion.ops.module.asset.define.config.AppSftpConfig; import com.orion.ops.module.asset.define.config.AppSftpConfig;
import com.orion.ops.module.asset.entity.domain.UploadTaskFileDO; import com.orion.ops.module.asset.entity.domain.UploadTaskFileDO;
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO; import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
import com.orion.ops.module.asset.enums.HostSshOsTypeEnum;
import com.orion.ops.module.asset.enums.UploadTaskFileStatusEnum; import com.orion.ops.module.asset.enums.UploadTaskFileStatusEnum;
import com.orion.ops.module.asset.handler.host.upload.dto.FileUploadFileItemDTO; import com.orion.ops.module.asset.handler.host.upload.dto.FileUploadFileItemDTO;
import com.orion.ops.module.asset.service.HostTerminalService; import com.orion.ops.module.asset.service.HostTerminalService;
@@ -23,6 +27,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -100,9 +105,10 @@ public class FileUploader implements IFileUploader {
private boolean initSession() { private boolean initSession() {
log.info("HostFileUploader.initSession start taskId: {}, hostId: {}", taskId, hostId); log.info("HostFileUploader.initSession start taskId: {}, hostId: {}", taskId, hostId);
try { try {
// TODO 测试看看有没有问题, 则修改为 打开 executor 后 是否会connect, 不需要的话就关闭 executor 然后重新打开 // 替换用户路径
// 打开会话
HostTerminalConnectDTO connectInfo = hostTerminalService.getTerminalConnectInfo(hostId); HostTerminalConnectDTO connectInfo = hostTerminalService.getTerminalConnectInfo(hostId);
this.replaceRemotePathVariable(connectInfo.getOsType(), connectInfo.getUsername());
// 打开会话
this.sessionStore = hostTerminalService.openSessionStore(connectInfo); this.sessionStore = hostTerminalService.openSessionStore(connectInfo);
this.executor = sessionStore.getSftpExecutor(connectInfo.getFileNameCharset()); this.executor = sessionStore.getSftpExecutor(connectInfo.getFileNameCharset());
executor.connect(); executor.connect();
@@ -215,6 +221,27 @@ public class FileUploader implements IFileUploader {
uploadTaskFileDAO.updateById(update); uploadTaskFileDAO.updateById(update);
} }
/**
* 替换文件路径变量
*
* @param osType osType
* @param username username
*/
private void replaceRemotePathVariable(String osType, String username) {
// 包含变量
if (!files.get(0).getRemotePath().contains(Const.DOLLAR)) {
return;
}
String home = PathUtils.getHomePath(HostSshOsTypeEnum.WINDOWS.name().equals(osType), username);
// 替换变量
Map<String, String> env = Maps.newMap(4);
env.put("username", username);
env.put("home", home);
for (FileUploadFileItemDTO file : files) {
file.setRemotePath(Files1.getPath(Strings.format(file.getRemotePath(), env)));
}
}
@Override @Override
public void cancel() { public void cancel() {
log.info("HostFileUploader.cancel taskId: {}, hostId: {}, canceled: {}, closed: {}", taskId, hostId, canceled, closed); log.info("HostFileUploader.cancel taskId: {}, hostId: {}, canceled: {}, closed: {}", taskId, hostId, canceled, closed);

View File

@@ -5,6 +5,7 @@ import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest; import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest; import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest;
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO; import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskStatusVO;
import com.orion.ops.module.asset.entity.vo.UploadTaskVO; import com.orion.ops.module.asset.entity.vo.UploadTaskVO;
import java.util.List; import java.util.List;
@@ -51,7 +52,7 @@ public interface UploadTaskService {
* @param queryFiles queryFiles * @param queryFiles queryFiles
* @return rows * @return rows
*/ */
List<UploadTaskVO> getUploadTaskStatus(List<Long> idList, Boolean queryFiles); List<UploadTaskStatusVO> getUploadTaskStatus(List<Long> idList, Boolean queryFiles);
/** /**
* 获取上传任务数量 * 获取上传任务数量

View File

@@ -241,6 +241,7 @@ public class HostTerminalServiceImpl implements HostTerminalService {
conn.setHostId(host.getId()); conn.setHostId(host.getId());
conn.setHostName(host.getName()); conn.setHostName(host.getName());
conn.setHostAddress(host.getAddress()); conn.setHostAddress(host.getAddress());
conn.setOsType(config.getOsType());
conn.setPort(config.getPort()); conn.setPort(config.getPort());
conn.setTimeout(config.getConnectTimeout()); conn.setTimeout(config.getConnectTimeout());
conn.setCharset(config.getCharset()); conn.setCharset(config.getCharset());

View File

@@ -112,6 +112,8 @@ public class UploadTaskServiceImpl implements UploadTaskService {
record.setUsername(user.getUsername()); record.setUsername(user.getUsername());
record.setDescription(Strings.def(record.getDescription(), () -> Strings.format(DEFAULT_DESC, Dates.current()))); record.setDescription(Strings.def(record.getDescription(), () -> Strings.format(DEFAULT_DESC, Dates.current())));
record.setStatus(UploadTaskStatusEnum.WAITING.name()); record.setStatus(UploadTaskStatusEnum.WAITING.name());
record.setFileCount(files.size());
record.setHostCount(hostIdList.size());
UploadTaskExtraDTO extra = UploadTaskExtraDTO.builder() UploadTaskExtraDTO extra = UploadTaskExtraDTO.builder()
.hostIdList(hostIdList) .hostIdList(hostIdList)
.hosts(hosts) .hosts(hosts)
@@ -159,7 +161,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
// 计算传输进度 // 计算传输进度
this.computeUploadProgress(id, files); this.computeUploadProgress(id, files);
// 设置任务文件 // 设置任务文件
this.setTaskFiles(uploadTask, files); this.setTaskHostFiles(uploadTask, files);
return uploadTask; return uploadTask;
} }
@@ -174,40 +176,39 @@ public class UploadTaskServiceImpl implements UploadTaskService {
} }
@Override @Override
public List<UploadTaskVO> getUploadTaskStatus(List<Long> idList, Boolean queryFiles) { public List<UploadTaskStatusVO> getUploadTaskStatus(List<Long> idList, Boolean queryFiles) {
// 查询任务 // 查询任务
List<UploadTaskVO> tasks = uploadTaskDAO.of() List<UploadTaskStatusVO> tasks = uploadTaskDAO.of()
.createWrapper() .createWrapper()
.select(UploadTaskDO::getId, UploadTaskDO::getStatus, .select(UploadTaskDO::getId, UploadTaskDO::getStatus,
UploadTaskDO::getStartTime, UploadTaskDO::getEndTime) UploadTaskDO::getStartTime, UploadTaskDO::getEndTime)
.in(UploadTaskDO::getId, idList) .in(UploadTaskDO::getId, idList)
.then() .then()
.list(UploadTaskConvert.MAPPER::to); .list(UploadTaskConvert.MAPPER::toStatus);
if (!Booleans.isTrue(queryFiles)) { if (!Booleans.isTrue(queryFiles)) {
return tasks; return tasks;
} }
// 查询任务文件 // 查询任务文件
Map<Long, List<UploadTaskFileVO>> filesMap = uploadTaskFileDAO.of() Map<Long, List<UploadTaskFileVO>> taskFilesMap = uploadTaskFileDAO.of()
.createWrapper() .createWrapper()
.select(UploadTaskFileDO::getId, UploadTaskFileDO::getTaskId, .select(UploadTaskFileDO::getId, UploadTaskFileDO::getTaskId, UploadTaskFileDO::getHostId,
UploadTaskFileDO::getHostId, UploadTaskFileDO::getStatus, UploadTaskFileDO::getStatus, UploadTaskFileDO::getFileSize,
UploadTaskFileDO::getStartTime, UploadTaskFileDO::getEndTime) UploadTaskFileDO::getStartTime, UploadTaskFileDO::getEndTime)
.in(UploadTaskFileDO::getTaskId, idList) .in(UploadTaskFileDO::getTaskId, idList)
.then() .then()
.stream() .stream()
.map(UploadTaskFileConvert.MAPPER::to) .map(UploadTaskFileConvert.MAPPER::to)
.collect(Collectors.groupingBy(UploadTaskFileVO::getTaskId)); .collect(Collectors.groupingBy(UploadTaskFileVO::getTaskId));
for (UploadTaskVO task : tasks) { for (UploadTaskStatusVO task : tasks) {
Long id = task.getId(); Long id = task.getId();
List<UploadTaskFileVO> files = filesMap.get(id); List<UploadTaskFileVO> files = taskFilesMap.get(id);
if (files == null) { if (files == null) {
files = Lists.empty(); files = Lists.empty();
} else { } else {
// 计算进度 // 计算进度
this.computeUploadProgress(id, files); this.computeUploadProgress(id, files);
// 设置任务文件
} }
this.setTaskFiles(task, files); task.setFiles(files);
} }
return tasks; return tasks;
} }
@@ -311,8 +312,8 @@ public class UploadTaskServiceImpl implements UploadTaskService {
return uploadTaskDAO.wrapper() return uploadTaskDAO.wrapper()
.eq(UploadTaskDO::getId, request.getId()) .eq(UploadTaskDO::getId, request.getId())
.eq(UploadTaskDO::getUserId, request.getUserId()) .eq(UploadTaskDO::getUserId, request.getUserId())
.in(UploadTaskDO::getDescription, request.getDescription()) .like(UploadTaskDO::getDescription, request.getDescription())
.eq(UploadTaskDO::getRemotePath, request.getRemotePath()) .like(UploadTaskDO::getRemotePath, request.getRemotePath())
.eq(UploadTaskDO::getStatus, request.getStatus()) .eq(UploadTaskDO::getStatus, request.getStatus())
.ge(UploadTaskDO::getCreateTime, Arrays1.getIfPresent(request.getCreateTimeRange(), 0)) .ge(UploadTaskDO::getCreateTime, Arrays1.getIfPresent(request.getCreateTimeRange(), 0))
.le(UploadTaskDO::getCreateTime, Arrays1.getIfPresent(request.getCreateTimeRange(), 1)) .le(UploadTaskDO::getCreateTime, Arrays1.getIfPresent(request.getCreateTimeRange(), 1))
@@ -450,12 +451,12 @@ public class UploadTaskServiceImpl implements UploadTaskService {
} }
/** /**
* 设置任务文件 * 设置主机任务文件
* *
* @param task task * @param task task
* @param files files * @param files files
*/ */
private void setTaskFiles(UploadTaskVO task, List<UploadTaskFileVO> files) { private void setTaskHostFiles(UploadTaskVO task, List<UploadTaskFileVO> files) {
Map<Long, List<UploadTaskFileVO>> hostFiles = files.stream() Map<Long, List<UploadTaskFileVO>> hostFiles = files.stream()
.collect(Collectors.groupingBy(UploadTaskFileVO::getHostId)); .collect(Collectors.groupingBy(UploadTaskFileVO::getHostId));
List<UploadTaskHostVO> hosts = JSON.parseObject(task.getExtraInfo(), UploadTaskExtraDTO.class) List<UploadTaskHostVO> hosts = JSON.parseObject(task.getExtraInfo(), UploadTaskExtraDTO.class)

View File

@@ -11,6 +11,8 @@
<result column="description" property="description"/> <result column="description" property="description"/>
<result column="status" property="status"/> <result column="status" property="status"/>
<result column="extra_info" property="extraInfo"/> <result column="extra_info" property="extraInfo"/>
<result column="file_count" property="fileCount"/>
<result column="host_count" property="hostCount"/>
<result column="start_time" property="startTime"/> <result column="start_time" property="startTime"/>
<result column="end_time" property="endTime"/> <result column="end_time" property="endTime"/>
<result column="create_time" property="createTime"/> <result column="create_time" property="createTime"/>
@@ -22,7 +24,7 @@
<!-- 通用查询结果列 --> <!-- 通用查询结果列 -->
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, user_id, username, remote_path, description, status, extra_info, start_time, end_time, create_time, update_time, creator, updater, deleted id, user_id, username, remote_path, description, status, extra_info, file_count, host_count, start_time, end_time, create_time, update_time, creator, updater, deleted
</sql> </sql>
</mapper> </mapper>

View File

@@ -53,6 +53,8 @@ export interface UploadTaskQueryResponse extends TableData {
description: string; description: string;
status: string; status: string;
extraInfo: string; extraInfo: string;
fileCount: number;
hostCount: number;
startTime: number; startTime: number;
endTime: number; endTime: number;
createTime: number; createTime: number;
@@ -86,6 +88,17 @@ export interface UploadTaskFile {
current: number; current: number;
} }
/**
* 上传任务状态响应
*/
export interface UploadTaskStatusResponse extends TableData {
id: number;
status: string;
startTime: number;
endTime: number;
files: Array<UploadTaskFile>;
}
/** /**
* 创建上传任务 * 创建上传任务
*/ */
@@ -125,7 +138,7 @@ export function getUploadTaskPage(request: UploadTaskQueryRequest) {
* 查询上传任务状态 * 查询上传任务状态
*/ */
export function getUploadTaskStatus(idList: Array<number>, queryFiles: boolean) { export function getUploadTaskStatus(idList: Array<number>, queryFiles: boolean) {
return axios.get<Array<UploadTaskQueryResponse>>('/asset/upload-task/status', { return axios.get<Array<UploadTaskStatusResponse>>('/asset/upload-task/status', {
params: { idList, queryFiles }, params: { idList, queryFiles },
paramsSerializer: params => { paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' }); return qs.stringify(params, { arrayFormat: 'comma' });

View File

@@ -41,14 +41,10 @@
<template #file-name="{ fileItem }"> <template #file-name="{ fileItem }">
<div class="file-name-wrapper"> <div class="file-name-wrapper">
<!-- 文件名称 --> <!-- 文件名称 -->
<a-tooltip position="left" <span class="file-name text-ellipsis"
:mini="true" :title="fileItem.file.webkitRelativePath || fileItem.file.name">
:content="fileItem.file.webkitRelativePath || fileItem.file.name">
<!-- 文件名称 -->
<span class="file-name text-ellipsis">
{{ fileItem.file.webkitRelativePath || fileItem.file.name }} {{ fileItem.file.webkitRelativePath || fileItem.file.name }}
</span> </span>
</a-tooltip>
<!-- 文件大小 --> <!-- 文件大小 -->
<span class="file-size span-blue"> <span class="file-size span-blue">
{{ getFileSize(fileItem.file.size) }} {{ getFileSize(fileItem.file.size) }}
@@ -144,7 +140,7 @@
:deep(.waiting-files-wrapper) { :deep(.waiting-files-wrapper) {
.arco-upload-list { .arco-upload-list {
padding: 0 6px 0 0 !important; padding: 0 12px 0 0 !important;
} }
.arco-upload-list-item-name { .arco-upload-list-item-name {
@@ -205,16 +201,18 @@
justify-content: space-between; justify-content: space-between;
.file-name { .file-name {
color: var(--color-text-1);
display: inline-block;
width: calc(100% - @file-size-width); width: calc(100% - @file-size-width);
padding: 2px 0;
color: var(--color-text-1);
} }
.file-size { .file-size {
font-size: 13px;
display: inline-block;
width: @file-size-width; width: @file-size-width;
text-align: end; display: inline-flex;
font-size: 13px;
justify-content: flex-end;
align-items: center;
user-select: none;
} }
} }

View File

@@ -6,19 +6,19 @@
<!-- 操作 --> <!-- 操作 -->
<a-button-group size="mini"> <a-button-group size="mini">
<!-- 重置 --> <!-- 重置 -->
<a-button v-if="status.value === UploadTaskStatus.WAITING.value" <a-button v-if="status.value === UploadTaskStepStatus.WAITING.value"
@click="emits('clear')"> @click="emits('clear')">
重置 重置
</a-button> </a-button>
<!-- 取消上传 --> <!-- 取消上传 -->
<a-button v-if="status.value === UploadTaskStatus.REQUESTING.value" <a-button v-if="status.value === UploadTaskStepStatus.REQUESTING.value"
type="primary" type="primary"
status="warning" status="warning"
@click="emits('abort')"> @click="emits('abort')">
取消上传 取消上传
</a-button> </a-button>
<!-- 开始上传 --> <!-- 开始上传 -->
<a-button v-if="status.value === UploadTaskStatus.WAITING.value" <a-button v-if="status.value === UploadTaskStepStatus.WAITING.value"
type="primary" type="primary"
@click="submit"> @click="submit">
开始上传 开始上传
@@ -39,7 +39,10 @@
allow-clear /> allow-clear />
</a-form-item> </a-form-item>
<!-- 上传路径 --> <!-- 上传路径 -->
<a-form-item field="remotePath" label="上传路径"> <a-form-item field="remotePath"
style="margin-bottom: 4px;"
label="上传路径"
help="${username} 用户名 ${home} 家目录">
<a-input v-model="formModel.remotePath" <a-input v-model="formModel.remotePath"
placeholder="请输入上传路径" placeholder="请输入上传路径"
allow-clear /> allow-clear />
@@ -71,7 +74,7 @@
import type { UploadTaskStatusType } from '../types/const'; import type { UploadTaskStatusType } from '../types/const';
import { ref } from 'vue'; import { ref } from 'vue';
import formRules from '../types/form.rules'; import formRules from '../types/form.rules';
import { UploadTaskStatus } from '../types/const'; import { UploadTaskStepStatus } from '../types/const';
const emits = defineEmits(['upload', 'openHost', 'abort', 'clear']); const emits = defineEmits(['upload', 'openHost', 'abort', 'clear']);
const props = defineProps<{ const props = defineProps<{

View File

@@ -8,7 +8,8 @@
<!-- 返回 --> <!-- 返回 -->
<a-button @click="emits('back')">返回</a-button> <a-button @click="emits('back')">返回</a-button>
<!-- 取消上传 --> <!-- 取消上传 -->
<a-button type="primary" <a-button v-if="status.value === UploadTaskStepStatus.UPLOADING.value"
type="primary"
status="warning" status="warning"
@click="emits('cancel')"> @click="emits('cancel')">
取消上传 取消上传
@@ -37,14 +38,20 @@
<!-- 主机状态 --> <!-- 主机状态 -->
<a-space class="host-item-status" direction="vertical"> <a-space class="host-item-status" direction="vertical">
<!-- 未完成 --> <!-- 未完成 -->
<a-tag class="host-item-status-tag" color="#52C41A"> <a-tag class="host-item-status-tag"
color="#73D13D"
title="未完成数量"
size="small">
{{ host.files.length - getFinishCount(host.files) }} {{ host.files.length - getFinishCount(host.files) }}
<template #icon> <template #icon>
<icon-clock-circle class="host-item-status-icon" /> <icon-clock-circle class="host-item-status-icon" />
</template> </template>
</a-tag> </a-tag>
<!-- 已完成 --> <!-- 已完成 -->
<a-tag class="host-item-status-tag" color="#1890FF"> <a-tag class="host-item-status-tag"
color="#40A9FF"
title="已完成数量"
size="small">
{{ getFinishCount(host.files) }} {{ getFinishCount(host.files) }}
<template #icon> <template #icon>
<icon-check-circle class="host-item-status-icon" /> <icon-check-circle class="host-item-status-icon" />
@@ -66,10 +73,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { UploadTaskQueryResponse } from '@/api/exec/upload-task'; import type { UploadTaskQueryResponse } from '@/api/exec/upload-task';
import type { UploadTaskFile } from '@/api/exec/upload-task'; import type { UploadTaskFile } from '@/api/exec/upload-task';
import { UploadTaskFileStatus } from '../types/const'; import type { UploadTaskStatusType } from '../types/const';
import { UploadTaskStepStatus } from '../types/const';
import { UploadTaskFileStatus } from '@/views/exec/upload-task/types/const';
const emits = defineEmits(['update:selectedHost', 'back', 'cancel']); const emits = defineEmits(['update:selectedHost', 'back', 'cancel']);
const props = defineProps<{ const props = defineProps<{
status: UploadTaskStatusType;
selectedHost: number; selectedHost: number;
task: UploadTaskQueryResponse; task: UploadTaskQueryResponse;
}>(); }>();
@@ -126,6 +136,7 @@
&-tag { &-tag {
max-width: 64px; max-width: 64px;
width: 100%;
} }
&-icon { &-icon {

View File

@@ -16,12 +16,28 @@
<div class="file-item-path text-ellipsis" :title="file.filePath"> <div class="file-item-path text-ellipsis" :title="file.filePath">
{{ file.filePath }} {{ file.filePath }}
</div> </div>
<!-- 进度 --> <!-- 状态 -->
<div class="file-item-progress"> <div class="file-item-status">
<a-progress type="circle" <!-- 文件大小 -->
size="mini" <div class="file-item-size span-blue">
:status="getDictValue(fileStatusKey, file.status, 'status') as any" <!-- 当前大小 -->
:percent="file.current / file.fileSize" /> <template v-if="file.status === UploadTaskFileStatus.WAITING || file.status === UploadTaskFileStatus.UPLOADING">
{{ getFileSize(file.current || 0) }}
</template>
<!-- 总大小 -->
<template v-else>
{{ getFileSize(file.fileSize) }}
</template>
</div>
<!-- 进度 -->
<a-tooltip position="left"
:content="((file.current || 0) / file.fileSize * 100).toFixed(2) + '%'"
mini>
<a-progress type="circle"
size="mini"
:status="getDictValue(fileStatusKey, file.status, 'status') as any"
:percent="(file.current || 0) / file.fileSize" />
</a-tooltip>
</div> </div>
</div> </div>
</a-scrollbar> </a-scrollbar>
@@ -38,7 +54,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { UploadTaskFile } from '@/api/exec/upload-task'; import type { UploadTaskFile } from '@/api/exec/upload-task';
import { fileStatusKey } from '../types/const'; import { fileStatusKey } from '../types/const';
import { UploadTaskFileStatus } from '@/views/exec/upload-task/types/const';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { getFileSize } from '@/utils/file';
const emits = defineEmits(['update:selectedHost']); const emits = defineEmits(['update:selectedHost']);
const props = defineProps<{ const props = defineProps<{
@@ -47,12 +65,11 @@
const { getDictValue } = useDictStore(); const { getDictValue } = useDictStore();
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@icon-width: 24px; @icon-width: 24px;
@progress-width: 24px; @status-width: 102px;
.wrapper { .wrapper {
width: 100%; width: 100%;
@@ -83,15 +100,24 @@
} }
&-path { &-path {
width: calc(100% - @icon-width - @progress-width); padding: 2px 0;
width: calc(100% - @icon-width - @status-width);
display: inline-block;
font-size: 14px; font-size: 14px;
color: var(--color-text-1); color: var(--color-text-1);
} }
&-progress { &-size {
width: @progress-width; font-size: 12px;
margin-right: 12px;
user-select: none;
}
&-status {
width: @status-width;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center;
} }
} }
} }

View File

@@ -2,12 +2,12 @@
<a-spin class="panel-container full" :loading="loading"> <a-spin class="panel-container full" :loading="loading">
<!-- 上传步骤 --> <!-- 上传步骤 -->
<batch-upload-step class="panel-item first-panel-container" <batch-upload-step class="panel-item first-panel-container"
:status="status" /> :status="taskStatus" />
<!-- 上传表单 --> <!-- 上传表单 -->
<batch-upload-form v-if="status.formPanel" <batch-upload-form v-if="taskStatus.formPanel"
class="panel-item center-panel-container" class="panel-item center-panel-container"
:form-model="formModel" :form-model="formModel"
:status="status" :status="taskStatus"
@upload="doCreateUploadTask" @upload="doCreateUploadTask"
@abort="abortUploadRequest" @abort="abortUploadRequest"
@open-host="openHostModal" @open-host="openHostModal"
@@ -16,11 +16,12 @@
<batch-upload-hosts v-else <batch-upload-hosts v-else
class="panel-item center-panel-container" class="panel-item center-panel-container"
v-model:selected-host="selectedHost" v-model:selected-host="selectedHost"
:status="taskStatus"
:task="task" :task="task"
@back="backFormPanel" @back="backFormPanel"
@cancel="doCancelUploadTask" /> @cancel="doCancelUploadTask" />
<!-- 文件列表 --> <!-- 文件列表 -->
<batch-upload-files v-if="status.formPanel" <batch-upload-files v-if="taskStatus.formPanel"
v-model:file-list="fileList" v-model:file-list="fileList"
class="panel-item last-panel-container" class="panel-item last-panel-container"
ref="filesRef" ref="filesRef"
@@ -51,9 +52,10 @@
import type { FileItem } from '@arco-design/web-vue'; import type { FileItem } from '@arco-design/web-vue';
import type { UploadTaskCreateRequest, UploadTaskQueryResponse } from '@/api/exec/upload-task'; import type { UploadTaskCreateRequest, UploadTaskQueryResponse } from '@/api/exec/upload-task';
import type { UploadTaskStatusType } from '../types/const'; import type { UploadTaskStatusType } from '../types/const';
import { ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import { UploadTaskStatus } from '../types/const'; import { UploadTaskStepStatus } from '../types/const';
import { cancelUploadTask, createUploadTask, startUploadTask, getUploadTask } from '@/api/exec/upload-task'; import { UploadTaskStatus } from '@/views/exec/upload-task/types/const';
import { cancelUploadTask, createUploadTask, startUploadTask, getUploadTask, getUploadTaskStatus } from '@/api/exec/upload-task';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import BatchUploadStep from './batch-upload-step.vue'; import BatchUploadStep from './batch-upload-step.vue';
@@ -66,24 +68,23 @@
const defaultForm = (): UploadTaskCreateRequest => { const defaultForm = (): UploadTaskCreateRequest => {
return { return {
description: '', description: '',
remotePath: '/root/batch', remotePath: '',
hostIdList: [1], hostIdList: [],
files: [] files: []
}; };
}; };
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const pullStatusId = ref();
const taskId = ref(); const taskId = ref();
const task = ref<UploadTaskQueryResponse>({} as UploadTaskQueryResponse); const task = ref<UploadTaskQueryResponse>({} as UploadTaskQueryResponse);
const selectedHost = ref(); const selectedHost = ref();
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() }); const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
const fileList = ref<Array<FileItem>>([]); const fileList = ref<Array<FileItem>>([]);
const status = ref<UploadTaskStatusType>(UploadTaskStatus.WAITING); const taskStatus = ref<UploadTaskStatusType>(UploadTaskStepStatus.WAITING);
const filesRef = ref(); const filesRef = ref();
const hostModal = ref<any>(); const hostModal = ref();
// TODO pullstatus
// 设置选中主机 // 设置选中主机
const setSelectedHost = (hosts: Array<number>) => { const setSelectedHost = (hosts: Array<number>) => {
@@ -106,16 +107,16 @@
} }
// 创建任务 // 创建任务
setLoading(true); setLoading(true);
status.value = UploadTaskStatus.WAITING; taskStatus.value = UploadTaskStepStatus.WAITING;
try { try {
formModel.value.files = files; formModel.value.files = files;
const { data } = await createUploadTask(formModel.value); const { data } = await createUploadTask(formModel.value);
taskId.value = data.id; taskId.value = data.id;
status.value = UploadTaskStatus.REQUESTING; taskStatus.value = UploadTaskStepStatus.REQUESTING;
// 上传文件 // 上传文件
await filesRef.value.startUpload(data.token); await filesRef.value.startUpload(data.token);
} catch (e) { } catch (e) {
status.value = UploadTaskStatus.FAILED; taskStatus.value = UploadTaskStepStatus.FAILED;
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -127,7 +128,7 @@
try { try {
// 取消上传 // 取消上传
await cancelUploadTask(taskId.value, false); await cancelUploadTask(taskId.value, false);
status.value = UploadTaskStatus.WAITING; taskStatus.value = UploadTaskStepStatus.WAITING;
Message.success('已取消'); Message.success('已取消');
} catch (e) { } catch (e) {
} finally { } finally {
@@ -137,13 +138,13 @@
// 中断上传请求 // 中断上传请求
const abortUploadRequest = () => { const abortUploadRequest = () => {
status.value = UploadTaskStatus.WAITING; taskStatus.value = UploadTaskStepStatus.WAITING;
filesRef.value?.close(); filesRef.value?.close();
}; };
// 上传请求结束 // 上传请求结束
const uploadRequestEnd = async () => { const uploadRequestEnd = async () => {
if (status.value.value === UploadTaskStatus.REQUESTING.value) { if (taskStatus.value.value === UploadTaskStepStatus.REQUESTING.value) {
// 如果结束后还是请求中则代表请求完毕 // 如果结束后还是请求中则代表请求完毕
setLoading(true); setLoading(true);
try { try {
@@ -153,7 +154,7 @@
const { data } = await getUploadTask(taskId.value); const { data } = await getUploadTask(taskId.value);
task.value = data; task.value = data;
selectedHost.value = data.hosts[0].id; selectedHost.value = data.hosts[0].id;
status.value = UploadTaskStatus.UPLOADING; taskStatus.value = UploadTaskStepStatus.UPLOADING;
} catch (e) { } catch (e) {
// 设置失败 // 设置失败
await uploadRequestError(); await uploadRequestError();
@@ -172,13 +173,48 @@
try { try {
// 开始上传 // 开始上传
await cancelUploadTask(taskId.value, true); await cancelUploadTask(taskId.value, true);
status.value = UploadTaskStatus.FAILED; taskStatus.value = UploadTaskStepStatus.FAILED;
} catch (e) { } catch (e) {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
// 加载轮询状态
const pullTaskStatus = async () => {
if (!taskId.value || !task.value) {
return;
}
// 非上传中则不查询
if (taskStatus.value.value !== UploadTaskStepStatus.UPLOADING.value) {
return;
}
// 查询状态
const { data } = await getUploadTaskStatus([taskId.value], true);
if (!data.length) {
return;
}
const taskStatusData = data[0];
// 设置任务状态
if (taskStatusData.status === UploadTaskStatus.FINISHED) {
taskStatus.value = UploadTaskStepStatus.FINISHED;
} else if (taskStatusData.status === UploadTaskStatus.CANCELED) {
taskStatus.value = UploadTaskStepStatus.FINISHED;
} else if (taskStatusData.status === UploadTaskStatus.FAILED) {
taskStatus.value = UploadTaskStepStatus.FAILED;
}
// 设置文件进度
for (let host of task.value.hosts) {
for (let file of host.files) {
const fileStatus = taskStatusData.files.find(s => s.id === file.id);
if (fileStatus) {
file.status = fileStatus.status;
file.current = fileStatus.current;
}
}
}
};
// 打开主机模态框 // 打开主机模态框
const openHostModal = () => { const openHostModal = () => {
hostModal.value.open(formModel.value.hostIdList); hostModal.value.open(formModel.value.hostIdList);
@@ -186,7 +222,7 @@
// 返回表单页面 // 返回表单页面
const backFormPanel = () => { const backFormPanel = () => {
status.value = UploadTaskStatus.WAITING; taskStatus.value = UploadTaskStepStatus.WAITING;
taskId.value = undefined; taskId.value = undefined;
task.value = undefined as any; task.value = undefined as any;
selectedHost.value = undefined as any; selectedHost.value = undefined as any;
@@ -202,6 +238,16 @@
fileList.value = []; fileList.value = [];
}; };
// 设置轮询状态
onMounted(() => {
pullStatusId.value = setInterval(pullTaskStatus, 5000);
});
// 卸载状态查询
onUnmounted(() => {
clearInterval(pullStatusId.value);
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -7,7 +7,7 @@ export interface UploadTaskStatusType {
} }
// 上传任务状态 // 上传任务状态
export const UploadTaskStatus = { export const UploadTaskStepStatus = {
// 等待中 // 等待中
WAITING: { WAITING: {
value: 'WAITING', value: 'WAITING',
@@ -45,20 +45,6 @@ export const UploadTaskStatus = {
}, },
}; };
// 上传任务文件状态
export const UploadTaskFileStatus = {
// 等待中
WAITING: 'WAITING',
// 上传中
UPLOADING: 'UPLOADING',
// 已完成
FINISHED: 'FINISHED',
// 已完成
FAILED: 'FAILED',
// 已取消
CANCELED: 'CANCELED',
};
// 上传任务状态 字典项 // 上传任务状态 字典项
export const taskStatusKey = 'uploadTaskStatus'; export const taskStatusKey = 'uploadTaskStatus';

View File

@@ -102,6 +102,7 @@
}; };
defineExpose({ open }); defineExpose({ open });
// 确定 // 确定
const handlerOk = async () => { const handlerOk = async () => {
setLoading(true); setLoading(true);

View File

@@ -1,17 +0,0 @@
<template>
<div>upload-task-log</div>
</template>
<script lang="ts">
export default {
name: 'index'
};
</script>
<script lang="ts" setup>
</script>
<style lang="less" scoped>
</style>