From a4e995d46c2584cae230dc5fe50386363b950926 Mon Sep 17 00:00:00 2001 From: ageerle Date: Mon, 19 May 2025 15:33:29 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Epdf=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E8=A7=A3=E6=9E=90=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/ruoyi/service/impl/VectorStoreServiceImpl.java | 3 +-- .../chat/service/knowledge/KnowledgeInfoServiceImpl.java | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java index 917652c4..7d156a3e 100644 --- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java @@ -1,13 +1,11 @@ package org.ruoyi.service.impl; -import cn.hutool.core.util.RandomUtil; import com.google.protobuf.ServiceException; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.ollama.OllamaEmbeddingModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; -import dev.langchain4j.model.output.Response; import dev.langchain4j.store.embedding.EmbeddingMatch; import dev.langchain4j.store.embedding.EmbeddingSearchRequest; import dev.langchain4j.store.embedding.EmbeddingStore; @@ -31,6 +29,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * 向量库管理 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java index 06ad06b6..14a83fd6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java @@ -354,7 +354,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { /** * 第二步 定时 解析图片内容 */ - //@Scheduled(fixedDelay = 15000) + @Scheduled(fixedDelay = 15000) public void dealKnowledgeAttachPicAnys() throws Exception { //获取未处理的图片记录 List knowledgeAttachPics = picMapper.selectList( @@ -371,7 +371,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { /** * 第三步 定时 处理 附件上传后上传向量数据库 */ - //@Scheduled(fixedDelay = 30000) // 每3秒执行一次 + @Scheduled(fixedDelay = 30000) // 每3秒执行一次 public void dealKnowledgeAttachVector() throws Exception { //处理 需要上传向量数据库的记录 List knowledgeAttaches = attachMapper.selectList( @@ -390,7 +390,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { /** * 第四步 定时 处理 失败数据 */ - //@Scheduled(fixedDelay = 30 * 60 * 1000) + @Scheduled(fixedDelay = 30 * 60 * 1000) public void dealKnowledge40Status() throws Exception { //拆解PDF失败 重新设置状态 attachMapper.update(new LambdaUpdateWrapper() @@ -405,5 +405,5 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10) .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40)); } - @Scheduled(fixedDelay = 180000) // 3分钟执行一次 + } From e43e14454d4498cf8bbd7477ab172606c7d7c713 Mon Sep 17 00:00:00 2001 From: zhouweiyi <> Date: Tue, 20 May 2025 09:22:41 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0knowledge=5Fattach?= =?UTF-8?q?=5Fpic=E8=A1=A8=E7=BB=93=E6=9E=84sql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/sql/update/202505141010.sql | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/script/sql/update/202505141010.sql b/script/sql/update/202505141010.sql index e1b81b25..775a20b1 100644 --- a/script/sql/update/202505141010.sql +++ b/script/sql/update/202505141010.sql @@ -8,3 +8,48 @@ ADD PRIMARY KEY (`id`) USING BTREE; ALTER TABLE `knowledge_attach` MODIFY COLUMN `remark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注' AFTER `update_time`; + +/* + Navicat Premium Data Transfer + + Source Server : localhost-57 + Source Server Type : MySQL + Source Server Version : 50731 (5.7.31) + Source Host : localhost:3306 + Source Schema : ruoyi-ai + + Target Server Type : MySQL + Target Server Version : 50731 (5.7.31) + File Encoding : 65001 + + Date: 19/05/2025 15:22:09 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for knowledge_attach_pic +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_attach_pic`; +CREATE TABLE `knowledge_attach_pic` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `kid` varchar(50) NOT NULL COMMENT '知识库id', + `aid` varchar(50) NOT NULL COMMENT '附件id', + `doc_name` varchar(500) DEFAULT NULL COMMENT '文档名称', + `doc_type` varchar(50) NOT NULL COMMENT '文档类型', + `content` longtext COMMENT '文档内容', + `page_num` int(5) DEFAULT '0' COMMENT '所在页数', + `index_num` int(5) DEFAULT '0' COMMENT '所在页index', + `pic_anys_status` int(5) NOT NULL DEFAULT '10' COMMENT '分析图片状态10未开始,20进行中,30已完成', + `oss_id` bigint(20) NOT NULL COMMENT '对象存储主键', + `create_dept` varchar(255) DEFAULT NULL COMMENT '部门', + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` text COMMENT '备注', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1922929659800637443 DEFAULT CHARSET=utf8mb4 COMMENT='知识库附件图片列表'; + +SET FOREIGN_KEY_CHECKS = 1; From 1d51a103d0d515aefb083bd5330d2866008a49a1 Mon Sep 17 00:00:00 2001 From: zhouweiyi <> Date: Wed, 4 Jun 2025 17:55:47 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E7=99=BE=E7=82=BCAPI=E5=AE=9E=E7=8E=B0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=86=85=E5=AE=B9=E8=AF=86=E5=88=AB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加DashscopeService接口及实现,用于调用阿里百炼API进行图片内容识别 修改PdfImageExtractService增加基于百炼API的图片处理逻辑 新增OSS服务方法支持临时文件处理和删除 更新配置文件添加百炼模型相关配置 --- .../src/main/resources/application-local.yml | 111 +++++++++++++ .../src/main/resources/application-prod.yml | 6 +- ruoyi-modules-api/ruoyi-knowledge-api/pom.xml | 6 + .../org/ruoyi/service/DashscopeService.java | 23 +++ .../ruoyi/service/PdfImageExtractService.java | 16 ++ .../service/impl/DashscopeServiceImpl.java | 149 ++++++++++++++++++ .../impl/PdfImageExtractServiceImpl.java | 59 +++++-- .../ruoyi/system/service/ISysOssService.java | 9 ++ .../service/impl/SysOssServiceImpl.java | 44 ++++++ .../knowledge/KnowledgeController.java | 16 +- .../service/knowledge/DealFileService.java | 53 +++---- .../knowledge/KnowledgeInfoServiceImpl.java | 61 ++++--- script/sql/update/202506041541.sql | 2 + 13 files changed, 472 insertions(+), 83 deletions(-) create mode 100644 ruoyi-admin/src/main/resources/application-local.yml create mode 100644 ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/DashscopeService.java create mode 100644 ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java create mode 100644 script/sql/update/202506041541.sql diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml new file mode 100644 index 00000000..edf30cb5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-local.yml @@ -0,0 +1,111 @@ +--- # 监控中心配置 +spring.boot.admin.client: + # 增加客户端开关 + enabled: false + url: http://localhost:9090/admin + instance: + service-host-type: IP + username: ruoyi + password: 123456 + +--- # 数据源配置 +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content + dynamic: + # 性能分析插件(有性能损耗 不建议生产环境使用) + p6spy: true + # 设置默认的数据源或者数据源组,默认值即为 master + primary: master + # 严格模式 匹配不到数据源则报错 + strict: true + datasource: + # 主库数据源 + master: + type: ${spring.datasource.type} + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true + username: root + password: root + + + hikari: + # 最大连接池数量 + maxPoolSize: 20 + # 最小空闲线程数量 + minIdle: 10 + # 配置获取连接等待超时的时间 + connectionTimeout: 30000 + # 校验超时时间 + validationTimeout: 5000 + # 空闲连接存活最大时间,默认10分钟 + idleTimeout: 600000 + # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟 + maxLifetime: 1800000 + # 连接测试query(配置检测连接是否有效) + connectionTestQuery: SELECT 1 + # 多久检查一次连接的活性 + keepaliveTime: 30000 + +--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉) +spring.data: + redis: + # 地址 + host: 127.0.0.1 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码(如没有密码请注释掉) + password: root + # 连接超时时间 + timeout: 10S + # 是否开启ssl + ssl: false + +redisson: + # redis key前缀 + keyPrefix: + # 线程池数量 + threads: 4 + # Netty线程池数量 + nettyThreads: 8 + # 单节点配置 + singleServerConfig: + # 客户端名称 + clientName: ${ruoyi.name} + # 最小空闲连接数 + connectionMinimumIdleSize: 8 + # 连接池大小 + connectionPoolSize: 32 + # 连接空闲超时,单位:毫秒 + idleConnectionTimeout: 10000 + # 命令等待超时,单位:毫秒 + timeout: 3000 + # 发布和订阅连接池大小 + subscriptionConnectionPoolSize: 50 + +--- # sms 短信 +sms: + enabled: false + # 阿里云 dysmsapi.aliyuncs.com + # 腾讯云 sms.tencentcloudapi.com + endpoint: "dysmsapi.aliyuncs.com" + accessKeyId: xxxxxxx + accessKeySecret: xxxxxx + signName: 测试 + # 腾讯专用 + sdkAppId: + +pdf: + extract: + service: + url: http://localhost:8080 + ai-api: + url: https://api.pandarobot.chat/v1/chat/completions + key: sk-xxxx +#百炼模型配置 +dashscope: + key: sk-0a4a86f3712b47ac825c1632319a8b1a + model: qvq-max \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index cb495f9f..bbad32bd 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -179,4 +179,8 @@ pdf: url: http://localhost:8080 ai-api: url: https://api.pandarobot.chat/v1/chat/completions - key: sk-XXXXXX \ No newline at end of file + key: sk-XXXXXX +#百炼模型配置 +dashscope: + key: sk-XXXX + model: qvq-max \ No newline at end of file diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/pom.xml b/ruoyi-modules-api/ruoyi-knowledge-api/pom.xml index 4b4c1fe6..1065ac13 100644 --- a/ruoyi-modules-api/ruoyi-knowledge-api/pom.xml +++ b/ruoyi-modules-api/ruoyi-knowledge-api/pom.xml @@ -119,6 +119,12 @@ ruoyi-system-api + + com.alibaba + dashscope-sdk-java + 2.19.0 + + diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/DashscopeService.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/DashscopeService.java new file mode 100644 index 00000000..3c8f498e --- /dev/null +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/DashscopeService.java @@ -0,0 +1,23 @@ +package org.ruoyi.service; + +import java.io.IOException; + +/** + * @Description: 阿里百炼api + * @Date: 2025/6/4 下午2:24 + */ +public interface DashscopeService { + + /** + * 视觉推理(QVQ) + * @param imageUrl 图片可访问的地址 + * @return + */ + String qvq(String imageUrl) throws IOException; + /** + * 视觉推理(QVQ) 使用本地文件(输入Base64编码或本地路径) + * @param localPath 图片文件的绝对路径 + * @return + */ + String qvq4LocalPath(String localPath) throws IOException; +} diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/PdfImageExtractService.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/PdfImageExtractService.java index 759b4397..1b68243f 100644 --- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/PdfImageExtractService.java +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/PdfImageExtractService.java @@ -30,7 +30,23 @@ public interface PdfImageExtractService { * @throws IOException 如果API调用过程中发生错误 */ List dealFileContent(String[] unzip) throws IOException; + /** + *利用百炼接口处理文件内容 + * + * @param imageUrl 传入图片地址 + * @return 文件内容结果列表 + * @throws IOException 如果API调用过程中发生错误 + */ + List dealFileContent4Dashscope(String imageUrl) throws IOException; + /** + * 利用百炼接口处理文件内容 + * + * 视觉推理(QVQ) 使用本地文件(输入Base64编码或本地路径) + * @param localPath 图片文件的绝对路径 + * @return + */ + List dealFileContent4DashscopeBase64(String localPath)throws IOException; /** * 提取PDF中的图片并调用gpt-4o-mini,识别图片内容并返回 * @param file diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java new file mode 100644 index 00000000..c325c4f0 --- /dev/null +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java @@ -0,0 +1,149 @@ +package org.ruoyi.service.impl; + +import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation; +import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam; +import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; +import com.alibaba.dashscope.common.MultiModalMessage; +import com.alibaba.dashscope.common.Role; +import io.reactivex.Flowable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.domain.PdfFileContentResult; +import org.ruoyi.service.DashscopeService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * @Description: 阿里百炼API + * @Date: 2025/6/4 下午2:28 + */ +@Service +@Slf4j +public class DashscopeServiceImpl implements DashscopeService { + + private static StringBuilder reasoningContent = new StringBuilder(); + private static StringBuilder finalContent = new StringBuilder(); + private static boolean isFirstPrint = true; + + @Value("${dashscope.model}") + private String serviceModel; + @Value("${dashscope.key}") + private String serviceKey; + + /** + * 视觉推理(QVQ) + * @param imageUrl 图片可访问地址 + * @return + */ + @Override + public String qvq(String imageUrl) throws IOException { + try { + // 构建多模态消息 + MultiModalMessage userMessage = MultiModalMessage.builder() + .role(Role.USER.getValue()) + .content(Arrays.asList( + Collections.singletonMap("text", "这张图片有什么"), + Collections.singletonMap("image", imageUrl) + )) + .build(); + + // 构建请求参数 + MultiModalConversationParam param = MultiModalConversationParam.builder() + .apiKey(serviceKey) // 使用配置文件中的API Key + .model(serviceModel) + .message(userMessage) + .build(); + + MultiModalConversation conv = new MultiModalConversation(); + + // 调用API + Flowable result = conv.streamCall( + param); + + reasoningContent = new StringBuilder(); + finalContent = new StringBuilder(); + isFirstPrint = true; + + result.blockingForEach(DashscopeServiceImpl::handleGenerationResult); + + return finalContent.toString().replaceAll("[\n\r\s]", ""); + } catch (Exception e) { + log.error("调用百炼API失败: {}", e.getMessage(), e); + throw new IOException("百炼API调用失败: " + e.getMessage(), e); + } + } + /** + * 视觉推理(QVQ) 使用本地文件(输入Base64编码或本地路径) + * @param localPath 图片文件的绝对路径 + * @return + */ + @Override + public String qvq4LocalPath(String localPath) throws IOException { + try { + // 构建多模态消息 + String filePath = "file://"+ localPath; + log.info("filePath: {}", filePath); + MultiModalMessage userMessage = MultiModalMessage.builder().role(Role.USER.getValue()) + .content(Arrays.asList(new HashMap(){{put("image", filePath);}}, + new HashMap(){{put("text", "这张图片有什么");}})).build(); + + // 构建请求参数 + MultiModalConversationParam param = MultiModalConversationParam.builder() + .apiKey(serviceKey) // 使用配置文件中的API Key + .model(serviceModel) + .message(userMessage) + .build(); + MultiModalConversation conv = new MultiModalConversation(); + + // 调用API + Flowable result = conv.streamCall( + param); + + reasoningContent = new StringBuilder(); + finalContent = new StringBuilder(); + isFirstPrint = true; + + result.blockingForEach(DashscopeServiceImpl::handleGenerationResult); + + return finalContent.toString().replaceAll("[\n\r\s]", ""); + } catch (Exception e) { + log.error("调用百炼API失败: {}", e.getMessage(), e); + throw new IOException("百炼API调用失败: " + e.getMessage(), e); + } + } + + + private static void handleGenerationResult(MultiModalConversationResult message) { + + String re = message.getOutput().getChoices().get(0).getMessage().getReasoningContent(); + String reasoning = Objects.isNull(re) ? "" : re; // 默认值 + + List> content = message.getOutput().getChoices().get(0).getMessage() + .getContent(); + if (!reasoning.isEmpty()) { + reasoningContent.append(reasoning); + if (isFirstPrint) { + System.out.println("====================思考过程===================="); + isFirstPrint = false; + } + System.out.print(reasoning); + } + + if (Objects.nonNull(content) && !content.isEmpty()) { + Object text = content.get(0).get("text"); + finalContent.append(text); + if (!isFirstPrint) { + System.out.println("\n====================完整回复===================="); + isFirstPrint = true; + } + System.out.print(text); + } + } +} diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java index 29aae8eb..d05943b4 100644 --- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; @@ -14,10 +13,11 @@ import okhttp3.OkHttpClient.Builder; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import org.ruoyi.common.core.domain.R; import org.ruoyi.domain.PdfFileContentResult; +import org.ruoyi.service.DashscopeService; import org.ruoyi.service.PdfImageExtractService; import org.ruoyi.utils.ZipUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -25,20 +25,21 @@ import org.springframework.web.multipart.MultipartFile; /** * PDF图片提取服务实现类 */ -//@Service +@Service @Slf4j @Data -@AllArgsConstructor -//public class PdfImageExtractServiceImpl implements PdfImageExtractService { -public class PdfImageExtractServiceImpl { +public class PdfImageExtractServiceImpl implements PdfImageExtractService { -// @Value("${pdf.extract.service.url}") + @Value("${pdf.extract.service.url}") private String serviceUrl; -// @Value("${pdf.extract.ai-api.url}") + @Value("${pdf.extract.ai-api.url}") private String aiApiUrl; -// @Value("${pdf.extract.ai-api.key}") + @Value("${pdf.extract.ai-api.key}") private String aiApiKey; + @Autowired + private DashscopeService dashscopeService; + private final OkHttpClient client = new Builder() .connectTimeout(100, TimeUnit.SECONDS) .readTimeout(150, TimeUnit.SECONDS) @@ -48,7 +49,7 @@ public class PdfImageExtractServiceImpl { private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); -// @Override + // @Override public byte[] extractImages(MultipartFile pdfFile, String imageFormat, boolean allowDuplicates) throws IOException { // 构建multipart请求 @@ -135,7 +136,43 @@ public class PdfImageExtractServiceImpl { return results; } -// @Override + /** + * 利用百炼接口处理文件内容 + * + * @param imageUrl 传入图片地址 + * @return 文件内容结果列表 + * @throws IOException 如果API调用过程中发生错误 + */ + @Override + public List dealFileContent4Dashscope(String imageUrl) throws IOException { + String qvq = dashscopeService.qvq(imageUrl); + // 构建结果列表 + List results = new ArrayList<>(); + String filename = "image_" + System.currentTimeMillis(); + results.add(new PdfFileContentResult(filename, qvq)); + return results; + } + + /** + * 利用百炼接口处理文件内容 + * + * 视觉推理(QVQ) 使用本地文件(输入Base64编码或本地路径) + * @param localPath 图片文件的绝对路径 + * @return + */ + @Override + public List dealFileContent4DashscopeBase64(String localPath) throws IOException { + String qvq = dashscopeService.qvq4LocalPath(localPath); + // 构建结果列表 + List results = new ArrayList<>(); + String filename = "image_" + System.currentTimeMillis(); + results.add(new PdfFileContentResult(filename, qvq)); + return results; + } + + + + // @Override public List extractImages(MultipartFile file) throws IOException { String format = "png"; boolean allowDuplicates = true; diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOssService.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOssService.java index a9ff4952..f5640872 100644 --- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOssService.java +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/ISysOssService.java @@ -32,6 +32,15 @@ public interface ISysOssService { String downloadByByte(Long ossId) throws IOException; + String downloadToTempPath(Long ossId) throws IOException; + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + /** + * 根据文件路径删除文件 + * + * @param filePath 文件路径 + * @return 是否删除成功 + */ + boolean deleteFile(String filePath); } diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java index 4d14a75b..edbaa526 100644 --- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java @@ -213,4 +213,48 @@ public class SysOssServiceImpl implements ISysOssService, OssService { } return oss; } + @Override + public String downloadToTempPath(Long ossId) throws IOException { + SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId); + if (ObjectUtil.isNull(sysOss)) { + throw new ServiceException("文件数据不存在!"); + } + + OssClient storage = OssFactory.instance(); + try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) { + // 创建临时文件 + String suffix = StringUtils.isNotEmpty(sysOss.getFileSuffix()) ? sysOss.getFileSuffix() : ""; + java.io.File tempFile = java.io.File.createTempFile("download_", suffix); + // 确保临时文件在JVM退出时删除 + tempFile.deleteOnExit(); + // 将输入流内容写入临时文件 + cn.hutool.core.io.FileUtil.writeFromStream(inputStream, tempFile); + // 返回临时文件的绝对路径 + return tempFile.getAbsolutePath(); + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + /** + * 根据文件路径删除文件 + * + * @param filePath 文件路径 + * @return 是否删除成功 + */ + @Override + public boolean deleteFile(String filePath) { + if (StringUtils.isEmpty(filePath)) { + return false; + } + + try { + java.io.File file = new java.io.File(filePath); + if (file.exists() && file.isFile()) { + return file.delete(); + } + return false; + } catch (Exception e) { + throw new ServiceException("删除文件失败: " + e.getMessage()); + } + } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java index 82362538..068df42c 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java @@ -52,7 +52,7 @@ public class KnowledgeController extends BaseController { private final IKnowledgeFragmentService fragmentService; -// private final PdfImageExtractService pdfImageExtractService; + private final PdfImageExtractService pdfImageExtractService; /** * 根据用户信息查询本地知识库 @@ -170,11 +170,11 @@ public class KnowledgeController extends BaseController { * @param file PDF文件 * @return 文件名称和图片内容 */ -// @PostMapping("/extract-images") -// @Operation(summary = "提取PDF中的图片并调用大模型,识别图片内容并返回", description = "提取PDF中的图片并调用gpt-4o-mini,识别图片内容并返回") -// public R> extractImages( -// @RequestPart("file") MultipartFile file -// ) throws IOException { -// return R.ok(pdfImageExtractService.extractImages(file)); -// } + @PostMapping("/extract-images") + @Operation(summary = "提取PDF中的图片并调用大模型,识别图片内容并返回", description = "提取PDF中的图片并调用gpt-4o-mini,识别图片内容并返回") + public R> extractImages( + ) throws IOException { + return R.ok(pdfImageExtractService + .dealFileContent4Dashscope("https://hnzuoran02-1327573163.cos.ap-nanjing.myqcloud.com/crmebimage/public/content/2025/06/04/e115264eb22f423ea0b211709361c29f071avy39ez.jpg")); + } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java index 2951c20f..0628ffde 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java @@ -26,6 +26,7 @@ import org.ruoyi.mapper.KnowledgeAttachPicMapper; import org.ruoyi.mapper.KnowledgeFragmentMapper; import org.ruoyi.mapper.KnowledgeInfoMapper; import org.ruoyi.service.IChatModelService; +import org.ruoyi.service.PdfImageExtractService; import org.ruoyi.service.VectorStoreService; import org.ruoyi.service.impl.PdfImageExtractServiceImpl; import org.ruoyi.system.domain.vo.SysOssVo; @@ -64,18 +65,10 @@ public class DealFileService { private final ISysOssService ossService; -// private final PdfImageExtractService pdfImageExtractService; + private final PdfImageExtractService pdfImageExtractService; private final KnowledgeAttachPicMapper picMapper; - @Value("${pdf.extract.service.url}") - private String serviceUrl; - @Value("${pdf.extract.ai-api.url}") - private String aiApiUrl; - @Value("${pdf.extract.ai-api.key}") - private String aiApiKey; - - @Async public void dealVectorStatus(KnowledgeAttach attachItem) throws Exception { try { @@ -169,8 +162,6 @@ public class DealFileService { //获取oss文件 MultipartFile multipartFile = ossService.downloadByFile(attachItem.getOssId()); //拆解出图片ZIP - PdfImageExtractServiceImpl pdfImageExtractService = new PdfImageExtractServiceImpl(serviceUrl, - aiApiUrl, aiApiKey); byte[] pngs = pdfImageExtractService.extractImages(multipartFile, "png", true); //解压zip,得到图片文件 MultipartFile[] multipartFiles = ZipUtils.unzipToMultipartFiles(pngs); @@ -236,6 +227,7 @@ public class DealFileService { @Async public void dealPicAnysStatus(KnowledgeAttachPic picItem) throws Exception { + String filePath = null; try { //锁定数据 更改 getPicAnysStatus 到进行中 if (picMapper.update(new LambdaUpdateWrapper() @@ -247,13 +239,10 @@ public class DealFileService { } SysOssVo ossVo = ossService.getById(picItem.getOssId()); if (ObjectUtil.isNotEmpty(ossVo)) { - String fileStr = ossService.downloadByByte(picItem.getOssId()); + filePath = ossService.downloadToTempPath(picItem.getOssId()); //调用第三方 分析图片内容 - PdfImageExtractServiceImpl pdfImageExtractService = new PdfImageExtractServiceImpl( - serviceUrl, - aiApiUrl, aiApiKey); - List pdfFileContentResults = pdfImageExtractService.dealFileContent( - new String[]{fileStr}); + List pdfFileContentResults = pdfImageExtractService.dealFileContent4DashscopeBase64( + filePath); if (ObjectUtil.isNotEmpty(pdfFileContentResults)) { for (PdfFileContentResult resultItem : pdfFileContentResults) { //图片解析内容回写到pic表 @@ -302,6 +291,11 @@ public class DealFileService { .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_20) .eq(KnowledgeAttachPic::getId, picItem.getId())); throw new RuntimeException(e); + } finally { + //无论成功还是失败,都要删除临时文件 + if (ObjectUtil.isNotEmpty(filePath)) { + ossService.deleteFile(filePath); + } } } @@ -349,35 +343,32 @@ public class DealFileService { return null; } - public static String parseContent(String jsonString) { + public static String parseContent(String content) { try { - // 创建ObjectMapper实例 + // 首先尝试作为JSON解析 ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(content); - // 解析JSON字符串 - JsonNode rootNode = objectMapper.readTree(jsonString); - - // 获取choices数组的第一个元素 + // 如果是JSON格式,按原有逻辑处理 JsonNode choicesNode = rootNode.get("choices"); if (choicesNode != null && choicesNode.isArray() && choicesNode.size() > 0) { - // 获取第一个choice JsonNode firstChoice = choicesNode.get(0); - - // 获取message节点 JsonNode messageNode = firstChoice.get("message"); if (messageNode != null) { - // 获取content字段的值 JsonNode contentNode = messageNode.get("content"); if (contentNode != null) { return contentNode.asText(); } } + return "无法找到content内容"; } - - return "无法找到content内容"; + + // 如果不是预期的JSON格式,直接返回原始内容 + return content; + } catch (Exception e) { - e.printStackTrace(); - return "解析JSON时发生错误: " + e.getMessage(); + // 如果解析JSON失败,说明是普通文本,直接返回 + return content; } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java index 14a83fd6..cc66ed3c 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java @@ -57,7 +57,6 @@ import org.ruoyi.system.service.ISysOssService; import java.io.IOException; import java.util.*; - /** * 知识库Service业务层处理 * @@ -83,7 +82,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { private final ISysOssService ossService; -// private final PdfImageExtractService pdfImageExtractService; + // private final PdfImageExtractService pdfImageExtractService; private final KnowledgeAttachPicMapper picMapper; @@ -170,7 +169,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { * 保存前的数据校验 */ private void validEntityBeforeSave(KnowledgeInfo entity) { - //TODO 做一些数据校验,如唯一约束 + // TODO 做一些数据校验,如唯一约束 } /** @@ -179,7 +178,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { - //TODO 做一些业务上的校验,判断是否需要校验 + // TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteBatchIds(ids) > 0; } @@ -223,10 +222,10 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { Collection ossIds = knowledgeAttachVos.stream() .map(KnowledgeAttachVo::getOssId) .collect(Collectors.toList()); - //删除oss + // 删除oss ossService.deleteWithValidByIds(ossIds, false); - //删除图片oss + // 删除图片oss List knowledgeAttachPics = picMapper.selectList( new LambdaQueryWrapper() .in(KnowledgeAttachPic::getKid, @@ -234,8 +233,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { .collect(Collectors.toList())) .in(KnowledgeAttachPic::getAid, knowledgeAttachVos.stream().map(KnowledgeAttachVo::getId) - .collect(Collectors.toList())) - ); + .collect(Collectors.toList()))); if (ObjectUtil.isNotEmpty(knowledgeAttachPics)) { Collection tossIds = knowledgeAttachPics.stream() .map(KnowledgeAttachPic::getOssId) @@ -302,7 +300,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { knowledgeAttach.setCreateTime(new Date()); if (ObjectUtil.isNotEmpty(uploadDto) && ObjectUtil.isNotEmpty(uploadDto.getOssId())) { knowledgeAttach.setOssId(uploadDto.getOssId()); - //只有pdf文件 才需要拆解图片和分析图片内容 + // 只有pdf文件 才需要拆解图片和分析图片内容 if (FileType.PDF.equals(knowledgeAttach.getDocType())) { knowledgeAttach.setPicStatus(DealStatus.STATUS_10); knowledgeAttach.setPicAnysStatus(DealStatus.STATUS_10); @@ -310,7 +308,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { knowledgeAttach.setPicStatus(DealStatus.STATUS_30); knowledgeAttach.setPicAnysStatus(DealStatus.STATUS_30); } - //所有文件上传后,都需要同步到向量数据库 + // 所有文件上传后,都需要同步到向量数据库 knowledgeAttach.setVectorStatus(DealStatus.STATUS_10); } attachMapper.insert(knowledgeAttach); @@ -334,15 +332,14 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { /** * 第一步 定时 拆解PDF文件中的图片 */ - //@Scheduled(fixedDelay = 15000) // 每3秒执行一次 + @Scheduled(fixedDelay = 15000) // 每3秒执行一次 public void dealKnowledgeAttachPic() throws Exception { - //处理 拆解PDF文件中的图片的记录 + // 处理 拆解PDF文件中的图片的记录 List knowledgeAttaches = attachMapper.selectList( new LambdaQueryWrapper() .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_10) .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_10) - .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10) - ); + .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)); log.info("===============拆解PDF文件中的图片 size = {}", knowledgeAttaches.size()); if (ObjectUtil.isNotEmpty(knowledgeAttaches)) { for (KnowledgeAttach attachItem : knowledgeAttaches) { @@ -356,30 +353,29 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { */ @Scheduled(fixedDelay = 15000) public void dealKnowledgeAttachPicAnys() throws Exception { - //获取未处理的图片记录 + // 获取未处理的图片记录 List knowledgeAttachPics = picMapper.selectList( new LambdaQueryWrapper() .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_10) - .last("LIMIT 20") - ); + .last("LIMIT 20")); if (ObjectUtil.isNotEmpty(knowledgeAttachPics)) { for (KnowledgeAttachPic picItem : knowledgeAttachPics) { dealFileService.dealPicAnysStatus(picItem); } } } + /** * 第三步 定时 处理 附件上传后上传向量数据库 */ @Scheduled(fixedDelay = 30000) // 每3秒执行一次 public void dealKnowledgeAttachVector() throws Exception { - //处理 需要上传向量数据库的记录 + // 处理 需要上传向量数据库的记录 List knowledgeAttaches = attachMapper.selectList( new LambdaQueryWrapper() .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_30) .eq(KnowledgeAttach::getPicAnysStatus, DealStatus.STATUS_30) - .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10) - ); + .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10)); log.info("===============上传向量数据库 size = {}", knowledgeAttaches.size()); if (ObjectUtil.isNotEmpty(knowledgeAttaches)) { for (KnowledgeAttach attachItem : knowledgeAttaches) { @@ -387,23 +383,24 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { } } } + /** * 第四步 定时 处理 失败数据 */ @Scheduled(fixedDelay = 30 * 60 * 1000) public void dealKnowledge40Status() throws Exception { - //拆解PDF失败 重新设置状态 - attachMapper.update(new LambdaUpdateWrapper() - .set(KnowledgeAttach::getPicStatus, DealStatus.STATUS_10) - .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_40)); - //将图片分析失败的数据 重新设置状态 - picMapper.update(new LambdaUpdateWrapper() - .set(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_10) - .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_40)); - //上传向量库失败 重新设置状态 - attachMapper.update(new LambdaUpdateWrapper() - .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10) - .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40)); + // 拆解PDF失败 重新设置状态 + attachMapper.update(new LambdaUpdateWrapper() + .set(KnowledgeAttach::getPicStatus, DealStatus.STATUS_10) + .eq(KnowledgeAttach::getPicStatus, DealStatus.STATUS_40)); + // 将图片分析失败的数据 重新设置状态 + picMapper.update(new LambdaUpdateWrapper() + .set(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_10) + .eq(KnowledgeAttachPic::getPicAnysStatus, DealStatus.STATUS_40)); + // 上传向量库失败 重新设置状态 + attachMapper.update(new LambdaUpdateWrapper() + .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_10) + .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40)); } } diff --git a/script/sql/update/202506041541.sql b/script/sql/update/202506041541.sql new file mode 100644 index 00000000..6e25d24e --- /dev/null +++ b/script/sql/update/202506041541.sql @@ -0,0 +1,2 @@ +ALTER TABLE `knowledge_attach` +MODIFY COLUMN `oss_id` bigint(20) NULL DEFAULT NULL COMMENT '对象存储ID' AFTER `doc_type`; \ No newline at end of file From cbe882af665157f165cf84dc7dac95baf4dd3fa8 Mon Sep 17 00:00:00 2001 From: zhouweiyi <> Date: Wed, 4 Jun 2025 18:02:26 +0800 Subject: [PATCH 4/5] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=99=BE?= =?UTF-8?q?=E7=82=BC=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E7=9A=84API?= =?UTF-8?q?=E5=AF=86=E9=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将开发环境和本地环境的百炼模型配置中的API密钥统一更新为占位符'sk-xxxx' --- ruoyi-admin/src/main/resources/application-dev.yml | 5 ++++- ruoyi-admin/src/main/resources/application-local.yml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 954deb66..19a52ebc 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -101,4 +101,7 @@ pdf: ai-api: url: https://api.pandarobot.chat/v1/chat/completions key: sk-xxxx - +#百炼模型配置 +dashscope: + key: sk-xxxx + model: qvq-max diff --git a/ruoyi-admin/src/main/resources/application-local.yml b/ruoyi-admin/src/main/resources/application-local.yml index edf30cb5..95bb3a93 100644 --- a/ruoyi-admin/src/main/resources/application-local.yml +++ b/ruoyi-admin/src/main/resources/application-local.yml @@ -107,5 +107,5 @@ pdf: key: sk-xxxx #百炼模型配置 dashscope: - key: sk-0a4a86f3712b47ac825c1632319a8b1a + key: sk-xxxx model: qvq-max \ No newline at end of file From 6a73e09ac778d140667cb546c8ca17358facae94 Mon Sep 17 00:00:00 2001 From: zhouweiyi <> Date: Mon, 9 Jun 2025 18:01:51 +0800 Subject: [PATCH 5/5] =?UTF-8?q?refactor(DashscopeServiceImpl):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E9=9D=99=E6=80=81=E5=8F=98=E9=87=8F=E5=B9=B6=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将静态变量改为实例变量以避免并发问题 重构日志输出逻辑,仅在最后响应时输出完整内容 添加异常堆栈打印以方便调试 --- .../service/impl/DashscopeServiceImpl.java | 43 ++++++++++--------- .../service/knowledge/DealFileService.java | 1 + 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java index c325c4f0..0f6b0014 100644 --- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/DashscopeServiceImpl.java @@ -28,9 +28,7 @@ import org.springframework.stereotype.Service; @Slf4j public class DashscopeServiceImpl implements DashscopeService { - private static StringBuilder reasoningContent = new StringBuilder(); - private static StringBuilder finalContent = new StringBuilder(); - private static boolean isFirstPrint = true; + private boolean isFirstPrint; @Value("${dashscope.model}") private String serviceModel; @@ -67,11 +65,11 @@ public class DashscopeServiceImpl implements DashscopeService { Flowable result = conv.streamCall( param); - reasoningContent = new StringBuilder(); - finalContent = new StringBuilder(); + StringBuilder reasoningContent = new StringBuilder(); + StringBuilder finalContent = new StringBuilder(); isFirstPrint = true; - result.blockingForEach(DashscopeServiceImpl::handleGenerationResult); + result.blockingForEach(message -> handleGenerationResult(message, reasoningContent, finalContent)); return finalContent.toString().replaceAll("[\n\r\s]", ""); } catch (Exception e) { @@ -106,11 +104,11 @@ public class DashscopeServiceImpl implements DashscopeService { Flowable result = conv.streamCall( param); - reasoningContent = new StringBuilder(); - finalContent = new StringBuilder(); + StringBuilder reasoningContent = new StringBuilder(); + StringBuilder finalContent = new StringBuilder(); isFirstPrint = true; - result.blockingForEach(DashscopeServiceImpl::handleGenerationResult); + result.blockingForEach(message -> handleGenerationResult(message, reasoningContent, finalContent)); return finalContent.toString().replaceAll("[\n\r\s]", ""); } catch (Exception e) { @@ -120,30 +118,33 @@ public class DashscopeServiceImpl implements DashscopeService { } - private static void handleGenerationResult(MultiModalConversationResult message) { - + private void handleGenerationResult(MultiModalConversationResult message, StringBuilder reasoningContent, StringBuilder finalContent) { String re = message.getOutput().getChoices().get(0).getMessage().getReasoningContent(); - String reasoning = Objects.isNull(re) ? "" : re; // 默认值 + String reasoning = Objects.isNull(re) ? "" : re; List> content = message.getOutput().getChoices().get(0).getMessage() .getContent(); if (!reasoning.isEmpty()) { reasoningContent.append(reasoning); - if (isFirstPrint) { - System.out.println("====================思考过程===================="); - isFirstPrint = false; - } - System.out.print(reasoning); } if (Objects.nonNull(content) && !content.isEmpty()) { Object text = content.get(0).get("text"); finalContent.append(text); - if (!isFirstPrint) { - System.out.println("\n====================完整回复===================="); - isFirstPrint = true; + } + + // 检查是否是最后一个响应 + if (message.getOutput().getChoices().get(0).getFinishReason() != null) { + // 输出思考过程 + if (reasoningContent.length() > 0) { + System.out.println("====================思考过程===================="); + System.out.println(reasoningContent.toString()); + } + // 输出完整回复 + if (finalContent.length() > 0) { + System.out.println("====================完整回复===================="); + System.out.println(finalContent.toString()); } - System.out.print(text); } } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java index 0628ffde..730cfc5d 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/DealFileService.java @@ -129,6 +129,7 @@ public class DealFileService { .eq(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_20) .eq(KnowledgeAttach::getId, attachItem.getId())); } catch (Exception e) { + e.printStackTrace(); //设置处理失败 attachMapper.update(new LambdaUpdateWrapper() .set(KnowledgeAttach::getVectorStatus, DealStatus.STATUS_40)