优化文件下载逻辑.

This commit is contained in:
lijiahangmax
2024-10-10 23:18:53 +08:00
parent c229029c1d
commit 6e977dabf6
22 changed files with 357 additions and 48 deletions

View File

@@ -18,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import javax.annotation.Resource;
@@ -60,6 +61,24 @@ public class HostSftpLogController {
return hostSftpService.deleteHostSftpLog(idList);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get-content")
@Operation(summary = "获取文件内容")
@Parameter(name = "token", description = "token", required = true)
public void getFileContentByToken(@RequestParam("token") String token, HttpServletResponse response) throws Exception {
hostSftpService.getFileContentByToken(token, response);
}
@PostMapping("/set-content")
@Operation(summary = "设置文件内容")
@Parameter(name = "token", description = "token", required = true)
@Parameter(name = "file", description = "file", required = true)
public Boolean setFileContentByToken(@RequestParam("token") String token,
@RequestParam("file") MultipartFile file) throws Exception {
hostSftpService.setFileContentByToken(token, file);
return true;
}
@PermitAll
@IgnoreWrapper
@IgnoreLog(IgnoreLogMode.RET)

View File

@@ -5,6 +5,8 @@ import com.orion.lang.define.cache.key.CacheKeyDefine;
import com.orion.lang.define.cache.key.struct.RedisCacheStruct;
import com.orion.visor.module.asset.entity.dto.HostTerminalAccessDTO;
import com.orion.visor.module.asset.entity.dto.HostTerminalTransferDTO;
import com.orion.visor.module.asset.entity.dto.SftpGetContentCacheDTO;
import com.orion.visor.module.asset.entity.dto.SftpSetContentCacheDTO;
import java.util.concurrent.TimeUnit;
@@ -33,4 +35,20 @@ public interface HostTerminalCacheKeyDefine {
.timeout(3, TimeUnit.MINUTES)
.build();
CacheKeyDefine SFTP_GET_CONTENT = new CacheKeyBuilder()
.key("sftp:get:content:{}")
.desc("sftp 获取文件内容 ${token}")
.type(SftpGetContentCacheDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(5, TimeUnit.MINUTES)
.build();
CacheKeyDefine SFTP_SET_CONTENT = new CacheKeyBuilder()
.key("sftp:set:content:{}")
.desc("sftp 设置文件内容 ${token}")
.type(SftpSetContentCacheDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(5, TimeUnit.MINUTES)
.build();
}

View File

@@ -0,0 +1,33 @@
package com.orion.visor.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* sftp 获取文件内容缓存对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/10 20:49
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SftpGetContentCacheDTO", description = "sftp 获取文件内容缓存对象")
public class SftpGetContentCacheDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "文件路径")
private String path;
}

View File

@@ -0,0 +1,33 @@
package com.orion.visor.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* sftp 获取文件内容缓存对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/10 20:49
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SftpSetContentCacheDTO", description = "sftp 设置文件内容缓存对象")
public class SftpSetContentCacheDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "文件路径")
private String path;
}

View File

@@ -159,8 +159,8 @@ public enum InputTypeEnum {
*/
SFTP_SET_CONTENT("sc",
SftpSetContentHandler.class,
new String[]{"type", "sessionId", "path", "content"},
SftpSetContentRequest.class,
new String[]{"type", "sessionId", "path"},
SftpBaseRequest.class,
true),
;

View File

@@ -83,12 +83,12 @@ public enum OutputTypeEnum {
/**
* SFTP 获取文件内容
*/
SFTP_GET_CONTENT("gc", "${type}|${sessionId}|${path}|${result}|${msg}|${content}"),
SFTP_GET_CONTENT("gc", "${type}|${sessionId}|${result}|${msg}|${token}"),
/**
* SFTP 修改文件内容
*/
SFTP_SET_CONTENT("sc", "${type}|${sessionId}|${result}|${msg}"),
SFTP_SET_CONTENT("sc", "${type}|${sessionId}|${result}|${msg}|${token}"),
;

View File

@@ -1,7 +1,10 @@
package com.orion.visor.module.asset.handler.host.terminal.handler;
import com.orion.visor.framework.common.constant.Const;
import com.orion.lang.id.UUIds;
import com.orion.visor.framework.common.enums.BooleanBit;
import com.orion.visor.framework.redis.core.utils.RedisStrings;
import com.orion.visor.module.asset.define.cache.HostTerminalCacheKeyDefine;
import com.orion.visor.module.asset.entity.dto.SftpGetContentCacheDTO;
import com.orion.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.visor.module.asset.handler.host.terminal.model.request.SftpBaseRequest;
import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpGetContentResponse;
@@ -28,11 +31,18 @@ public class SftpGetContentHandler extends AbstractTerminalHandler<SftpBaseReque
ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId);
String path = payload.getPath();
log.info("SftpGetContentHandler-handle start sessionId: {}, path: {}", sessionId, path);
String content = Const.EMPTY;
String token = UUIds.random32();
Exception ex = null;
// 获取内容
try {
content = session.getContent(path);
// 检查文件是否可编辑
session.checkCanEdit(path);
// 设置缓存
String key = HostTerminalCacheKeyDefine.SFTP_GET_CONTENT.format(token);
SftpGetContentCacheDTO cache = SftpGetContentCacheDTO.builder()
.hostId(session.getConfig().getHostId())
.path(path)
.build();
RedisStrings.setJson(key, HostTerminalCacheKeyDefine.SFTP_GET_CONTENT, cache);
log.info("SftpGetContentHandler-handle success sessionId: {}, path: {}", sessionId, path);
} catch (Exception e) {
log.error("SftpGetContentHandler-handle error sessionId: {}", sessionId, e);
@@ -44,7 +54,7 @@ public class SftpGetContentHandler extends AbstractTerminalHandler<SftpBaseReque
SftpGetContentResponse.builder()
.sessionId(sessionId)
.result(BooleanBit.of(ex == null).getValue())
.content(content)
.token(token)
.msg(this.getErrorMessage(ex))
.build());
}

View File

@@ -1,12 +1,16 @@
package com.orion.visor.module.asset.handler.host.terminal.handler;
import com.orion.lang.id.UUIds;
import com.orion.lang.utils.collect.Maps;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.enums.BooleanBit;
import com.orion.visor.framework.redis.core.utils.RedisStrings;
import com.orion.visor.module.asset.define.cache.HostTerminalCacheKeyDefine;
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.visor.module.asset.entity.dto.SftpSetContentCacheDTO;
import com.orion.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.visor.module.asset.handler.host.terminal.model.request.SftpSetContentRequest;
import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpBaseResponse;
import com.orion.visor.module.asset.handler.host.terminal.model.request.SftpBaseRequest;
import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpSetContentResponse;
import com.orion.visor.module.asset.handler.host.terminal.session.ISftpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -23,20 +27,28 @@ import java.util.Map;
*/
@Slf4j
@Component
public class SftpSetContentHandler extends AbstractTerminalHandler<SftpSetContentRequest> {
public class SftpSetContentHandler extends AbstractTerminalHandler<SftpBaseRequest> {
@Override
public void handle(WebSocketSession channel, SftpSetContentRequest payload) {
public void handle(WebSocketSession channel, SftpBaseRequest payload) {
long startTime = System.currentTimeMillis();
// 获取会话
String sessionId = payload.getSessionId();
ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId);
String path = payload.getPath();
log.info("SftpSetContentHandler-handle start sessionId: {}, path: {}", sessionId, path);
String token = UUIds.random32();
Exception ex = null;
// 修改内容
try {
session.setContent(path, payload.getContent());
// 检查文件是否可编辑
session.checkCanEdit(path);
// 设置缓存
String key = HostTerminalCacheKeyDefine.SFTP_SET_CONTENT.format(token);
SftpSetContentCacheDTO cache = SftpSetContentCacheDTO.builder()
.hostId(session.getConfig().getHostId())
.path(path)
.build();
RedisStrings.setJson(key, HostTerminalCacheKeyDefine.SFTP_SET_CONTENT, cache);
log.info("SftpSetContentHandler-handle success sessionId: {}, path: {}", sessionId, path);
} catch (Exception e) {
log.error("SftpSetContentHandler-handle error sessionId: {}", sessionId, e);
@@ -45,9 +57,10 @@ public class SftpSetContentHandler extends AbstractTerminalHandler<SftpSetConten
// 返回
this.send(channel,
OutputTypeEnum.SFTP_SET_CONTENT,
SftpBaseResponse.builder()
SftpSetContentResponse.builder()
.sessionId(sessionId)
.result(BooleanBit.of(ex == null).getValue())
.token(token)
.msg(this.getErrorMessage(ex))
.build());
// 保存操作日志

View File

@@ -20,14 +20,9 @@ import lombok.experimental.SuperBuilder;
@EqualsAndHashCode(callSuper = true)
public class SftpGetContentResponse extends SftpBaseResponse {
/**
* path
*/
private String path;
/**
* content
*/
private String content;
private String token;
}

View File

@@ -1,4 +1,4 @@
package com.orion.visor.module.asset.handler.host.terminal.model.request;
package com.orion.visor.module.asset.handler.host.terminal.model.response;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -7,24 +7,22 @@ import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* sftp 设置文件内容 实体对象
* <p>
* i|eff00a1|path|content
* sftp 设置内容响应
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/2/6 13:31
* @since 2024/2/6 16:20
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SftpSetContentRequest extends SftpBaseRequest {
public class SftpSetContentResponse extends SftpBaseResponse {
/**
* content
*/
private String content;
private String token;
}

View File

@@ -102,4 +102,11 @@ public interface ISftpSession extends ITerminalSession {
*/
void setContent(String path, String content);
/**
* 检测文件是否可编辑
*
* @param path path
*/
void checkCanEdit(String path);
}

View File

@@ -9,6 +9,7 @@ import com.orion.net.host.SessionStore;
import com.orion.net.host.sftp.SftpExecutor;
import com.orion.net.host.sftp.SftpFile;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.module.asset.handler.host.terminal.model.TerminalConfig;
import com.orion.visor.module.asset.handler.host.terminal.model.response.SftpFileVO;
@@ -145,6 +146,13 @@ public class SftpSession extends TerminalSession implements ISftpSession {
}
}
@Override
public void checkCanEdit(String path) {
path = Valid.checkNormalize(path);
// 检查文件是否存在
Valid.isTrue(executor.isExist(path), ErrorMessage.FILE_ABSENT);
}
@Override
public void keepAlive() {
try {

View File

@@ -3,9 +3,11 @@ package com.orion.visor.module.asset.service;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.visor.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.visor.module.asset.entity.vo.HostSftpLogVO;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
@@ -33,6 +35,24 @@ public interface HostSftpService {
*/
Integer deleteHostSftpLog(List<Long> idList);
/**
* 设置文件内容
*
* @param token token
* @param response response
* @throws IOException IOException
*/
void getFileContentByToken(String token, HttpServletResponse response) throws IOException;
/**
* 获取文件内容
*
* @param token token
* @param file file
* @throws IOException IOException
*/
void setFileContentByToken(String token, MultipartFile file) throws IOException;
/**
* 通过 transferToken 下载
*

View File

@@ -4,29 +4,47 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orion.lang.constant.StandardContentType;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.define.wrapper.HttpWrapper;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.Valid;
import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.io.Streams;
import com.orion.net.host.SessionStore;
import com.orion.net.host.sftp.SftpExecutor;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.constant.ExtraFieldConst;
import com.orion.visor.framework.redis.core.utils.RedisStrings;
import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.asset.convert.HostSftpLogConvert;
import com.orion.visor.module.asset.define.cache.HostTerminalCacheKeyDefine;
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.SftpGetContentCacheDTO;
import com.orion.visor.module.asset.entity.dto.SftpSetContentCacheDTO;
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.handler.host.jsch.SessionStores;
import com.orion.visor.module.asset.handler.host.transfer.manager.HostTransferManager;
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.HostTerminalService;
import com.orion.visor.module.infra.api.OperatorLogApi;
import com.orion.visor.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import com.orion.web.servlet.web.Servlets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Optional;
@@ -47,6 +65,9 @@ public class HostSftpServiceImpl implements HostSftpService {
@Resource
private HostTransferManager hostTransferManager;
@Resource
private HostTerminalService hostTerminalService;
@Override
public DataGrid<HostSftpLogVO> getHostSftpLogPage(HostSftpLogQueryRequest request) {
// 查询
@@ -78,6 +99,73 @@ public class HostSftpServiceImpl implements HostSftpService {
return effect;
}
@Override
public void getFileContentByToken(String token, HttpServletResponse response) throws IOException {
// 解析 token
String key = HostTerminalCacheKeyDefine.SFTP_GET_CONTENT.format(token);
SftpGetContentCacheDTO cache = RedisStrings.getJson(key, HostTerminalCacheKeyDefine.SFTP_GET_CONTENT);
if (cache == null) {
Servlets.writeHttpWrapper(response, HttpWrapper.error(ErrorMessage.FILE_ABSENT));
return;
}
// 删除缓存
RedisStrings.delete(key);
// 获取文件内容
SessionStore sessionStore = null;
SftpExecutor executor = null;
InputStream in = null;
try {
// 获取终端连接信息
HostTerminalConnectDTO connectInfo = hostTerminalService.getTerminalConnectInfo(SecurityUtils.getLoginUserId(), cache.getHostId());
sessionStore = SessionStores.openSessionStore(connectInfo);
executor = sessionStore.getSftpExecutor(connectInfo.getFileNameCharset());
executor.connect();
// 读取文件
in = executor.openInputStream(cache.getPath());
// 设置返回
Servlets.setContentType(response, StandardContentType.TEXT_PLAIN);
Servlets.transfer(response, in);
} catch (Exception e) {
Servlets.writeHttpWrapper(response, HttpWrapper.error(ErrorMessage.FILE_READ_ERROR));
} finally {
Streams.close(executor);
Streams.close(sessionStore);
Streams.close(in);
}
}
@Override
public void setFileContentByToken(String token, MultipartFile file) {
// 解析 token
String key = HostTerminalCacheKeyDefine.SFTP_SET_CONTENT.format(token);
SftpSetContentCacheDTO cache = RedisStrings.getJson(key, HostTerminalCacheKeyDefine.SFTP_SET_CONTENT);
Valid.notNull(cache, ErrorMessage.FILE_ABSENT);
// 删除缓存
RedisStrings.delete(key);
// 写入文件内容
SessionStore sessionStore = null;
SftpExecutor executor = null;
OutputStream out = null;
InputStream in = null;
try {
// 获取终端连接信息
HostTerminalConnectDTO connectInfo = hostTerminalService.getTerminalConnectInfo(SecurityUtils.getLoginUserId(), cache.getHostId());
sessionStore = SessionStores.openSessionStore(connectInfo);
executor = sessionStore.getSftpExecutor(connectInfo.getFileNameCharset());
executor.connect();
// 写入文件
out = executor.openOutputStream(cache.getPath());
Streams.transfer(in = file.getInputStream(), out);
} catch (Exception e) {
throw Exceptions.app(ErrorMessage.OPERATE_ERROR);
} finally {
Streams.close(executor);
Streams.close(sessionStore);
Streams.close(out);
Streams.close(in);
}
}
@Override
public StreamingResponseBody downloadWithTransferToken(String channelId, String transferToken, HttpServletResponse response) {
// 获取会话