Commit 8d0a69cb authored by alex yao's avatar alex yao

feat(ai-software-copyright): 实现压缩文件自动解压和软著保存功能

- 在BizSoftwareCopyrightRestImpl中添加基础信息和技术内容验证
- 实现文件上传保存功能,支持基础信息、技术内容和参考文档文件URL
- 添加FileUtils工具类的文件名参数重载方法isCompressedFile
- 在SoftwareCopyRightAgent中集成SoftwareCopyRightUtils进行文件解压处理
- 添加downloadAndUnzipPlugin方法处理压缩文件下载解压和上传流程
- 实现SoftwareCopyRightServiceImpl的save方法支持软著信息保存
- 创建SoftwareCopyRightUtils工具类处理文件解压和上传逻辑
- 优化异步处理机制,使用CompletableFuture进行并行文件处理
- 添加临时文件清理机制确保系统资源正确释放
parent 4d5c2967
package cn.com.poc.ai_software_copyright.agent;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
......@@ -27,6 +28,7 @@ import cn.com.poc.ai_software_copyright.contant.AiSoftWareCopyRightRedisKey;
import cn.com.poc.ai_software_copyright.domain.GeneratedDoc;
import cn.com.poc.ai_software_copyright.domian.BaseInfo;
import cn.com.poc.ai_software_copyright.domian.TechnicalContent;
import cn.com.poc.ai_software_copyright.utils.SoftwareCopyRightUtils;
import cn.com.poc.common.pool.CommonThreadPoolExecutor;
import cn.com.poc.common.utils.DateUtils;
import cn.com.poc.common.utils.JsonUtils;
......@@ -57,12 +59,16 @@ public class SoftwareCopyRightAgent {
@Value("${dify.software_copyright.callball.url}")
private String CALL_BACK_URL;
@Resource
private SoftwareCopyRightUtils softwareCopyRightUtils;
/**
* 软著AI助手
*/
public void softwareCopyRightAssistant(List<String> fileUrls, String question, Map<String, Object> data)
throws DifyApiException, IOException, InterruptedException {
String API_KEY = "app-S3y8vmyX95cTa0sb9V9oBfSX";
fileUrls = softwareCopyRightUtils.downloadAndUnzipPlugin(fileUrls);
streamChat(
fileUrls,
question,
......@@ -85,13 +91,14 @@ public class SoftwareCopyRightAgent {
String api_key = "app-VgynqQ7MDYW6CjUbtDu4osBf";
String ques = "执行";
String user = "generatedBaseDoc" + DateUtils.getCurrTime();
List<String> filesURL = softwareCopyRightUtils.downloadAndUnzipPlugin(files);
Map<String, Object> data = new HashMap<>();
data.put("task_id", taskId);
data.put("baseinfo", JsonUtils.serialize(baseInfo));
data.put("technical_content", JsonUtils.serialize(technicalContent));
data.put("callback_url", CALL_BACK_URL);
CommonThreadPoolExecutor.addTask(() -> blockingChat(files, ques, data, user, api_key));
CommonThreadPoolExecutor.addTask(() -> blockingChat(filesURL, ques, data, user, api_key));
}
......@@ -132,7 +139,7 @@ public class SoftwareCopyRightAgent {
public String technicalContent(List<String> fileUrls, String query, Map<String, Object> data) {
DifyChatClient chatClient = DifyClientFactory.createChatClient(DIFY_BASE_URL, "app-VuwhZGr7UqFb2gkg7jOmibGM");
fileUrls = softwareCopyRightUtils.downloadAndUnzipPlugin(fileUrls);
List<FileInfo> files = new ArrayList<>();
if (CollectionUtils.isNotEmpty(fileUrls)) {
for (String fileUrl : fileUrls) {
......@@ -166,6 +173,7 @@ public class SoftwareCopyRightAgent {
private void sourceCode(GeneratedDoc doc) {
String api_key = "app-c0PWYmcythg7bIVxvt4d4kDM";
String q = "生成源代码";
List<String> fileUrls = softwareCopyRightUtils.downloadAndUnzipPlugin(doc.getFileURLs());
Map<String, Object> data = new HashMap<>();
data.put("ui", doc.getUi());
data.put("structure", doc.getStructure());
......@@ -176,13 +184,14 @@ public class SoftwareCopyRightAgent {
data.put("callback_url", CALL_BACK_URL);
data.put("task_id", AiSoftWareCopyRightRedisKey.CALL_BACK + doc.getId());
String user = "phOperatingManual" + DateUtils.getCurrTime();
CommonThreadPoolExecutor.addTask(() -> blockingChat(doc.getFileURLs(), q, data, user, api_key));
CommonThreadPoolExecutor.addTask(() -> blockingChat(fileUrls, q, data, user, api_key));
}
// 软著AI助手-手机端说明书
private void phOperatingManual(GeneratedDoc doc) {
String api_key = "app-c0PWYmcythg7bIVxvt4d4kDM";
String q = "生成手机端使用说明书";
List<String> fileUrls = softwareCopyRightUtils.downloadAndUnzipPlugin(doc.getFileURLs());
Map<String, Object> data = new HashMap<>();
data.put("ui", doc.getUi());
data.put("structure", doc.getStructure());
......@@ -193,13 +202,14 @@ public class SoftwareCopyRightAgent {
data.put("callback_url", CALL_BACK_URL);
data.put("task_id", AiSoftWareCopyRightRedisKey.CALL_BACK + doc.getId());
String user = "phOperatingManual" + DateUtils.getCurrTime();
CommonThreadPoolExecutor.addTask(() -> blockingChat(doc.getFileURLs(), q, data, user, api_key));
CommonThreadPoolExecutor.addTask(() -> blockingChat(fileUrls, q, data, user, api_key));
}
// 软著AI助手-PC端说明书
private void pcOperatingManual(GeneratedDoc doc) {
String api_key = "app-c0PWYmcythg7bIVxvt4d4kDM";
String q = "生成电脑端使用说明书";
List<String> fileUrls = softwareCopyRightUtils.downloadAndUnzipPlugin(doc.getFileURLs());
Map<String, Object> data = new HashMap<>();
data.put("ui", doc.getUi());
data.put("structure", doc.getStructure());
......@@ -210,7 +220,7 @@ public class SoftwareCopyRightAgent {
data.put("callback_url", CALL_BACK_URL);
data.put("task_id", AiSoftWareCopyRightRedisKey.CALL_BACK + doc.getId());
String user = "pcOperatingManual" + DateUtils.getCurrTime();
CommonThreadPoolExecutor.addTask(() -> blockingChat(doc.getFileURLs(), q, data, user, api_key));
CommonThreadPoolExecutor.addTask(() -> blockingChat(fileUrls, q, data, user, api_key));
}
// 软著AI助手-信息采集表
......
......@@ -8,7 +8,6 @@ import cn.com.gsst.dify_client.exception.DifyApiException;
import cn.com.poc.ai_software_copyright.contant.AiSoftWareCopyRightEnum;
import cn.com.poc.ai_software_copyright.domian.BaseInfo;
import cn.com.poc.ai_software_copyright.domian.TechnicalContent;
import cn.com.poc.ai_software_copyright.dto.SoftwareCopyRightDto;
import cn.com.poc.ai_software_copyright.entity.BizSoftwareCopyrightDocRecordEntity;
import cn.com.poc.ai_software_copyright.entity.BizSoftwareCopyrightEntity;
import cn.com.poc.ai_software_copyright.entity.CallbackEntity;
......@@ -23,6 +22,16 @@ import org.springframework.web.multipart.MultipartFile;
*/
public interface SoftwareCopyRightService {
/**
* 软著-保存
*
* @param baseInfo 软著基本信息
* @param technicalContent 软著技术内容
* @param referenceDocumentFileUrl 参考文件
*/
BizSoftwareCopyrightEntity save(BaseInfo baseInfo, TechnicalContent technicalContent,
List<String> referenceDocumentFileUrl);
/**
* 软著AI助手
*
......@@ -38,7 +47,6 @@ public interface SoftwareCopyRightService {
Map<String, Object> data
) throws InterruptedException, DifyApiException, IOException;
/**
* AI-生成技术内容
*
......@@ -48,7 +56,6 @@ public interface SoftwareCopyRightService {
*/
TechnicalContent createTechnicalContent(List<String> fileUrls, BaseInfo baseInfo) throws IOException;
/**
* AI软著-文件列表
*
......@@ -64,7 +71,6 @@ public interface SoftwareCopyRightService {
*/
String getDownloadUrl(Long id) throws Exception;
/**
* 文件上传
*
......@@ -81,8 +87,8 @@ public interface SoftwareCopyRightService {
* @param pagingInfo 分页信息
* @return 软著列表
*/
List<BizSoftwareCopyrightEntity> querySoftwareCopyRight(String query, String generatedStatus, PagingInfo pagingInfo);
List<BizSoftwareCopyrightEntity> querySoftwareCopyRight(String query, String generatedStatus,
PagingInfo pagingInfo);
/**
* 回调-生成基础文档
......
......@@ -30,6 +30,7 @@ import cn.com.poc.ai_software_copyright.query.SoftwareCopyrightQueryCondition;
import cn.com.poc.ai_software_copyright.query.SoftwareCopyrightQueryItem;
import cn.com.poc.ai_software_copyright.service.BizSoftwareCopyrightDocRecordService;
import cn.com.poc.ai_software_copyright.service.BizSoftwareCopyrightService;
import cn.com.poc.ai_software_copyright.utils.SoftwareCopyRightUtils;
import cn.com.poc.common.constant.CommonConstant;
import cn.com.poc.common.model.BizFileUploadRecordModel;
import cn.com.poc.common.service.BizFileUploadRecordService;
......@@ -39,14 +40,10 @@ import cn.com.poc.common.utils.DocumentLoad;
import cn.com.poc.common.utils.FileUtils;
import cn.com.poc.common.utils.JsonUtils;
import cn.com.poc.common.utils.MD5Util;
import cn.com.poc.common.utils.MD5Utils;
import cn.com.poc.common.utils.StringUtils;
import cn.com.yict.framemax.core.exception.BusinessException;
import cn.com.yict.framemax.data.model.PagingInfo;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.crypto.digest.MD5;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -70,6 +67,9 @@ public class SoftwareCopyRightServiceImpl implements SoftwareCopyRightService {
AiSoftWareCopyRightEnum.GENERATED_STATUS.Running.name()
);
@Resource
private SoftwareCopyRightUtils softwareCopyRightUtils;
@Resource
private BosConfigService bosConfigService;
......@@ -88,6 +88,17 @@ public class SoftwareCopyRightServiceImpl implements SoftwareCopyRightService {
@Resource
private BizSoftwareCopyrightDocRecordService bizSoftwareCopyrightDocRecordService;
@Override
public BizSoftwareCopyrightEntity save(BaseInfo baseInfo, TechnicalContent technicalContent,
List<String> referenceDocumentFileUrl) {
BizSoftwareCopyrightEntity entity = new BizSoftwareCopyrightEntity();
entity.setBaseInfo(JsonUtils.serialize(baseInfo));
entity.setTechnicalContent(JsonUtils.serialize(technicalContent));
if (CollectionUtils.isNotEmpty(referenceDocumentFileUrl)) {
entity.setReferenceDocumentFileUrl(JsonUtils.serialize(referenceDocumentFileUrl));
}
return bizSoftwareCopyrightService.save(entity);
}
@Override
public void softwareCopyRightAssistant(
......@@ -112,6 +123,7 @@ public class SoftwareCopyRightServiceImpl implements SoftwareCopyRightService {
default:
throw new BusinessException("不支持的AI助手类型: " + type);
}
fileUrls = softwareCopyRightUtils.downloadAndUnzipPlugin(fileUrls);
softwareCopyRightAgent.softwareCopyRightAssistant(fileUrls, question, data);
}
......@@ -394,4 +406,5 @@ public class SoftwareCopyRightServiceImpl implements SoftwareCopyRightService {
throw new BusinessException("任务未完成");
}
}
}
\ No newline at end of file
......@@ -45,9 +45,14 @@ public class BizSoftwareCopyrightRestImpl implements BizSoftwareCopyrightRest {
public SoftwareCopyRightDto save(SoftwareCopyRightDto dto) throws Exception {
Assert.notNull(dto);
BizSoftwareCopyrightEntity entity = BizSoftwareCopyrightConvert.dtoToEntity(dto);
entity.setGeneratedStatus(AiSoftWareCopyRightEnum.GENERATED_STATUS.Daft.name());
return BizSoftwareCopyrightConvert.entityToDto(bizSoftwareCopyrightService.save(entity));
Assert.notNull(dto.getBaseInfo(), "基础信息不能为空");
Assert.notNull(dto.getTechnicalContent(), "技术内容不能为空");
return BizSoftwareCopyrightConvert.entityToDto(
softwareCopyRightService.save(
dto.getBaseInfo(),
dto.getTechnicalContent(),
dto.getReferenceDocumentFileUrl())
);
}
public void deletedById(java.lang.Long id) throws Exception {
......
package cn.com.poc.ai_software_copyright.utils;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import cn.com.poc.common.service.BizFileUploadRecordService;
import cn.com.poc.common.service.BosConfigService;
import cn.com.poc.common.utils.DocumentLoad;
import cn.com.poc.common.utils.FileUtils;
import cn.com.yict.framemax.core.exception.BusinessException;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author alex.yao
* @date 2026/1/9
*/
@Component
public class SoftwareCopyRightUtils {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private BosConfigService bosConfigService;
@Resource
private BizFileUploadRecordService bizFileUploadRecordService;
/**
* 插件是否存在压缩文件,存在则下载并解压文件
*/
public List<String> downloadAndUnzipPlugin(List<String> fileURLS) {
if (CollectionUtils.isEmpty(fileURLS)) {
return fileURLS;
}
CopyOnWriteArrayList<String> result = new CopyOnWriteArrayList<>();
List<File> tempFilesToClean = new ArrayList<>(); // 记录需要清理的临时文件
try {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (String fileURL : fileURLS) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
String fileName = bizFileUploadRecordService.getFileNameByFileUrl(fileURL);
boolean compressedFile = FileUtils.isCompressedFile(fileName);
if (compressedFile) {
File downloadedFile = DocumentLoad.downloadURLDocument(fileURL);
tempFilesToClean.add(downloadedFile); // 添加到待清理列表
List<File> files = null;
try {
files = FileUtils.unGzip(downloadedFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
processUnzippedFiles(files, result, tempFilesToClean);
} else {
result.add(fileURL);
}
});
futures.add(future);
}
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join(); // 这里会抛出异常,如果任何一个任务失败
return result;
} catch (Exception e) {
// 清理所有临时文件
cleanupTempFiles(tempFilesToClean);
throw new BusinessException("处理插件文件时发生异常: " + e.getMessage(), e);
} finally {
// 确保临时文件被清理
cleanupTempFiles(tempFilesToClean);
}
}
private void processUnzippedFiles(List<File> files, CopyOnWriteArrayList<String> result,
List<File> tempFilesToClean) {
if (CollectionUtils.isEmpty(files)) {
return;
}
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (File file : files) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
String upload = bosConfigService.upload(fileInputStream, file.getName(), null);
result.add(upload);
tempFilesToClean.add(file); // 添加到待清理列表,以便在外部统一清理
} catch (IOException e) {
throw new RuntimeException("上传文件失败: " + e.getMessage(), e);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
// 记录日志但不抛出异常
logger.error("关闭文件流失败: " + e.getMessage());
}
}
}
});
futures.add(future);
}
// 等待所有上传任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
/**
* 清理临时文件
*/
private void cleanupTempFiles(List<File> tempFiles) {
for (File tempFile : tempFiles) {
if (tempFile != null && tempFile.exists()) {
try {
tempFile.delete();
} catch (Exception e) {
System.err.println("删除临时文件失败: " + tempFile.getAbsolutePath() + ", 错误: " + e.getMessage());
}
}
}
}
}
......@@ -83,6 +83,17 @@ public class FileUtils {
*/
public static boolean isCompressedFile(File file) {
String fileName = file.getName().toLowerCase();
return isCompressedFile(fileName);
}
/**
* 判断文件是否为压缩文件
*
* @param fileName 文件名
* @return 是否为压缩文件
*/
public static boolean isCompressedFile(String fileName) {
fileName = fileName.toLowerCase();
return fileName.endsWith(".zip") || fileName.endsWith(".tar") ||
fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz") ||
fileName.endsWith(".rar") || fileName.endsWith(".7z");
......
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