package com.tcm.training.admin.upload;
|
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import java.io.IOException;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.time.LocalDate;
|
import java.time.format.DateTimeFormatter;
|
import java.util.UUID;
|
|
/**
|
* 安装包本地存储服务。
|
*/
|
@Service
|
public class LocalAppPackageStorageService {
|
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
|
private final LocalUploadProperties localUploadProperties;
|
|
public LocalAppPackageStorageService(LocalUploadProperties localUploadProperties) {
|
this.localUploadProperties = localUploadProperties;
|
}
|
|
/**
|
* 将安装包写入服务器本地目录,并返回可下载地址。
|
*/
|
public UploadFileResult store(MultipartFile file, String platform) throws IOException {
|
validateConfig();
|
String originalName = file.getOriginalFilename();
|
String extension = resolveExtension(originalName);
|
String relativePath = buildRelativePath(platform, extension);
|
Path targetPath = resolveTargetPath(relativePath);
|
Files.createDirectories(targetPath.getParent());
|
file.transferTo(targetPath.toFile());
|
return new UploadFileResult(buildPublicUrl(relativePath), originalName, relativePath);
|
}
|
|
/**
|
* 解析安装包最终写入路径。
|
*/
|
Path resolveTargetPath(String relativePath) {
|
Path uploadRoot = resolveUploadRoot();
|
String normalizedRelativePath = relativePath.replace("/", java.io.File.separator);
|
return uploadRoot.resolve(normalizedRelativePath).normalize();
|
}
|
|
/**
|
* 生成对外下载地址。
|
*/
|
String buildPublicUrl(String relativePath) {
|
String baseUrl = trimTrailingSlash(localUploadProperties.getPublicBaseUrl());
|
String pathPrefix = ensureLeadingSlash(trimTrailingSlash(localUploadProperties.getPathPrefix()));
|
return baseUrl + pathPrefix + "/" + relativePath;
|
}
|
|
/**
|
* 组装安装包相对路径。
|
*/
|
String buildRelativePath(String platform, String extension) {
|
String datePath = LocalDate.now().format(DATE_FORMATTER);
|
String fileName = UUID.randomUUID().toString().replace("-", "") + extension;
|
return normalizePackageDir() + "/" + platform.toLowerCase() + "/" + datePath + "/" + fileName;
|
}
|
|
private Path resolveUploadRoot() {
|
String uploadDir = localUploadProperties.getDir();
|
if (!StringUtils.hasText(uploadDir)) {
|
throw new IllegalArgumentException("本地上传根目录未配置");
|
}
|
Path uploadRoot = Paths.get(uploadDir).toAbsolutePath().normalize();
|
Path storageRoot = uploadRoot.resolve(normalizePackageDir()).normalize();
|
if (!storageRoot.startsWith(uploadRoot)) {
|
throw new IllegalArgumentException("安装包存储目录必须位于上传根目录下");
|
}
|
return uploadRoot;
|
}
|
|
private void validateConfig() {
|
if (!StringUtils.hasText(localUploadProperties.getPublicBaseUrl())) {
|
throw new IllegalArgumentException("安装包公网下载地址未配置");
|
}
|
normalizePackageDir();
|
resolveUploadRoot();
|
}
|
|
private String normalizePackageDir() {
|
String appPackageDir = localUploadProperties.getAppPackageDir();
|
if (!StringUtils.hasText(appPackageDir)) {
|
throw new IllegalArgumentException("安装包存储目录未配置");
|
}
|
String normalized = trimSlash(appPackageDir.replace("\\", "/"));
|
if (!StringUtils.hasText(normalized)) {
|
throw new IllegalArgumentException("安装包存储目录未配置");
|
}
|
if (normalized.startsWith("..") || normalized.contains("/../")) {
|
throw new IllegalArgumentException("安装包存储目录不合法");
|
}
|
return normalized;
|
}
|
|
private String resolveExtension(String originalName) {
|
if (!StringUtils.hasText(originalName) || !originalName.contains(".")) {
|
return "";
|
}
|
return originalName.substring(originalName.lastIndexOf(".")).toLowerCase();
|
}
|
|
private String trimTrailingSlash(String value) {
|
String normalized = value == null ? "" : value.trim();
|
while (normalized.endsWith("/")) {
|
normalized = normalized.substring(0, normalized.length() - 1);
|
}
|
return normalized;
|
}
|
|
private String ensureLeadingSlash(String value) {
|
return value.startsWith("/") ? value : "/" + value;
|
}
|
|
private String trimSlash(String value) {
|
String normalized = value == null ? "" : value.trim();
|
while (normalized.startsWith("/")) {
|
normalized = normalized.substring(1);
|
}
|
while (normalized.endsWith("/")) {
|
normalized = normalized.substring(0, normalized.length() - 1);
|
}
|
return normalized;
|
}
|
}
|