更新带货视频工作流接口和页面

This commit is contained in:
super
2026-07-09 00:27:49 +08:00
parent 0ecf5cd45a
commit aab4ce942c
51 changed files with 4777 additions and 820 deletions
@@ -4,6 +4,7 @@ import com.nanri.aiimage.common.api.ApiResponse;
import com.nanri.aiimage.modules.file.model.vo.ExcelInfoVo;
import com.nanri.aiimage.modules.file.model.vo.UploadFileVo;
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
@@ -19,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/files")
@@ -26,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile;
public class FileUploadController {
private final LocalFileStorageService localFileStorageService;
private final OssStorageService ossStorageService;
@PostMapping("/upload")
@Operation(
@@ -47,8 +51,22 @@ public class FileUploadController {
public ApiResponse<UploadFileVo> upload(
@Parameter(name = "file", description = "待上传的 Excel 或业务源文件", required = true, in = ParameterIn.QUERY)
MultipartFile file,
@RequestParam(required = false) String relativePath) throws Exception {
return ApiResponse.success(localFileStorageService.saveTempFile(file, relativePath));
@RequestParam(required = false) String relativePath,
@RequestParam(defaultValue = "false") boolean uploadToOss,
@RequestParam(defaultValue = "COMMON") String moduleType) throws Exception {
UploadFileVo vo = localFileStorageService.saveTempFile(file, relativePath);
if (uploadToOss) {
File localFile = new File(vo.getLocalPath());
OssStorageService.UploadedResult uploaded = ossStorageService.uploadPublicFileWithFreshDownloadUrl(
localFile,
moduleType,
file.getOriginalFilename()
);
vo.setObjectKey(uploaded.objectKey());
vo.setUrl(uploaded.downloadUrl());
vo.setMediaType(resolveMediaType(file.getContentType(), file.getOriginalFilename()));
}
return ApiResponse.success(vo);
}
@GetMapping("/excel-info")
@@ -60,4 +78,19 @@ public class FileUploadController {
public ApiResponse<ExcelInfoVo> excelInfo(@RequestParam String fileKey) throws Exception {
return ApiResponse.success(localFileStorageService.getExcelInfo(fileKey));
}
private String resolveMediaType(String contentType, String filename) {
String type = contentType == null ? "" : contentType.toLowerCase();
String name = filename == null ? "" : filename.toLowerCase();
if (type.startsWith("image/") || name.matches(".*\\.(png|jpe?g|webp|gif|bmp|svg)$")) {
return "image";
}
if (type.startsWith("video/") || name.matches(".*\\.(mp4|mov|webm|m4v|ogg|avi|mkv)$")) {
return "video";
}
if (type.startsWith("audio/") || name.matches(".*\\.(mp3|wav|m4a|aac|flac)$")) {
return "audio";
}
return "file";
}
}
@@ -21,4 +21,13 @@ public class UploadFileVo {
@Schema(description = "相对上传目录路径")
private String relativePath;
@Schema(description = "OSS 对象 key。仅 uploadToOss=true 时返回")
private String objectKey;
@Schema(description = "OSS 公网 URL。仅 uploadToOss=true 时返回")
private String url;
@Schema(description = "媒体类型:image/video/audio/file。仅 uploadToOss=true 时返回")
private String mediaType;
}
@@ -47,6 +47,24 @@ public class OssStorageService {
}
}
public UploadedResult uploadPublicFileWithFreshDownloadUrl(File file, String moduleType, String originalFilename) {
String normalizedModuleType = moduleType == null || moduleType.isBlank()
? "common"
: moduleType.trim().toLowerCase();
String objectName = sanitizeObjectName(originalFilename);
if (objectName.isBlank()) {
objectName = file.getName();
}
String objectKey = String.format("upload/%s/%s/%s", normalizedModuleType, UUID.randomUUID(), objectName);
OSS ossClient = buildClient();
try {
ossClient.putObject(ossProperties.getBucket(), objectKey, file);
return new UploadedResult(objectKey, getPublicUrl(objectKey));
} finally {
ossClient.shutdown();
}
}
public String uploadText(String objectKey, String content) {
if (objectKey == null || objectKey.isBlank()) {
throw new IllegalArgumentException("objectKey must not be blank");
@@ -109,6 +127,19 @@ public class OssStorageService {
}
}
public boolean objectExists(String value) {
String objectKey = resolveObjectKey(value);
if (objectKey == null || objectKey.isBlank()) {
return false;
}
OSS ossClient = buildClient();
try {
return ossClient.doesObjectExist(ossProperties.getBucket(), objectKey);
} finally {
ossClient.shutdown();
}
}
/**
* 根据 objectKey 生成公开直链下载 URL。
*/
@@ -125,7 +156,7 @@ public class OssStorageService {
}
String objectKey = resolveObjectKey(value);
String normalizedKey = objectKey.startsWith("/") ? objectKey.substring(1) : objectKey;
String host = String.format("%s.%s", ossProperties.getBucket(), ossProperties.getEndpoint());
String host = String.format("%s.%s", ossProperties.getBucket(), publicEndpoint());
try {
return new URI("https", host, "/" + normalizedKey, null).toASCIIString();
} catch (Exception ignored) {
@@ -179,6 +210,32 @@ public class OssStorageService {
);
}
private String publicEndpoint() {
String endpoint = ossProperties.getPublicEndpoint();
if (endpoint == null || endpoint.isBlank()) {
endpoint = ossProperties.getEndpoint();
}
endpoint = endpoint.trim();
if (endpoint.startsWith("http://")) {
endpoint = endpoint.substring("http://".length());
} else if (endpoint.startsWith("https://")) {
endpoint = endpoint.substring("https://".length());
}
return endpoint.endsWith("/") ? endpoint.substring(0, endpoint.length() - 1) : endpoint;
}
private String sanitizeObjectName(String filename) {
if (filename == null || filename.isBlank()) {
return "";
}
String normalized = filename.trim().replace('\\', '/');
int slashIndex = normalized.lastIndexOf('/');
if (slashIndex >= 0 && slashIndex + 1 < normalized.length()) {
normalized = normalized.substring(slashIndex + 1);
}
return normalized.replaceAll("[\\r\\n]", "_");
}
public record UploadedResult(String objectKey, String downloadUrl) {
}
}