Commit 779f5d7b by zhangxingmin

Merge remote-tracking branch 'origin/test' into dev

parents ecd8f9fb 93c23a0c
# 构建阶段 - 创建包含LibreOffice的基础镜像 # ============================================================
FROM openjdk:8-jre-slim as base-builder # 构建阶段 - 创建包含 LibreOffice 的基础镜像
# ============================================================
FROM eclipse-temurin:8-jre-jammy as base-builder
ENV LANG=zh_CN.UTF-8 \ ENV LANG=zh_CN.UTF-8 \
LANGUAGE=zh_CN:zh \ LANGUAGE=zh_CN:zh \
LC_ALL=zh_CN.UTF-8 \ LC_ALL=zh_CN.UTF-8 \
DEBIAN_FRONTEND=noninteractive DEBIAN_FRONTEND=noninteractive
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \ # 替换为阿里云 Ubuntu 源(jammy = 22.04)
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list RUN sed -i 's|archive.ubuntu.com|mirrors.aliyun.com|g' /etc/apt/sources.list && \
sed -i 's|security.ubuntu.com|mirrors.aliyun.com|g' /etc/apt/sources.list
# 安装字体、LibreOffice 及中文语言包
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
fonts-liberation \ fonts-liberation \
...@@ -29,12 +33,15 @@ RUN apt-get update && \ ...@@ -29,12 +33,15 @@ RUN apt-get update && \
RUN mkdir -p /home/app RUN mkdir -p /home/app
WORKDIR /home/app WORKDIR /home/app
# ============================================================
# 最终阶段 # 最终阶段
# ============================================================
FROM base-builder FROM base-builder
# 只拷贝jar包 # 拷贝 Spring Boot 可执行 JAR
COPY target/yd-oss-api-1.0-SNAPSHOT-exec.jar /home/app/yd-oss-api.jar COPY target/yd-oss-api-1.0-SNAPSHOT-exec.jar /home/app/yd-oss-api.jar
# 执行命令启动jar,并设置JVM内存参数 # 启动命令,设置 JVM 内存参数
ENTRYPOINT ["java", "-Xmx1024m", "-Xms512m", "-jar", "/home/app/yd-oss-api.jar"] ENTRYPOINT ["java", "-Xmx1024m", "-Xms512m", "-jar", "/home/app/yd-oss-api.jar"]
EXPOSE 9106 EXPOSE 9106
\ No newline at end of file
package com.yd.oss.api.config;
import org.jodconverter.core.office.OfficeManager;
import org.jodconverter.local.office.LocalOfficeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class OfficeManagerHolder {
private static final Logger logger = LoggerFactory.getLogger(OfficeManagerHolder.class);
private static OfficeManager instance = null;
private static final Object lock = new Object();
public static OfficeManager getInstance(String libreOfficeHome) throws Exception {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
logger.info("正在启动 LibreOffice (home: {}) ...", libreOfficeHome);
instance = LocalOfficeManager.builder()
.officeHome(new File(libreOfficeHome))
.build();
instance.start();
logger.info("LibreOffice 启动成功");
}
}
}
return instance;
}
public static void stop() {
if (instance != null) {
try {
instance.stop();
logger.info("LibreOffice 已停止");
} catch (Exception e) {
logger.error("停止 LibreOffice 失败", e);
}
instance = null;
}
}
}
\ No newline at end of file
package com.yd.oss.api.controller;
import com.yd.common.result.Result;
import com.yd.oss.api.service.ApiChunkedUploadContextService;
import com.yd.oss.feign.client.ApiChunkedUploadContextFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.Map;
/**
* 分片上传上下文信息
* (一个任务上传文件信息拆分成多个ETag上传存储,任务执行完毕,阿里云合并ETag列表为完整的文件信息)
* @author zxm
* @since 2026-08-05
*/
@Slf4j
@RestController
@RequestMapping("/chunkedUploadContext")
@Validated
public class ApiChunkedUploadContextController implements ApiChunkedUploadContextFeignClient {
@Resource
private ApiChunkedUploadContextService apiChunkedUploadContextService;
/**
* 上传单个分片
* @param chunk
* @param taskId
* @param chunkIndex
* @param projectBizId
* @param source
* @return
*/
@Override
public Result<Void> uploadChunk(MultipartFile chunk, String taskId, Integer chunkIndex, String projectBizId, String source) {
apiChunkedUploadContextService.uploadChunk(taskId, chunkIndex, chunk,projectBizId,source);
return Result.success();
}
/**
* 完成分片上传(合并)
*/
public Result<Map<String, Object>> finishChunks(String taskId,
String projectBizId) {
return apiChunkedUploadContextService.finishChunks(taskId,projectBizId);
}
}
package com.yd.oss.api.controller; package com.yd.oss.api.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yd.common.result.Result; import com.yd.common.result.Result;
import com.yd.oss.api.service.ApiOssService; import com.yd.oss.api.service.ApiOssService;
import com.yd.oss.feign.client.ApiOssFeignClient; import com.yd.oss.feign.client.ApiOssFeignClient;
...@@ -7,9 +8,13 @@ import com.yd.oss.feign.request.ApiUploadFileRequest; ...@@ -7,9 +8,13 @@ import com.yd.oss.feign.request.ApiUploadFileRequest;
import com.yd.oss.feign.response.ApiBatchUploadResponse; import com.yd.oss.feign.response.ApiBatchUploadResponse;
import com.yd.oss.feign.response.ApiFileMetadataResponse; import com.yd.oss.feign.response.ApiFileMetadataResponse;
import com.yd.oss.feign.response.ApiUploadResponse; import com.yd.oss.feign.response.ApiUploadResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
...@@ -21,6 +26,7 @@ import java.util.List; ...@@ -21,6 +26,7 @@ import java.util.List;
* @author zxm * @author zxm
* @since 2025-07-31 * @since 2025-07-31
*/ */
@Slf4j
@RestController @RestController
@RequestMapping("/oss") @RequestMapping("/oss")
@Validated @Validated
...@@ -29,6 +35,9 @@ public class ApiOssController implements ApiOssFeignClient { ...@@ -29,6 +35,9 @@ public class ApiOssController implements ApiOssFeignClient {
@Autowired @Autowired
private ApiOssService apiOssService; private ApiOssService apiOssService;
@Autowired
private ObjectMapper objectMapper;
/** /**
* 上传文件 * 上传文件
* @param file 上传的文件 * @param file 上传的文件
...@@ -50,6 +59,17 @@ public class ApiOssController implements ApiOssFeignClient { ...@@ -50,6 +59,17 @@ public class ApiOssController implements ApiOssFeignClient {
return apiOssService.uploadFileBody(file,request); return apiOssService.uploadFileBody(file,request);
} }
public Result<ApiUploadResponse> uploadFileBodyWithJson(MultipartFile file,String requestJson) {
try {
// 反序列化 JSON 为 ApiUploadFileRequest
ApiUploadFileRequest request = objectMapper.readValue(requestJson, ApiUploadFileRequest.class);
return apiOssService.uploadFileBody(file, request);
} catch (Exception e) {
log.error("解析请求参数失败", e);
return Result.fail("请求参数格式错误");
}
}
/** /**
* 下载文件 * 下载文件
* @param fileKey 文件唯一标识 * @param fileKey 文件唯一标识
......
package com.yd.oss.api.controller;
import com.yd.oss.api.utils.LocalDocumentConversionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 文档转换接口
* 提供将 Office 文档(Word/Excel/PPT)转换为 PDF/HTML/图片等格式的 HTTP 接口。
* 供其他微服务通过内网调用。
*/
@RestController
@RequestMapping("/convert")
public class DocumentConvertController {
private static final Logger logger = LoggerFactory.getLogger(DocumentConvertController.class);
@Autowired
private LocalDocumentConversionService conversionService;
/**
* 将指定 URL 的文档转换为目标格式,并返回字节流。
*
* @param fileUrl 源文件 URL(支持 HTTP/HTTPS/OSS)
* @param targetFormat 目标格式,如 pdf、html、png、jpg 等,默认 pdf
* @return 转换后的文件字节流,Content-Type 根据格式自动设置
*/
@PostMapping("/file")
public ResponseEntity<byte[]> convert(
@RequestParam("fileUrl") String fileUrl,
@RequestParam(value = "targetFormat", defaultValue = "pdf") String targetFormat) {
logger.info("收到转换请求: fileUrl={}, targetFormat={}", fileUrl, targetFormat);
try {
byte[] result = conversionService.convertToBytes(fileUrl, targetFormat);
HttpHeaders headers = new HttpHeaders();
// 根据目标格式设置 Content-Type
MediaType mediaType = getMediaType(targetFormat);
headers.setContentType(mediaType);
// 可选:设置下载文件名
String filename = extractFileName(fileUrl) + "." + targetFormat;
try {
headers.setContentDispositionFormData("attachment",
URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
// 忽略
}
logger.info("转换成功,返回字节数: {}", result.length);
return new ResponseEntity<>(result, headers, HttpStatus.OK);
} catch (Exception e) {
logger.error("文档转换失败: fileUrl={}, targetFormat={}", fileUrl, targetFormat, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(("转换失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
}
}
private MediaType getMediaType(String format) {
if (format == null) return MediaType.APPLICATION_OCTET_STREAM;
switch (format.toLowerCase()) {
case "pdf":
return MediaType.APPLICATION_PDF;
case "html":
return MediaType.TEXT_HTML;
case "png":
return MediaType.IMAGE_PNG;
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
default:
return MediaType.APPLICATION_OCTET_STREAM;
}
}
private String extractFileName(String url) {
if (url == null) return "converted";
int lastSlash = url.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash < url.length() - 1) {
String name = url.substring(lastSlash + 1);
int dot = name.lastIndexOf('.');
return dot > 0 ? name.substring(0, dot) : name;
}
return "converted";
}
}
\ No newline at end of file
package com.yd.oss.api.service;
import com.yd.common.result.Result;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
public interface ApiChunkedUploadContextService {
void uploadChunk(String taskId, Integer chunkIndex, MultipartFile chunk,String projectBizId,String source);
Result<Map<String, Object>> finishChunks(String taskId,String projectBizId);
}
package com.yd.oss.api.utils;
import com.yd.oss.api.config.OfficeManagerHolder;
import org.jodconverter.core.DocumentConverter;
import org.jodconverter.core.office.OfficeException;
import org.jodconverter.local.LocalConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
@Service
public class LocalDocumentConversionService {
private static final Logger logger = LoggerFactory.getLogger(LocalDocumentConversionService.class);
@Value("${jodconverter.libreoffice.home:}")
private String libreOfficeHome;
private DocumentConverter converter;
@PostConstruct
public void init() {
if (libreOfficeHome == null || libreOfficeHome.trim().isEmpty()) {
libreOfficeHome = System.getenv("LIBREOFFICE_HOME");
}
if (libreOfficeHome == null || libreOfficeHome.trim().isEmpty()) {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
libreOfficeHome = "D:/LibreOffice";
} else if (os.contains("linux")) {
libreOfficeHome = "/opt/libreoffice_standalone/libreoffice7.4";
} else if (os.contains("mac")) {
libreOfficeHome = "/Applications/LibreOffice.app/Contents";
} else {
throw new RuntimeException("无法确定 LibreOffice 安装路径,请通过配置或环境变量设置");
}
logger.warn("未配置 libreOfficeHome,使用默认路径: {}", libreOfficeHome);
}
try {
OfficeManagerHolder.getInstance(libreOfficeHome);
converter = LocalConverter.builder()
.officeManager(OfficeManagerHolder.getInstance(libreOfficeHome))
.build();
logger.info("JodConverter 初始化成功,LibreOffice 路径: {}", libreOfficeHome);
} catch (Exception e) {
logger.error("JodConverter 初始化失败,请检查 LibreOffice 安装", e);
throw new RuntimeException("JodConverter 初始化失败", e);
}
}
public byte[] convertToPdfBytes(String fileUrl) throws Exception {
return convertToBytes(fileUrl, "pdf");
}
public byte[] convertToBytes(String fileUrl, String targetExtension) throws Exception {
if (targetExtension.startsWith(".")) {
targetExtension = targetExtension.substring(1);
}
if (targetExtension == null || targetExtension.trim().isEmpty()) {
throw new IllegalArgumentException("目标扩展名不能为空");
}
logger.info("开始转换文档: {} -> 格式: {}", fileUrl, targetExtension);
Path tempInput = Files.createTempFile("convert_", "_" + UUID.randomUUID());
try (InputStream in = new URL(fileUrl).openStream()) {
Files.copy(in, tempInput, StandardCopyOption.REPLACE_EXISTING);
}
logger.info("源文件下载到临时路径: {}", tempInput.toAbsolutePath());
Path tempOutput = Files.createTempFile("converted_", "." + targetExtension);
try {
converter.convert(tempInput.toFile())
.to(tempOutput.toFile())
.execute();
logger.info("转换完成,临时输出: {}", tempOutput.toAbsolutePath());
byte[] resultBytes = Files.readAllBytes(tempOutput);
logger.info("转换结果大小: {} bytes", resultBytes.length);
return resultBytes;
} catch (OfficeException e) {
logger.error("转换失败 (格式: {})", targetExtension, e);
throw new Exception("文档转换失败 (格式: " + targetExtension + "): " + e.getMessage(), e);
} finally {
try {
Files.deleteIfExists(tempInput);
Files.deleteIfExists(tempOutput);
} catch (Exception ignored) {
logger.warn("清理临时文件失败");
}
}
}
@PreDestroy
public void destroy() {
OfficeManagerHolder.stop();
}
}
\ No newline at end of file
...@@ -291,5 +291,13 @@ ...@@ -291,5 +291,13 @@
<orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.19" level="project" /> <orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.19" level="project" />
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" /> <orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.apache.xmlbeans:xmlbeans:3.1.0" level="project" /> <orderEntry type="library" name="Maven: org.apache.xmlbeans:xmlbeans:3.1.0" level="project" />
<orderEntry type="library" name="Maven: org.jodconverter:jodconverter-core:4.4.7" level="project" />
<orderEntry type="library" name="Maven: org.jodconverter:jodconverter-local:4.4.7" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:juh:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:jurt:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:ridl:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:unoil:4.1.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
</component> </component>
</module> </module>
\ No newline at end of file
package com.yd.oss.feign.client;
import com.yd.common.result.Result;
import com.yd.oss.feign.fallback.ApiChunkedUploadContextFeignFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
/**
* 分片上传上下文信息Feign客户端
*/
@FeignClient(name = "yd-oss-api", path = "/oss/api/chunkedUploadContext", fallbackFactory = ApiChunkedUploadContextFeignFallbackFactory.class)
public interface ApiChunkedUploadContextFeignClient {
/**
* 上传单个分片
* @param chunk
* @param taskId
* @param chunkIndex
* @param projectBizId
* @param source
* @return
*/
@PostMapping(value = "/uploadChunk", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<Void> uploadChunk(
@RequestPart("chunk") MultipartFile chunk,
@RequestParam("taskId") String taskId,
@RequestParam("chunkIndex") Integer chunkIndex,
@RequestParam("projectBizId") String projectBizId,
@RequestParam("source") String source
);
/**
* 完成分片上传(合并)
*/
@PostMapping("/finishChunks")
Result<Map<String, Object>> finishChunks(
@RequestParam("taskId") String taskId,
@RequestParam("projectBizId") String projectBizId
);
}
...@@ -10,14 +10,14 @@ import org.springframework.cloud.openfeign.FeignClient; ...@@ -10,14 +10,14 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotBlank;
import java.util.List; import java.util.List;
/** /**
* OSS服务信息Feign客户端 * OSS服务信息Feign客户端
*/ */
@FeignClient(name = "yd-oss-api", fallbackFactory = ApiOssFeignFallbackFactory.class) @FeignClient(name = "yd-oss-api",
path = "/oss/api/oss",
fallbackFactory = ApiOssFeignFallbackFactory.class)
public interface ApiOssFeignClient { public interface ApiOssFeignClient {
/** /**
...@@ -42,7 +42,10 @@ public interface ApiOssFeignClient { ...@@ -42,7 +42,10 @@ public interface ApiOssFeignClient {
Result<ApiUploadResponse> uploadFileBody(@RequestPart("file") MultipartFile file, Result<ApiUploadResponse> uploadFileBody(@RequestPart("file") MultipartFile file,
@RequestPart("request") ApiUploadFileRequest request); @RequestPart("request") ApiUploadFileRequest request);
@PostMapping(value = "/upload/body/json", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<ApiUploadResponse> uploadFileBodyWithJson(
@RequestPart("file") MultipartFile file,
@RequestPart("request") String requestJson);
/** /**
* 下载文件 * 下载文件
......
package com.yd.oss.feign.fallback;
import com.yd.common.result.Result;
import com.yd.oss.feign.client.ApiChunkedUploadContextFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
/**
* 分片上传上下文信息Feign降级处理
*/
@Slf4j
@Component
public class ApiChunkedUploadContextFeignFallbackFactory implements FallbackFactory<ApiChunkedUploadContextFeignClient> {
@Override
public ApiChunkedUploadContextFeignClient create(Throwable cause) {
return new ApiChunkedUploadContextFeignClient() {
@Override
public Result<Void> uploadChunk(MultipartFile chunk, String taskId, Integer chunkIndex, String projectBizId, String source) {
return null;
}
@Override
public Result<Map<String, Object>> finishChunks(String taskId, String projectBizId) {
return null;
}
};
}
}
...@@ -4,7 +4,6 @@ import com.yd.common.result.Result; ...@@ -4,7 +4,6 @@ import com.yd.common.result.Result;
import com.yd.oss.feign.client.ApiExcelFeignClient; import com.yd.oss.feign.client.ApiExcelFeignClient;
import com.yd.oss.feign.dto.ExportResult; import com.yd.oss.feign.dto.ExportResult;
import com.yd.oss.feign.request.ApiExportRequest; import com.yd.oss.feign.request.ApiExportRequest;
import com.yd.oss.feign.request.ApiOssExcelParseRequest;
import com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest; import com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest;
import com.yd.oss.feign.request.MultiSheetExportRequest; import com.yd.oss.feign.request.MultiSheetExportRequest;
import com.yd.oss.feign.response.ApiOssExcelParseResponse; import com.yd.oss.feign.response.ApiOssExcelParseResponse;
......
...@@ -33,6 +33,11 @@ public class ApiOssFeignFallbackFactory implements FallbackFactory<ApiOssFeignCl ...@@ -33,6 +33,11 @@ public class ApiOssFeignFallbackFactory implements FallbackFactory<ApiOssFeignCl
} }
@Override @Override
public Result<ApiUploadResponse> uploadFileBodyWithJson(MultipartFile file, String requestJson) {
return null;
}
@Override
public Result downloadFile(String fileKey) { public Result downloadFile(String fileKey) {
return null; return null;
} }
......
...@@ -8,6 +8,11 @@ import javax.validation.constraints.NotBlank; ...@@ -8,6 +8,11 @@ import javax.validation.constraints.NotBlank;
public class ApiUploadFileRequest { public class ApiUploadFileRequest {
/** /**
* 租户唯一标识(业务ID)
*/
private String tenantBizId;
/**
* 项目ID * 项目ID
*/ */
private String projectBizId; private String projectBizId;
......
...@@ -61,6 +61,6 @@ public class ApiUploadResponse { ...@@ -61,6 +61,6 @@ public class ApiUploadResponse {
/** /**
* 创建时间 * 创建时间
*/ */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime; private LocalDateTime createTime;
} }
...@@ -120,5 +120,30 @@ ...@@ -120,5 +120,30 @@
<artifactId>xmlbeans</artifactId> <artifactId>xmlbeans</artifactId>
</dependency> </dependency>
<!-- JodConverter 文档转换 -->
<dependency>
<groupId>org.jodconverter</groupId>
<artifactId>jodconverter-core</artifactId>
<version>4.4.7</version>
</dependency>
<dependency>
<groupId>org.jodconverter</groupId>
<artifactId>jodconverter-local</artifactId>
<version>4.4.7</version>
</dependency>
<!-- iText PDF 处理 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
package com.yd.oss.service.dao;
import com.yd.oss.service.model.ChunkedUploadContext;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 分片上传上下文表(一个任务上传文件信息拆分成多个ETag上传存储,任务执行完毕,阿里云合并ETag列表为完整的文件信息) Mapper 接口
* </p>
*
* @author zxm
* @since 2026-08-05
*/
public interface ChunkedUploadContextMapper extends BaseMapper<ChunkedUploadContext> {
}
...@@ -15,6 +15,11 @@ import lombok.NoArgsConstructor; ...@@ -15,6 +15,11 @@ import lombok.NoArgsConstructor;
public class OssUploadFileReqDto { public class OssUploadFileReqDto {
/** /**
* 租户唯一标识(业务ID)
*/
private String tenantBizId;
/**
* 项目ID * 项目ID
*/ */
private String projectBizId; private String projectBizId;
......
package com.yd.oss.service.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 分片上传上下文表(一个任务上传文件信息拆分成多个ETag上传存储,任务执行完毕,阿里云合并ETag列表为完整的文件信息)
* </p>
*
* @author zxm
* @since 2026-08-05
*/
@Getter
@Setter
@TableName("chunked_upload_context")
public class ChunkedUploadContext implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 任务ID
*/
@TableField("task_id")
private String taskId;
/**
* OSS 分片上传 ID(由 OSS 初始化时返回)
*/
@TableField("upload_id")
private String uploadId;
/**
* 文件在 OSS 中的完整路径(不含 Bucket 名称)
*/
@TableField("object_key")
private String objectKey;
/**
* 已上传分片的 ETag 列表(JSON 数组格式,如 [{"partNumber":1,"eTag":"xxx"}])
*/
@TableField("part_etags_json")
private String partEtagsJson;
/**
* 状态:0-初始化,1-上传中,2-已完成,3-已取消
*/
@TableField("status")
private Integer status;
/**
* 通用备注
*/
@TableField("remark")
private String remark;
/**
* 删除标识: 0-正常, 1-删除
*/
@TableField("is_deleted")
private Integer isDeleted;
/**
* 创建人ID
*/
@TableField("creator_id")
private String creatorId;
/**
* 更新人ID
*/
@TableField("updater_id")
private String updaterId;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
}
package com.yd.oss.service.service;
import com.yd.oss.service.model.ChunkedUploadContext;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 分片上传上下文表(一个任务上传文件信息拆分成多个ETag上传存储,任务执行完毕,阿里云合并ETag列表为完整的文件信息) 服务类
* </p>
*
* @author zxm
* @since 2026-08-05
*/
public interface IChunkedUploadContextService extends IService<ChunkedUploadContext> {
}
package com.yd.oss.service.service.impl; package com.yd.oss.service.service.impl;
import com.alibaba.fastjson2.JSON;
import com.aliyun.oss.OSS; import com.aliyun.oss.OSS;
import com.aliyun.oss.model.CannedAccessControlList; import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.OSSObject; import com.aliyun.oss.model.OSSObject;
...@@ -83,7 +84,7 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -83,7 +84,7 @@ public class AliYunOssServiceImpl implements OssService {
OSS ossClient = null; OSS ossClient = null;
try { try {
ossClient = ossClientFactory.createOssClient(provider); ossClient = ossClientFactory.createOssClient(provider);
return doUploadFile(ossClient, provider, inputStream, fileName, bucketName, uploadUser, type, null, null, null, null, null, null); return doUploadFile(ossClient, provider, inputStream, fileName, bucketName, uploadUser, type, null, null, null, null, null, null,null,null);
} finally { } finally {
if (ossClient != null) { if (ossClient != null) {
ossClient.shutdown(); ossClient.shutdown();
...@@ -115,7 +116,8 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -115,7 +116,8 @@ public class AliYunOssServiceImpl implements OssService {
FileUtil.getFileType(file.getOriginalFilename()), FileUtil.getFileType(file.getOriginalFilename()),
reqDto.getObjectBizId(), reqDto.getObjectName(), reqDto.getObjectBizId(), reqDto.getObjectName(),
reqDto.getObjectTableName(), reqDto.getObjectType(), reqDto.getObjectTableName(), reqDto.getObjectType(),
reqDto.getObjectBizId(), reqDto.getObjectName()); // 这里需要根据实际情况调整,原代码中存储了object信息 reqDto.getObjectBizId(), reqDto.getObjectName(),
reqDto.getTenantBizId(),reqDto.getProjectBizId()); // 这里需要根据实际情况调整,原代码中存储了object信息
} catch (IOException e) { } catch (IOException e) {
log.error("文件输入流解析异常", e); log.error("文件输入流解析异常", e);
throw new BusinessException("文件输入流解析异常"); throw new BusinessException("文件输入流解析异常");
...@@ -142,7 +144,7 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -142,7 +144,7 @@ public class AliYunOssServiceImpl implements OssService {
try { try {
ossClient = ossClientFactory.createOssClient(defaultProvider); ossClient = ossClientFactory.createOssClient(defaultProvider);
OssUploadFileResDto result = doUploadFile(ossClient, defaultProvider, inputStream, fileName, OssUploadFileResDto result = doUploadFile(ossClient, defaultProvider, inputStream, fileName,
defaultProvider.getBucketName(), uploadUser, null, null, null, null, null, null, null); defaultProvider.getBucketName(), uploadUser, null, null, null, null, null, null, null,null,null);
return result.getUrl(); return result.getUrl();
} finally { } finally {
if (ossClient != null) { if (ossClient != null) {
...@@ -159,7 +161,8 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -159,7 +161,8 @@ public class AliYunOssServiceImpl implements OssService {
String bucketName, String uploadUser, String type, String bucketName, String uploadUser, String type,
String objectBizId, String objectName, String objectBizId, String objectName,
String objectTableName, String objectType, String objectTableName, String objectType,
String creatorId, String creatorName) { String creatorId, String creatorName,
String tenantBizId,String projectBizId) {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
String operationResult = "success"; String operationResult = "success";
...@@ -203,8 +206,15 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -203,8 +206,15 @@ public class AliYunOssServiceImpl implements OssService {
ossFile.setObjectName(objectName); ossFile.setObjectName(objectName);
ossFile.setObjectTableName(objectTableName); ossFile.setObjectTableName(objectTableName);
ossFile.setObjectType(objectType); ossFile.setObjectType(objectType);
ossFile.setTenantBizId(tenantBizId);
ossFile.setProjectBizId(projectBizId);
// 获取当前登录用户信息(如果存在) // 获取当前登录用户信息(如果存在)
AuthUserDto authUserDto = SecurityUtil.getCurrentLoginUser(); AuthUserDto authUserDto = null;
try {
authUserDto = SecurityUtil.getCurrentLoginUser();
}catch (Exception e){
log.info("获取当前登录用户信息失败:{}", e.getMessage());
}
if (authUserDto != null) { if (authUserDto != null) {
ossFile.setCreatorId(authUserDto.getUserBizId()); ossFile.setCreatorId(authUserDto.getUserBizId());
ossFile.setCreatorName(authUserDto.getRealName()); ossFile.setCreatorName(authUserDto.getRealName());
......
package com.yd.oss.service.service.impl;
import com.yd.oss.service.model.ChunkedUploadContext;
import com.yd.oss.service.dao.ChunkedUploadContextMapper;
import com.yd.oss.service.service.IChunkedUploadContextService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 分片上传上下文表(一个任务上传文件信息拆分成多个ETag上传存储,任务执行完毕,阿里云合并ETag列表为完整的文件信息) 服务实现类
* </p>
*
* @author zxm
* @since 2026-08-05
*/
@Service
public class ChunkedUploadContextServiceImpl extends ServiceImpl<ChunkedUploadContextMapper, ChunkedUploadContext> implements IChunkedUploadContextService {
}
...@@ -21,7 +21,7 @@ public class MyBatisPlusCodeGenerator { ...@@ -21,7 +21,7 @@ public class MyBatisPlusCodeGenerator {
}) })
.strategyConfig(builder -> { .strategyConfig(builder -> {
builder.addInclude( builder.addInclude(
"material","rel_object_material" "chunked_upload_context"
) )
.entityBuilder() .entityBuilder()
.enableLombok() .enableLombok()
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.oss.service.dao.ChunkedUploadContextMapper">
</mapper>
...@@ -287,5 +287,13 @@ ...@@ -287,5 +287,13 @@
<orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.19" level="project" /> <orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.19" level="project" />
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" /> <orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.apache.xmlbeans:xmlbeans:3.1.0" level="project" /> <orderEntry type="library" name="Maven: org.apache.xmlbeans:xmlbeans:3.1.0" level="project" />
<orderEntry type="library" name="Maven: org.jodconverter:jodconverter-core:4.4.7" level="project" />
<orderEntry type="library" name="Maven: org.jodconverter:jodconverter-local:4.4.7" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:juh:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:jurt:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:ridl:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.openoffice:unoil:4.1.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
</component> </component>
</module> </module>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment