Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,3 @@
docker/*.sh text eol=lf
docker/Dockerfile text eol=lf
docker/docker-compose.yml text eol=lf

# Other files: auto-detect (will be CRLF on Windows, LF in repo)
* text=auto
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public ResponseEntity<byte[]> downloadData(
return ResponseEntity.notFound().build();
}

// 检查文件大小限制:只允许下载 10MB 以下的0文件
long fileSize = meta.getFileSize() != null ? meta.getFileSize() : 0L;
long maxDownloadSize = 100 * 1024 * 1024; // 10MB
if (fileSize > maxDownloadSize) {
String errorMsg = String.format("文件太大,不允许下载。文件大小: %.2f MB,最大允许: 100 MB",
fileSize / (1024.0 * 1024.0));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(errorMsg.getBytes(StandardCharsets.UTF_8));
}

byte[] data = accessService.downloadData(logicalPath, fileName);
if (data == null || data.length == 0) {
return ResponseEntity.noContent().build();
Expand Down
10 changes: 8 additions & 2 deletions backend/src/main/java/com/storage/engine/dao/IGinxDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,18 @@ public Void run(Session session) throws SessionException {

public SessionExecuteSqlResult queryDataByPath(String pathPrefix) {
String queryPath = StorageUtils.normalizeEscapedPath(pathPrefix);
return executeSql("select * from " + queryPath + ";");
String quotedPath = StorageUtils.quoteIdentifierPath(queryPath);
String sql = "select * from " + quotedPath + ";";
logger.info("[IGinX-SQL] {}", sql);
return executeSql(sql);
}

public SessionExecuteSqlResult queryDataByPathWithLimit(String pathPrefix, int limit) {
String queryPath = StorageUtils.normalizeEscapedPath(pathPrefix);
return executeSql("select * from " + queryPath + " limit " + limit + ";");
String quotedPath = StorageUtils.quoteIdentifierPath(queryPath);
String sql = "select * from " + quotedPath + " limit " + limit + ";";
logger.info("[IGinX-SQL] {}", sql);
return executeSql(sql);
}

// ==================== Cluster Info Operations ====================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public class AddStorageEngineRequest {
private String dummyDir;
private Integer iginxPort;

private String sizeCalculationStrategy;
private String sshUsername;
private String sshPassword;
private Integer sshPort;

public String getSourceType() {
return sourceType;
}
Expand Down Expand Up @@ -67,4 +72,36 @@ public Integer getIginxPort() {
public void setIginxPort(Integer iginxPort) {
this.iginxPort = iginxPort;
}

public String getSizeCalculationStrategy() {
return sizeCalculationStrategy;
}

public void setSizeCalculationStrategy(String sizeCalculationStrategy) {
this.sizeCalculationStrategy = sizeCalculationStrategy;
}

public String getSshUsername() {
return sshUsername;
}

public void setSshUsername(String sshUsername) {
this.sshUsername = sshUsername;
}

public String getSshPassword() {
return sshPassword;
}

public void setSshPassword(String sshPassword) {
this.sshPassword = sshPassword;
}

public Integer getSshPort() {
return sshPort;
}

public void setSshPort(Integer sshPort) {
this.sshPort = sshPort;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,17 @@ private String resolveIginxDataPath(DataItem item) {
externalBody = externalBody.substring(sourceKey.length() + 1);
}

String normalized = externalBody.replace("/", ".").replaceAll("[^a-zA-Z0-9._-]", "_");
String basePath = normalized.isEmpty() ? schemaPrefix : (schemaPrefix + "." + normalized);
// Split by / and escape dots in each segment
String[] segments = externalBody.isEmpty() ? new String[0] : externalBody.split("/");
StringBuilder pathBuilder = new StringBuilder(schemaPrefix);
for (String seg : segments) {
String cleaned = seg == null ? "" : seg.trim().replaceAll("[^a-zA-Z0-9._-]", "_");
if (!cleaned.isEmpty()) {
pathBuilder.append('.').append(cleaned.replace(".", "\\."));
}
}
String basePath = pathBuilder.toString();

if (!fileName.isEmpty()) {
if (isFilesystemLikeSourceKey(sourceKey)) {
return StorageUtils.toFileLeafPath(basePath, fileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
import com.storage.engine.dao.IGinxDao;
import com.storage.engine.model.NodeDeployRequest;
import com.storage.engine.model.NodeDeployTaskStatus;
import com.storage.engine.utils.ScriptExecutionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -46,8 +41,8 @@ public class NodeDeployService {
@Autowired
private IGinxDao iginxDao;

@Value("${resource.base-path:classpath:/}")
private String resourceBasePath;
@Autowired
private ScriptExecutionUtils scriptExecutionUtils;

@Value("${deploy.iginx.default-package-path:/home/ubuntu/IGinX-0.9.0-SNAPSHOT.tar.gz}")
private String defaultPackagePath;
Expand Down Expand Up @@ -80,8 +75,8 @@ public NodeDeployTaskStatus startDeployTask(final NodeDeployRequest request, fin
String zkConnection = safeValue(request.getZookeeperConnectionString(), defaultZkConnection);
String pythonCmd = safeValue(request.getPythonCmd(), defaultPythonCmd);

File scriptFile = resolveScriptFile(DEPLOY_SCRIPT_RELATIVE_PATH, "部署脚本");
File udfListFile = resolveResourceFile("udf/udf_list", "UDF列表文件");
File scriptFile = scriptExecutionUtils.resolveScriptFile(DEPLOY_SCRIPT_RELATIVE_PATH, "部署脚本");
File udfListFile = scriptExecutionUtils.resolveResourceFile("udf/udf_list", "UDF列表文件");
File metadataDir = resolveMetadataDirectory();

String iginxPort = safeValue(request.getPort(), "6888");
Expand Down Expand Up @@ -157,7 +152,7 @@ public NodeDeployTaskStatus startStopTask(final String targetIp, final String no

String deployDir = safeValue(deployDirectory, defaultDeployDirectory);

File scriptFile = resolveScriptFile(STOP_SCRIPT_RELATIVE_PATH, "停止脚本");
File scriptFile = scriptExecutionUtils.resolveScriptFile(STOP_SCRIPT_RELATIVE_PATH, "停止脚本");

final List<String> command = new ArrayList<String>();
command.add("bash");
Expand Down Expand Up @@ -442,200 +437,14 @@ private void runCommand(List<String> command, DeployTaskState taskState) {
}
}

private File resolveScriptFile(String relativePath, String label) {
try {
if (isClasspathRoot()) {
String classpathPath = joinClasspathPath(relativePath);
ClassPathResource resource = new ClassPathResource(classpathPath);
if (!resource.exists()) {
throw new RuntimeException(label + "不存在: classpath:" + classpathPath);
}
File scriptFile = materializeResourceFile(resource, relativePath);
if (!scriptFile.canExecute()) {
scriptFile.setExecutable(true);
}
return scriptFile;
}

String root = removeFilePrefix(resourceBasePath);
File scriptFile = new File(root, relativePath.replace("/", File.separator));
if (!scriptFile.exists()) {
throw new RuntimeException(label + "不存在: " + scriptFile.getPath());
}
return scriptFile;
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException("加载" + label + "失败: " + e.getMessage(), e);
}
}

private File resolveResourceFile(String relativePath, String label) {
try {
if (isClasspathRoot()) {
String classpathPath = joinClasspathPath(relativePath);
ClassPathResource resource = new ClassPathResource(classpathPath);
if (!resource.exists()) {
throw new RuntimeException(label + "不存在: classpath:" + classpathPath);
}
return materializeResourceFile(resource, relativePath);
}

String root = removeFilePrefix(resourceBasePath);
File file = new File(root, relativePath.replace("/", File.separator));
if (!file.exists() || !file.isFile()) {
throw new RuntimeException(label + "不存在: " + file.getPath());
}
return file;
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException("加载" + label + "失败: " + e.getMessage(), e);
}
}

private File resolveMetadataDirectory() {
File metadataDir = resolveOptionalResourceDirectory("udf/metadata");
File metadataDir = scriptExecutionUtils.resolveOptionalResourceDirectory("udf/metadata");
if (metadataDir != null) {
return metadataDir;
}
throw new RuntimeException("UDF元数据目录不存在: 期望 resource.base-path 下的 udf/metadata");
}

private File resolveOptionalResourceDirectory(String relativePath) {
try {
if (isClasspathRoot()) {
String classpathPath = joinClasspathPath(relativePath);
ClassPathResource dirResource = new ClassPathResource(classpathPath);
if (!dirResource.exists()) {
return null;
}

try {
File file = dirResource.getFile();
if (file.exists() && file.isDirectory()) {
return file;
}
} catch (Exception ignored) {
// Fall through to materialize-from-pattern for packaged classpath resources.
}

return materializeResourceDirectory(classpathPath, relativePath);
}

String root = removeFilePrefix(resourceBasePath);
File dir = new File(root, relativePath.replace("/", File.separator));
if (!dir.exists() || !dir.isDirectory()) {
return null;
}
return dir;
} catch (Exception e) {
return null;
}
}

private File materializeResourceFile(ClassPathResource resource, String relativePath) throws Exception {
String name = new File(relativePath).getName();
File tmpScript = File.createTempFile("scalestore-", "-" + name);
try (InputStream input = resource.getInputStream()) {
Files.copy(input, tmpScript.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
tmpScript.deleteOnExit();
return tmpScript;
}

private File materializeResourceDirectory(String classpathPath, String relativePath) throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:" + classpathPath + "/**");
if (resources == null || resources.length == 0) {
return null;
}

File baseDir = Files.createTempDirectory("scalestore-" + new File(relativePath).getName() + "-").toFile();
markDeleteOnExit(baseDir);

String normalizedPath = classpathPath.replace('\\', '/');
String anchor = normalizedPath + "/";
boolean copied = false;

for (Resource resource : resources) {
if (resource == null || !resource.exists() || !resource.isReadable()) {
continue;
}

String url = resource.getURL().toString().replace('\\', '/');
int idx = url.indexOf(anchor);
if (idx < 0) {
continue;
}

String sub = url.substring(idx + anchor.length());
if (sub.isEmpty() || sub.endsWith("/")) {
continue;
}

File target = new File(baseDir, sub);
File parent = target.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
try (InputStream input = resource.getInputStream()) {
Files.copy(input, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
copied = true;
}

return copied ? baseDir : null;
}

private void markDeleteOnExit(File file) {
if (file == null || !file.exists()) {
return;
}
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
markDeleteOnExit(child);
}
}
}
file.deleteOnExit();
}

private boolean isClasspathRoot() {
String root = resourceBasePath == null ? "" : resourceBasePath.trim();
return root.isEmpty() || ".".equals(root) || root.startsWith("classpath:");
}

private String joinClasspathPath(String relativePath) {
String root = resourceBasePath == null ? "" : resourceBasePath.trim();
if (root.startsWith("classpath:")) {
root = root.substring("classpath:".length());
}
root = root.replace('\\', '/');
while (root.startsWith("/")) {
root = root.substring(1);
}
while (root.endsWith("/")) {
root = root.substring(0, root.length() - 1);
}
if (root.isEmpty() || ".".equals(root)) {
return relativePath;
}
return root + "/" + relativePath;
}

private String removeFilePrefix(String path) {
String value = path == null ? "" : path.trim();
if (value.startsWith("file:")) {
return value.substring("file:".length());
}
return value;
}

private String required(String value, String message) {
if (value == null || value.trim().isEmpty()) {
throw new RuntimeException(message);
Expand Down
Loading
Loading