⚡ 优化文件上传逻辑.
This commit is contained in:
@@ -50,7 +50,7 @@ public class TransferMessageDispatcher extends AbstractWebSocketHandler {
|
|||||||
// 获取处理器
|
// 获取处理器
|
||||||
ITransferHandler handler = hostTransferManager.getHandler(session.getId());
|
ITransferHandler handler = hostTransferManager.getHandler(session.getId());
|
||||||
// 添加数据
|
// 添加数据
|
||||||
handler.putContent(message.getPayload().array());
|
handler.handleMessage(message.getPayload().array());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.orion.visor.module.asset.handler.host.transfer.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输操作类型
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2024/2/21 22:03
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum TransferOperator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始传输
|
||||||
|
*/
|
||||||
|
START("start"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输完成
|
||||||
|
*/
|
||||||
|
FINISH("finish"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输失败
|
||||||
|
*/
|
||||||
|
ERROR("error"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输中断
|
||||||
|
*/
|
||||||
|
ABORT("abort"),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
public static TransferOperator of(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (TransferOperator value : values()) {
|
||||||
|
if (value.type.equals(type)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.enums;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 传输操作类型
|
|
||||||
*
|
|
||||||
* @author Jiahang Li
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/2/21 22:03
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum TransferOperatorType {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始上传
|
|
||||||
*/
|
|
||||||
UPLOAD_START(TransferOperatorType.UPLOAD, "uploadStart"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传完成
|
|
||||||
*/
|
|
||||||
UPLOAD_FINISH(TransferOperatorType.UPLOAD, "uploadFinish"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传失败
|
|
||||||
*/
|
|
||||||
UPLOAD_ERROR(TransferOperatorType.UPLOAD, "uploadError"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化下载
|
|
||||||
*/
|
|
||||||
DOWNLOAD_INIT(TransferOperatorType.DOWNLOAD, "downloadInit"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 中断下载
|
|
||||||
*/
|
|
||||||
DOWNLOAD_ABORT(TransferOperatorType.DOWNLOAD, "downloadAbort"),
|
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
public static final String UPLOAD = "UPLOAD";
|
|
||||||
|
|
||||||
public static final String DOWNLOAD = "DOWNLOAD";
|
|
||||||
|
|
||||||
private final String kind;
|
|
||||||
|
|
||||||
private final String type;
|
|
||||||
|
|
||||||
public static TransferOperatorType of(String type) {
|
|
||||||
if (type == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (TransferOperatorType value : values()) {
|
|
||||||
if (value.type.equals(type)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.orion.visor.module.asset.handler.host.transfer.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输响应类型
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2024/2/21 22:03
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum TransferReceiver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求下一分片
|
||||||
|
*/
|
||||||
|
NEXT_PART("nextPart"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始
|
||||||
|
*/
|
||||||
|
START("start"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进度
|
||||||
|
*/
|
||||||
|
PROGRESS("progress"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成
|
||||||
|
*/
|
||||||
|
FINISH("finish"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败
|
||||||
|
*/
|
||||||
|
ERROR("error"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭
|
||||||
|
*/
|
||||||
|
ABORT("abort"),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
public static TransferReceiver of(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (TransferReceiver value : values()) {
|
||||||
|
if (value.type.equals(type)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.enums;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 传输响应类型
|
|
||||||
*
|
|
||||||
* @author Jiahang Li
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/2/21 22:03
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum TransferReceiverType {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求下一个传输任务
|
|
||||||
*/
|
|
||||||
NEXT_TRANSFER("nextTransfer"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求下一块上传数据
|
|
||||||
*/
|
|
||||||
UPLOAD_NEXT_BLOCK("uploadNextBlock"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传完成
|
|
||||||
*/
|
|
||||||
UPLOAD_FINISH("uploadFinish"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传失败
|
|
||||||
*/
|
|
||||||
UPLOAD_ERROR("uploadError"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始下载
|
|
||||||
*/
|
|
||||||
DOWNLOAD_START("downloadStart"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载进度
|
|
||||||
*/
|
|
||||||
DOWNLOAD_PROGRESS("downloadProgress"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载完成
|
|
||||||
*/
|
|
||||||
DOWNLOAD_FINISH("downloadFinish"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载失败
|
|
||||||
*/
|
|
||||||
DOWNLOAD_ERROR("downloadError"),
|
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
private final String type;
|
|
||||||
|
|
||||||
public static TransferReceiverType of(String type) {
|
|
||||||
if (type == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (TransferReceiverType value : values()) {
|
|
||||||
if (value.type.equals(type)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.orion.visor.module.asset.handler.host.transfer.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输类型
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2024/7/12 12:41
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum TransferType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传
|
||||||
|
*/
|
||||||
|
UPLOAD("upload"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载
|
||||||
|
*/
|
||||||
|
DOWNLOAD("download"),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
public static TransferType of(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (TransferType value : values()) {
|
||||||
|
if (value.type.equals(type)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,9 +2,7 @@ package com.orion.visor.module.asset.handler.host.transfer.handler;
|
|||||||
|
|
||||||
import com.orion.lang.able.SafeCloseable;
|
import com.orion.lang.able.SafeCloseable;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.session.IDownloadSession;
|
import com.orion.visor.module.asset.handler.host.transfer.session.ITransferSession;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 传输处理器定义
|
* 传输处理器定义
|
||||||
@@ -16,24 +14,25 @@ import java.util.Map;
|
|||||||
public interface ITransferHandler extends SafeCloseable {
|
public interface ITransferHandler extends SafeCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理消息
|
* 处理文本消息
|
||||||
*
|
*
|
||||||
* @param payload payload
|
* @param payload payload
|
||||||
*/
|
*/
|
||||||
void handleMessage(TransferOperatorRequest payload);
|
void handleMessage(TransferOperatorRequest payload);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入内容
|
* 处理二进制消息
|
||||||
*
|
*
|
||||||
* @param content content
|
* @param content content
|
||||||
*/
|
*/
|
||||||
void putContent(byte[] content);
|
void handleMessage(byte[] content);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 token sessions
|
* 通过 token 获取 session
|
||||||
*
|
*
|
||||||
* @return token sessions
|
* @param token token
|
||||||
|
* @return session
|
||||||
*/
|
*/
|
||||||
Map<String, IDownloadSession> getTokenSessions();
|
ITransferSession getSessionByToken(String token);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.handler;
|
package com.orion.visor.module.asset.handler.host.transfer.handler;
|
||||||
|
|
||||||
import com.orion.lang.id.UUIds;
|
import com.orion.lang.id.UUIds;
|
||||||
import com.orion.lang.utils.Exceptions;
|
|
||||||
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.spring.SpringHolder;
|
import com.orion.spring.SpringHolder;
|
||||||
import com.orion.visor.framework.common.constant.ErrorMessage;
|
|
||||||
import com.orion.visor.framework.common.constant.ExtraFieldConst;
|
import com.orion.visor.framework.common.constant.ExtraFieldConst;
|
||||||
import com.orion.visor.framework.websocket.core.utils.WebSockets;
|
import com.orion.visor.framework.websocket.core.utils.WebSockets;
|
||||||
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferOperatorType;
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferOperator;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiverType;
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiver;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferType;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.session.*;
|
import com.orion.visor.module.asset.handler.host.transfer.session.DownloadSession;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.session.ITransferSession;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.session.UploadSession;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
||||||
import com.orion.visor.module.asset.service.HostTerminalService;
|
import com.orion.visor.module.asset.service.HostTerminalService;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
@@ -33,98 +33,72 @@ public class TransferHandler implements ITransferHandler {
|
|||||||
|
|
||||||
private static final HostTerminalService hostTerminalService = SpringHolder.getBean(HostTerminalService.class);
|
private static final HostTerminalService hostTerminalService = SpringHolder.getBean(HostTerminalService.class);
|
||||||
|
|
||||||
private final Long userId;
|
|
||||||
|
|
||||||
private final WebSocketSession channel;
|
private final WebSocketSession channel;
|
||||||
|
|
||||||
/**
|
private ITransferSession currentSession;
|
||||||
* 当前会话
|
|
||||||
*/
|
|
||||||
private ITransferHostSession currentSession;
|
|
||||||
|
|
||||||
/**
|
private final ConcurrentHashMap<String, ITransferSession> sessions;
|
||||||
* 会话列表
|
|
||||||
*/
|
|
||||||
private final ConcurrentHashMap<String, ITransferHostSession> sessions;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final ConcurrentHashMap<String, IDownloadSession> tokenSessions;
|
|
||||||
|
|
||||||
public TransferHandler(WebSocketSession channel) {
|
public TransferHandler(WebSocketSession channel) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.userId = WebSockets.getAttr(channel, ExtraFieldConst.USER_ID);
|
|
||||||
this.sessions = new ConcurrentHashMap<>();
|
this.sessions = new ConcurrentHashMap<>();
|
||||||
this.tokenSessions = new ConcurrentHashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(TransferOperatorRequest payload) {
|
public void handleMessage(TransferOperatorRequest payload) {
|
||||||
// 解析消息类型
|
// 解析消息类型
|
||||||
TransferOperatorType type = TransferOperatorType.of(payload.getType());
|
TransferOperator operator = TransferOperator.of(payload.getOperator());
|
||||||
// 获取会话
|
// 获取会话
|
||||||
if (!this.getAndInitSession(payload, type)) {
|
if (!this.getAndInitSession(payload)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 处理消息
|
// 处理消息
|
||||||
switch (type) {
|
if (TransferOperator.START.equals(operator)) {
|
||||||
case UPLOAD_START:
|
// 开始
|
||||||
// 开始上传
|
currentSession.setToken(UUIds.random32());
|
||||||
((IUploadSession) currentSession).startUpload(payload.getPath());
|
currentSession.onStart(payload);
|
||||||
break;
|
} else if (TransferOperator.FINISH.equals(operator)) {
|
||||||
case UPLOAD_FINISH:
|
// 完成
|
||||||
// 上传完成
|
currentSession.onFinish(payload);
|
||||||
((IUploadSession) currentSession).uploadFinish();
|
} else if (TransferOperator.ERROR.equals(operator)) {
|
||||||
break;
|
// 失败
|
||||||
case UPLOAD_ERROR:
|
currentSession.onError(payload);
|
||||||
// 上传失败
|
} else if (TransferOperator.ABORT.equals(operator)) {
|
||||||
((IUploadSession) currentSession).uploadError();
|
// 中断
|
||||||
break;
|
currentSession.onAbort(payload);
|
||||||
case DOWNLOAD_INIT:
|
|
||||||
// 开始下载
|
|
||||||
String token = UUIds.random32();
|
|
||||||
tokenSessions.put(token, (IDownloadSession) currentSession);
|
|
||||||
((IDownloadSession) currentSession).downloadInit(payload.getPath(), token);
|
|
||||||
break;
|
|
||||||
case DOWNLOAD_ABORT:
|
|
||||||
// 中断下载
|
|
||||||
((IDownloadSession) currentSession).abortDownload();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putContent(byte[] content) {
|
public void handleMessage(byte[] content) {
|
||||||
((IUploadSession) currentSession).putContent(content);
|
currentSession.handleBinary(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取并且初始化会话
|
* 获取并且初始化会话
|
||||||
*
|
*
|
||||||
* @param payload payload
|
* @param payload payload
|
||||||
* @param type type
|
|
||||||
* @return success
|
* @return success
|
||||||
*/
|
*/
|
||||||
private boolean getAndInitSession(TransferOperatorRequest payload, TransferOperatorType type) {
|
private boolean getAndInitSession(TransferOperatorRequest payload) {
|
||||||
Long hostId = payload.getHostId();
|
Long hostId = payload.getHostId();
|
||||||
String sessionKey = hostId + "_" + type.getKind();
|
TransferType type = TransferType.of(payload.getType());
|
||||||
|
String sessionKey = hostId + "_" + type.getType();
|
||||||
try {
|
try {
|
||||||
// 获取会话
|
// 获取会话
|
||||||
ITransferHostSession session = sessions.get(sessionKey);
|
ITransferSession session = sessions.get(sessionKey);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
// 获取主机信息
|
// 获取主机连接信息
|
||||||
HostTerminalConnectDTO connectInfo = hostTerminalService.getTerminalConnectInfo(this.userId, hostId);
|
Long userId = WebSockets.getAttr(channel, ExtraFieldConst.USER_ID);
|
||||||
|
HostTerminalConnectDTO connectInfo = hostTerminalService.getTerminalConnectInfo(userId, hostId);
|
||||||
SessionStore sessionStore = hostTerminalService.openSessionStore(connectInfo);
|
SessionStore sessionStore = hostTerminalService.openSessionStore(connectInfo);
|
||||||
// 打开会话并初始化
|
// 打开会话并初始化
|
||||||
if (TransferOperatorType.UPLOAD.equals(type.getKind())) {
|
if (TransferType.UPLOAD.equals(type)) {
|
||||||
// 上传操作
|
// 上传操作
|
||||||
session = new UploadSession(connectInfo, sessionStore, this.channel);
|
session = new UploadSession(connectInfo, sessionStore, this.channel);
|
||||||
} else if (TransferOperatorType.DOWNLOAD.equals(type.getKind())) {
|
} else if (TransferType.DOWNLOAD.equals(type)) {
|
||||||
// 下载操作
|
// 下载操作
|
||||||
session = new DownloadSession(connectInfo, sessionStore, this.channel);
|
session = new DownloadSession(connectInfo, sessionStore, this.channel);
|
||||||
} else {
|
|
||||||
throw Exceptions.invalidArgument(ErrorMessage.UNKNOWN_TYPE);
|
|
||||||
}
|
}
|
||||||
session.init();
|
session.init();
|
||||||
sessions.put(sessionKey, session);
|
sessions.put(sessionKey, session);
|
||||||
@@ -135,16 +109,25 @@ public class TransferHandler implements ITransferHandler {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("TransferHandler.getAndInitSession error channelId: {}", channel.getId(), e);
|
log.error("TransferHandler.getAndInitSession error channelId: {}", channel.getId(), e);
|
||||||
// 响应结果
|
// 响应结果
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.NEXT_TRANSFER, e);
|
TransferUtils.sendMessage(this.channel, TransferReceiver.ERROR, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ITransferSession getSessionByToken(String token) {
|
||||||
|
return sessions.values()
|
||||||
|
.stream()
|
||||||
|
.filter(s -> token.equals(s.getToken()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
log.info("TransferHandler.close channelId: {}", channel.getId());
|
log.info("TransferHandler.close channelId: {}", channel.getId());
|
||||||
sessions.values().forEach(Streams::close);
|
sessions.values().forEach(Streams::close);
|
||||||
tokenSessions.clear();
|
sessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ public class TransferOperatorRequest {
|
|||||||
*/
|
*/
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* operator
|
||||||
|
*/
|
||||||
|
private String operator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主机id
|
* 主机id
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import com.orion.visor.framework.common.constant.ErrorMessage;
|
|||||||
import com.orion.visor.module.asset.define.AssetThreadPools;
|
import com.orion.visor.module.asset.define.AssetThreadPools;
|
||||||
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
|
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
|
||||||
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiverType;
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiver;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -29,10 +30,11 @@ import java.io.OutputStream;
|
|||||||
* @since 2024/2/22 22:25
|
* @since 2024/2/22 22:25
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DownloadSession extends TransferHostSession implements IDownloadSession {
|
public class DownloadSession extends TransferSession implements StreamingResponseBody {
|
||||||
|
|
||||||
@Getter
|
private static final int BUFFER_SIZE = Const.BUFFER_KB_32;
|
||||||
private String path;
|
|
||||||
|
private static final int FLUSH_COUNT = Const.BUFFER_KB_1 * Const.BUFFER_KB_1 / Const.BUFFER_KB_32;
|
||||||
|
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
|
|
||||||
@@ -41,10 +43,9 @@ public class DownloadSession extends TransferHostSession implements IDownloadSes
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void downloadInit(String path, String token) {
|
public void onStart(TransferOperatorRequest request) {
|
||||||
this.path = path;
|
|
||||||
String channelId = channel.getId();
|
|
||||||
try {
|
try {
|
||||||
|
super.onStart(request);
|
||||||
log.info("DownloadSession.startDownload open start channelId: {}, path: {}", channelId, path);
|
log.info("DownloadSession.startDownload open start channelId: {}, path: {}", channelId, path);
|
||||||
// 保存操作日志
|
// 保存操作日志
|
||||||
this.saveOperatorLog(HostTerminalOperatorType.SFTP_DOWNLOAD, path);
|
this.saveOperatorLog(HostTerminalOperatorType.SFTP_DOWNLOAD, path);
|
||||||
@@ -56,13 +57,13 @@ public class DownloadSession extends TransferHostSession implements IDownloadSes
|
|||||||
if (file.getSize() == 0L) {
|
if (file.getSize() == 0L) {
|
||||||
// 文件为空
|
// 文件为空
|
||||||
log.info("DownloadSession.startDownload file empty channelId: {}, path: {}", channelId, path);
|
log.info("DownloadSession.startDownload file empty channelId: {}, path: {}", channelId, path);
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.DOWNLOAD_FINISH, null);
|
TransferUtils.sendMessage(channel, TransferReceiver.FINISH, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 打开输入流
|
// 打开输入流
|
||||||
this.inputStream = executor.openInputStream(path);
|
this.inputStream = executor.openInputStream(path);
|
||||||
// 响应开始下载
|
// 响应开始下载
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.DOWNLOAD_START, null, e -> {
|
TransferUtils.sendMessage(channel, TransferReceiver.START, null, e -> {
|
||||||
e.setChannelId(channelId);
|
e.setChannelId(channelId);
|
||||||
e.setTransferToken(token);
|
e.setTransferToken(token);
|
||||||
});
|
});
|
||||||
@@ -70,23 +71,23 @@ public class DownloadSession extends TransferHostSession implements IDownloadSes
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("DownloadSession.startDownload open error channelId: {}, path: {}", channelId, path, e);
|
log.error("DownloadSession.startDownload open error channelId: {}, path: {}", channelId, path, e);
|
||||||
// 响应下载失败
|
// 响应下载失败
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.DOWNLOAD_ERROR, e);
|
TransferUtils.sendMessage(channel, TransferReceiver.ERROR, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abortDownload() {
|
public void onAbort(TransferOperatorRequest request) {
|
||||||
log.info("DownloadSession.abortDownload channelId: {}", channel.getId());
|
log.info("TransferSession.abort channelId: {}, path: {}", channelId, path);
|
||||||
// 关闭流
|
// 关闭流
|
||||||
this.closeStream();
|
this.closeStream();
|
||||||
|
// download 的 abort 无需发送回调
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream outputStream) {
|
public void writeTo(OutputStream outputStream) {
|
||||||
String channelId = channel.getId();
|
|
||||||
Ref<Exception> ex = new Ref<>();
|
Ref<Exception> ex = new Ref<>();
|
||||||
try {
|
try {
|
||||||
byte[] buffer = new byte[Const.BUFFER_KB_32];
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
int len;
|
int len;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int size = 0;
|
int size = 0;
|
||||||
@@ -95,7 +96,7 @@ public class DownloadSession extends TransferHostSession implements IDownloadSes
|
|||||||
outputStream.write(buffer, 0, len);
|
outputStream.write(buffer, 0, len);
|
||||||
size += len;
|
size += len;
|
||||||
// 不要每次都 flush 和 send > 1mb
|
// 不要每次都 flush 和 send > 1mb
|
||||||
if (i == 32) {
|
if (i == FLUSH_COUNT) {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
// 首次触发
|
// 首次触发
|
||||||
@@ -122,9 +123,9 @@ public class DownloadSession extends TransferHostSession implements IDownloadSes
|
|||||||
// 响应结果
|
// 响应结果
|
||||||
Exception e = ex.getValue();
|
Exception e = ex.getValue();
|
||||||
if (e == null) {
|
if (e == null) {
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.DOWNLOAD_FINISH, null);
|
TransferUtils.sendMessage(channel, TransferReceiver.FINISH, null);
|
||||||
} else {
|
} else {
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.DOWNLOAD_ERROR, e);
|
TransferUtils.sendMessage(channel, TransferReceiver.ERROR, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -140,13 +141,12 @@ public class DownloadSession extends TransferHostSession implements IDownloadSes
|
|||||||
// flush
|
// flush
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
// send
|
// send
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.DOWNLOAD_PROGRESS, null, e -> e.setCurrentSize(size));
|
TransferUtils.sendMessage(channel, TransferReceiver.PROGRESS, null, e -> e.setCurrentSize(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void closeStream() {
|
protected void closeStream() {
|
||||||
// 关闭 inputStream 可能会被阻塞 ???...??? 只能关闭 executor
|
// 关闭 inputStream 可能会被阻塞 ???...??? 只能关闭 executor
|
||||||
this.path = null;
|
|
||||||
Streams.close(this.executor);
|
Streams.close(this.executor);
|
||||||
this.executor = null;
|
this.executor = null;
|
||||||
this.inputStream = null;
|
this.inputStream = null;
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.session;
|
|
||||||
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载会话定义
|
|
||||||
*
|
|
||||||
* @author Jiahang Li
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/2/22 22:25
|
|
||||||
*/
|
|
||||||
public interface IDownloadSession extends StreamingResponseBody {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化下载
|
|
||||||
*
|
|
||||||
* @param path path
|
|
||||||
* @param token token
|
|
||||||
*/
|
|
||||||
void downloadInit(String path, String token);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止下载
|
|
||||||
*/
|
|
||||||
void abortDownload();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取下载文件路径
|
|
||||||
*
|
|
||||||
* @return path
|
|
||||||
*/
|
|
||||||
String getPath();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.session;
|
|
||||||
|
|
||||||
import com.orion.lang.able.SafeCloseable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主机传输会话定义
|
|
||||||
*
|
|
||||||
* @author Jiahang Li
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/2/21 23:06
|
|
||||||
*/
|
|
||||||
public interface ITransferHostSession extends SafeCloseable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*/
|
|
||||||
void init();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.orion.visor.module.asset.handler.host.transfer.session;
|
||||||
|
|
||||||
|
import com.orion.lang.able.SafeCloseable;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主机传输会话定义
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2024/2/21 23:06
|
||||||
|
*/
|
||||||
|
public interface ITransferSession extends SafeCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理二进制内容
|
||||||
|
*
|
||||||
|
* @param bytes bytes
|
||||||
|
*/
|
||||||
|
void handleBinary(byte[] bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始传输
|
||||||
|
*
|
||||||
|
* @param request request
|
||||||
|
*/
|
||||||
|
void onStart(TransferOperatorRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输完成
|
||||||
|
*
|
||||||
|
* @param request request
|
||||||
|
*/
|
||||||
|
void onFinish(TransferOperatorRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输失败
|
||||||
|
*
|
||||||
|
* @param request request
|
||||||
|
*/
|
||||||
|
void onError(TransferOperatorRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输中断
|
||||||
|
*
|
||||||
|
* @param request request
|
||||||
|
*/
|
||||||
|
void onAbort(TransferOperatorRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件路径
|
||||||
|
*
|
||||||
|
* @return path
|
||||||
|
*/
|
||||||
|
String getPath();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 token
|
||||||
|
*
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
String getToken();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 token
|
||||||
|
*
|
||||||
|
* @param token token
|
||||||
|
*/
|
||||||
|
void setToken(String token);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传会话定义
|
|
||||||
*
|
|
||||||
* @author Jiahang Li
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/2/22 22:03
|
|
||||||
*/
|
|
||||||
public interface IUploadSession extends ITransferHostSession {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始上传
|
|
||||||
*
|
|
||||||
* @param path path
|
|
||||||
*/
|
|
||||||
void startUpload(String path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 写入内容
|
|
||||||
*
|
|
||||||
* @param bytes bytes
|
|
||||||
*/
|
|
||||||
void putContent(byte[] bytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传完成
|
|
||||||
*/
|
|
||||||
void uploadFinish();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传失败
|
|
||||||
*/
|
|
||||||
void uploadError();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.session;
|
package com.orion.visor.module.asset.handler.host.transfer.session;
|
||||||
|
|
||||||
|
import com.orion.lang.exception.argument.InvalidArgumentException;
|
||||||
import com.orion.lang.utils.collect.Maps;
|
import com.orion.lang.utils.collect.Maps;
|
||||||
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;
|
||||||
@@ -11,6 +12,12 @@ import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
|||||||
import com.orion.visor.module.asset.define.config.AppSftpConfig;
|
import com.orion.visor.module.asset.define.config.AppSftpConfig;
|
||||||
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
||||||
import com.orion.visor.module.asset.handler.host.terminal.utils.TerminalUtils;
|
import com.orion.visor.module.asset.handler.host.terminal.utils.TerminalUtils;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiver;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -22,7 +29,8 @@ import java.util.Map;
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @since 2024/2/21 21:12
|
* @since 2024/2/21 21:12
|
||||||
*/
|
*/
|
||||||
public abstract class TransferHostSession implements ITransferHostSession {
|
@Slf4j
|
||||||
|
public abstract class TransferSession implements ITransferSession {
|
||||||
|
|
||||||
protected static final AppSftpConfig SFTP_CONFIG = SpringHolder.getBean(AppSftpConfig.class);
|
protected static final AppSftpConfig SFTP_CONFIG = SpringHolder.getBean(AppSftpConfig.class);
|
||||||
|
|
||||||
@@ -34,10 +42,20 @@ public abstract class TransferHostSession implements ITransferHostSession {
|
|||||||
|
|
||||||
protected SftpExecutor executor;
|
protected SftpExecutor executor;
|
||||||
|
|
||||||
public TransferHostSession(HostTerminalConnectDTO connectInfo, SessionStore sessionStore, WebSocketSession channel) {
|
protected String channelId;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
protected String path;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
protected String token;
|
||||||
|
|
||||||
|
public TransferSession(HostTerminalConnectDTO connectInfo, SessionStore sessionStore, WebSocketSession channel) {
|
||||||
this.connectInfo = connectInfo;
|
this.connectInfo = connectInfo;
|
||||||
this.sessionStore = sessionStore;
|
this.sessionStore = sessionStore;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
this.channelId = channel.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,6 +72,52 @@ public abstract class TransferHostSession implements ITransferHostSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleBinary(byte[] bytes) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(TransferOperatorRequest request) {
|
||||||
|
this.path = request.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish(TransferOperatorRequest request) {
|
||||||
|
log.info("TransferSession.uploadFinish channelId: {}", channelId);
|
||||||
|
this.closeStream();
|
||||||
|
// 响应结果
|
||||||
|
TransferUtils.sendMessage(channel, TransferReceiver.FINISH, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(TransferOperatorRequest request) {
|
||||||
|
log.error("TransferSession.uploadError channelId: {}", channelId);
|
||||||
|
this.closeStream();
|
||||||
|
// 响应结果
|
||||||
|
TransferUtils.sendMessage(channel, TransferReceiver.ERROR, new InvalidArgumentException((String) null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAbort(TransferOperatorRequest request) {
|
||||||
|
log.info("TransferSession.abort channelId: {}, path: {}", channelId, path);
|
||||||
|
// 关闭流
|
||||||
|
this.closeStream();
|
||||||
|
// 响应结果
|
||||||
|
TransferUtils.sendMessage(channel, TransferReceiver.ABORT, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭流
|
||||||
|
*/
|
||||||
|
protected abstract void closeStream();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.closeStream();
|
||||||
|
Streams.close(executor);
|
||||||
|
Streams.close(sessionStore);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存操作日志
|
* 保存操作日志
|
||||||
*
|
*
|
||||||
@@ -73,16 +137,4 @@ public abstract class TransferHostSession implements ITransferHostSession {
|
|||||||
SpringHolder.getBean(OperatorLogFrameworkService.class).insert(model);
|
SpringHolder.getBean(OperatorLogFrameworkService.class).insert(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭流
|
|
||||||
*/
|
|
||||||
protected abstract void closeStream();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
this.closeStream();
|
|
||||||
Streams.close(executor);
|
|
||||||
Streams.close(sessionStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.orion.visor.module.asset.handler.host.transfer.session;
|
package com.orion.visor.module.asset.handler.host.transfer.session;
|
||||||
|
|
||||||
import com.orion.lang.exception.argument.InvalidArgumentException;
|
|
||||||
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.visor.module.asset.define.operator.HostTerminalOperatorType;
|
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
|
||||||
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiverType;
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiver;
|
||||||
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorRequest;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
import com.orion.visor.module.asset.handler.host.transfer.utils.TransferUtils;
|
||||||
import com.orion.visor.module.asset.utils.SftpUtils;
|
import com.orion.visor.module.asset.utils.SftpUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -22,7 +22,7 @@ import java.io.OutputStream;
|
|||||||
* @since 2024/2/22 22:04
|
* @since 2024/2/22 22:04
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class UploadSession extends TransferHostSession implements IUploadSession {
|
public class UploadSession extends TransferSession {
|
||||||
|
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ public class UploadSession extends TransferHostSession implements IUploadSession
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startUpload(String path) {
|
public void onStart(TransferOperatorRequest request) {
|
||||||
String channelId = channel.getId();
|
super.onStart(request);
|
||||||
try {
|
try {
|
||||||
log.info("UploadSession.startUpload start channelId: {}, path: {}", channelId, path);
|
log.info("UploadSession.startUpload start channelId: {}, path: {}", channelId, path);
|
||||||
// 保存操作日志
|
// 保存操作日志
|
||||||
@@ -44,52 +44,38 @@ public class UploadSession extends TransferHostSession implements IUploadSession
|
|||||||
// 打开输出流
|
// 打开输出流
|
||||||
this.outputStream = executor.openOutputStream(path);
|
this.outputStream = executor.openOutputStream(path);
|
||||||
// 响应结果
|
// 响应结果
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.UPLOAD_NEXT_BLOCK, null);
|
TransferUtils.sendMessage(channel, TransferReceiver.NEXT_PART, null);
|
||||||
log.info("UploadSession.startUpload transfer channelId: {}, path: {}", channelId, path);
|
log.info("UploadSession.startUpload transfer channelId: {}, path: {}", channelId, path);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("UploadSession.startUpload error channelId: {}, path: {}", channelId, path, e);
|
log.error("UploadSession.startUpload error channelId: {}, path: {}", channelId, path, e);
|
||||||
this.closeStream();
|
this.closeStream();
|
||||||
// 响应结果
|
// 响应结果
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.UPLOAD_ERROR, e);
|
TransferUtils.sendMessage(channel, TransferReceiver.ERROR, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putContent(byte[] bytes) {
|
public void handleBinary(byte[] bytes) {
|
||||||
try {
|
try {
|
||||||
// 写入内容
|
// 写入内容
|
||||||
outputStream.write(bytes);
|
outputStream.write(bytes);
|
||||||
// 响应结果
|
// 响应结果
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.UPLOAD_NEXT_BLOCK, null);
|
TransferUtils.sendMessage(channel, TransferReceiver.NEXT_PART, null);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("UploadSession.putContent error channelId: {}", channel.getId(), e);
|
log.error("UploadSession.handleBinary error channelId: {}", channel.getId(), e);
|
||||||
this.closeStream();
|
this.closeStream();
|
||||||
// 响应结果
|
// 响应结果
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.UPLOAD_ERROR, e);
|
TransferUtils.sendMessage(channel, TransferReceiver.ERROR, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void uploadFinish() {
|
|
||||||
log.info("UploadSession.uploadFinish channelId: {}", channel.getId());
|
|
||||||
this.closeStream();
|
|
||||||
// 响应结果
|
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.UPLOAD_FINISH, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void uploadError() {
|
|
||||||
log.error("UploadSession.uploadError channelId: {}", channel.getId());
|
|
||||||
this.closeStream();
|
|
||||||
// 响应结果
|
|
||||||
TransferUtils.sendMessage(this.channel, TransferReceiverType.UPLOAD_ERROR, new InvalidArgumentException((String) null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void closeStream() {
|
protected void closeStream() {
|
||||||
// 关闭流
|
if (this.outputStream != null) {
|
||||||
Streams.close(outputStream);
|
// 关闭流
|
||||||
this.outputStream = null;
|
Streams.close(outputStream);
|
||||||
|
this.outputStream = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.orion.lang.exception.argument.InvalidArgumentException;
|
|||||||
import com.orion.lang.utils.Strings;
|
import com.orion.lang.utils.Strings;
|
||||||
import com.orion.visor.framework.common.constant.ErrorMessage;
|
import com.orion.visor.framework.common.constant.ErrorMessage;
|
||||||
import com.orion.visor.framework.websocket.core.utils.WebSockets;
|
import com.orion.visor.framework.websocket.core.utils.WebSockets;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiverType;
|
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiver;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorResponse;
|
import com.orion.visor.module.asset.handler.host.transfer.model.TransferOperatorResponse;
|
||||||
import org.apache.catalina.connector.ClientAbortException;
|
import org.apache.catalina.connector.ClientAbortException;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
@@ -13,11 +13,9 @@ import org.springframework.web.socket.WebSocketSession;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 传输工具类
|
|
||||||
*
|
|
||||||
* @author Jiahang Li
|
* @author Jiahang Li
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @since 2024/2/22 22:14
|
* @since 2024/7/12 15:06
|
||||||
*/
|
*/
|
||||||
public class TransferUtils {
|
public class TransferUtils {
|
||||||
|
|
||||||
@@ -31,7 +29,7 @@ public class TransferUtils {
|
|||||||
* @param type type
|
* @param type type
|
||||||
* @param ex ex
|
* @param ex ex
|
||||||
*/
|
*/
|
||||||
public static void sendMessage(WebSocketSession channel, TransferReceiverType type, Exception ex) {
|
public static void sendMessage(WebSocketSession channel, TransferReceiver type, Exception ex) {
|
||||||
sendMessage(channel, type, ex, null);
|
sendMessage(channel, type, ex, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +41,7 @@ public class TransferUtils {
|
|||||||
* @param ex ex
|
* @param ex ex
|
||||||
* @param filler filler
|
* @param filler filler
|
||||||
*/
|
*/
|
||||||
public static void sendMessage(WebSocketSession channel, TransferReceiverType type, Exception ex, Consumer<TransferOperatorResponse> filler) {
|
public static void sendMessage(WebSocketSession channel, TransferReceiver type, Exception ex, Consumer<TransferOperatorResponse> filler) {
|
||||||
TransferOperatorResponse resp = TransferOperatorResponse.builder()
|
TransferOperatorResponse resp = TransferOperatorResponse.builder()
|
||||||
.type(type.getType())
|
.type(type.getType())
|
||||||
.success(ex == null)
|
.success(ex == null)
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ import com.orion.visor.module.asset.convert.HostSftpLogConvert;
|
|||||||
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
|
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
|
||||||
import com.orion.visor.module.asset.entity.request.host.HostSftpLogQueryRequest;
|
import com.orion.visor.module.asset.entity.request.host.HostSftpLogQueryRequest;
|
||||||
import com.orion.visor.module.asset.entity.vo.HostSftpLogVO;
|
import com.orion.visor.module.asset.entity.vo.HostSftpLogVO;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.handler.ITransferHandler;
|
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.manager.HostTransferManager;
|
import com.orion.visor.module.asset.handler.host.transfer.manager.HostTransferManager;
|
||||||
import com.orion.visor.module.asset.handler.host.transfer.session.IDownloadSession;
|
import com.orion.visor.module.asset.handler.host.transfer.session.DownloadSession;
|
||||||
import com.orion.visor.module.asset.service.HostSftpService;
|
import com.orion.visor.module.asset.service.HostSftpService;
|
||||||
import com.orion.visor.module.infra.api.OperatorLogApi;
|
import com.orion.visor.module.infra.api.OperatorLogApi;
|
||||||
import com.orion.visor.module.infra.entity.dto.operator.OperatorLogQueryDTO;
|
import com.orion.visor.module.infra.entity.dto.operator.OperatorLogQueryDTO;
|
||||||
@@ -82,10 +81,10 @@ public class HostSftpServiceImpl implements HostSftpService {
|
|||||||
@Override
|
@Override
|
||||||
public StreamingResponseBody downloadWithTransferToken(String channelId, String transferToken, HttpServletResponse response) {
|
public StreamingResponseBody downloadWithTransferToken(String channelId, String transferToken, HttpServletResponse response) {
|
||||||
// 获取会话
|
// 获取会话
|
||||||
IDownloadSession session = Optional.ofNullable(channelId)
|
DownloadSession session = (DownloadSession) Optional.ofNullable(channelId)
|
||||||
.map(hostTransferManager::getHandler)
|
.map(hostTransferManager::getHandler)
|
||||||
.map(ITransferHandler::getTokenSessions)
|
.map(s -> s.getSessionByToken(transferToken))
|
||||||
.map(s -> s.remove(transferToken))
|
.filter(s -> s instanceof DownloadSession)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
// 响应会话
|
// 响应会话
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
:options="toOptions(sshOsTypeKey)"
|
:options="toOptions(sshOsTypeKey)"
|
||||||
placeholder="请选择系统类型" />
|
placeholder="请选择系统类型" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<!-- SSH 端口 -->
|
||||||
|
<a-form-item field="port"
|
||||||
|
label="SSH端口"
|
||||||
|
:hide-asterisk="true">
|
||||||
|
<a-input-number v-model="formModel.port"
|
||||||
|
placeholder="请输入SSH端口"
|
||||||
|
hide-button />
|
||||||
|
</a-form-item>
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
<a-form-item field="username"
|
<a-form-item field="username"
|
||||||
label="用户名"
|
label="用户名"
|
||||||
@@ -38,14 +46,6 @@
|
|||||||
:disabled="SshAuthType.IDENTITY === formModel.authType"
|
:disabled="SshAuthType.IDENTITY === formModel.authType"
|
||||||
placeholder="请输入用户名" />
|
placeholder="请输入用户名" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- SSH 端口 -->
|
|
||||||
<a-form-item field="port"
|
|
||||||
label="SSH端口"
|
|
||||||
:hide-asterisk="true">
|
|
||||||
<a-input-number v-model="formModel.port"
|
|
||||||
placeholder="请输入SSH端口"
|
|
||||||
hide-button />
|
|
||||||
</a-form-item>
|
|
||||||
<!-- 验证方式 -->
|
<!-- 验证方式 -->
|
||||||
<a-form-item field="authType"
|
<a-form-item field="authType"
|
||||||
label="验证方式"
|
label="验证方式"
|
||||||
|
|||||||
@@ -187,6 +187,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.arco-upload-list-item-name-link) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.arco-upload-list-item-name-text) {
|
:deep(.arco-upload-list-item-name-text) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 进度 -->
|
<!-- 进度 -->
|
||||||
<a-tooltip position="left"
|
<a-tooltip position="left"
|
||||||
:content="((file.current || 0) / file.fileSize * 100).toFixed(2) + '%'"
|
:content="file.fileSize ? ((file.current || 0) / file.fileSize * 100).toFixed(2) + '%' : '0%'"
|
||||||
mini>
|
mini>
|
||||||
<a-progress type="circle"
|
<a-progress type="circle"
|
||||||
size="mini"
|
size="mini"
|
||||||
:status="getDictValue(fileStatusKey, file.status, 'status') as any"
|
:status="getDictValue(fileStatusKey, file.status, 'status') as any"
|
||||||
:percent="(file.current || 0) / file.fileSize" />
|
:percent="file.fileSize ? (file.current || 0) / file.fileSize : 0" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,41 +1,33 @@
|
|||||||
import type { ISftpTransferDownloader, SftpTransferItem } from '../types/terminal.type';
|
import type { SftpTransferItem } from '../types/terminal.type';
|
||||||
import { TransferOperatorType, TransferStatus } from '../types/terminal.const';
|
import { TransferStatus, TransferType } from '../types/terminal.const';
|
||||||
import { getFileName, getPath } from '@/utils/file';
|
import { getFileName, openDownloadFile } from '@/utils/file';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
import { getDownloadTransferUrl } from '@/api/asset/host-sftp';
|
||||||
|
import SftpTransferHandler from './sftp-transfer-handler';
|
||||||
|
|
||||||
// sftp 下载器实现
|
// sftp 下载器实现
|
||||||
export default class SftpTransferDownloader implements ISftpTransferDownloader {
|
export default class SftpTransferDownloader extends SftpTransferHandler {
|
||||||
|
|
||||||
public abort: boolean;
|
|
||||||
|
|
||||||
private client: WebSocket;
|
|
||||||
private item: SftpTransferItem;
|
|
||||||
|
|
||||||
constructor(item: SftpTransferItem, client: WebSocket) {
|
constructor(item: SftpTransferItem, client: WebSocket) {
|
||||||
this.abort = false;
|
super(TransferType.DOWNLOAD, item, client);
|
||||||
this.item = item;
|
|
||||||
this.client = client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始下载
|
// 开始回调
|
||||||
initDownload() {
|
onStart(channelId: string, token: string) {
|
||||||
this.item.status = TransferStatus.TRANSFERRING;
|
super.onStart(channelId, token);
|
||||||
// 发送开始下载信息
|
// 获取下载 url
|
||||||
this.client?.send(JSON.stringify({
|
const url = getDownloadTransferUrl(channelId, token);
|
||||||
type: TransferOperatorType.DOWNLOAD_INIT,
|
// 打开
|
||||||
path: getPath(this.item.parentPath + '/' + this.item.name),
|
openDownloadFile(url);
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载完成
|
// 完成回调
|
||||||
downloadFinish() {
|
onFinish() {
|
||||||
if (this.abort) {
|
super.onFinish();
|
||||||
|
if (this.aborted) {
|
||||||
// 中断则不触发下载
|
// 中断则不触发下载
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 设置实际大小
|
|
||||||
this.item.currentSize = this.item.totalSize;
|
|
||||||
if (this.item.totalSize === 0) {
|
if (this.item.totalSize === 0) {
|
||||||
// 空文件直接触发下载
|
// 空文件直接触发下载
|
||||||
try {
|
try {
|
||||||
@@ -53,20 +45,4 @@ export default class SftpTransferDownloader implements ISftpTransferDownloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载失败
|
|
||||||
downloadError(msg: string | undefined) {
|
|
||||||
this.item.status = TransferStatus.ERROR;
|
|
||||||
this.item.errorMessage = msg || '下载失败';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载中断
|
|
||||||
downloadAbort() {
|
|
||||||
this.abort = true;
|
|
||||||
// 发送下载中断信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
type: TransferOperatorType.DOWNLOAD_ABORT,
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import type { ISftpTransferHandler, SftpTransferItem } from '../types/terminal.type';
|
||||||
|
import { TransferOperator, TransferStatus } from '../types/terminal.const';
|
||||||
|
import { getPath } from '@/utils/file';
|
||||||
|
|
||||||
|
// sftp 传输处理器定义
|
||||||
|
export default abstract class SftpTransferHandler implements ISftpTransferHandler {
|
||||||
|
|
||||||
|
public type: string;
|
||||||
|
public finished: boolean;
|
||||||
|
public aborted: boolean;
|
||||||
|
protected client: WebSocket;
|
||||||
|
protected item: SftpTransferItem;
|
||||||
|
|
||||||
|
protected constructor(type: string, item: SftpTransferItem, client: WebSocket) {
|
||||||
|
this.type = type;
|
||||||
|
this.finished = false;
|
||||||
|
this.aborted = false;
|
||||||
|
this.item = item;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始
|
||||||
|
start() {
|
||||||
|
this.item.status = TransferStatus.TRANSFERRING;
|
||||||
|
// 发送开始信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.START,
|
||||||
|
type: this.type,
|
||||||
|
path: getPath(this.item.parentPath + '/' + this.item.name),
|
||||||
|
hostId: this.item.hostId
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 完成
|
||||||
|
finish() {
|
||||||
|
this.finished = true;
|
||||||
|
this.item.status = TransferStatus.SUCCESS;
|
||||||
|
// 发送完成的信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.FINISH,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.item.hostId
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 失败
|
||||||
|
error() {
|
||||||
|
this.finished = true;
|
||||||
|
this.item.status = TransferStatus.ERROR;
|
||||||
|
// 发送上传失败的信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.ERROR,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.item.hostId
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中断
|
||||||
|
abort() {
|
||||||
|
this.aborted = true;
|
||||||
|
// 发送中断的信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.ABORT,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.item.hostId
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否有下一个分片
|
||||||
|
hasNextPart() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下一页分片回调
|
||||||
|
onNextPart() {
|
||||||
|
return undefined as unknown as any;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始回调
|
||||||
|
onStart(channelId: string, token: string) {
|
||||||
|
};
|
||||||
|
|
||||||
|
// 进度回调
|
||||||
|
onProgress(size: number) {
|
||||||
|
if (this.item && size) {
|
||||||
|
this.item.currentSize = size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 失败回调
|
||||||
|
onError(msg: string | undefined) {
|
||||||
|
this.finished = true;
|
||||||
|
this.item.status = TransferStatus.ERROR;
|
||||||
|
this.item.errorMessage = msg || '传输失败';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成回调
|
||||||
|
onFinish() {
|
||||||
|
this.finished = true;
|
||||||
|
this.item.status = TransferStatus.SUCCESS;
|
||||||
|
this.item.currentSize = this.item.totalSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中断回调
|
||||||
|
onAbort() {
|
||||||
|
this.aborted = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import type { ISftpTransferManager, ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
|
import type { ISftpTransferHandler, ISftpTransferManager, SftpFile, SftpTransferItem, TransferOperatorResponse } from '../types/terminal.type';
|
||||||
import { ISftpTransferDownloader, SftpFile, TransferOperatorResponse } from '../types/terminal.type';
|
import { sessionCloseMsg, TransferReceiver, TransferStatus, TransferType } from '../types/terminal.const';
|
||||||
import { sessionCloseMsg, TransferReceiverType, TransferStatus, TransferType } from '../types/terminal.const';
|
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { getTerminalAccessToken, openHostTransferChannel } from '@/api/asset/host-terminal';
|
import { getTerminalAccessToken, openHostTransferChannel } from '@/api/asset/host-terminal';
|
||||||
import { nextId } from '@/utils';
|
import { nextId } from '@/utils';
|
||||||
import { getDownloadTransferUrl } from '@/api/asset/host-sftp';
|
|
||||||
import SftpTransferUploader from './sftp-transfer-uploader';
|
import SftpTransferUploader from './sftp-transfer-uploader';
|
||||||
import SftpTransferDownloader from './sftp-transfer-downloader';
|
import SftpTransferDownloader from './sftp-transfer-downloader';
|
||||||
import { openDownloadFile } from '@/utils/file';
|
|
||||||
|
|
||||||
// sftp 传输管理器实现
|
// sftp 传输管理器实现
|
||||||
export default class SftpTransferManager implements ISftpTransferManager {
|
export default class SftpTransferManager implements ISftpTransferManager {
|
||||||
@@ -20,9 +17,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
|
|
||||||
private currentItem?: SftpTransferItem;
|
private currentItem?: SftpTransferItem;
|
||||||
|
|
||||||
private currentUploader?: ISftpTransferUploader;
|
private currentTransfer?: ISftpTransferHandler;
|
||||||
|
|
||||||
private currentDownloader?: ISftpTransferDownloader;
|
|
||||||
|
|
||||||
public transferList: Array<SftpTransferItem>;
|
public transferList: Array<SftpTransferItem>;
|
||||||
|
|
||||||
@@ -57,13 +52,14 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
|
|
||||||
// 添加下载任务
|
// 添加下载任务
|
||||||
addDownload(hostId: number, currentPath: string, files: Array<SftpFile>) {
|
addDownload(hostId: number, currentPath: string, files: Array<SftpFile>) {
|
||||||
|
let pathIndex = currentPath === '/' ? 1 : currentPath.length + 1;
|
||||||
// 转为下载文件
|
// 转为下载文件
|
||||||
const items = files.map(s => {
|
const items = files.map(s => {
|
||||||
return {
|
return {
|
||||||
fileId: nextId(10),
|
fileId: nextId(10),
|
||||||
type: TransferType.DOWNLOAD,
|
type: TransferType.DOWNLOAD,
|
||||||
hostId: hostId,
|
hostId: hostId,
|
||||||
name: s.path.substring(currentPath.length + 1),
|
name: s.path.substring(pathIndex),
|
||||||
parentPath: currentPath,
|
parentPath: currentPath,
|
||||||
currentSize: 0,
|
currentSize: 0,
|
||||||
totalSize: s.size,
|
totalSize: s.size,
|
||||||
@@ -87,11 +83,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
const item = this.transferList[index];
|
const item = this.transferList[index];
|
||||||
if (item.status === TransferStatus.TRANSFERRING) {
|
if (item.status === TransferStatus.TRANSFERRING) {
|
||||||
// 传输中则中断传输
|
// 传输中则中断传输
|
||||||
if (this.currentUploader) {
|
this.currentTransfer?.abort();
|
||||||
this.currentUploader.uploadAbort();
|
|
||||||
} else if (this.currentDownloader) {
|
|
||||||
this.currentDownloader.downloadAbort();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 从列表中移除
|
// 从列表中移除
|
||||||
this.transferList.splice(index, 1);
|
this.transferList.splice(index, 1);
|
||||||
@@ -154,8 +146,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
|
|
||||||
// 传输下一条任务
|
// 传输下一条任务
|
||||||
private transferNextItem() {
|
private transferNextItem() {
|
||||||
this.currentUploader = undefined;
|
this.currentTransfer = undefined;
|
||||||
this.currentDownloader = undefined;
|
|
||||||
// 释放内存
|
// 释放内存
|
||||||
if (this.currentItem) {
|
if (this.currentItem) {
|
||||||
this.currentItem.file = null as unknown as File;
|
this.currentItem.file = null as unknown as File;
|
||||||
@@ -163,14 +154,15 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
// 获取任务
|
// 获取任务
|
||||||
this.currentItem = this.transferList.find(s => s.status === TransferStatus.WAITING);
|
this.currentItem = this.transferList.find(s => s.status === TransferStatus.WAITING);
|
||||||
if (this.currentItem) {
|
if (this.currentItem) {
|
||||||
// 开始传输
|
|
||||||
if (this.currentItem.type === TransferType.UPLOAD) {
|
if (this.currentItem.type === TransferType.UPLOAD) {
|
||||||
// 上传
|
// 上传
|
||||||
this.uploadFile();
|
this.currentTransfer = new SftpTransferUploader(this.currentItem, this.client as WebSocket);
|
||||||
} else {
|
} else {
|
||||||
// 下载
|
// 下载
|
||||||
this.downloadFile();
|
this.currentTransfer = new SftpTransferDownloader(this.currentItem, this.client as WebSocket);
|
||||||
}
|
}
|
||||||
|
// 开始
|
||||||
|
this.currentTransfer?.start();
|
||||||
} else {
|
} else {
|
||||||
// 无任务关闭会话
|
// 无任务关闭会话
|
||||||
this.client?.close();
|
this.client?.close();
|
||||||
@@ -181,110 +173,33 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
private async resolveMessage(message: MessageEvent) {
|
private async resolveMessage(message: MessageEvent) {
|
||||||
// 文本消息
|
// 文本消息
|
||||||
const data = JSON.parse(message.data) as TransferOperatorResponse;
|
const data = JSON.parse(message.data) as TransferOperatorResponse;
|
||||||
if (data.type === TransferReceiverType.NEXT_TRANSFER
|
if (data.type === TransferReceiver.NEXT_PART) {
|
||||||
|| data.type === TransferReceiverType.UPLOAD_FINISH
|
// 接收下一块数据回调
|
||||||
|| data.type === TransferReceiverType.UPLOAD_ERROR) {
|
await this.currentTransfer?.onNextPart();
|
||||||
// 执行下一个传输任务
|
} else if (data.type === TransferReceiver.START) {
|
||||||
this.resolveNextTransfer(data);
|
// 开始回调
|
||||||
} else if (data.type === TransferReceiverType.UPLOAD_NEXT_BLOCK) {
|
this.currentTransfer?.onStart(data.channelId as string, data.transferToken as string);
|
||||||
// 接收下一块上传数据
|
} else if (data.type === TransferReceiver.PROGRESS) {
|
||||||
await this.resolveUploadNextBlock();
|
// 进度回调
|
||||||
} else if (data.type === TransferReceiverType.DOWNLOAD_START) {
|
this.currentTransfer?.onProgress(data.currentSize as number);
|
||||||
// 开始下载
|
} else if (data.type === TransferReceiver.FINISH) {
|
||||||
this.resolveDownloadStart(data);
|
// 完成回调
|
||||||
} else if (data.type === TransferReceiverType.DOWNLOAD_PROGRESS) {
|
this.currentTransfer?.onFinish();
|
||||||
// 下载进度
|
// 开始下一个传输任务
|
||||||
this.resolveDownloadProgress(data);
|
this.transferNextItem();
|
||||||
} else if (data.type === TransferReceiverType.DOWNLOAD_FINISH) {
|
} else if (data.type === TransferReceiver.ERROR) {
|
||||||
// 下载完成
|
// 失败回调
|
||||||
this.resolveDownloadFinish();
|
this.currentTransfer?.onError(data.msg);
|
||||||
} else if (data.type === TransferReceiverType.DOWNLOAD_ERROR) {
|
// 开始下一个传输任务
|
||||||
// 下载失败
|
this.transferNextItem();
|
||||||
this.resolveDownloadError(data.msg);
|
} else if (data.type === TransferReceiver.ABORT) {
|
||||||
|
// 中断回调
|
||||||
|
this.currentTransfer?.onAbort();
|
||||||
|
// 开始下一个传输任务
|
||||||
|
this.transferNextItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传文件
|
|
||||||
private uploadFile() {
|
|
||||||
// 创建上传器
|
|
||||||
this.currentUploader = new SftpTransferUploader(this.currentItem as SftpTransferItem, this.client as WebSocket);
|
|
||||||
// 开始上传
|
|
||||||
this.currentUploader.startUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载文件
|
|
||||||
private downloadFile() {
|
|
||||||
// 创建下载器
|
|
||||||
this.currentDownloader = new SftpTransferDownloader(this.currentItem as SftpTransferItem, this.client as WebSocket);
|
|
||||||
// 初始化下载
|
|
||||||
this.currentDownloader.initDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收下一个传输任务响应
|
|
||||||
private resolveNextTransfer(data: TransferOperatorResponse) {
|
|
||||||
if (this.currentItem) {
|
|
||||||
if (data.success) {
|
|
||||||
this.currentItem.status = TransferStatus.SUCCESS;
|
|
||||||
} else {
|
|
||||||
this.currentItem.status = TransferStatus.ERROR;
|
|
||||||
this.currentItem.errorMessage = data.msg || '传输失败';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 开始下一个传输任务
|
|
||||||
this.transferNextItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收下一块上传数据响应
|
|
||||||
private async resolveUploadNextBlock() {
|
|
||||||
// 只可能为上传并且成功
|
|
||||||
if (!this.currentUploader) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.currentUploader.hasNextBlock()
|
|
||||||
&& !this.currentUploader.abort
|
|
||||||
&& !this.currentUploader.finish) {
|
|
||||||
try {
|
|
||||||
// 有下一个分片则上传 (上一个分片传输完成)
|
|
||||||
await this.currentUploader.uploadNextBlock();
|
|
||||||
} catch (e) {
|
|
||||||
// 读取文件失败
|
|
||||||
this.currentUploader.uploadError((e as Error).message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 没有下一个分片则发送完成
|
|
||||||
this.currentUploader.uploadFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收开始下载响应
|
|
||||||
private resolveDownloadStart(data: TransferOperatorResponse) {
|
|
||||||
// 获取下载 url
|
|
||||||
const url = getDownloadTransferUrl(data.channelId as string, data.transferToken as string);
|
|
||||||
// 打开
|
|
||||||
openDownloadFile(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收下载进度响应
|
|
||||||
private resolveDownloadProgress(data: TransferOperatorResponse) {
|
|
||||||
if (this.currentItem && data.currentSize) {
|
|
||||||
this.currentItem.currentSize = data.currentSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收下载完成响应
|
|
||||||
private resolveDownloadFinish() {
|
|
||||||
this.currentDownloader?.downloadFinish();
|
|
||||||
// 开始下一个传输任务
|
|
||||||
this.transferNextItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收下载失败响应
|
|
||||||
private resolveDownloadError(msg: string | undefined) {
|
|
||||||
this.currentDownloader?.downloadError(msg);
|
|
||||||
// 开始下一个传输任务
|
|
||||||
this.transferNextItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭 释放资源
|
// 关闭 释放资源
|
||||||
private close() {
|
private close() {
|
||||||
// 重置 run
|
// 重置 run
|
||||||
|
|||||||
@@ -1,52 +1,54 @@
|
|||||||
import type { ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
|
import type { SftpTransferItem } from '../types/terminal.type';
|
||||||
import { TransferOperatorType, TransferStatus } from '../types/terminal.const';
|
import { TransferType } from '../types/terminal.const';
|
||||||
import { getPath } from '@/utils/file';
|
import SftpTransferHandler from './sftp-transfer-handler';
|
||||||
|
|
||||||
// 512 KB
|
// 512 KB
|
||||||
export const BLOCK_SIZE = 512 * 1024;
|
export const PART_SIZE = 512 * 1024;
|
||||||
|
|
||||||
// sftp 上传器实现
|
// sftp 上传器实现
|
||||||
export default class SftpTransferUploader implements ISftpTransferUploader {
|
export default class SftpTransferUploader extends SftpTransferHandler {
|
||||||
|
|
||||||
public finish: boolean;
|
private currentPart: number;
|
||||||
public abort: boolean;
|
private readonly totalPart: number;
|
||||||
private currentBlock: number;
|
|
||||||
private totalBlock: number;
|
|
||||||
private client: WebSocket;
|
|
||||||
private item: SftpTransferItem;
|
|
||||||
private file: File;
|
private file: File;
|
||||||
|
|
||||||
constructor(item: SftpTransferItem, client: WebSocket) {
|
constructor(item: SftpTransferItem, client: WebSocket) {
|
||||||
this.abort = false;
|
super(TransferType.UPLOAD, item, client);
|
||||||
this.finish = false;
|
|
||||||
this.item = item;
|
|
||||||
this.client = client;
|
|
||||||
this.file = item.file;
|
this.file = item.file;
|
||||||
this.currentBlock = 0;
|
this.currentPart = 0;
|
||||||
this.totalBlock = Math.ceil(item.file.size / BLOCK_SIZE);
|
this.totalPart = Math.ceil(item.file.size / PART_SIZE);
|
||||||
}
|
|
||||||
|
|
||||||
// 开始上传
|
|
||||||
startUpload() {
|
|
||||||
this.item.status = TransferStatus.TRANSFERRING;
|
|
||||||
// 发送开始上传信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
type: TransferOperatorType.UPLOAD_START,
|
|
||||||
path: getPath(this.item.parentPath + '/' + this.item.name),
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否有下一个分片
|
// 是否有下一个分片
|
||||||
hasNextBlock() {
|
hasNextPart() {
|
||||||
return this.currentBlock < this.totalBlock;
|
return this.currentPart < this.totalPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传下一个分片
|
// 上传下一个分片
|
||||||
async uploadNextBlock() {
|
async onNextPart() {
|
||||||
|
super.onNextPart();
|
||||||
|
// 完成或者中断直接跳过
|
||||||
|
if (this.aborted || this.finished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.hasNextPart()) {
|
||||||
|
try {
|
||||||
|
// 有下一个分片则上传
|
||||||
|
await this.doUploadNextPart();
|
||||||
|
} catch (e) {
|
||||||
|
// 读取文件失败
|
||||||
|
this.error();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行上传下一分片
|
||||||
|
private async doUploadNextPart() {
|
||||||
// 读取数据
|
// 读取数据
|
||||||
const start = this.currentBlock * BLOCK_SIZE;
|
const start = this.currentPart * PART_SIZE;
|
||||||
const end = Math.min(this.file.size, start + BLOCK_SIZE);
|
const end = Math.min(this.file.size, start + PART_SIZE);
|
||||||
const chunk = this.file.slice(start, end);
|
const chunk = this.file.slice(start, end);
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
const arrayBuffer = await new Promise((resolve, reject) => {
|
const arrayBuffer = await new Promise((resolve, reject) => {
|
||||||
@@ -56,36 +58,8 @@ export default class SftpTransferUploader implements ISftpTransferUploader {
|
|||||||
});
|
});
|
||||||
// 发送数据
|
// 发送数据
|
||||||
this.client?.send(arrayBuffer as ArrayBuffer);
|
this.client?.send(arrayBuffer as ArrayBuffer);
|
||||||
this.currentBlock++;
|
this.currentPart++;
|
||||||
this.item.currentSize += (end - start);
|
this.item.currentSize += (end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传完成
|
|
||||||
uploadFinish() {
|
|
||||||
this.finish = true;
|
|
||||||
this.item.status = TransferStatus.SUCCESS;
|
|
||||||
// 发送上传完成的信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
type: TransferOperatorType.UPLOAD_FINISH,
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传失败
|
|
||||||
uploadError(msg: string | undefined) {
|
|
||||||
this.finish = true;
|
|
||||||
this.item.status = TransferStatus.ERROR;
|
|
||||||
this.item.errorMessage = msg || '上传失败';
|
|
||||||
// 发送上传完成的信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
type: TransferOperatorType.UPLOAD_ERROR,
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传中断
|
|
||||||
uploadAbort() {
|
|
||||||
this.abort = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {
|
import type {
|
||||||
ISftpSession,
|
ISftpSession,
|
||||||
ISshSession,
|
ISshSession,
|
||||||
ITerminalChannel,
|
ITerminalChannel,
|
||||||
@@ -76,7 +76,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
|||||||
ssh.connect();
|
ssh.connect();
|
||||||
} else {
|
} else {
|
||||||
// 未成功展示错误信息
|
// 未成功展示错误信息
|
||||||
ssh.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
ssh.write(`[91m${msg || ''}[0m\r\n[91m输入回车重新连接...[0m\r\n\r\n`);
|
||||||
ssh.status = TerminalStatus.CLOSED;
|
ssh.status = TerminalStatus.CLOSED;
|
||||||
}
|
}
|
||||||
}, sftp => {
|
}, sftp => {
|
||||||
@@ -109,7 +109,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
|||||||
// ssh 拼接关闭消息
|
// ssh 拼接关闭消息
|
||||||
ssh.write(`\r\n\r\n[91m${msg || ''}[0m\r\n`);
|
ssh.write(`\r\n\r\n[91m${msg || ''}[0m\r\n`);
|
||||||
if (!isForceClose) {
|
if (!isForceClose) {
|
||||||
ssh.write('[91m输入回车重新连接...[0m\r\n\r\n');
|
ssh.write(`[91m${msg || ''}[0m\r\n[91m输入回车重新连接...[0m\r\n\r\n`);
|
||||||
}
|
}
|
||||||
// 设置状态
|
// 设置状态
|
||||||
ssh.status = TerminalStatus.CLOSED;
|
ssh.status = TerminalStatus.CLOSED;
|
||||||
|
|||||||
@@ -327,25 +327,22 @@ export const TransferType = {
|
|||||||
DOWNLOAD: 'download'
|
DOWNLOAD: 'download'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 传输操作类型
|
// 传输操作
|
||||||
export const TransferOperatorType = {
|
export const TransferOperator = {
|
||||||
UPLOAD_START: 'uploadStart',
|
START: 'start',
|
||||||
UPLOAD_FINISH: 'uploadFinish',
|
FINISH: 'finish',
|
||||||
UPLOAD_ERROR: 'uploadError',
|
ERROR: 'error',
|
||||||
DOWNLOAD_INIT: 'downloadInit',
|
ABORT: 'abort',
|
||||||
DOWNLOAD_ABORT: 'downloadAbort',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 传输响应类型
|
// 传输响应
|
||||||
export const TransferReceiverType = {
|
export const TransferReceiver = {
|
||||||
NEXT_TRANSFER: 'nextTransfer',
|
NEXT_PART: 'nextPart',
|
||||||
UPLOAD_NEXT_BLOCK: 'uploadNextBlock',
|
START: 'start',
|
||||||
UPLOAD_FINISH: 'uploadFinish',
|
PROGRESS: 'progress',
|
||||||
UPLOAD_ERROR: 'uploadError',
|
FINISH: 'finish',
|
||||||
DOWNLOAD_START: 'downloadStart',
|
ERROR: 'error',
|
||||||
DOWNLOAD_PROGRESS: 'downloadProgress',
|
ABORT: 'abort',
|
||||||
DOWNLOAD_FINISH: 'downloadFinish',
|
|
||||||
DOWNLOAD_ERROR: 'downloadError',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 会话关闭信息
|
// 会话关闭信息
|
||||||
|
|||||||
@@ -414,38 +414,41 @@ export interface ISftpTransferManager {
|
|||||||
cancelAllTransfer: () => void;
|
cancelAllTransfer: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sftp 上传器定义
|
|
||||||
export interface ISftpTransferUploader {
|
// sftp 传输处理回调定义
|
||||||
// 是否完成
|
export interface ISftpTransferCallback {
|
||||||
finish: boolean;
|
// 下一分片回调
|
||||||
// 是否中断
|
onNextPart: () => Promise<void>;
|
||||||
abort: boolean;
|
// 开始回调
|
||||||
// 开始上传
|
onStart: (channelId: string, token: string) => void;
|
||||||
startUpload: () => void;
|
// 进度回调
|
||||||
// 是否有下一个分片
|
onProgress: (size: number) => void;
|
||||||
hasNextBlock: () => boolean;
|
// 失败回调
|
||||||
// 上传下一个分片
|
onError: (msg: string | undefined) => void;
|
||||||
uploadNextBlock: () => Promise<void>;
|
// 完成回调
|
||||||
// 上传完成
|
onFinish: () => void;
|
||||||
uploadFinish: () => void;
|
// 中断回调
|
||||||
// 上传失败
|
onAbort: () => void;
|
||||||
uploadError: (msg: string | undefined) => void;
|
|
||||||
// 上传中断
|
|
||||||
uploadAbort: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sftp 下载器定义
|
// sftp 传输处理器定义
|
||||||
export interface ISftpTransferDownloader {
|
export interface ISftpTransferHandler extends ISftpTransferCallback {
|
||||||
|
// 类型
|
||||||
|
type: string;
|
||||||
|
// 是否完成
|
||||||
|
finished: boolean;
|
||||||
// 是否中断
|
// 是否中断
|
||||||
abort: boolean;
|
aborted: boolean;
|
||||||
// 初始化下载
|
// 开始
|
||||||
initDownload: () => void;
|
start: () => void;
|
||||||
// 下载完成
|
// 完成
|
||||||
downloadFinish: () => void;
|
finish: () => void;
|
||||||
// 下载失败
|
// 失败
|
||||||
downloadError: (msg: string | undefined) => void;
|
error: () => void;
|
||||||
// 下载中断
|
// 中断
|
||||||
downloadAbort: () => void;
|
abort: () => void;
|
||||||
|
// 是否有下一个分片
|
||||||
|
hasNextPart: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sftp 上传文件项
|
// sftp 上传文件项
|
||||||
|
|||||||
Reference in New Issue
Block a user