diff --git a/README.md b/README.md index bb8c4859..04732f71 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,13 @@ ## ✨ 核心亮点 -| 模块 | 现有能力 | 扩展方向 | -|:---:|---|---| -| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成 | 自动模式、容错机制 | -| **知识库** | 本地RAG + 向量库(Milvus/Weaviate) + 知识图谱 + 文档解析 +重排序 | 音频视频解析、知识出处 | -| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态 | 工具插件市场、toolAgent自动加载工具 | -| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点 | 更多节点类型 | -| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型 | 智能体可配置 | -| **AI编程** | 智能代码分析、项目脚手架生成、Copilot助手 | 代码生成优化 | +| 模块 | 现有能力 | 扩展方向 | +|:----------:|---|------------------------| +| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成 | 自动模式、容错机制、计费管理 | +| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate) + 文档解析 | 多模态、知识出处、知识图谱、重排序 | +| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态 | 工具插件市场、 | +| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点 | 更多节点类型 | +| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型 | 智能体可配置 | ## 🚀 快速体验 diff --git a/pom.xml b/pom.xml index 8771fb18..434e5f6a 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,8 @@ 1.5.3 1.19.6 1.0.7 + + 1.62.2 1.27.1 @@ -129,6 +131,15 @@ import + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + cn.hutool @@ -352,24 +363,12 @@ ${revision} - - org.ruoyi - ruoyi-job - ${revision} - - org.ruoyi ruoyi-generator ${revision} - - org.ruoyi - ruoyi-demo - ${revision} - - org.ruoyi ruoyi-chat @@ -383,13 +382,6 @@ ${revision} - - - org.ruoyi - ruoyi-wechat - ${revision} - - org.ruoyi @@ -397,13 +389,6 @@ ${revision} - - - com.github.binarywang - weixin-java-cp - ${weixin-java-cp.version} - - com.fasterxml.jackson.dataformat diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 49750f04..35391480 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -70,23 +70,12 @@ ruoyi-system - - org.ruoyi - ruoyi-job - - org.ruoyi ruoyi-generator - - - org.ruoyi - ruoyi-demo - - org.ruoyi ruoyi-chat @@ -98,12 +87,6 @@ ruoyi-workflow - - - org.ruoyi - ruoyi-wechat - - org.ruoyi diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 5743c299..5f7179e2 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -125,9 +125,6 @@ security: - /*/api-docs/** - /warm-flow-ui/config - /workflow/run - - /user/qrcode - - /user/login/qrcode - - /weixin/check # 多租户配置 tenant: # 是否开启 @@ -206,6 +203,7 @@ springdoc: name: ageerle email: ageerle@163.com url: https://gitee.com/ageerle/ruoyi-ai + #这里定义了两个分组,可定义多个,也可以不定义 group-configs: - group: 1.演示模块 @@ -247,12 +245,6 @@ management: show-details: ALWAYS logfile: external-file: ./logs/sys-console.log - health: - # ⚠️ 禁用 Neo4j 健康检查 - # Spring Boot Actuator 会自动为 Neo4j 创建健康检查器 - # 这会导致应用在启动时尝试连接到 Neo4j - neo4j: - enabled: false --- # 默认/推荐使用sse推送 sse: @@ -285,7 +277,7 @@ warm-flow: vector-store: # 向量存储类型 可选(weaviate/milvus) # 如需修改向量库类型,请修改此配置值! - type: weaviate + type: milvus # Weaviate配置 weaviate: protocol: http @@ -295,81 +287,3 @@ vector-store: milvus: url: http://localhost:19530 collectionname: LocalKnowledge - -chat: - memory: - enabled: true - maxMessages: 20 - persistenceEnabled: true - - # 企业微信应用 -wechat: - cp: - corpId: - appConfigs: - - agentId: - secret: '' - token: '' - aesKey: '' - ---- # Neo4j 知识图谱配置 -neo4j: - uri: bolt://117.72.192.162:7687 - username: neo4j - password: MySecurePass123! - database: neo4j - max-connection-pool-size: 50 - connection-timeout-seconds: 30 - -# 知识图谱配置 -knowledge: - graph: - # 是否启用知识图谱功能 - enabled: false - # 图数据库类型: neo4j 或 apache-age - database-type: neo4j - # 是否自动创建索引 - auto-create-index: true - # 批量处理大小 - batch-size: 1000 - # 最大重试次数 - max-retry-count: 3 - - # 实体抽取配置 - extraction: - # 置信度阈值(低于此值的实体将被过滤) - confidence-threshold: 0.7 - # 最大实体数量(每个文档) - max-entities-per-doc: 100 - # 最大关系数量(每个文档) - max-relations-per-doc: 200 - # 文本分片大小(用于长文档) - chunk-size: 2000 - # 分片重叠大小 - chunk-overlap: 200 - - # 查询配置 - query: - # 默认查询限制数量 - default-limit: 100 - # 最大查询限制数量 - max-limit: 1000 - # 路径查询最大深度 - max-path-depth: 5 - # 查询超时时间(秒) - timeout-seconds: 30 - # 是否启用查询缓存 - cache-enabled: true - # 缓存过期时间(分钟) - cache-expire-minutes: 60 - ---- # MCP 模块配置 -app: - mcp: - client: - # 请求超时时间(秒) - request-timeout: 30 - # 连接超时时间(秒) - connection-timeout: 10 - # 最大重试次数 - max-retries: 3 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java index 2cbb051b..e5df36b0 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatMessageBo.java @@ -48,11 +48,6 @@ public class ChatMessageBo extends BaseEntity { */ private String role; - /** - * 扣除金额 - */ - private Long deductCost; - /** * 累计 Tokens */ @@ -63,11 +58,6 @@ public class ChatMessageBo extends BaseEntity { */ private String modelName; - /** - * 计费类型(1-token计费,2-次数计费) - */ - private String billingType; - /** * 备注 */ diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java index 4d97dd05..e8640887 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/bo/chat/ChatModelBo.java @@ -45,30 +45,15 @@ public class ChatModelBo extends BaseEntity { */ private String modelDescribe; - /** - * 模型价格 - */ - private Long modelPrice; - - /** - * 计费类型 - */ - private String modelType; - /** * 是否显示 */ private String modelShow; /** - * 是否免费 + * 向量维度 */ - private String modelFree; - - /** - * 模型优先级(值越大优先级越高) - */ - private Long priority; + private Integer modelDimension; /** * 请求地址 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java index 17f4d707..bb5a9339 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatMessageVo.java @@ -56,12 +56,6 @@ public class ChatMessageVo implements Serializable { @ExcelProperty(value = "对话角色") private String role; - /** - * 扣除金额 - */ - @ExcelProperty(value = "扣除金额") - private Long deductCost; - /** * 累计 Tokens */ @@ -74,12 +68,6 @@ public class ChatMessageVo implements Serializable { @ExcelProperty(value = "模型名称") private String modelName; - /** - * 计费类型(1-token计费,2-次数计费) - */ - @ExcelProperty(value = "计费类型", converter = ExcelDictConvert.class) - @ExcelDictFormat(readConverterExp = "1=-token计费,2-次数计费") - private String billingType; /** * 备注 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java index f79f9f23..6e0c2f4a 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/vo/chat/ChatModelVo.java @@ -54,17 +54,6 @@ public class ChatModelVo implements Serializable { @ExcelProperty(value = "模型描述") private String modelDescribe; - /** - * 模型价格 - */ - @ExcelProperty(value = "模型价格") - private Long modelPrice; - - /** - * 计费类型 - */ - @ExcelProperty(value = "计费类型") - private String modelType; /** * 是否显示 @@ -73,16 +62,10 @@ public class ChatModelVo implements Serializable { private String modelShow; /** - * 是否免费 + * 向量维度 */ - @ExcelProperty(value = "是否免费") - private String modelFree; - - /** - * 模型优先级(值越大优先级越高) - */ - @ExcelProperty(value = "模型优先级(值越大优先级越高)") - private Long priority; + @ExcelProperty(value = "向量维度") + private Integer modelDimension; /** * 请求地址 @@ -102,11 +85,5 @@ public class ChatModelVo implements Serializable { @ExcelProperty(value = "备注") private String remark; - /** - * 模型维度 - */ - private Integer dimension; - - } diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java index c0055bd4..89325dc2 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatMessage.java @@ -48,10 +48,6 @@ public class ChatMessage extends TenantEntity { */ private String role; - /** - * 扣除金额 - */ - private Long deductCost; /** * 累计 Tokens @@ -63,11 +59,6 @@ public class ChatMessage extends TenantEntity { */ private String modelName; - /** - * 计费类型(1-token计费,2-次数计费) - */ - private String billingType; - /** * 备注 */ diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java index 4fc63bfd..b4621de9 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatModel.java @@ -48,15 +48,6 @@ public class ChatModel extends TenantEntity { */ private String modelDescribe; - /** - * 模型价格 - */ - private Long modelPrice; - - /** - * 计费类型 - */ - private String modelType; /** * 是否显示 @@ -64,14 +55,9 @@ public class ChatModel extends TenantEntity { private String modelShow; /** - * 是否免费 + * 向量维度 */ - private String modelFree; - - /** - * 模型优先级(值越大优先级越高) - */ - private Long priority; + private Integer modelDimension; /** * 请求地址 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java index 687605eb..d9cffb57 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/service/chatMessage/AbstractChatMessageService.java @@ -41,7 +41,6 @@ public abstract class AbstractChatMessageService { messageBO.setContent(content); messageBO.setRole(role); messageBO.setModelName(chatRequest.getModel()); - messageBO.setBillingType(chatModelVo.getModelType()); messageBO.setRemark(null); chatMessageService.insertByBo(messageBO); diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index 5db99df1..dba13098 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -12,11 +12,8 @@ ruoyi-aiflow ruoyi-chat - ruoyi-demo ruoyi-generator - ruoyi-job ruoyi-system - ruoyi-wechat ruoyi-workflow diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphAsyncConfig.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphAsyncConfig.java deleted file mode 100644 index fe276efb..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphAsyncConfig.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.ruoyi.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadPoolExecutor; - -/** - * 图谱构建异步任务配置 - * - * @author ruoyi - * @date 2025-10-11 - */ -@Slf4j -@EnableAsync -@Configuration -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphAsyncConfig { - - /** - * 图谱构建专用线程池 - * 用于执行图谱构建任务,避免阻塞主线程池 - */ - @Bean("graphBuildExecutor") - public Executor graphBuildExecutor() { - log.info("初始化图谱构建线程池..."); - - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - - // 核心线程数:CPU核心数 - int processors = Runtime.getRuntime().availableProcessors(); - executor.setCorePoolSize(processors); - - // 最大线程数:CPU核心数 * 2 - executor.setMaxPoolSize(processors * 2); - - // 队列容量:100个任务 - executor.setQueueCapacity(100); - - // 线程空闲时间:60秒 - executor.setKeepAliveSeconds(60); - - // 线程名称前缀 - executor.setThreadNamePrefix("graph-build-"); - - // 拒绝策略:由调用线程处理 - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); - - // 等待所有任务完成后关闭线程池 - executor.setWaitForTasksToCompleteOnShutdown(true); - - // 等待时间:60秒 - executor.setAwaitTerminationSeconds(60); - - // 初始化 - executor.initialize(); - - log.info("图谱构建线程池初始化完成: corePoolSize={}, maxPoolSize={}, queueCapacity={}", - processors, processors * 2, 100); - - return executor; - } -} - diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphExtractPrompt.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphExtractPrompt.java deleted file mode 100644 index 0f8362ed..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphExtractPrompt.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.ruoyi.config; - - -import org.ruoyi.constant.GraphConstants; - -import java.util.Arrays; -import java.util.stream.Collectors; - -/** - * 图谱实体关系抽取提示词 - * 参考 Microsoft GraphRAG 项目 - * https://github.com/microsoft/graphrag/blob/main/graphrag/index/graph/extractors/graph/prompts.py - * - * @author ruoyi - * @date 2025-09-30 - */ -public class GraphExtractPrompt { - - /** - * 中文实体关系抽取提示词 - */ - public static final String GRAPH_EXTRACTION_PROMPT_CN = """ - -目标- - 给定一个可能与此活动相关的文本文档以及实体类型列表,从文本中识别出所有这些类型的实体以及识别出的实体之间的所有关系。 - - -步骤- - 1. 识别所有实体。对于每个识别出的实体,提取以下信息: - - entity_name:实体的名称,首字母大写 - - entity_type:以下类型之一:[{entity_types}] - - entity_description:实体的属性和活动的全面描述 - 将每个实体格式化为 ("entity"{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}) - - 2. 从步骤1中识别出的实体中,识别出所有明确相关的 (source_entity, target_entity) 对。 - 对于每对相关的实体,提取以下信息: - - source_entity:在步骤1中识别的源实体的名称 - - target_entity:在步骤1中识别的目标实体的名称 - - relationship_description:解释你认为源实体和目标实体之间相关的原因 - - relationship_strength:一个表示源实体和目标实体之间关系强度的数字分数(0-10) - 将每个关系格式化为 ("relationship"{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}{tuple_delimiter}) - - 3. 以英文返回输出,作为所有在步骤1和步骤2中识别的实体和关系的列表。使用 **{record_delimiter}** 作为列表分隔符。 - - 4. 完成时,输出 {completion_delimiter} - - ###################### - -示例- - ###################### - 示例 1: - Entity_types: ORGANIZATION,PERSON - 文本: - The Verdantis's Central Institution is scheduled to meet on Monday and Thursday, with the institution planning to release its latest policy decision on Thursday at 1:30 p.m. PDT, followed by a press conference where Central Institution Chair Martin Smith will take questions. Investors expect the Market Strategy Committee to hold its benchmark interest rate steady in a range of 3.5%-3.75%. - ###################### - 输出: - ("entity"{tuple_delimiter}CENTRAL INSTITUTION{tuple_delimiter}ORGANIZATION{tuple_delimiter}The Central Institution is the Federal Reserve of Verdantis, which is setting interest rates on Monday and Thursday) - {record_delimiter} - ("entity"{tuple_delimiter}MARTIN SMITH{tuple_delimiter}PERSON{tuple_delimiter}Martin Smith is the chair of the Central Institution) - {record_delimiter} - ("entity"{tuple_delimiter}MARKET STRATEGY COMMITTEE{tuple_delimiter}ORGANIZATION{tuple_delimiter}The Central Institution committee makes key decisions about interest rates and the growth of Verdantis's money supply) - {record_delimiter} - ("relationship"{tuple_delimiter}MARTIN SMITH{tuple_delimiter}CENTRAL INSTITUTION{tuple_delimiter}Martin Smith is the Chair of the Central Institution and will answer questions at a press conference{tuple_delimiter}9) - {completion_delimiter} - - ###################### - 示例 2: - Entity_types: ORGANIZATION - 文本: - TechGlobal's (TG) stock skyrocketed in its opening day on the Global Exchange Thursday. But IPO experts warn that the semiconductor corporation's debut on the public markets isn't indicative of how other newly listed companies may perform. - - TechGlobal, a formerly public company, was taken private by Vision Holdings in 2014. The well-established chip designer says it powers 85% of premium smartphones. - ###################### - 输出: - ("entity"{tuple_delimiter}TECHGLOBAL{tuple_delimiter}ORGANIZATION{tuple_delimiter}TechGlobal is a stock now listed on the Global Exchange which powers 85% of premium smartphones) - {record_delimiter} - ("entity"{tuple_delimiter}VISION HOLDINGS{tuple_delimiter}ORGANIZATION{tuple_delimiter}Vision Holdings is a firm that previously owned TechGlobal) - {record_delimiter} - ("relationship"{tuple_delimiter}TECHGLOBAL{tuple_delimiter}VISION HOLDINGS{tuple_delimiter}Vision Holdings formerly owned TechGlobal from 2014 until present{tuple_delimiter}5) - {completion_delimiter} - - ###################### - 示例 3: - Entity_types: ORGANIZATION,LOCATION,PERSON - 文本: - Five Aurelians jailed for 8 years in Firuzabad and widely regarded as hostages are on their way home to Aurelia. - - The swap orchestrated by Quintara was finalized when $8bn of Firuzi funds were transferred to financial institutions in Krohaara, the capital of Quintara. - - The exchange initiated in Firuzabad's capital, Tiruzia, led to the four men and one woman, who are also Firuzi nationals, boarding a chartered flight to Krohaara. - - They were welcomed by senior Aurelian officials and are now on their way to Aurelia's capital, Cashion. - - The Aurelians include 39-year-old businessman Samuel Namara, who has been held in Tiruzia's Alhamia Prison, as well as journalist Durke Bataglani, 59, and environmentalist Meggie Tazbah, 53, who also holds Bratinas nationality. - ###################### - 输出: - ("entity"{tuple_delimiter}FIRUZABAD{tuple_delimiter}LOCATION{tuple_delimiter}Firuzabad held Aurelians as hostages) - {record_delimiter} - ("entity"{tuple_delimiter}AURELIA{tuple_delimiter}LOCATION{tuple_delimiter}Country seeking to release hostages) - {record_delimiter} - ("entity"{tuple_delimiter}QUINTARA{tuple_delimiter}LOCATION{tuple_delimiter}Country that negotiated a swap of money in exchange for hostages) - {record_delimiter} - ("entity"{tuple_delimiter}TIRUZIA{tuple_delimiter}LOCATION{tuple_delimiter}Capital of Firuzabad where the Aurelians were being held) - {record_delimiter} - ("entity"{tuple_delimiter}KROHAARA{tuple_delimiter}LOCATION{tuple_delimiter}Capital city in Quintara) - {record_delimiter} - ("entity"{tuple_delimiter}CASHION{tuple_delimiter}LOCATION{tuple_delimiter}Capital city in Aurelia) - {record_delimiter} - ("entity"{tuple_delimiter}SAMUEL NAMARA{tuple_delimiter}PERSON{tuple_delimiter}Aurelian who spent time in Tiruzia's Alhamia Prison) - {record_delimiter} - ("entity"{tuple_delimiter}ALHAMIA PRISON{tuple_delimiter}LOCATION{tuple_delimiter}Prison in Tiruzia) - {record_delimiter} - ("entity"{tuple_delimiter}DURKE BATAGLANI{tuple_delimiter}PERSON{tuple_delimiter}Aurelian journalist who was held hostage) - {record_delimiter} - ("entity"{tuple_delimiter}MEGGIE TAZBAH{tuple_delimiter}PERSON{tuple_delimiter}Bratinas national and environmentalist who was held hostage) - {record_delimiter} - ("relationship"{tuple_delimiter}FIRUZABAD{tuple_delimiter}AURELIA{tuple_delimiter}Firuzabad negotiated a hostage exchange with Aurelia{tuple_delimiter}2) - {record_delimiter} - ("relationship"{tuple_delimiter}QUINTARA{tuple_delimiter}AURELIA{tuple_delimiter}Quintara brokered the hostage exchange between Firuzabad and Aurelia{tuple_delimiter}2) - {record_delimiter} - ("relationship"{tuple_delimiter}SAMUEL NAMARA{tuple_delimiter}ALHAMIA PRISON{tuple_delimiter}Samuel Namara was a prisoner at Alhamia prison{tuple_delimiter}8) - {record_delimiter} - ("relationship"{tuple_delimiter}SAMUEL NAMARA{tuple_delimiter}MEGGIE TAZBAH{tuple_delimiter}Samuel Namara and Meggie Tazbah were exchanged in the same hostage release{tuple_delimiter}2) - {completion_delimiter} - - ###################### - -真实数据- - ###################### - Entity_types: {entity_types} - 文本: {input_text} - ###################### - 输出: - """.replace("{tuple_delimiter}", GraphConstants.GRAPH_TUPLE_DELIMITER) - .replace("{entity_types}", Arrays.stream(GraphConstants.DEFAULT_ENTITY_TYPES).collect(Collectors.joining(","))) - .replace("{completion_delimiter}", GraphConstants.GRAPH_COMPLETION_DELIMITER) - .replace("{record_delimiter}", GraphConstants.GRAPH_RECORD_DELIMITER); - - /** - * 继续抽取提示词(当第一次抽取遗漏实体时使用) - */ - public static final String CONTINUE_PROMPT = """ - 在上一次抽取中遗漏了许多实体和关系。 - 请记住只提取与之前提取的类型匹配的实体。 - 使用相同的格式在下面添加它们: - """; - - /** - * 循环检查提示词 - */ - public static final String LOOP_PROMPT = """ - 似乎仍然可能遗漏了一些实体和关系。 - 如果还有需要添加的实体或关系,请回答 YES | NO。 - """; - - /** - * 生成提取提示词 - * - * @param inputText 输入文本 - * @return 完整的提示词 - */ - public static String buildExtractionPrompt(String inputText) { - return GRAPH_EXTRACTION_PROMPT_CN.replace("{input_text}", inputText); - } - - /** - * 生成提取提示词(自定义实体类型) - * - * @param inputText 输入文本 - * @param entityTypes 实体类型列表 - * @return 完整的提示词 - */ - public static String buildExtractionPrompt(String inputText, String[] entityTypes) { - String entityTypesStr = Arrays.stream(entityTypes).collect(Collectors.joining(",")); - return GRAPH_EXTRACTION_PROMPT_CN - .replace("{input_text}", inputText) - .replace(Arrays.stream(GraphConstants.DEFAULT_ENTITY_TYPES).collect(Collectors.joining(",")), entityTypesStr); - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphProperties.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphProperties.java deleted file mode 100644 index cb574ffb..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/GraphProperties.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.ruoyi.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 知识图谱配置属性 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@Component -@ConfigurationProperties(prefix = "knowledge.graph") -public class GraphProperties { - - /** - * 是否启用知识图谱功能 - */ - private Boolean enabled = false; - - /** - * 图数据库类型: neo4j 或 apache-age - */ - private String databaseType = "neo4j"; - - /** - * 是否自动创建索引 - */ - private Boolean autoCreateIndex = true; - - /** - * 批量处理大小 - */ - private Integer batchSize = 1000; - - /** - * 最大重试次数 - */ - private Integer maxRetryCount = 3; - - /** - * 实体抽取配置 - */ - private ExtractionConfig extraction = new ExtractionConfig(); - - /** - * 查询配置 - */ - private QueryConfig query = new QueryConfig(); - - @Data - public static class ExtractionConfig { - /** - * 置信度阈值(低于此值的实体将被过滤) - */ - private Double confidenceThreshold = 0.7; - - /** - * 最大实体数量(每个文档) - */ - private Integer maxEntitiesPerDoc = 100; - - /** - * 最大关系数量(每个文档) - */ - private Integer maxRelationsPerDoc = 200; - - /** - * 文本分片大小(用于长文档) - */ - private Integer chunkSize = 2000; - - /** - * 分片重叠大小 - */ - private Integer chunkOverlap = 200; - } - - @Data - public static class QueryConfig { - /** - * 默认查询限制数量 - */ - private Integer defaultLimit = 100; - - /** - * 最大查询限制数量 - */ - private Integer maxLimit = 1000; - - /** - * 路径查询最大深度 - */ - private Integer maxPathDepth = 5; - - /** - * 查询超时时间(秒) - */ - private Integer timeoutSeconds = 30; - - /** - * 是否启用查询缓存 - */ - private Boolean cacheEnabled = true; - - /** - * 缓存过期时间(分钟) - */ - private Integer cacheExpireMinutes = 60; - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/Neo4jConfig.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/Neo4jConfig.java deleted file mode 100644 index f5aec04e..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/Neo4jConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.ruoyi.config; - -import lombok.extern.slf4j.Slf4j; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Neo4j配置类 - - */ -@Slf4j -@Configuration -@EnableConfigurationProperties(Neo4jProperties.class) -public class Neo4jConfig { - - public Neo4jConfig() { - log.warn("========== Neo4jConfig 已激活 =========="); - log.warn("知识图谱功能(Neo4j): 已启用"); - log.warn("========================================"); - } - - /** - * 创建Neo4j Driver Bean - * - * @param neo4jProperties Neo4j 配置属性 - * @return Neo4j Driver - */ - @Bean - public Driver neo4jDriver(Neo4jProperties neo4jProperties) { - log.info("========== 正在初始化 Neo4j Driver =========="); - log.info("Neo4j 连接地址: {}", neo4jProperties.getUri()); - log.info("Neo4j 用户名: {}", neo4jProperties.getUsername()); - log.info("Neo4j 数据库: {}", neo4jProperties.getDatabase()); - log.info("最大连接池大小: {}", neo4jProperties.getMaxConnectionPoolSize()); - log.info("连接超时时间: {} 秒", neo4jProperties.getConnectionTimeoutSeconds()); - - try { - Driver driver = GraphDatabase.driver( - neo4jProperties.getUri(), - AuthTokens.basic(neo4jProperties.getUsername(), neo4jProperties.getPassword()), - org.neo4j.driver.Config.builder() - .withMaxConnectionPoolSize(neo4jProperties.getMaxConnectionPoolSize()) - .withConnectionTimeout(neo4jProperties.getConnectionTimeoutSeconds(), java.util.concurrent.TimeUnit.SECONDS) - .build() - ); - log.info("========== Neo4j Driver 初始化完成 =========="); - return driver; - } catch (Exception e) { - log.error("Neo4j Driver 初始化失败", e); - throw new RuntimeException("Failed to initialize Neo4j Driver", e); - } - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/Neo4jProperties.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/Neo4jProperties.java deleted file mode 100644 index a68b265f..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/config/Neo4jProperties.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.ruoyi.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -/** - * Neo4j 配置属性 - * - * 分离 @ConfigurationProperties 避免与 @ConditionalOnProperty 冲突 - * 不使用 @Component,而是在 Neo4jConfig 中通过 @EnableConfigurationProperties 启用 - * 参考: https://github.com/spring-projects/spring-boot/issues/26251 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@ConfigurationProperties(prefix = "neo4j") -public class Neo4jProperties { - - /** - * Neo4j连接URI - * 例如: bolt://localhost:7687 - */ - private String uri; - - /** - * 用户名 - */ - private String username; - - /** - * 密码 - */ - private String password; - - /** - * 数据库名称(Neo4j 4.0+支持多数据库) - * 默认: neo4j - */ - private String database = "neo4j"; - - /** - * 最大连接池大小 - */ - private Integer maxConnectionPoolSize = 50; - - /** - * 连接超时时间(秒) - */ - private Integer connectionTimeoutSeconds = 30; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/constant/GraphConstants.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/constant/GraphConstants.java deleted file mode 100644 index 40fb36f3..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/constant/GraphConstants.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.ruoyi.constant; - -/** - * 知识图谱常量 - * - * @author ruoyi - * @date 2025-09-30 - */ -public class GraphConstants { - - /** - * 图谱记录分隔符 - */ - public static final String GRAPH_RECORD_DELIMITER = "##"; - - /** - * 图谱元组分隔符 - */ - public static final String GRAPH_TUPLE_DELIMITER = "<|>"; - - /** - * 图谱完成标记 - */ - public static final String GRAPH_COMPLETION_DELIMITER = "<|COMPLETE|>"; - - /** - * 实体类型:人物 - */ - public static final String ENTITY_TYPE_PERSON = "PERSON"; - - /** - * 实体类型:组织机构 - */ - public static final String ENTITY_TYPE_ORGANIZATION = "ORGANIZATION"; - - /** - * 实体类型:地点 - */ - public static final String ENTITY_TYPE_LOCATION = "LOCATION"; - - /** - * 实体类型:概念 - */ - public static final String ENTITY_TYPE_CONCEPT = "CONCEPT"; - - /** - * 实体类型:事件 - */ - public static final String ENTITY_TYPE_EVENT = "EVENT"; - - /** - * 实体类型:产品 - */ - public static final String ENTITY_TYPE_PRODUCT = "PRODUCT"; - - /** - * 实体类型:技术 - */ - public static final String ENTITY_TYPE_TECHNOLOGY = "TECHNOLOGY"; - - /** - * 默认实体抽取类型列表 - */ - public static final String[] DEFAULT_ENTITY_TYPES = { - ENTITY_TYPE_PERSON, - ENTITY_TYPE_ORGANIZATION, - ENTITY_TYPE_LOCATION, - ENTITY_TYPE_CONCEPT, - ENTITY_TYPE_EVENT, - ENTITY_TYPE_PRODUCT, - ENTITY_TYPE_TECHNOLOGY - }; - - /** - * 元数据键:知识库UUID - */ - public static final String METADATA_KB_UUID = "kb_uuid"; - - /** - * 元数据键:知识库条目UUID - */ - public static final String METADATA_KB_ITEM_UUID = "kb_item_uuid"; - - /** - * 元数据键:文档UUID - */ - public static final String METADATA_DOC_UUID = "doc_uuid"; - - /** - * 元数据键:片段UUID - */ - public static final String METADATA_SEGMENT_UUID = "segment_uuid"; - - /** - * RAG最大片段大小(token数) - */ - public static final int RAG_MAX_SEGMENT_SIZE_IN_TOKENS = 512; - - /** - * RAG片段重叠大小(token数) - */ - public static final int RAG_SEGMENT_OVERLAP_IN_TOKENS = 50; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/GraphInstanceController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/GraphInstanceController.java deleted file mode 100644 index 6c1f4852..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/GraphInstanceController.java +++ /dev/null @@ -1,418 +0,0 @@ -package org.ruoyi.controller.graph; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.domain.bo.graph.GraphBuildTask; -import org.ruoyi.domain.bo.graph.GraphInstance; -import org.ruoyi.enums.GraphStatusEnum; -import org.ruoyi.service.graph.IGraphBuildTaskService; -import org.ruoyi.service.graph.IGraphInstanceService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 图谱实例管理控制器 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/graph/instance") -@Tag(name = "图谱实例管理", description = "知识图谱实例的创建、查询、更新、删除") -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphInstanceController extends BaseController { - - private final IGraphInstanceService graphInstanceService; - private final IGraphBuildTaskService buildTaskService; - - /** - * 辅助方法:根据ID或UUID获取图谱实例 - */ - private GraphInstance getInstanceByIdOrUuid(String id) { - GraphInstance instance = null; - - // 尝试作为数字ID查询 - try { - Long numericId = Long.parseLong(id); - instance = graphInstanceService.getById(numericId); - } catch (NumberFormatException e) { - // 不是数字,尝试作为UUID查询 - instance = graphInstanceService.getByUuid(id); - } - - return instance; - } - - /** - * 创建图谱实例 - */ - @Operation(summary = "创建图谱实例") - @PostMapping - public R createInstance(@RequestBody GraphInstance graphInstance) { - try { - if (graphInstance.getKnowledgeId() == null || graphInstance.getKnowledgeId().trim().isEmpty()) { - return R.fail("知识库ID不能为空"); - } - if (graphInstance.getInstanceName() == null || graphInstance.getInstanceName().trim().isEmpty()) { - return R.fail("图谱名称不能为空"); - } - - // 创建基础实例 - GraphInstance instance = graphInstanceService.createInstance( - graphInstance.getKnowledgeId(), - graphInstance.getInstanceName(), - graphInstance.getConfig() - ); - - // 设置扩展属性 - boolean needUpdate = false; - if (graphInstance.getModelName() != null) { - instance.setModelName(graphInstance.getModelName()); - needUpdate = true; - } - if (graphInstance.getEntityTypes() != null) { - instance.setEntityTypes(graphInstance.getEntityTypes()); - needUpdate = true; - } - if (graphInstance.getRelationTypes() != null) { - instance.setRelationTypes(graphInstance.getRelationTypes()); - needUpdate = true; - } - if (graphInstance.getRemark() != null) { - instance.setRemark(graphInstance.getRemark()); - needUpdate = true; - } - - // 如果有扩展属性,更新到数据库 - if (needUpdate) { - graphInstanceService.updateInstance(instance); - } - - return R.ok(instance); - } catch (Exception e) { - return R.fail("创建图谱实例失败: " + e.getMessage()); - } - } - - /** - * 更新图谱实例 - */ - @Operation(summary = "更新图谱实例") - @PutMapping - public R updateInstance(@RequestBody GraphInstance graphInstance) { - try { - if (graphInstance.getId() == null && (graphInstance.getGraphUuid() == null || graphInstance.getGraphUuid().trim().isEmpty())) { - return R.fail("图谱ID不能为空"); - } - - // 如果有 instanceName,更新基本信息 - if (graphInstance.getInstanceName() != null) { - // 这里可以添加更新实例名称的逻辑 - } - - // 更新配置 - if (graphInstance.getConfig() != null) { - graphInstanceService.updateConfig(graphInstance.getGraphUuid(), graphInstance.getConfig()); - } - - // 更新模型名称、实体类型、关系类型等 - // 注意:这里需要在 Service 层实现完整的更新逻辑 - - GraphInstance instance = graphInstanceService.getByUuid(graphInstance.getGraphUuid()); - return R.ok(instance); - } catch (Exception e) { - return R.fail("更新图谱实例失败: " + e.getMessage()); - } - } - - /** - * 根据ID或UUID获取图谱实例 - */ - @Operation(summary = "获取图谱实例") - @GetMapping("/{id}") - public R getByUuid(@PathVariable String id) { - try { - GraphInstance instance = getInstanceByIdOrUuid(id); - - if (instance == null) { - return R.fail("图谱实例不存在"); - } - return R.ok(instance); - } catch (Exception e) { - return R.fail("获取图谱实例失败: " + e.getMessage()); - } - } - - /** - * 获取图谱实例列表(支持分页和条件查询) - */ - @Operation(summary = "获取图谱实例列表") - @GetMapping("/list") - public R> list( - @RequestParam(required = false) String instanceName, - @RequestParam(required = false) String knowledgeId, - @RequestParam(required = false) String status, - @RequestParam(required = false, defaultValue = "1") Integer pageNum, - @RequestParam(required = false, defaultValue = "10") Integer pageSize) { - try { - // 使用枚举转换前端状态字符串为数字状态码 - Integer graphStatus = GraphStatusEnum.getCodeByStatusKey(status); - - // 创建分页对象 - Page page = new Page<>(pageNum, pageSize); - - // 调用 Service 层分页查询 - Page result = graphInstanceService.queryPage(page, instanceName, knowledgeId, graphStatus); - - // 构造返回结果 - Map data = new HashMap<>(); - data.put("rows", result.getRecords()); - data.put("total", result.getTotal()); - - return R.ok(data); - } catch (Exception e) { - return R.fail("获取图谱列表失败: " + e.getMessage()); - } - } - - /** - * 根据知识库ID获取图谱列表 - */ - @Operation(summary = "获取知识库的图谱列表") - @GetMapping("/knowledge/{knowledgeId}") - public R> listByKnowledge(@PathVariable String knowledgeId) { - try { - List instances = graphInstanceService.listByKnowledgeId(knowledgeId); - return R.ok(instances); - } catch (Exception e) { - return R.fail("获取图谱列表失败: " + e.getMessage()); - } - } - - /** - * 更新图谱状态 - */ - @Operation(summary = "更新图谱状态") - @PutMapping("/status/{graphUuid}") - public R updateStatus( - @PathVariable String graphUuid, - @RequestParam Integer status) { - try { - boolean success = graphInstanceService.updateStatus(graphUuid, status); - return success ? R.ok() : R.fail("更新状态失败"); - } catch (Exception e) { - return R.fail("更新状态失败: " + e.getMessage()); - } - } - - /** - * 更新图谱统计信息 - */ - @Operation(summary = "更新图谱统计") - @PutMapping("/counts/{graphUuid}") - public R updateCounts( - @PathVariable String graphUuid, - @RequestParam Integer nodeCount, - @RequestParam Integer relationshipCount) { - try { - boolean success = graphInstanceService.updateCounts(graphUuid, nodeCount, relationshipCount); - return success ? R.ok() : R.fail("更新统计失败"); - } catch (Exception e) { - return R.fail("更新统计失败: " + e.getMessage()); - } - } - - /** - * 更新图谱配置 - */ - @Operation(summary = "更新图谱配置") - @PutMapping("/config/{graphUuid}") - public R updateConfig( - @PathVariable String graphUuid, - @RequestBody Map request) { - try { - String config = request.get("config"); - boolean success = graphInstanceService.updateConfig(graphUuid, config); - return success ? R.ok() : R.fail("更新配置失败"); - } catch (Exception e) { - return R.fail("更新配置失败: " + e.getMessage()); - } - } - - /** - * 删除图谱实例(软删除) - */ - @Operation(summary = "删除图谱实例") - @DeleteMapping("/{id}") - public R deleteInstance(@PathVariable String id) { - try { - // 获取图谱实例 - GraphInstance instance = getInstanceByIdOrUuid(id); - if (instance == null) { - return R.fail("图谱实例不存在"); - } - - boolean success = graphInstanceService.deleteInstance(instance.getGraphUuid()); - return success ? R.ok() : R.fail("删除失败"); - } catch (Exception e) { - return R.fail("删除图谱实例失败: " + e.getMessage()); - } - } - - /** - * 物理删除图谱实例及其数据 - */ - @Operation(summary = "彻底删除图谱") - @DeleteMapping("/permanent/{graphUuid}") - public R deleteInstanceAndData(@PathVariable String graphUuid) { - try { - boolean success = graphInstanceService.deleteInstanceAndData(graphUuid); - return success ? R.ok() : R.fail("删除失败"); - } catch (Exception e) { - return R.fail("彻底删除图谱失败: " + e.getMessage()); - } - } - - /** - * 获取图谱统计信息 - */ - @Operation(summary = "获取图谱统计") - @GetMapping("/stats/{graphUuid}") - public R> getStatistics(@PathVariable String graphUuid) { - try { - Map stats = graphInstanceService.getStatistics(graphUuid); - return R.ok(stats); - } catch (Exception e) { - return R.fail("获取统计信息失败: " + e.getMessage()); - } - } - - /** - * 构建图谱(全量构建知识库) - */ - @Operation(summary = "构建图谱") - @PostMapping("/build/{id}") - public R buildGraph(@PathVariable String id) { - try { - // 获取图谱实例 - GraphInstance instance = getInstanceByIdOrUuid(id); - - if (instance == null) { - return R.fail("图谱实例不存在"); - } - - // 更新状态为构建中 - graphInstanceService.updateStatus(instance.getGraphUuid(), 10); // 10=构建中 - - // 创建构建任务(全量构建) - GraphBuildTask task = buildTaskService.createTask( - instance.getGraphUuid(), - instance.getKnowledgeId(), - null, // docId=null 表示全量构建 - 1 // taskType=1 全量构建 - ); - - // 异步启动任务 - buildTaskService.startTask(task.getTaskUuid()); - - return R.ok(task); - } catch (Exception e) { - return R.fail("启动构建任务失败: " + e.getMessage()); - } - } - - /** - * 重建图谱(清空后重新构建) - */ - @Operation(summary = "重建图谱") - @PostMapping("/rebuild/{id}") - public R rebuildGraph(@PathVariable String id) { - try { - // 获取图谱实例 - GraphInstance instance = getInstanceByIdOrUuid(id); - - if (instance == null) { - return R.fail("图谱实例不存在"); - } - - // 更新状态为构建中 - graphInstanceService.updateStatus(instance.getGraphUuid(), 10); // 10=构建中 - - // 创建重建任务 - GraphBuildTask task = buildTaskService.createTask( - instance.getGraphUuid(), - instance.getKnowledgeId(), - null, // docId=null 表示全量 - 2 // taskType=2 重建 - ); - - // 异步启动任务 - buildTaskService.startTask(task.getTaskUuid()); - - return R.ok(task); - } catch (Exception e) { - return R.fail("启动重建任务失败: " + e.getMessage()); - } - } - - /** - * 获取构建状态 - */ - @Operation(summary = "获取构建状态") - @GetMapping("/status/{id}") - public R> getBuildStatus(@PathVariable String id) { - try { - // 获取图谱实例 - GraphInstance instance = getInstanceByIdOrUuid(id); - - if (instance == null) { - return R.fail("图谱实例不存在"); - } - - // 获取最新的构建任务 - GraphBuildTask latestTask = buildTaskService.getLatestTask(instance.getGraphUuid()); - - Map result = new HashMap<>(); - result.put("graphStatus", instance.getGraphStatus()); - result.put("nodeCount", instance.getNodeCount()); - result.put("relationshipCount", instance.getRelationshipCount()); - - if (latestTask != null) { - result.put("taskStatus", latestTask.getTaskStatus()); - // ⭐ 确保 progress 不为 null,前端期望是 number 类型 - Integer progress = latestTask.getProgress(); - result.put("progress", progress != null ? progress : 0); - result.put("errorMessage", latestTask.getErrorMessage()); - - // 转换状态字符串(兼容前端) - String status = "NOT_BUILT"; - if (instance.getGraphStatus() == 10) status = "BUILDING"; - else if (instance.getGraphStatus() == 20) status = "COMPLETED"; - else if (instance.getGraphStatus() == 30) status = "FAILED"; - - result.put("status", status); - } else { - // ⭐ 如果没有任务,也返回默认值 - result.put("taskStatus", null); - result.put("progress", 0); - result.put("errorMessage", null); - result.put("status", "NOT_BUILT"); - } - - return R.ok(result); - } catch (Exception e) { - return R.fail("获取构建状态失败: " + e.getMessage()); - } - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/GraphQueryController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/GraphQueryController.java deleted file mode 100644 index f691b78b..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/GraphQueryController.java +++ /dev/null @@ -1,228 +0,0 @@ -package org.ruoyi.controller.graph; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.web.core.BaseController; - -import org.ruoyi.domain.bo.graph.GraphEdge; -import org.ruoyi.domain.bo.graph.GraphVertex; -import org.ruoyi.domain.dto.GraphExtractionResult; -import org.ruoyi.service.graph.IGraphExtractionService; -import org.ruoyi.service.graph.IGraphRAGService; -import org.ruoyi.service.graph.IGraphStoreService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 图谱查询控制器 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/graph/query") -@Tag(name = "图谱查询", description = "知识图谱查询相关接口") -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphQueryController extends BaseController { - - private final IGraphStoreService graphStoreService; - private final IGraphExtractionService graphExtractionService; - private final IGraphRAGService graphRAGService; - - /** - * 获取知识库的图谱数据 - */ - @Operation(summary = "获取知识库图谱") - @GetMapping("/knowledge/{knowledgeId}") - public R> getGraphByKnowledge( - @PathVariable String knowledgeId, - @RequestParam(defaultValue = "100") Integer limit) { - - try { - // 查询节点 - List vertices = graphStoreService.queryVerticesByKnowledgeId(knowledgeId, limit); - - // 查询关系 - List edges = graphStoreService.queryEdgesByKnowledgeId(knowledgeId, limit); - - Map result = new HashMap<>(); - result.put("vertices", vertices); - result.put("edges", edges); - result.put("vertexCount", vertices.size()); - result.put("edgeCount", edges.size()); - - return R.ok(result); - } catch (Exception e) { - return R.fail("获取图谱数据失败: " + e.getMessage()); - } - } - - /** - * 搜索实体节点 - */ - @Operation(summary = "搜索实体") - @GetMapping("/search/entity") - public R> searchEntity( - @RequestParam String keyword, - @RequestParam(required = false) String knowledgeId, - @RequestParam(defaultValue = "20") Integer limit) { - - try { - List vertices = graphStoreService.searchVerticesByName(keyword, knowledgeId, limit); - return R.ok(vertices); - } catch (Exception e) { - return R.fail("搜索实体失败: " + e.getMessage()); - } - } - - /** - * 查询实体的邻居节点 - */ - @Operation(summary = "查询邻居节点") - @GetMapping("/neighbors/{nodeId}") - public R> getNeighbors( - @PathVariable String nodeId, - @RequestParam(required = false) String knowledgeId, - @RequestParam(defaultValue = "20") Integer limit) { - - try { - List neighbors = graphStoreService.getNeighbors(nodeId, knowledgeId, limit); - return R.ok(neighbors); - } catch (Exception e) { - return R.fail("查询邻居节点失败: " + e.getMessage()); - } - } - - /** - * 查询两个实体之间的路径 - */ - @Operation(summary = "查询实体路径") - @GetMapping("/path") - public R>> findPath( - @RequestParam String startNodeId, - @RequestParam String endNodeId, - @RequestParam(defaultValue = "5") Integer maxDepth) { - - try { - List> paths = graphStoreService.findPaths(startNodeId, endNodeId, maxDepth); - return R.ok(paths); - } catch (Exception e) { - return R.fail("查询路径失败: " + e.getMessage()); - } - } - - /** - * 从文本抽取实体和关系(测试用) - */ - @Operation(summary = "文本实体抽取") - @PostMapping("/extract") - public R extractFromText(@RequestBody Map request) { - try { - String text = request.get("text"); - if (text == null || text.trim().isEmpty()) { - return R.fail("文本不能为空"); - } - - String modelName = request.get("modelName"); - GraphExtractionResult result; - - if (modelName != null && !modelName.trim().isEmpty()) { - result = graphExtractionService.extractFromTextWithModel(text, modelName); - } else { - result = graphExtractionService.extractFromText(text); - } - - return R.ok(result); - } catch (Exception e) { - return R.fail("实体抽取失败: " + e.getMessage()); - } - } - - /** - * 将文本入库到图谱 - */ - @Operation(summary = "文本入库") - @PostMapping("/ingest") - public R ingestText(@RequestBody Map request) { - try { - String text = (String) request.get("text"); - String knowledgeId = (String) request.get("knowledgeId"); - String modelName = (String) request.get("modelName"); - - if (text == null || text.trim().isEmpty()) { - return R.fail("文本不能为空"); - } - if (knowledgeId == null || knowledgeId.trim().isEmpty()) { - return R.fail("知识库ID不能为空"); - } - - @SuppressWarnings("unchecked") - Map metadata = (Map) request.get("metadata"); - - GraphExtractionResult result; - if (modelName != null && !modelName.trim().isEmpty()) { - result = graphRAGService.ingestTextWithModel(text, knowledgeId, metadata, modelName); - } else { - result = graphRAGService.ingestText(text, knowledgeId, metadata); - } - return R.ok(result); - } catch (Exception e) { - return R.fail("文本入库失败: " + e.getMessage()); - } - } - - /** - * 基于图谱检索 - */ - @Operation(summary = "图谱检索") - @GetMapping("/retrieve") - public R retrieveFromGraph( - @RequestParam String query, - @RequestParam String knowledgeId, - @RequestParam(defaultValue = "10") Integer maxResults) { - - try { - String result = graphRAGService.retrieveFromGraph(query, knowledgeId, maxResults); - return R.ok(result); - } catch (Exception e) { - return R.fail("图谱检索失败: " + e.getMessage()); - } - } - - /** - * 删除知识库的图谱数据 - */ - @Operation(summary = "删除图谱数据") - @DeleteMapping("/knowledge/{knowledgeId}") - public R deleteGraphData(@PathVariable String knowledgeId) { - try { - boolean success = graphRAGService.deleteGraphData(knowledgeId); - return success ? R.ok() : R.fail("删除失败"); - } catch (Exception e) { - return R.fail("删除图谱数据失败: " + e.getMessage()); - } - } - - /** - * 获取图谱统计信息 - */ - @Operation(summary = "图谱统计") - @GetMapping("/stats/{knowledgeId}") - public R> getGraphStats(@PathVariable String knowledgeId) { - try { - Map stats = graphStoreService.getStatistics(knowledgeId); - return R.ok(stats); - } catch (Exception e) { - return R.fail("获取统计信息失败: " + e.getMessage()); - } - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/Neo4jTestController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/Neo4jTestController.java deleted file mode 100644 index 70e03078..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/graph/Neo4jTestController.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.ruoyi.controller.graph; - -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.config.GraphProperties; -import org.ruoyi.config.Neo4jProperties; -import org.ruoyi.util.Neo4jTestUtil; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.Map; - -/** - * Neo4j测试控制器 - * 仅用于开发环境测试Neo4j连接 - * - * @author ruoyi - * @date 2025-09-30 - */ -@RestController -@RequestMapping("/graph/test") -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class Neo4jTestController { - - private final Neo4jTestUtil neo4jTestUtil; - private final Neo4jProperties neo4jProperties; - private final GraphProperties graphProperties; - - - @GetMapping("/connection") - public R> testConnection() { - Map result = neo4jTestUtil.testConnection(); - - if ("SUCCESS".equals(result.get("connection"))) { - return R.ok("Neo4j连接成功", result); - } else { - return R.fail(result.get("error").toString()); - } - } - - - @GetMapping("/config") - public R> getConfig() { - Map config = new HashMap<>(); - config.put("neo4j", Map.of( - "uri", neo4jProperties.getUri(), - "username", neo4jProperties.getUsername(), - "database", neo4jProperties.getDatabase(), - "maxConnectionPoolSize", neo4jProperties.getMaxConnectionPoolSize() - )); - config.put("graph", Map.of( - "enabled", graphProperties.getEnabled(), - "databaseType", graphProperties.getDatabaseType(), - "batchSize", graphProperties.getBatchSize(), - "extraction", graphProperties.getExtraction(), - "query", graphProperties.getQuery() - )); - return R.ok(config); - } - - - @PostMapping("/node") - public R> createTestNode(@RequestParam String name) { - Map result = neo4jTestUtil.createTestNode(name); - - if (Boolean.TRUE.equals(result.get("success"))) { - return R.ok("测试节点创建成功", result); - } else { - return R.fail(result.get("error").toString()); - } - } - - - @GetMapping("/node/{name}") - public R> queryTestNode(@PathVariable String name) { - Map result = neo4jTestUtil.queryTestNode(name); - - if (Boolean.TRUE.equals(result.get("found"))) { - return R.ok("节点查询成功", result); - } else { - return R.ok("未找到节点", result); - } - } - - - @PostMapping("/relationship") - public R> createTestRelationship( - @RequestParam String source, - @RequestParam String target) { - - Map result = neo4jTestUtil.createTestRelationship(source, target); - - if (Boolean.TRUE.equals(result.get("success"))) { - return R.ok("测试关系创建成功", result); - } else { - return R.fail(result.get("error").toString()); - } - } - - - @DeleteMapping("/nodes") - public R> deleteAllTestNodes() { - Map result = neo4jTestUtil.deleteAllTestNodes(); - - if (Boolean.TRUE.equals(result.get("success"))) { - return R.ok("测试节点清理完成", result); - } else { - return R.fail(result.get("error").toString()); - } - } - - - @GetMapping("/statistics") - public R> getStatistics() { - Map result = neo4jTestUtil.getStatistics(); - - if (result.containsKey("error")) { - return R.fail(result.get("error").toString()); - } - return R.ok(result); - } - - - @GetMapping("/health") - public R health() { - Map result = neo4jTestUtil.testConnection(); - - if ("SUCCESS".equals(result.get("connection"))) { - return R.ok("Neo4j服务正常"); - } else { - return R.fail("Neo4j服务异常: " + result.get("error")); - } - } - - /** - * 调试关系查询 - 查看指定知识库的所有关系 - */ - @GetMapping("/debug/relationships/{knowledgeId}") - public R> debugRelationships(@PathVariable String knowledgeId) { - Map result = neo4jTestUtil.debugRelationships(knowledgeId); - return R.ok(result); - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/tripartite/TaskController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/tripartite/TaskController.java deleted file mode 100644 index 19756a2c..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/controller/tripartite/TaskController.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.ruoyi.controller.tripartite; - -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "任务查询") -@RestController -@RequestMapping("/mj/task") -@RequiredArgsConstructor -@Slf4j -public class TaskController { - -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphBuildTask.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphBuildTask.java deleted file mode 100644 index 43f26d29..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphBuildTask.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; - -import java.io.Serial; -import java.util.Date; - -/** - * 图谱构建任务对象 graph_build_task - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@TableName("graph_build_task") -public class GraphBuildTask extends BaseEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "id") - private Long id; - - /** - * 任务UUID - */ - private String taskUuid; - - /** - * 图谱UUID - */ - private String graphUuid; - - /** - * 知识库ID - */ - private String knowledgeId; - - /** - * 文档ID(可选,null表示全量构建) - */ - private String docId; - - /** - * 任务类型:1全量构建、2增量更新、3重建 - */ - private Integer taskType; - - /** - * 任务状态:1待执行、2执行中、3成功、4失败 - */ - private Integer taskStatus; - - /** - * 进度百分比(0-100) - */ - private Integer progress; - - /** - * 总文档数 - */ - private Integer totalDocs; - - /** - * 已处理文档数 - */ - private Integer processedDocs; - - /** - * 提取的实体数 - */ - private Integer extractedEntities; - - /** - * 提取的关系数 - */ - private Integer extractedRelations; - - /** - * 错误信息 - */ - private String errorMessage; - - /** - * 结果摘要(JSON格式) - */ - private String resultSummary; - - /** - * 开始时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date startTime; - - /** - * 结束时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date endTime; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphEdge.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphEdge.java deleted file mode 100644 index 7de30cbe..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphEdge.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Map; - -/** - * 图关系实体 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -@TableName("graph_edge") -public class GraphEdge implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id") - private Long id; - - /** - * 图谱UUID - */ - private String graphUuid; - - /** - * 关系唯一标识(Neo4j中的关系ID) - */ - private String edgeId; - - /** - * 关系标签(类型) - */ - private String label; - - /** - * 源节点ID - */ - private String sourceNodeId; - - /** - * 源节点名称 - */ - private String sourceName; - - /** - * 目标节点ID - */ - private String targetNodeId; - - /** - * 目标节点名称 - */ - private String targetName; - - /** - * 关系类型编码 - */ - private String relationType; - - /** - * 关系描述 - */ - private String description; - - /** - * 关系权重(0.0-1.0) - */ - private Double weight; - - /** - * 置信度(0.0-1.0) - */ - private Double confidence; - - /** - * 来源知识库ID - */ - private String knowledgeId; - - /** - * 来源文档ID列表(JSON格式) - */ - private String docIds; - - /** - * 来源片段ID列表(JSON格式) - */ - private String fragmentIds; - - /** - * 文本段ID(关联到具体的文本段) - */ - @JsonProperty("text_segment_id") - private String textSegmentId; - - /** - * 其他属性(JSON格式) - */ - private String properties; - - /** - * 元数据(JSON格式) - */ - private Map metadata; - - /** - * 源节点元数据 - */ - private Map sourceMetadata; - - /** - * 目标节点元数据 - */ - private Map targetMetadata; - - /** - * 备注 - */ - private String remark; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphEntityType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphEntityType.java deleted file mode 100644 index af697686..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphEntityType.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; - -import java.io.Serial; - -/** - * 图谱实体类型定义对象 graph_entity_type - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("graph_entity_type") -public class GraphEntityType extends BaseEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "id") - private Long id; - - /** - * 实体类型名称 - */ - private String typeName; - - /** - * 类型编码 - */ - private String typeCode; - - /** - * 描述 - */ - private String description; - - /** - * 可视化颜色 - */ - private String color; - - /** - * 图标 - */ - private String icon; - - /** - * 显示顺序 - */ - private Integer sort; - - /** - * 是否启用(0否 1是) - */ - private Integer isEnable; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphInstance.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphInstance.java deleted file mode 100644 index 67b69392..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphInstance.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; - -import java.io.Serial; - -/** - * 知识图谱实例对象 knowledge_graph_instance - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("knowledge_graph_instance") -public class GraphInstance extends BaseEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "id") - private Long id; - - /** - * 图谱UUID - */ - private String graphUuid; - - /** - * 关联knowledge_info.kid - */ - private String knowledgeId; - - /** - * 图谱名称 - */ - private String graphName; - - /** - * 图谱实例名称(前端使用,不映射到数据库) - */ - @TableField(exist = false) - private String instanceName; - - /** - * 构建状态:10构建中、20已完成、30失败 - */ - private Integer graphStatus; - - /** - * 节点数量 - */ - private Integer nodeCount; - - /** - * 关系数量 - */ - private Integer relationshipCount; - - /** - * 图谱配置(JSON格式) - */ - private String config; - - /** - * LLM模型名称 - */ - private String modelName; - - /** - * 实体类型(逗号分隔) - */ - private String entityTypes; - - /** - * 关系类型(逗号分隔) - */ - private String relationTypes; - - /** - * 错误信息 - */ - private String errorMessage; - - /** - * 删除标志(0代表存在 1代表删除) - */ - private String delFlag; - - /** - * 备注 - */ - private String remark; - - /** - * 获取实例名称(兼容前端) - */ - public String getInstanceName() { - return instanceName != null ? instanceName : graphName; - } - - /** - * 设置实例名称(同步到graphName) - */ - public void setInstanceName(String instanceName) { - this.instanceName = instanceName; - this.graphName = instanceName; - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphRelationType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphRelationType.java deleted file mode 100644 index d17fce33..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphRelationType.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; - -import java.io.Serial; - -/** - * 图谱关系类型定义对象 graph_relation_type - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("graph_relation_type") -public class GraphRelationType extends BaseEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "id") - private Long id; - - /** - * 关系名称 - */ - private String relationName; - - /** - * 关系编码 - */ - private String relationCode; - - /** - * 描述 - */ - private String description; - - /** - * 关系方向:0双向、1单向 - */ - private Integer direction; - - /** - * 可视化样式(JSON格式) - */ - private String style; - - /** - * 显示顺序 - */ - private Integer sort; - - /** - * 是否启用(0否 1是) - */ - private Integer isEnable; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphVertex.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphVertex.java deleted file mode 100644 index 57ccf35d..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/GraphVertex.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Map; - -/** - * 图节点实体 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -@TableName("graph_vertex") -public class GraphVertex implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id") - private Long id; - - /** - * 图谱UUID - */ - private String graphUuid; - - /** - * 节点唯一标识(Neo4j中的节点ID) - */ - private String nodeId; - - /** - * 节点名称 - */ - private String name; - - /** - * 节点标签(类型) - */ - private String label; - - /** - * 节点类型编码 - */ - private String type; - - /** - * 描述信息 - */ - private String description; - - /** - * 置信度(0.0-1.0) - */ - private Double confidence; - - /** - * 来源知识库ID - */ - private String knowledgeId; - - /** - * 来源文档ID列表(JSON格式) - */ - private String docIds; - - /** - * 来源片段ID列表(JSON格式) - */ - private String fragmentIds; - - /** - * 文本段ID(关联到具体的文本段) - */ - @JsonProperty("text_segment_id") - private String textSegmentId; - - /** - * 别名列表(JSON格式) - */ - private String aliases; - - /** - * 其他属性(JSON格式) - */ - private String properties; - - /** - * 元数据(JSON格式) - */ - private Map metadata; - - /** - * 备注 - */ - private String remark; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/KnowledgeBaseGraphSegment.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/KnowledgeBaseGraphSegment.java deleted file mode 100644 index ce2a74fd..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/domain/bo/graph/KnowledgeBaseGraphSegment.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.ruoyi.domain.bo.graph; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; - -import java.io.Serial; - -/** - * 知识图谱片段实体 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("knowledge_base_graph_segment") -public class KnowledgeBaseGraphSegment extends BaseEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键ID - */ - @TableId(value = "id", type = IdType.AUTO) - private Long id; - - /** - * 片段UUID - */ - private String uuid; - - /** - * 知识库UUID - */ - private String kbUuid; - - /** - * 知识库条目UUID - */ - private String kbItemUuid; - - /** - * 文档UUID - */ - private String docUuid; - - /** - * 片段文本内容 - */ - private String segmentText; - - /** - * 片段索引(第几个片段) - */ - private Integer chunkIndex; - - /** - * 总片段数 - */ - private Integer totalChunks; - - /** - * 抽取状态:0-待处理 1-处理中 2-已完成 3-失败 - */ - private Integer extractionStatus; - - /** - * 抽取的实体数量 - */ - private Integer entityCount; - - /** - * 抽取的关系数量 - */ - private Integer relationCount; - - /** - * 消耗的token数 - */ - private Integer tokenUsed; - - /** - * 错误信息 - */ - private String errorMessage; - - /** - * 用户ID - */ - private Long userId; -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java index 3312388b..c07d9444 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/ChatModeType.java @@ -14,7 +14,8 @@ public enum ChatModeType { ZHI_PU("zhipu", "智谱清言"), DEEP_SEEK("deepseek", "深度求索"), QIAN_WEN("qianwen", "通义千问"), - OPEN_AI("openai", "openai"); + OPEN_AI("openai", "openai"), + PPIO("ppio", "ppio"); private final String code; private final String description; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/GraphStatusEnum.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/GraphStatusEnum.java deleted file mode 100644 index a7026c80..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/enums/GraphStatusEnum.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.ruoyi.enums; - -import lombok.Getter; - -/** - * 图谱构建状态枚举 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Getter -public enum GraphStatusEnum { - - /** - * 未构建 - */ - NOT_BUILT(0, "未构建", "NOT_BUILT"), - - /** - * 构建中 - */ - BUILDING(10, "构建中", "BUILDING"), - - /** - * 已完成 - */ - COMPLETED(20, "已完成", "COMPLETED"), - - /** - * 失败 - */ - FAILED(30, "失败", "FAILED"); - - private final Integer code; - private final String description; - private final String statusKey; - - GraphStatusEnum(Integer code, String description, String statusKey) { - this.code = code; - this.description = description; - this.statusKey = statusKey; - } - - /** - * 根据code获取枚举 - */ - public static GraphStatusEnum getByCode(Integer code) { - for (GraphStatusEnum status : values()) { - if (status.getCode().equals(code)) { - return status; - } - } - return null; - } - - /** - * 根据前端状态字符串获取状态码 - */ - public static Integer getCodeByStatusKey(String statusKey) { - if (statusKey == null || statusKey.trim().isEmpty()) { - return null; - } - for (GraphStatusEnum status : values()) { - if (status.getStatusKey().equals(statusKey)) { - return status.getCode(); - } - } - return null; - } - - /** - * 根据状态码获取前端状态字符串 - */ - public static String getStatusKeyByCode(Integer code) { - GraphStatusEnum status = getByCode(code); - return status != null ? status.getStatusKey() : "NOT_BUILT"; - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java index 41ae8160..a4d93725 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/EmbeddingModelFactory.java @@ -36,17 +36,14 @@ public class EmbeddingModelFactory { * 如果模型已存在于缓存中,则直接返回;否则创建新的实例 * * @param embeddingModelName 嵌入模型名称 - * @param dimension 模型维度大小 */ - public BaseEmbedModelService createModel(String embeddingModelName, Integer dimension) { + public BaseEmbedModelService createModel(String embeddingModelName) { return modelCache.computeIfAbsent(embeddingModelName, name -> { ChatModelVo modelConfig = chatModelService.selectModelByName(embeddingModelName); + if (modelConfig == null) { throw new IllegalArgumentException("未找到模型配置,name=" + name); } - if (modelConfig.getDimension() != null) { - modelConfig.setDimension(dimension); - } return createModelInstance(modelConfig.getProviderCode(), modelConfig); }); } @@ -58,7 +55,7 @@ public class EmbeddingModelFactory { * @return boolean 如果模型支持多模态则返回true,否则返回false */ public boolean isMultimodalModel(String embeddingModelName) { - return createModel(embeddingModelName, null) instanceof MultiModalEmbedModelService; + return createModel(embeddingModelName) instanceof MultiModalEmbedModelService; } /** @@ -69,7 +66,7 @@ public class EmbeddingModelFactory { * @throws IllegalArgumentException 当模型不支持多模态时抛出 */ public MultiModalEmbedModelService createMultimodalModel(String embeddingModelName) { - BaseEmbedModelService model = createModel(embeddingModelName, null); + BaseEmbedModelService model = createModel(embeddingModelName); if (model instanceof MultiModalEmbedModelService) { return (MultiModalEmbedModelService) model; } @@ -113,7 +110,6 @@ public class EmbeddingModelFactory { // 配置模型参数 model.configure(config); log.info("成功创建嵌入模型: factory={}, modelId={}", config.getProviderCode(), config.getId()); - return model; } catch (NoSuchBeanDefinitionException e) { throw new IllegalArgumentException("获取不到嵌入模型: " + factory, e); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/GraphLLMServiceFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/GraphLLMServiceFactory.java deleted file mode 100644 index 1c8a678c..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/factory/GraphLLMServiceFactory.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.ruoyi.factory; - -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.service.graph.IGraphLLMService; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 图谱LLM服务工厂类 - * 参考 ruoyi-chat 的 ChatServiceFactory 设计 - * 根据模型类别自动选择对应的LLM服务实现 - * - * @author ruoyi - * @date 2025-10-11 - */ -@Slf4j -@Component -public class GraphLLMServiceFactory implements ApplicationContextAware { - - private final Map llmServiceMap = new ConcurrentHashMap<>(); - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - // 初始化时收集所有 IGraphLLMService 的实现 - Map serviceMap = applicationContext.getBeansOfType(IGraphLLMService.class); - - for (IGraphLLMService service : serviceMap.values()) { - if (service != null) { - String category = service.getCategory(); - llmServiceMap.put(category, service); - log.info("注册图谱LLM服务: category={}, service={}", - category, service.getClass().getSimpleName()); - } - } - - log.info("图谱LLM服务工厂初始化完成,共注册 {} 个服务", llmServiceMap.size()); - } - - /** - * 根据模型类别获取对应的LLM服务实现 - * - * @param category 模型类别(如: openai, qwen, zhipu) - * @return LLM服务实现 - * @throws IllegalArgumentException 如果不支持该类别 - */ - public IGraphLLMService getLLMService(String category) { - IGraphLLMService service = llmServiceMap.get(category); - - if (service == null) { - log.error("不支持的模型类别: {}, 可用类别: {}", category, llmServiceMap.keySet()); - throw new IllegalArgumentException("不支持的模型类别: " + category + - ", 可用类别: " + llmServiceMap.keySet()); - } - - return service; - } - - /** - * 获取所有支持的模型类别 - * - * @return 模型类别集合 - */ - public java.util.Set getSupportedCategories() { - return llmServiceMap.keySet(); - } - - /** - * 检查是否支持指定的模型类别 - * - * @param category 模型类别 - * @return 是否支持 - */ - public boolean isSupported(String category) { - return llmServiceMap.containsKey(category); - } -} - diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/listener/GraphExtractionListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/listener/GraphExtractionListener.java deleted file mode 100644 index 66106691..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/listener/GraphExtractionListener.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.ruoyi.listener; - -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.langchain4j.model.openai.internal.chat.ChatCompletionResponse; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Response; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.CompletableFuture; - -/** - * 图谱抽取LLM响应监听器 - * 用于收集完整的LLM响应(非流式) - * - * @author ruoyi-ai - */ -@Slf4j -public class GraphExtractionListener extends EventSourceListener { - - private final StringBuilder responseBuilder = new StringBuilder(); - private final CompletableFuture responseFuture; - private final ObjectMapper objectMapper = new ObjectMapper(); - - public GraphExtractionListener(CompletableFuture responseFuture) { - this.responseFuture = responseFuture; - } - - @Override - public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) { - log.debug("LLM连接已建立"); - } - - @Override - public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) { - try { - if ("[DONE]".equals(data)) { - // 响应完成,返回完整内容 - responseFuture.complete(responseBuilder.toString()); - return; - } - - // 解析响应 - ChatCompletionResponse completionResponse = objectMapper.readValue(data, ChatCompletionResponse.class); - if (completionResponse != null && !completionResponse.choices().isEmpty()) { - Object content = completionResponse.choices().get(0).delta().content(); - if (content != null) { - responseBuilder.append(content); - } - } - } catch (Exception e) { - log.error("解析LLM响应失败: {}", e.getMessage(), e); - responseFuture.completeExceptionally(e); - } - } - - @Override - public void onClosed(@NotNull EventSource eventSource) { - log.debug("LLM连接已关闭"); - // 如果还没有完成,就用当前内容完成 - if (!responseFuture.isDone()) { - responseFuture.complete(responseBuilder.toString()); - } - } - - @Override - public void onFailure(@NotNull EventSource eventSource, Throwable t, Response response) { - String errorMsg = "LLM调用失败"; - if (response != null && response.body() != null) { - try { - errorMsg = response.body().string(); - } catch (Exception e) { - errorMsg = response.toString(); - } - } - log.error("LLM调用失败: {}", errorMsg, t); - responseFuture.completeExceptionally( - new RuntimeException(errorMsg, t) - ); - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphBuildTaskMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphBuildTaskMapper.java deleted file mode 100644 index f5f42bf4..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphBuildTaskMapper.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ruoyi.mapper.graph; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.ruoyi.domain.bo.graph.GraphBuildTask; - -import java.util.List; - -/** - * 图谱构建任务Mapper接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface GraphBuildTaskMapper extends BaseMapper { - - /** - * 根据任务UUID查询 - * - * @param taskUuid 任务UUID - * @return 构建任务 - */ - GraphBuildTask selectByTaskUuid(String taskUuid); - - /** - * 根据图谱UUID查询任务列表 - * - * @param graphUuid 图谱UUID - * @return 任务列表 - */ - List selectByGraphUuid(String graphUuid); - - /** - * 根据知识库ID查询任务列表 - * - * @param knowledgeId 知识库ID - * @return 任务列表 - */ - List selectByKnowledgeId(String knowledgeId); - - /** - * 查询待执行和执行中的任务 - * - * @return 任务列表 - */ - List selectPendingAndRunningTasks(); - - /** - * 更新任务进度 - * - * @param taskUuid 任务UUID - * @param progress 进度 - * @param processedDocs 已处理文档数 - * @return 影响行数 - */ - int updateProgress(String taskUuid, Integer progress, Integer processedDocs); - - /** - * 更新任务状态 - * - * @param taskUuid 任务UUID - * @param status 状态 - * @return 影响行数 - */ - int updateStatus(String taskUuid, Integer status); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphEntityTypeMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphEntityTypeMapper.java deleted file mode 100644 index 1abe61e0..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphEntityTypeMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.ruoyi.mapper.graph; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.ruoyi.domain.bo.graph.GraphEntityType; - -import java.util.List; - -/** - * 图谱实体类型Mapper接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface GraphEntityTypeMapper extends BaseMapper { - - /** - * 根据类型编码查询 - * - * @param typeCode 类型编码 - * @return 实体类型 - */ - GraphEntityType selectByTypeCode(String typeCode); - - /** - * 查询所有启用的实体类型 - * - * @return 实体类型列表 - */ - List selectEnabledTypes(); - - /** - * 批量查询实体类型 - * - * @param typeCodes 类型编码列表 - * @return 实体类型列表 - */ - List selectByTypeCodes(List typeCodes); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphInstanceMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphInstanceMapper.java deleted file mode 100644 index cd391852..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphInstanceMapper.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.ruoyi.mapper.graph; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.ruoyi.domain.bo.graph.GraphInstance; - -/** - * 知识图谱实例Mapper接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface GraphInstanceMapper extends BaseMapper { - - /** - * 根据图谱UUID查询 - * - * @param graphUuid 图谱UUID - * @return 图谱实例 - */ - GraphInstance selectByGraphUuid(String graphUuid); - - /** - * 根据知识库ID查询图谱列表 - * - * @param knowledgeId 知识库ID - * @return 图谱实例列表 - */ - java.util.List selectByKnowledgeId(String knowledgeId); - - /** - * 更新节点和关系数量 - * - * @param graphUuid 图谱UUID - * @param nodeCount 节点数量 - * @param relationshipCount 关系数量 - * @return 影响行数 - */ - int updateCounts(String graphUuid, Integer nodeCount, Integer relationshipCount); - - /** - * 更新图谱状态 - * - * @param graphUuid 图谱UUID - * @param status 状态 - * @return 影响行数 - */ - int updateStatus(String graphUuid, Integer status); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphRelationTypeMapper.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphRelationTypeMapper.java deleted file mode 100644 index 325c21c2..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/mapper/graph/GraphRelationTypeMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.ruoyi.mapper.graph; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.ruoyi.domain.bo.graph.GraphRelationType; - -import java.util.List; - -/** - * 图谱关系类型Mapper接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface GraphRelationTypeMapper extends BaseMapper { - - /** - * 根据关系编码查询 - * - * @param relationCode 关系编码 - * @return 关系类型 - */ - GraphRelationType selectByRelationCode(String relationCode); - - /** - * 查询所有启用的关系类型 - * - * @return 关系类型列表 - */ - List selectEnabledTypes(); - - /** - * 批量查询关系类型 - * - * @param relationCodes 关系编码列表 - * @return 关系类型列表 - */ - List selectByRelationCodes(List relationCodes); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java index 73e72096..98c0b3d1 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java @@ -148,7 +148,6 @@ public class AgentChatHandler implements ChatHandler { messageBO.setContent(content); messageBO.setRole(role); messageBO.setModelName(chatRequest.getModel()); - messageBO.setBillingType(chatModelVo.getModelType()); messageBO.setRemark(null); chatMessageService.insertByBo(messageBO); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java index 95f681fd..de2aee2b 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatMessageServiceImpl.java @@ -79,10 +79,8 @@ public class ChatMessageServiceImpl implements IChatMessageService { lqw.eq(bo.getUserId() != null, ChatMessage::getUserId, bo.getUserId()); lqw.eq(StringUtils.isNotBlank(bo.getContent()), ChatMessage::getContent, bo.getContent()); lqw.eq(StringUtils.isNotBlank(bo.getRole()), ChatMessage::getRole, bo.getRole()); - lqw.eq(bo.getDeductCost() != null, ChatMessage::getDeductCost, bo.getDeductCost()); lqw.eq(bo.getTotalTokens() != null, ChatMessage::getTotalTokens, bo.getTotalTokens()); lqw.like(StringUtils.isNotBlank(bo.getModelName()), ChatMessage::getModelName, bo.getModelName()); - lqw.eq(StringUtils.isNotBlank(bo.getBillingType()), ChatMessage::getBillingType, bo.getBillingType()); return lqw; } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java index 127d66a5..0c2cf0c4 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatModelServiceImpl.java @@ -92,11 +92,7 @@ public class ChatModelServiceImpl implements IChatModelService { lqw.like(StringUtils.isNotBlank(bo.getModelName()), ChatModel::getModelName, bo.getModelName()); lqw.like(StringUtils.isNotBlank(bo.getProviderCode()), ChatModel::getProviderCode, bo.getProviderCode()); lqw.eq(StringUtils.isNotBlank(bo.getModelDescribe()), ChatModel::getModelDescribe, bo.getModelDescribe()); - lqw.eq(bo.getModelPrice() != null, ChatModel::getModelPrice, bo.getModelPrice()); - lqw.eq(StringUtils.isNotBlank(bo.getModelType()), ChatModel::getModelType, bo.getModelType()); lqw.eq(StringUtils.isNotBlank(bo.getModelShow()), ChatModel::getModelShow, bo.getModelShow()); - lqw.eq(StringUtils.isNotBlank(bo.getModelFree()), ChatModel::getModelFree, bo.getModelFree()); - lqw.eq(bo.getPriority() != null, ChatModel::getPriority, bo.getPriority()); lqw.eq(StringUtils.isNotBlank(bo.getApiHost()), ChatModel::getApiHost, bo.getApiHost()); lqw.eq(StringUtils.isNotBlank(bo.getApiKey()), ChatModel::getApiKey, bo.getApiKey()); return lqw; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java new file mode 100644 index 00000000..9c8c42ef --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/PPIOServiceImpl.java @@ -0,0 +1,50 @@ +package org.ruoyi.service.chat.impl.provider; + + +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.model.chat.StreamingChatModel; +import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; +import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.enums.ChatModeType; +import org.ruoyi.service.chat.impl.AbstractStreamingChatService; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * OPENAI服务调用 + * + * @author ageerle@163.com + * @date 2025/12/13 + */ +@Service +@Slf4j +public class PPIOServiceImpl extends AbstractStreamingChatService { + + @Override + public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) { + return OpenAiStreamingChatModel.builder() + .baseUrl(chatModelVo.getApiHost()) + .apiKey(chatModelVo.getApiKey()) + .modelName(chatModelVo.getModelName()) + .returnThinking(chatRequest.getEnableThinking()) + .build(); + } + + @Override + public void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, List messagesWithMemory, StreamingChatResponseHandler handler) { + StreamingChatModel streamingChatModel = buildStreamingChatModel(chatModelVo, chatRequest); + streamingChatModel.chat(messagesWithMemory, handler); + } + + + @Override + public String getProviderName() { + return ChatModeType.PPIO.getCode(); + } + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java index 9a0f67f5..083f98d6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/AliBaiLianBaseEmbedProvider.java @@ -38,7 +38,7 @@ public class AliBaiLianBaseEmbedProvider extends OpenAiEmbeddingProvider { return QwenEmbeddingModel.builder() .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) - .dimension(1024) + .dimension(chatModelVo.getModelDimension()) .build() .embedAll(textSegments); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java index c44b5ad8..2997d5b1 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/OpenAiEmbeddingProvider.java @@ -37,7 +37,7 @@ public class OpenAiEmbeddingProvider implements BaseEmbedModelService { .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) - .dimensions(chatModelVo.getDimension()) + .dimensions(chatModelVo.getModelDimension()) .build() .embedAll(textSegments); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/PPIOEmbeddingProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/PPIOEmbeddingProvider.java new file mode 100644 index 00000000..0c5d335b --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/PPIOEmbeddingProvider.java @@ -0,0 +1,14 @@ +package org.ruoyi.service.embed.impl; + + +import org.springframework.stereotype.Component; + +/** + * @Author: Robust_H + * @Date: 2025-09-30-下午3:59 + * @Description: 硅基流动(兼容 OpenAi) + */ +@Component("ppio") +public class PPIOEmbeddingProvider extends OpenAiEmbeddingProvider { + +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/ZhiPuAiEmbeddingProvider.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/ZhiPuAiEmbeddingProvider.java deleted file mode 100644 index d8383255..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/embed/impl/ZhiPuAiEmbeddingProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -//package org.ruoyi.service.embed.impl; -// -//import dev.langchain4j.community.model.zhipu.ZhipuAiEmbeddingModel; -//import dev.langchain4j.data.embedding.Embedding; -//import dev.langchain4j.data.segment.TextSegment; -//import dev.langchain4j.model.output.Response; -//import org.ruoyi.domain.vo.chat.ChatModelVo; -//import org.ruoyi.enums.ModalityType; -//import org.ruoyi.service.embed.BaseEmbedModelService; -//import org.springframework.stereotype.Component; -// -//import java.util.List; -//import java.util.Set; -// -///** -// * @Author: Robust_H -// * @Date: 2025-09-30-下午4:02 -// * @Description: 智谱AI -// */ -//@Component("zhipu") -//public class ZhiPuAiEmbeddingProvider implements BaseEmbedModelService { -// private ChatModelVo chatModelVo; -// -// @Override -// public void configure(ChatModelVo config) { -// this.chatModelVo = config; -// } -// -// @Override -// public Set getSupportedModalities() { -// return Set.of(); -// } -// -// @Override -// public Response> embedAll(List textSegments) { -// return ZhipuAiEmbeddingModel.builder() -// .baseUrl(chatModelVo.getApiHost()) -// .apiKey(chatModelVo.getApiKey()) -// .model(chatModelVo.getModelName()) -// .dimensions(chatModelVo.getDimension()) -// .build() -// .embedAll(textSegments); -// } -//} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphBuildTaskService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphBuildTaskService.java deleted file mode 100644 index 568cf49d..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphBuildTaskService.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.ruoyi.service.graph; - - -import org.ruoyi.domain.bo.graph.GraphBuildTask; - -import java.util.List; - -/** - * 图谱构建任务服务接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface IGraphBuildTaskService { - - /** - * 创建构建任务 - * - * @param graphUuid 图谱UUID - * @param knowledgeId 知识库ID - * @param docId 文档ID(可选) - * @param taskType 任务类型 - * @return 任务信息 - */ - GraphBuildTask createTask(String graphUuid, String knowledgeId, String docId, Integer taskType); - - /** - * 启动构建任务(异步) - * - * @param taskUuid 任务UUID - * @return 异步结果 - */ - void startTask(String taskUuid); - - /** - * 根据UUID获取任务 - * - * @param taskUuid 任务UUID - * @return 任务信息 - */ - GraphBuildTask getByUuid(String taskUuid); - - /** - * 根据图谱UUID获取任务列表 - * - * @param graphUuid 图谱UUID - * @return 任务列表 - */ - List listByGraphUuid(String graphUuid); - - /** - * 获取图谱的最新构建任务 - * - * @param graphUuid 图谱UUID - * @return 最新任务 - */ - GraphBuildTask getLatestTask(String graphUuid); - - /** - * 根据知识库ID获取任务列表 - * - * @param knowledgeId 知识库ID - * @return 任务列表 - */ - List listByKnowledgeId(String knowledgeId); - - /** - * 获取待执行和执行中的任务 - * - * @return 任务列表 - */ - List getPendingAndRunningTasks(); - - /** - * 更新任务进度 - * - * @param taskUuid 任务UUID - * @param progress 进度百分比 - * @param processedDocs 已处理文档数 - * @return 是否成功 - */ - boolean updateProgress(String taskUuid, Integer progress, Integer processedDocs); - - /** - * 更新任务状态 - * - * @param taskUuid 任务UUID - * @param status 状态 - * @return 是否成功 - */ - boolean updateStatus(String taskUuid, Integer status); - - /** - * 更新提取统计信息 - * - * @param taskUuid 任务UUID - * @param extractedEntities 提取的实体数 - * @param extractedRelations 提取的关系数 - * @return 是否成功 - */ - boolean updateExtractionStats(String taskUuid, Integer extractedEntities, Integer extractedRelations); - - /** - * 标记任务为成功 - * - * @param taskUuid 任务UUID - * @param resultSummary 结果摘要 - * @return 是否成功 - */ - boolean markSuccess(String taskUuid, String resultSummary); - - /** - * 标记任务为失败 - * - * @param taskUuid 任务UUID - * @param errorMessage 错误信息 - * @return 是否成功 - */ - boolean markFailed(String taskUuid, String errorMessage); - - /** - * 取消任务 - * - * @param taskUuid 任务UUID - * @return 是否成功 - */ - boolean cancelTask(String taskUuid); - - /** - * 重试失败的任务 - * - * @param taskUuid 任务UUID - * @return 新任务UUID - */ - String retryTask(String taskUuid); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphExtractionService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphExtractionService.java deleted file mode 100644 index d7bd69d2..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphExtractionService.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.ruoyi.service.graph; - - -import org.ruoyi.domain.dto.GraphExtractionResult; - -/** - * 图谱实体关系抽取服务接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface IGraphExtractionService { - - /** - * 从文本中抽取实体和关系 - * - * @param text 输入文本 - * @return 抽取结果 - */ - GraphExtractionResult extractFromText(String text); - - /** - * 从文本中抽取实体和关系(自定义实体类型) - * - * @param text 输入文本 - * @param entityTypes 实体类型列表 - * @return 抽取结果 - */ - GraphExtractionResult extractFromText(String text, String[] entityTypes); - - /** - * 从文本中抽取实体和关系(使用指定的LLM模型) - * - * @param text 输入文本 - * @param modelName LLM模型名称 - * @return 抽取结果 - */ - GraphExtractionResult extractFromTextWithModel(String text, String modelName); - - /** - * 解析LLM响应为实体和关系 - * - * @param response LLM响应文本 - * @return 抽取结果 - */ - GraphExtractionResult parseGraphResponse(String response); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphInstanceService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphInstanceService.java deleted file mode 100644 index 223d4fd6..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphInstanceService.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.ruoyi.service.graph; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import org.ruoyi.domain.bo.graph.GraphInstance; - -import java.util.List; - -/** - * 图谱实例服务接口 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface IGraphInstanceService { - - /** - * 创建图谱实例 - * - * @param knowledgeId 知识库ID - * @param graphName 图谱名称 - * @param config 配置信息 - * @return 图谱实例 - */ - GraphInstance createInstance(String knowledgeId, String graphName, String config); - - /** - * 根据主键ID获取图谱实例 - * - * @param id 主键ID - * @return 图谱实例 - */ - GraphInstance getById(Long id); - - /** - * 根据UUID获取图谱实例 - * - * @param graphUuid 图谱UUID - * @return 图谱实例 - */ - GraphInstance getByUuid(String graphUuid); - - /** - * 更新图谱实例 - * - * @param instance 图谱实例 - * @return 是否成功 - */ - boolean updateInstance(GraphInstance instance); - - /** - * 根据知识库ID获取图谱列表 - * - * @param knowledgeId 知识库ID - * @return 图谱实例列表 - */ - List listByKnowledgeId(String knowledgeId); - - /** - * 条件查询图谱实例列表(分页) - * - * @param page 分页对象 - * @param instanceName 图谱名称(模糊查询) - * @param knowledgeId 知识库ID - * @param graphStatus 图谱状态码 - * @return 分页结果 - */ - Page queryPage(Page page, String instanceName, String knowledgeId, Integer graphStatus); - - /** - * 更新图谱状态 - * - * @param graphUuid 图谱UUID - * @param status 状态 - * @return 是否成功 - */ - boolean updateStatus(String graphUuid, Integer status); - - /** - * 更新图谱统计信息 - * - * @param graphUuid 图谱UUID - * @param nodeCount 节点数量 - * @param relationshipCount 关系数量 - * @return 是否成功 - */ - boolean updateCounts(String graphUuid, Integer nodeCount, Integer relationshipCount); - - /** - * 更新图谱配置 - * - * @param graphUuid 图谱UUID - * @param config 配置信息 - * @return 是否成功 - */ - boolean updateConfig(String graphUuid, String config); - - /** - * 删除图谱实例(软删除) - * - * @param graphUuid 图谱UUID - * @return 是否成功 - */ - boolean deleteInstance(String graphUuid); - - /** - * 物理删除图谱实例及其数据 - * - * @param graphUuid 图谱UUID - * @return 是否成功 - */ - boolean deleteInstanceAndData(String graphUuid); - - /** - * 获取图谱统计信息 - * - * @param graphUuid 图谱UUID - * @return 统计信息 - */ - java.util.Map getStatistics(String graphUuid); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphLLMService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphLLMService.java deleted file mode 100644 index 4369d10d..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphLLMService.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.ruoyi.service.graph; - - -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; - -/** - * 图谱LLM服务接口 - * 参考 ruoyi-chat 的 IChatService 设计 - * 支持多种LLM模型(OpenAI、Qwen、Zhipu等) - * - * @author ruoyi - * @date 2025-10-11 - */ -public interface IGraphLLMService { - - /** - * 调用LLM进行图谱实体关系抽取 - * - * @param prompt 提示词(包含文本和抽取指令) - * @param chatModel 模型配置 - * @return LLM响应文本 - */ - String extractGraph(String prompt, ChatModelVo chatModel); - - /** - * 获取此服务支持的模型类别 - * 例如: "openai", "qwen", "zhipu", "ollama" - * - * @return 模型类别标识 - */ - String getCategory(); -} - diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphRAGService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphRAGService.java deleted file mode 100644 index 714f38e1..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphRAGService.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.ruoyi.service.graph; - -import org.ruoyi.domain.dto.GraphExtractionResult; - -import java.util.Map; - -/** - * GraphRAG服务接口 - * 负责文档的图谱化处理和基于图谱的检索 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface IGraphRAGService { - - /** - * 将文本入库到图谱 - * - * @param text 文本内容 - * @param knowledgeId 知识库ID - * @param metadata 元数据 - * @return 抽取结果 - */ - GraphExtractionResult ingestText(String text, String knowledgeId, Map metadata); - - /** - * 将文本入库到图谱(指定模型) - * - * @param text 文本内容 - * @param knowledgeId 知识库ID - * @param metadata 元数据 - * @param modelName LLM模型名称 - * @return 抽取结果 - */ - GraphExtractionResult ingestTextWithModel(String text, String knowledgeId, Map metadata, String modelName); - - /** - * 将文档入库到图谱(自动分片) - * - * @param documentText 文档内容 - * @param knowledgeId 知识库ID - * @param metadata 元数据 - * @return 总抽取结果(合并所有分片) - */ - GraphExtractionResult ingestDocument(String documentText, String knowledgeId, Map metadata); - - /** - * 将文档入库到图谱(自动分片,指定模型) - * - * @param documentText 文档内容 - * @param knowledgeId 知识库ID - * @param metadata 元数据 - * @param modelName LLM模型名称 - * @return 总抽取结果(合并所有分片) - */ - GraphExtractionResult ingestDocumentWithModel(String documentText, String knowledgeId, Map metadata, String modelName); - - /** - * 基于图谱检索相关内容 - * - * @param query 查询文本 - * @param knowledgeId 知识库ID - * @param maxResults 最大结果数 - * @return 检索到的相关实体和关系 - */ - String retrieveFromGraph(String query, String knowledgeId, int maxResults); - - /** - * 删除知识库的图谱数据 - * - * @param knowledgeId 知识库ID - * @return 是否成功 - */ - boolean deleteGraphData(String knowledgeId); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphStoreService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphStoreService.java deleted file mode 100644 index 1d10c127..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/IGraphStoreService.java +++ /dev/null @@ -1,268 +0,0 @@ -package org.ruoyi.service.graph; - - -import org.ruoyi.domain.bo.graph.GraphEdge; -import org.ruoyi.domain.bo.graph.GraphVertex; - -import java.util.List; - -/** - * 图存储服务接口 - * 核心服务:负责与Neo4j图数据库交互 - * - * @author ruoyi - * @date 2025-09-30 - */ -public interface IGraphStoreService { - - // ==================== 节点操作 ==================== - - /** - * 添加单个节点 - * - * @param vertex 节点信息 - * @return 是否成功 - */ - boolean addVertex(GraphVertex vertex); - - /** - * 批量添加节点 - * - * @param vertices 节点列表 - * @return 成功添加的节点数 - */ - int addVertices(List vertices); - - /** - * 获取节点信息 - * - * @param nodeId 节点ID - * @param graphUuid 图谱UUID - * @return 节点信息 - */ - GraphVertex getVertex(String nodeId, String graphUuid); - - /** - * 根据条件搜索节点 - * - * @param graphUuid 图谱UUID - * @param label 节点标签(可选) - * @param limit 返回数量限制 - * @return 节点列表 - */ - List searchVertices(String graphUuid, String label, Integer limit); - - /** - * 根据名称搜索节点 - * - * @param graphUuid 图谱UUID - * @param name 节点名称 - * @return 节点列表 - */ - List searchVerticesByName(String graphUuid, String name); - - /** - * 根据关键词和知识库ID搜索节点 - * - * @param keyword 关键词 - * @param knowledgeId 知识库ID(可选) - * @param limit 限制数量 - * @return 节点列表 - */ - List searchVerticesByName(String keyword, String knowledgeId, Integer limit); - - /** - * 根据知识库ID查询节点 - * - * @param knowledgeId 知识库ID - * @param limit 限制数量 - * @return 节点列表 - */ - List queryVerticesByKnowledgeId(String knowledgeId, Integer limit); - - /** - * 更新节点信息 - * - * @param vertex 节点信息 - * @return 是否成功 - */ - boolean updateVertex(GraphVertex vertex); - - /** - * 删除节点 - * - * @param nodeId 节点ID - * @param graphUuid 图谱UUID - * @param includeEdges 是否同时删除相关关系 - * @return 是否成功 - */ - boolean deleteVertex(String nodeId, String graphUuid, boolean includeEdges); - - // ==================== 关系操作 ==================== - - /** - * 添加关系 - * - * @param edge 关系信息 - * @return 是否成功 - */ - boolean addEdge(GraphEdge edge); - - /** - * 批量添加关系 - * - * @param edges 关系列表 - * @return 成功添加的关系数 - */ - int addEdges(List edges); - - /** - * 获取关系信息 - * - * @param edgeId 关系ID - * @param graphUuid 图谱UUID - * @return 关系信息 - */ - GraphEdge getEdge(String edgeId, String graphUuid); - - /** - * 搜索关系 - * - * @param graphUuid 图谱UUID - * @param sourceNodeId 源节点ID(可选) - * @param targetNodeId 目标节点ID(可选) - * @param limit 返回数量限制 - * @return 关系列表 - */ - List searchEdges(String graphUuid, String sourceNodeId, String targetNodeId, Integer limit); - - /** - * 根据知识库ID查询关系 - * - * @param knowledgeId 知识库ID - * @param limit 限制数量 - * @return 关系列表 - */ - List queryEdgesByKnowledgeId(String knowledgeId, Integer limit); - - /** - * 获取节点的所有关系 - * - * @param nodeId 节点ID - * @param graphUuid 图谱UUID - * @param direction 方向: IN(入边), OUT(出边), BOTH(双向) - * @return 关系列表 - */ - List getNodeEdges(String nodeId, String graphUuid, String direction); - - /** - * 更新关系信息 - * - * @param edge 关系信息 - * @return 是否成功 - */ - boolean updateEdge(GraphEdge edge); - - /** - * 删除关系 - * - * @param edgeId 关系ID - * @param graphUuid 图谱UUID - * @return 是否成功 - */ - boolean deleteEdge(String edgeId, String graphUuid); - - // ==================== 图谱管理 ==================== - - /** - * 创建图谱Schema - * - * @param graphUuid 图谱UUID - * @return 是否成功 - */ - boolean createGraphSchema(String graphUuid); - - /** - * 删除整个图谱数据 - * - * @param graphUuid 图谱UUID - * @return 是否成功 - */ - boolean deleteGraph(String graphUuid); - - /** - * 根据知识库ID删除图谱数据 - * - * @param knowledgeId 知识库ID - * @return 是否成功 - */ - boolean deleteByKnowledgeId(String knowledgeId); - - /** - * 获取图谱统计信息 - * - * @param graphUuid 图谱UUID - * @return 统计信息 {nodeCount, relationshipCount} - */ - java.util.Map getGraphStatistics(String graphUuid); - - /** - * 根据知识库ID获取统计信息 - * - * @param knowledgeId 知识库ID - * @return 统计信息 - */ - java.util.Map getStatistics(String knowledgeId); - - // ==================== 高级查询 ==================== - - /** - * 查找两个节点之间的路径 - * - * @param sourceNodeId 源节点ID - * @param targetNodeId 目标节点ID - * @param graphUuid 图谱UUID - * @param maxDepth 最大深度 - * @return 路径列表 - */ - List> findPaths(String sourceNodeId, String targetNodeId, String graphUuid, Integer maxDepth); - - /** - * 查找路径(简化版) - * - * @param startNodeId 起始节点ID - * @param endNodeId 结束节点ID - * @param maxDepth 最大深度 - * @return 路径列表 - */ - List> findPaths(String startNodeId, String endNodeId, Integer maxDepth); - - /** - * 查找节点的邻居节点 - * - * @param nodeId 节点ID - * @param graphUuid 图谱UUID - * @param depth 深度(几度关系) - * @return 邻居节点列表 - */ - List findNeighbors(String nodeId, String graphUuid, Integer depth); - - /** - * 获取节点的邻居(简化版) - * - * @param nodeId 节点ID - * @param knowledgeId 知识库ID(可选) - * @param limit 限制数量 - * @return 邻居节点列表 - */ - List getNeighbors(String nodeId, String knowledgeId, Integer limit); - - /** - * 执行自定义Cypher查询 - * - * @param cypher Cypher查询语句 - * @param params 参数 - * @return 查询结果 - */ - List> executeCypher(String cypher, java.util.Map params); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/DeepSeekGraphLLMServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/DeepSeekGraphLLMServiceImpl.java deleted file mode 100644 index 04d6f4be..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/DeepSeekGraphLLMServiceImpl.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import dev.langchain4j.model.chat.StreamingChatModel; -import dev.langchain4j.model.chat.response.ChatResponse; -import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; -import dev.langchain4j.model.openai.OpenAiStreamingChatModel; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.service.graph.IGraphLLMService; -import org.springframework.stereotype.Service; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -/** - * DeepSeek 图谱LLM服务实现 - * 支持 DeepSeek 系列模型 - *

- * 注意:使用 langchain4j 的 OpenAiStreamingChatModel,通过 CompletableFuture 转换为同步调用 - * 参考 DeepSeekChatImpl 的实现,但改为同步模式 - * - * @author ruoyi - * @date 2025-10-13 - */ -@Slf4j -@Service -public class DeepSeekGraphLLMServiceImpl implements IGraphLLMService { - - @Override - public String extractGraph(String prompt, ChatModelVo chatModel) { - log.info("DeepSeek模型调用: model={}, apiHost={}, 提示词长度={}", - chatModel.getModelName(), chatModel.getApiHost(), prompt.length()); - - try { - // 使用 langchain4j 的 OpenAiStreamingChatModel(参考 DeepSeekChatImpl) - StreamingChatModel streamingModel = OpenAiStreamingChatModel.builder() - .baseUrl(chatModel.getApiHost()) - .apiKey(chatModel.getApiKey()) - .modelName(chatModel.getModelName()) - .temperature(0.8) - .logRequests(false) - .logResponses(false) - .build(); - - // 用于收集完整响应 - StringBuilder fullResponse = new StringBuilder(); - CompletableFuture responseFuture = new CompletableFuture<>(); - - // 发送流式消息,但通过 CompletableFuture 转换为同步 - long startTime = System.currentTimeMillis(); - streamingModel.chat(prompt, new StreamingChatResponseHandler() { - @Override - public void onPartialResponse(String partialResponse) { - fullResponse.append(partialResponse); - } - - @Override - public void onCompleteResponse(ChatResponse completeResponse) { - long duration = System.currentTimeMillis() - startTime; - String responseText = fullResponse.toString(); - log.info("DeepSeek模型响应成功: 耗时={}ms, 响应长度={}", duration, responseText.length()); - responseFuture.complete(responseText); - } - - @Override - public void onError(Throwable error) { - log.error("DeepSeek模型调用错误: {}", error.getMessage()); - responseFuture.completeExceptionally(error); - } - }); - - // 同步等待结果(最多2分钟) - return responseFuture.get(2, TimeUnit.MINUTES); - - } catch (Exception e) { - log.error("DeepSeek模型调用失败: {}", e.getMessage(), e); - throw new RuntimeException("DeepSeek模型调用失败: " + e.getMessage(), e); - } - } - - @Override - public String getCategory() { - return "deepseek"; // 对应 ChatModel 表中的 category 字段 - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/DifyGraphLLMServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/DifyGraphLLMServiceImpl.java deleted file mode 100644 index 8204570c..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/DifyGraphLLMServiceImpl.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import io.github.imfangs.dify.client.DifyClient; -import io.github.imfangs.dify.client.DifyClientFactory; -import io.github.imfangs.dify.client.callback.ChatStreamCallback; -import io.github.imfangs.dify.client.enums.ResponseMode; -import io.github.imfangs.dify.client.event.ErrorEvent; -import io.github.imfangs.dify.client.event.MessageEndEvent; -import io.github.imfangs.dify.client.event.MessageEvent; -import io.github.imfangs.dify.client.model.DifyConfig; -import io.github.imfangs.dify.client.model.chat.ChatMessage; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.service.graph.IGraphLLMService; -import org.springframework.stereotype.Service; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -/** - * Dify 图谱LLM服务实现 - * 支持 Dify 平台的对话模型 - *

- * 注意:Dify 使用流式调用,通过 CompletableFuture 实现同步等待 - * - * @author ruoyi - * @date 2025-10-11 - */ -@Slf4j -@Service -public class DifyGraphLLMServiceImpl implements IGraphLLMService { - - @Override - public String extractGraph(String prompt, ChatModelVo chatModel) { - log.info("Dify模型调用: model={}, apiHost={}, 提示词长度={}", - chatModel.getModelName(), chatModel.getApiHost(), prompt.length()); - - try { - // 创建 Dify 客户端配置 - DifyConfig config = DifyConfig.builder() - .baseUrl(chatModel.getApiHost()) - .apiKey(chatModel.getApiKey()) - .connectTimeout(5000) - .readTimeout(120000) // 2分钟超时 - .writeTimeout(30000) - .build(); - - DifyClient chatClient = DifyClientFactory.createClient(config); - - // 创建聊天消息(使用流式模式) - ChatMessage message = ChatMessage.builder() - .query(prompt) - .user("graph-system") // 图谱系统用户 - .responseMode(ResponseMode.STREAMING) // 流式模式 - .build(); - - // 用于收集完整响应 - StringBuilder fullResponse = new StringBuilder(); - CompletableFuture responseFuture = new CompletableFuture<>(); - - // 发送流式消息 - long startTime = System.currentTimeMillis(); - chatClient.sendChatMessageStream(message, new ChatStreamCallback() { - @Override - public void onMessage(MessageEvent event) { - fullResponse.append(event.getAnswer()); - } - - @Override - public void onMessageEnd(MessageEndEvent event) { - long duration = System.currentTimeMillis() - startTime; - String responseText = fullResponse.toString(); - log.info("Dify模型响应成功: 耗时={}ms, 响应长度={}, messageId={}", - duration, responseText.length(), event.getMessageId()); - responseFuture.complete(responseText); - } - - @Override - public void onError(ErrorEvent event) { - log.error("Dify模型调用错误: {}", event.getMessage()); - responseFuture.completeExceptionally( - new RuntimeException("Dify调用错误: " + event.getMessage()) - ); - } - - @Override - public void onException(Throwable throwable) { - log.error("Dify模型调用异常: {}", throwable.getMessage(), throwable); - responseFuture.completeExceptionally(throwable); - } - }); - - // 同步等待结果(最多2分钟) - return responseFuture.get(2, TimeUnit.MINUTES); - - } catch (Exception e) { - log.error("Dify模型调用失败: {}", e.getMessage(), e); - throw new RuntimeException("Dify模型调用失败: " + e.getMessage(), e); - } - } - - @Override - public String getCategory() { - return "dify"; // 对应 ChatModel 表中的 category 字段 - } -} - diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphBuildTaskServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphBuildTaskServiceImpl.java deleted file mode 100644 index 96b2ea05..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphBuildTaskServiceImpl.java +++ /dev/null @@ -1,743 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSON; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.domain.bo.graph.GraphBuildTask; -import org.ruoyi.domain.bo.graph.GraphInstance; -import org.ruoyi.domain.bo.knowledge.KnowledgeAttachBo; -import org.ruoyi.domain.bo.knowledge.KnowledgeFragmentBo; -import org.ruoyi.domain.dto.GraphExtractionResult; -import org.ruoyi.domain.vo.knowledge.KnowledgeAttachVo; -import org.ruoyi.domain.vo.knowledge.KnowledgeFragmentVo; -import org.ruoyi.mapper.graph.GraphBuildTaskMapper; -import org.ruoyi.service.graph.IGraphBuildTaskService; -import org.ruoyi.service.graph.IGraphInstanceService; -import org.ruoyi.service.graph.IGraphRAGService; -import org.ruoyi.service.knowledge.IKnowledgeAttachService; -import org.ruoyi.service.knowledge.IKnowledgeFragmentService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 图谱构建任务服务实现 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphBuildTaskServiceImpl implements IGraphBuildTaskService { - - private final GraphBuildTaskMapper taskMapper; - - private final IGraphRAGService graphRAGService; - - private final IGraphInstanceService graphInstanceService; - - private final IKnowledgeFragmentService knowledgeFragmentService; - - private final IKnowledgeAttachService knowledgeAttachService; - - @Override - @Transactional(rollbackFor = Exception.class) - public GraphBuildTask createTask(String graphUuid, String knowledgeId, String docId, Integer taskType) { - GraphBuildTask task = new GraphBuildTask(); - task.setTaskUuid(IdUtil.fastSimpleUUID()); - task.setGraphUuid(graphUuid); - task.setKnowledgeId(knowledgeId); - task.setDocId(docId); - - // 设置任务类型和状态(使用整数) - task.setTaskType(taskType != null ? taskType : 1); - task.setTaskStatus(1); // 1-待处理 - task.setProgress(0); - - taskMapper.insert(task); - - log.info("创建图谱构建任务: taskId={}, taskUuid={}, graphUuid={}, knowledgeId={}, type={}", - task.getId(), task.getTaskUuid(), graphUuid, knowledgeId, task.getTaskType()); - - return task; - } - - @Override - @Async("graphBuildExecutor") // ⭐ 启用异步执行,使用专用线程池 - public void startTask(String taskUuid) { - // 记录线程信息 - String threadName = Thread.currentThread().getName(); - log.info("🚀 图谱构建任务启动 - taskUuid: {}, 线程: {}", taskUuid, threadName); - - long startTime = System.currentTimeMillis(); - - try { - // 1. 验证任务存在性 - GraphBuildTask task = getByUuid(taskUuid); - if (task == null) { - log.error("❌ 任务不存在: taskUuid={}", taskUuid); - return; - } - - // 2. 检查任务状态(防止重复执行) - if (task.getTaskStatus() != 1) { // 1-待处理 - log.warn("⚠️ 任务状态不允许执行: taskUuid={}, currentStatus={}", - taskUuid, task.getTaskStatus()); - return; - } - - // 3. 更新任务状态为运行中 - boolean statusUpdated = updateStatus(taskUuid, 2); // 2-运行中 - if (!statusUpdated) { - log.error("❌ 更新任务状态失败: taskUuid={}", taskUuid); - return; - } - - log.info("✅ 任务状态已更新为运行中: taskUuid={}", taskUuid); - - // 4. 执行图谱构建逻辑 - try { - executeTaskLogic(task); - - long duration = (System.currentTimeMillis() - startTime) / 1000; - log.info("🎉 图谱构建任务完成: taskUuid={}, 耗时: {}秒, 线程: {}", - taskUuid, duration, threadName); - - } catch (OutOfMemoryError oom) { - // 特殊处理OOM错误 - log.error("💥 图谱构建任务内存溢出: taskUuid={}, 线程: {}", taskUuid, threadName, oom); - markFailed(taskUuid, "内存溢出,请减少批处理文档数量或增加JVM内存"); - - // 建议垃圾回收 - System.gc(); - - } catch (InterruptedException ie) { - // 特殊处理中断异常 - Thread.currentThread().interrupt(); - log.error("⚠️ 图谱构建任务被中断: taskUuid={}, 线程: {}", taskUuid, threadName, ie); - markFailed(taskUuid, "任务被中断: " + ie.getMessage()); - - } catch (Exception e) { - // 处理其他业务异常 - log.error("❌ 图谱构建任务执行失败: taskUuid={}, 线程: {}", taskUuid, threadName, e); - - // 提取简洁的错误信息 - String errorMsg = extractErrorMessage(e); - markFailed(taskUuid, errorMsg); - } - - } catch (Exception e) { - // 处理外层异常(如数据库访问异常) - log.error("❌ 图谱构建任务启动失败: taskUuid={}, 线程: {}", taskUuid, threadName, e); - - try { - String errorMsg = extractErrorMessage(e); - markFailed(taskUuid, errorMsg); - } catch (Exception markFailEx) { - log.error("❌ 标记任务失败时出错: taskUuid={}", taskUuid, markFailEx); - } - } - } - - /** - * 提取简洁的错误信息(用于前端显示) - * - * @param e 异常对象 - * @return 简洁的错误信息 - */ - private String extractErrorMessage(Exception e) { - // 1. 优先使用自定义异常消息 - String message = e.getMessage(); - if (StrUtil.isNotBlank(message) && message.length() < 200) { - return message; - } - - // 2. 检查原因链 - Throwable cause = e.getCause(); - if (cause != null && StrUtil.isNotBlank(cause.getMessage())) { - String causeMsg = cause.getMessage(); - if (causeMsg.length() < 200) { - return causeMsg; - } - } - - // 3. 使用异常类名 - return e.getClass().getSimpleName() + ": " + - (message != null ? message.substring(0, Math.min(150, message.length())) : "未知错误"); - } - - @Override - public GraphBuildTask getByUuid(String taskUuid) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - return taskMapper.selectOne(wrapper); - } - - @Override - public List listByGraphUuid(String graphUuid) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphBuildTask::getGraphUuid, graphUuid); - wrapper.orderByDesc(GraphBuildTask::getCreateTime); - return taskMapper.selectList(wrapper); - } - - @Override - public GraphBuildTask getLatestTask(String graphUuid) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphBuildTask::getGraphUuid, graphUuid); - wrapper.orderByDesc(GraphBuildTask::getCreateTime); - wrapper.last("LIMIT 1"); - return taskMapper.selectOne(wrapper); - } - - @Override - public List listByKnowledgeId(String knowledgeId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphBuildTask::getKnowledgeId, knowledgeId); - wrapper.orderByDesc(GraphBuildTask::getCreateTime); - return taskMapper.selectList(wrapper); - } - - @Override - public List getPendingAndRunningTasks() { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.in(GraphBuildTask::getTaskStatus, 1, 2); // 1-待处理, 2-运行中 - wrapper.orderByAsc(GraphBuildTask::getCreateTime); - return taskMapper.selectList(wrapper); - } - - @Override - public boolean updateProgress(String taskUuid, Integer progress, Integer processedDocs) { - try { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - wrapper.set(GraphBuildTask::getProgress, progress); - - if (processedDocs != null) { - wrapper.set(GraphBuildTask::getProcessedDocs, processedDocs); - } - - int rows = taskMapper.update(null, wrapper); - log.info("📊 更新任务进度: taskUuid={}, progress={}%, processedDocs={}, rows={}", - taskUuid, progress, processedDocs, rows); - return rows > 0; - } catch (Exception e) { - log.error("更新任务进度失败: taskUuid={}, progress={}", taskUuid, progress, e); - return false; - } - } - - @Override - public boolean updateStatus(String taskUuid, Integer status) { - try { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - wrapper.set(GraphBuildTask::getTaskStatus, status); - - // 如果是开始运行,设置开始时间 - if (status == 2) { - wrapper.set(GraphBuildTask::getStartTime, new Date()); - } - - // 如果是完成或失败,设置结束时间 - if (status == 3 || status == 4) { - wrapper.set(GraphBuildTask::getEndTime, new Date()); - } - - int rows = taskMapper.update(null, wrapper); - - log.info("更新任务状态: taskUuid={}, status={}, rows={}", taskUuid, status, rows); - return rows > 0; - } catch (Exception e) { - log.error("更新任务状态失败: taskUuid={}, status={}", taskUuid, status, e); - return false; - } - } - - @Override - public boolean updateExtractionStats(String taskUuid, Integer extractedEntities, Integer extractedRelations) { - try { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - - if (extractedEntities != null) { - wrapper.set(GraphBuildTask::getExtractedEntities, extractedEntities); - } - if (extractedRelations != null) { - wrapper.set(GraphBuildTask::getExtractedRelations, extractedRelations); - } - - int rows = taskMapper.update(null, wrapper); - return rows > 0; - } catch (Exception e) { - log.error("更新提取统计失败: taskUuid={}", taskUuid, e); - return false; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean markSuccess(String taskUuid, String resultSummary) { - try { - // 1. 更新任务状态 - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - wrapper.set(GraphBuildTask::getTaskStatus, 3); // 3-已完成 - wrapper.set(GraphBuildTask::getProgress, 100); - wrapper.set(GraphBuildTask::getEndTime, new Date()); - wrapper.set(GraphBuildTask::getResultSummary, resultSummary); - - int rows = taskMapper.update(null, wrapper); - - // 2. 更新图谱实例状态为"已完成" - GraphBuildTask task = getByUuid(taskUuid); - if (task != null && task.getGraphUuid() != null) { - graphInstanceService.updateStatus(task.getGraphUuid(), 20); // 20-已完成 - log.info("更新图谱实例状态为已完成: graphUuid={}", task.getGraphUuid()); - } - - log.info("标记任务成功: taskUuid={}, rows={}", taskUuid, rows); - return rows > 0; - } catch (Exception e) { - log.error("标记任务成功失败: taskUuid={}", taskUuid, e); - return false; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean markFailed(String taskUuid, String errorMessage) { - try { - // 1. 更新任务状态 - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - wrapper.set(GraphBuildTask::getTaskStatus, 4); // 4-失败 - wrapper.set(GraphBuildTask::getErrorMessage, errorMessage); - wrapper.set(GraphBuildTask::getEndTime, new Date()); - - int rows = taskMapper.update(null, wrapper); - - // 2. 更新图谱实例状态为"失败" - GraphBuildTask task = getByUuid(taskUuid); - if (task != null && task.getGraphUuid() != null) { - graphInstanceService.updateStatus(task.getGraphUuid(), 30); // 30-失败 - log.info("更新图谱实例状态为失败: graphUuid={}", task.getGraphUuid()); - } - - log.error("标记任务失败: taskUuid={}, error={}, rows={}", taskUuid, errorMessage, rows); - return rows > 0; - } catch (Exception e) { - log.error("标记任务失败失败: taskUuid={}", taskUuid, e); - return false; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean cancelTask(String taskUuid) { - try { - GraphBuildTask task = getByUuid(taskUuid); - if (task == null) { - log.error("任务不存在: taskUuid={}", taskUuid); - return false; - } - - // 只能取消待处理或运行中的任务 - if (task.getTaskStatus() != 1 && task.getTaskStatus() != 2) { - log.warn("任务状态不允许取消: taskUuid={}, status={}", taskUuid, task.getTaskStatus()); - return false; - } - - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - wrapper.set(GraphBuildTask::getTaskStatus, 4); // 4-失败 - wrapper.set(GraphBuildTask::getErrorMessage, "任务已取消"); - wrapper.set(GraphBuildTask::getEndTime, new Date()); - - int rows = taskMapper.update(null, wrapper); - - log.info("取消任务: taskUuid={}, rows={}", taskUuid, rows); - return rows > 0; - } catch (Exception e) { - log.error("取消任务失败: taskUuid={}", taskUuid, e); - return false; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public String retryTask(String taskUuid) { - try { - GraphBuildTask oldTask = getByUuid(taskUuid); - if (oldTask == null) { - log.error("任务不存在: taskUuid={}", taskUuid); - return null; - } - - // 创建新任务 - GraphBuildTask newTask = createTask( - oldTask.getGraphUuid(), - oldTask.getKnowledgeId(), - oldTask.getDocId(), - oldTask.getTaskType() - ); - - log.info("重试任务: oldTaskUuid={}, newTaskUuid={}", taskUuid, newTask.getTaskUuid()); - return newTask.getTaskUuid(); - } catch (Exception e) { - log.error("重试任务失败: taskUuid={}", taskUuid, e); - return null; - } - } - - /** - * 执行图谱构建任务的核心逻辑 - * - * @param task 构建任务 - * @throws Exception 执行过程中的异常 - */ - private void executeTaskLogic(GraphBuildTask task) throws Exception { - String taskUuid = task.getTaskUuid(); - String graphUuid = task.getGraphUuid(); - String knowledgeId = task.getKnowledgeId(); - String docId = task.getDocId(); - Integer taskType = task.getTaskType(); - - long startTime = System.currentTimeMillis(); - int totalDocs = 0; - int processedDocs = 0; - int successDocs = 0; // ⭐ 新增:成功处理的文档数 - int failedDocs = 0; // ⭐ 新增:失败的文档数 - int totalEntities = 0; - int totalRelations = 0; - - // ⭐ 记录初始内存状态 - Runtime runtime = Runtime.getRuntime(); - long initialMemory = runtime.totalMemory() - runtime.freeMemory(); - log.info("📊 初始内存使用: {} MB / {} MB", - initialMemory / 1024 / 1024, - runtime.maxMemory() / 1024 / 1024); - - try { - // 0. 获取图谱实例配置(包括LLM模型) - String modelName = null; - if (StrUtil.isNotBlank(graphUuid)) { - GraphInstance graphInstance = graphInstanceService.getByUuid(graphUuid); - if (graphInstance != null && StrUtil.isNotBlank(graphInstance.getModelName())) { - modelName = graphInstance.getModelName(); - log.info("使用图谱实例配置的模型: {}", modelName); - } - } - - // 1. 获取需要处理的文档列表 - List documents; - - if (taskType == 1) { - // 类型1: 全量构建(知识库所有文档) - if (StrUtil.isBlank(knowledgeId)) { - throw new RuntimeException("知识库构建任务缺少知识库ID"); - } - - // 查询知识库下的所有文档(目前只处理单文档) - KnowledgeFragmentBo bo = new KnowledgeFragmentBo(); - bo.setDocId(docId); - log.info("🔍 准备查询文档: knowledgeId={}", knowledgeId); - documents = knowledgeFragmentService.queryList(bo); - log.info("📋 查询返回文档数: {}", documents != null ? documents.size() : "null"); - - } else if (taskType == 2) { - // 类型2: 重建(清空后全量重建) - if (StrUtil.isBlank(knowledgeId)) { - throw new RuntimeException("知识库构建任务缺少知识库ID"); - } - - // ⭐ 先清空该知识库的旧图谱数据 - log.info("🗑️ 重建模式:先清空知识库的旧图谱数据,knowledgeId: {}", knowledgeId); - boolean deleted = graphRAGService.deleteGraphData(knowledgeId); - if (deleted) { - log.info("✅ 旧图谱数据清空成功"); - } else { - log.warn("⚠️ 旧图谱数据清空失败(可能是没有旧数据)"); - } - - // 查询知识库下的所有文档 - KnowledgeFragmentBo bo = new KnowledgeFragmentBo(); - bo.setDocId(docId); - log.info("🔍 准备查询文档: knowledgeId={}, bo.getKid()={}", knowledgeId); - documents = knowledgeFragmentService.queryList(bo); - log.info("📋 查询返回文档数: {}", documents != null ? documents.size() : "null"); - - } else if (taskType == 3) { - // 类型3: 单文档增量构建 - if (StrUtil.isBlank(docId)) { - throw new RuntimeException("单文档构建任务缺少文档ID"); - } - - // 根据docId查询单个文档 - KnowledgeFragmentBo bo = new KnowledgeFragmentBo(); - bo.setDocId(docId); - log.info("🔍 准备查询单个文档: docId={}, bo.getDocId()={}", docId, bo.getDocId()); - documents = knowledgeFragmentService.queryList(bo); - log.info("📋 查询返回文档数: {}", documents != null ? documents.size() : "null"); - - } else { - throw new RuntimeException("未知的任务类型: " + taskType); - } - - if (documents == null || documents.isEmpty()) { - String errorMsg = String.format( - "❌ 没有找到需要处理的文档!\n" + - " taskUuid: %s\n" + - " knowledgeId: %s\n" + - " docId: %s\n" + - " taskType: %d\n" + - " documents: %s\n" + - "请检查:\n" + - " 1. knowledge_attach 表中是否有 kid='%s' 的记录\n" + - " 2. knowledgeId 是否正确传递\n" + - " 3. KnowledgeAttachService.queryList() 是否正确执行", - taskUuid, knowledgeId, docId, taskType, - documents == null ? "null" : "empty list", - knowledgeId - ); - log.warn(errorMsg); - - Map summary = new HashMap<>(); - summary.put("message", "没有找到需要处理的文档"); - summary.put("totalDocs", 0); - summary.put("knowledgeId", knowledgeId); - summary.put("taskType", taskType); - markSuccess(taskUuid, JSON.toJSONString(summary)); // ⭐ 使用 JSON 序列化 - return; - } - - totalDocs = documents.size(); - log.info("开始构建图谱,共 {} 个文档", totalDocs); - - // ⭐ 更新任务的 total_docs 字段 - LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); - updateWrapper.eq(GraphBuildTask::getTaskUuid, taskUuid); - updateWrapper.set(GraphBuildTask::getTotalDocs, totalDocs); - taskMapper.update(null, updateWrapper); - log.info("📊 更新任务total_docs: {}", totalDocs); - - // 限制处理文档数量,避免内存溢出 - int maxDocsPerBatch = 50; // 每批最多处理50个文档 - if (totalDocs > maxDocsPerBatch) { - log.warn("文档数量较多({}个),建议分批处理,当前批次限制为{}个", totalDocs, maxDocsPerBatch); - documents = documents.subList(0, Math.min(maxDocsPerBatch, totalDocs)); - totalDocs = documents.size(); - - // ⭐ 重新更新 total_docs(因为被限制了) - LambdaUpdateWrapper updateWrapper2 = new LambdaUpdateWrapper<>(); - updateWrapper2.eq(GraphBuildTask::getTaskUuid, taskUuid); - updateWrapper2.set(GraphBuildTask::getTotalDocs, totalDocs); - taskMapper.update(null, updateWrapper2); - log.info("📊 更新限制后的total_docs: {}", totalDocs); - } - - // 2. 逐个处理文档(带内存管理和错误恢复) - for (int i = 0; i < documents.size(); i++) { - KnowledgeFragmentVo doc = documents.get(i); - long docStartTime = System.currentTimeMillis(); - - try { - // ⭐ 检查内存状态 - long usedMemory = runtime.totalMemory() - runtime.freeMemory(); - long maxMemory = runtime.maxMemory(); - double memoryUsage = (double) usedMemory / maxMemory * 100; - - if (memoryUsage > 80) { - log.warn("⚠️ 内存使用率过高: {}/{}MB ({}%), 建议垃圾回收", - usedMemory / 1024 / 1024, - maxMemory / 1024 / 1024, - String.format("%.2f", memoryUsage)); - System.gc(); - try { - Thread.sleep(1000); // 等待GC完成 - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - log.warn("⚠️ 等待GC时被中断"); - } - } - - // 2.1 获取文档内容 - String content = doc.getContent(); - if (StrUtil.isBlank(content)) { - log.warn("⚠️ 文档内容为空,跳过: docId={}", doc.getDocId()); - processedDocs++; - failedDocs++; - continue; - } - - // 限制单个文档内容大小,避免内存溢出 - if (content.length() > 50000) { - log.warn("⚠️ 文档内容过大({} 字符),截断处理: docId={}", - content.length(), doc.getDocId()); - content = content.substring(0, 50000); - } - KnowledgeAttachBo bo = new KnowledgeAttachBo(); - bo.setDocId(docId); - KnowledgeAttachVo docInfo = knowledgeAttachService.queryList(bo).get(0); - - // 2.2 准备元数据(不包含大字段) - Map metadata = new HashMap<>(); - metadata.put("docId", doc.getDocId()); - metadata.put("docName", docInfo.getName()); - metadata.put("docType", docInfo.getType()); - metadata.put("kid", knowledgeId); - - // 2.3 调用GraphRAG服务进行图谱入库(使用图谱实例配置的模型) - GraphExtractionResult result = null; - try { - if (content.length() > 2000) { - // 长文档,使用分片处理 - result = graphRAGService.ingestDocumentWithModel( - content, knowledgeId, metadata, modelName); - } else { - // 短文档,直接处理 - result = graphRAGService.ingestTextWithModel( - content, knowledgeId, metadata, modelName); - } - } catch (OutOfMemoryError oom) { - // OOM单独处理:强制GC后继续 - log.error("💥 处理文档时OOM,强制垃圾回收: docId={}", doc.getDocId()); - System.gc(); - try { - Thread.sleep(2000); // 等待GC完成 - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - log.warn("⚠️ 等待GC时被中断"); - // 中断时不继续处理,跳出循环 - throw ie; - } - processedDocs++; - failedDocs++; - continue; - } catch (Exception e) { - log.error("❌ LLM调用失败,跳过文档: docId={}, error={}", - doc.getDocId(), e.getMessage()); - processedDocs++; - failedDocs++; - continue; - } - - // 2.4 统计结果 - if (result != null && result.getSuccess()) { - int entities = result.getEntities().size(); - int relations = result.getRelations().size(); - totalEntities += entities; - totalRelations += relations; - successDocs++; - - long docDuration = System.currentTimeMillis() - docStartTime; - log.info("✅ 文档处理成功: docId={}, 实体数={}, 关系数={}, 耗时={}ms", - doc.getDocId(), entities, relations, docDuration); - } else { - failedDocs++; - log.warn("⚠️ 文档处理失败: docId={}, error={}", - doc.getDocId(), result != null ? result.getErrorMessage() : "unknown"); - } - - // 2.5 更新进度 - processedDocs++; - int progress = (processedDocs * 100) / totalDocs; - log.info("📈 文档进度: {}/{}, 进度={}%", processedDocs, totalDocs, progress); - boolean updated = updateProgress(taskUuid, progress, processedDocs); - if (!updated) { - log.warn("⚠️ 进度更新失败: taskUuid={}, progress={}", taskUuid, progress); - } - - // 2.6 定期进行垃圾回收和内存检查 - if ((i + 1) % 10 == 0) { - long currentMemory = runtime.totalMemory() - runtime.freeMemory(); - log.info("📊 已处理{}/{}个文档, 内存使用: {} MB", - i + 1, totalDocs, currentMemory / 1024 / 1024); - System.gc(); - try { - Thread.sleep(500); // 短暂等待GC - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - log.warn("⚠️ 等待GC时被中断"); - } - } - - } catch (InterruptedException ie) { - // 中断异常:重新抛出,终止任务 - Thread.currentThread().interrupt(); - log.error("⚠️ 任务被中断,停止处理文档: docId={}", doc.getDocId()); - throw ie; - } catch (Exception e) { - log.error("❌ 处理文档时发生异常: docId={}, error={}", - doc.getDocId(), e.getMessage(), e); - processedDocs++; - failedDocs++; - // 继续处理下一个文档(不中断整个任务) - } finally { - // 释放文档引用,帮助GC - documents.set(i, null); - } - } - - // 3. 构建完成,生成详细摘要 - long duration = (System.currentTimeMillis() - startTime) / 1000; - long finalMemory = runtime.totalMemory() - runtime.freeMemory(); - - Map summary = new HashMap<>(); - summary.put("totalDocs", totalDocs); - summary.put("processedDocs", processedDocs); - summary.put("successDocs", successDocs); // ⭐ 成功文档数 - summary.put("failedDocs", failedDocs); // ⭐ 失败文档数 - summary.put("totalEntities", totalEntities); - summary.put("totalRelations", totalRelations); - summary.put("duration", duration + "秒"); - summary.put("avgTimePerDoc", totalDocs > 0 ? (duration * 1000 / totalDocs) + "ms" : "N/A"); // ⭐ 平均处理时间 - summary.put("memoryUsed", (finalMemory - initialMemory) / 1024 / 1024 + "MB"); // ⭐ 内存增量 - summary.put("status", "completed"); - summary.put("modelName", modelName != null ? modelName : "default"); // ⭐ 使用的模型 - - // 更新统计信息到任务 - updateExtractionStats(taskUuid, totalEntities, totalRelations); - - markSuccess(taskUuid, JSON.toJSONString(summary)); - - log.info("🎉 图谱构建任务完成汇总:"); - log.info(" - taskUuid: {}", taskUuid); - log.info(" - 文档总数: {}", totalDocs); - log.info(" - 成功处理: {} 个", successDocs); - log.info(" - 失败文档: {} 个", failedDocs); - log.info(" - 实体总数: {}", totalEntities); - log.info(" - 关系总数: {}", totalRelations); - log.info(" - 总耗时: {} 秒", duration); - log.info(" - 平均耗时: {} ms/文档", totalDocs > 0 ? duration * 1000 / totalDocs : 0); - log.info(" - 内存增量: {} MB", (finalMemory - initialMemory) / 1024 / 1024); - - } catch (InterruptedException ie) { - // 中断异常:向上抛出 - Thread.currentThread().interrupt(); - log.error("⚠️ 图谱构建任务被中断: taskUuid={}", taskUuid, ie); - throw ie; - } catch (Exception e) { - log.error("❌ 图谱构建任务执行失败: taskUuid={}", taskUuid, e); - throw e; - } finally { - // 清理资源,帮助GC - System.gc(); - log.info("📊 最终内存状态: {} MB / {} MB", - (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024, - runtime.maxMemory() / 1024 / 1024); - } - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java deleted file mode 100644 index bdceb35f..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphExtractionServiceImpl.java +++ /dev/null @@ -1,367 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import cn.hutool.core.util.StrUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.service.chat.IChatModelService; -import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.config.GraphExtractPrompt; -import org.ruoyi.constant.GraphConstants; -import org.ruoyi.domain.dto.ExtractedEntity; -import org.ruoyi.domain.dto.ExtractedRelation; -import org.ruoyi.domain.dto.GraphExtractionResult; -import org.ruoyi.factory.GraphLLMServiceFactory; -import org.ruoyi.service.graph.IGraphExtractionService; -import org.ruoyi.service.graph.IGraphLLMService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -/** - * 图谱实体关系抽取服务实现 - * 使用工厂模式支持多种LLM模型(参考 ruoyi-chat 设计) - * - * @author ruoyi - * @date 2025-09-30 - */ -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphExtractionServiceImpl implements IGraphExtractionService { - - private final IChatModelService chatModelService; - private final GraphLLMServiceFactory llmServiceFactory; - - /** - * 实体匹配正则表达式 - * 格式: ("entity"<|>ENTITY_NAME<|>ENTITY_TYPE<|>ENTITY_DESCRIPTION) - */ - private static final Pattern ENTITY_PATTERN = Pattern.compile( - "\\(\"entity\"" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^)]+)\\)" - ); - /** - * 关系匹配正则表达式 - * 格式: ("relationship"<|>SOURCE<|>TARGET<|>DESCRIPTION<|>STRENGTH) - */ - private static final Pattern RELATION_PATTERN = Pattern.compile( - "\\(\"relationship\"" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^" + Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + "]+)" + - Pattern.quote(GraphConstants.GRAPH_TUPLE_DELIMITER) + - "([^)]+)\\)" - ); - - - @Override - public GraphExtractionResult extractFromText(String text) { - return extractFromText(text, GraphConstants.DEFAULT_ENTITY_TYPES); - } - - @Override - public GraphExtractionResult extractFromText(String text, String[] entityTypes) { - log.info("开始从文本中抽取实体和关系,文本长度: {}", text.length()); - - try { - // 1. 构建提示词 - String prompt = GraphExtractPrompt.buildExtractionPrompt(text, entityTypes); - - // 2. 调用LLM(使用默认模型) - String llmResponse = callLLM(prompt); - - // 3. 解析响应 - GraphExtractionResult result = parseGraphResponse(llmResponse); - result.setRawResponse(llmResponse); - result.setSuccess(true); - - log.info("抽取完成,实体数: {}, 关系数: {}", - result.getEntities().size(), result.getRelations().size()); - - return result; - - } catch (Exception e) { - log.error("实体关系抽取失败", e); - return GraphExtractionResult.builder() - .entities(new ArrayList<>()) - .relations(new ArrayList<>()) - .success(false) - .errorMessage(e.getMessage()) - .build(); - } - } - - @Override - public GraphExtractionResult extractFromTextWithModel(String text, String modelName) { - log.info("开始从文本中抽取实体和关系,使用模型: {}, 文本长度: {}", modelName, text.length()); - - try { - // 1. 获取模型配置 - ChatModelVo chatModel = chatModelService.selectModelByName(modelName); - if (chatModel == null) { - log.warn("未找到模型: {}, 使用默认模型", modelName); - return extractFromText(text); - } - - // 2. 构建提示词 - String prompt = GraphExtractPrompt.buildExtractionPrompt(text, GraphConstants.DEFAULT_ENTITY_TYPES); - - // 3. 调用LLM(使用指定模型) - String llmResponse = callLLMWithModel(prompt, chatModel); - - // 4. 解析响应 - GraphExtractionResult result = parseGraphResponse(llmResponse); - result.setRawResponse(llmResponse); - result.setSuccess(true); - - log.info("抽取完成,实体数: {}, 关系数: {}, 使用模型: {}", - result.getEntities().size(), result.getRelations().size(), modelName); - - // ⭐ 调试:如果没有关系,记录原始响应(便于诊断) - if (result.getRelations().isEmpty() && !result.getEntities().isEmpty()) { - log.warn("⚠️ LLM 提取到 {} 个实体,但没有提取到任何关系!", result.getEntities().size()); - log.warn("LLM 原始响应预览(前500字符): {}", - llmResponse.length() > 500 ? llmResponse.substring(0, 500) + "..." : llmResponse); - } - - return result; - - } catch (Exception e) { - log.error("实体关系抽取失败,模型: {}", modelName, e); - return GraphExtractionResult.builder() - .entities(new ArrayList<>()) - .relations(new ArrayList<>()) - .success(false) - .errorMessage(e.getMessage()) - .build(); - } - } - - @Override - public GraphExtractionResult parseGraphResponse(String response) { - log.debug("开始解析图谱响应,响应长度: {}", response != null ? response.length() : 0); - - List entities = new ArrayList<>(); - List relations = new ArrayList<>(); - - if (StrUtil.isBlank(response)) { - log.warn("响应为空,无法解析"); - return GraphExtractionResult.builder() - .entities(entities) - .relations(relations) - .success(false) - .errorMessage("LLM响应为空") - .build(); - } - - try { - // 1. 解析实体 - Matcher entityMatcher = ENTITY_PATTERN.matcher(response); - while (entityMatcher.find()) { - String name = entityMatcher.group(1).trim(); - String type = entityMatcher.group(2).trim(); - String description = entityMatcher.group(3).trim(); - - // ⭐ 过滤无效实体(N/A 或包含特殊字符) - if (isInvalidEntity(name, type)) { - log.debug("跳过无效实体: name={}, type={}", name, type); - continue; - } - - ExtractedEntity entity = ExtractedEntity.builder() - .name(name) - .type(type) - .description(description) - .build(); - - entities.add(entity); - log.debug("解析到实体: name={}, type={}", name, type); - } - - // 2. 解析关系 - Matcher relationMatcher = RELATION_PATTERN.matcher(response); - while (relationMatcher.find()) { - String sourceEntity = relationMatcher.group(1).trim(); - String targetEntity = relationMatcher.group(2).trim(); - String description = relationMatcher.group(3).trim(); - String strengthStr = relationMatcher.group(4).trim(); - - Integer strength = parseStrength(strengthStr); - Double confidence = calculateConfidence(strength); - - ExtractedRelation relation = ExtractedRelation.builder() - .sourceEntity(sourceEntity) - .targetEntity(targetEntity) - .description(description) - .strength(strength) - .confidence(confidence) - .build(); - - relations.add(relation); - log.debug("解析到关系: sourceEntity={}, targetEntity={}, strength={}", - sourceEntity, targetEntity, strength); - } - - log.info("解析完成,实体数: {}, 关系数: {}", entities.size(), relations.size()); - - return GraphExtractionResult.builder() - .entities(entities) - .relations(relations) - .success(true) - .build(); - - } catch (Exception e) { - log.error("解析图谱响应失败", e); - return GraphExtractionResult.builder() - .entities(entities) - .relations(relations) - .success(false) - .errorMessage("解析失败: " + e.getMessage()) - .build(); - } - } - - /** - * 调用LLM获取响应(使用默认模型) - * - * @param prompt 提示词 - * @return LLM响应 - */ - private String callLLM(String prompt) { - // 获取聊天分类的最高优先级模型作为默认模型 - // 如果没有chat分类的模型,尝试查询任意可用模型 - ChatModelVo defaultModel = chatModelService.queryList(new ChatModelBo()).get(0); - - if (defaultModel == null) { - log.error("未找到可用的LLM模型"); - throw new RuntimeException("未找到可用的LLM模型,请先配置聊天模型"); - } - - log.info("使用默认模型: {}", defaultModel.getModelName()); - return callLLMWithModel(prompt, defaultModel); - } - - /** - * 使用指定模型调用LLM获取响应(使用工厂模式,支持多种LLM) - * - * @param prompt 提示词 - * @param chatModel 模型配置 - * @return LLM响应 - */ - private String callLLMWithModel(String prompt, ChatModelVo chatModel) { - log.info("调用LLM模型: model={}, category={}, 提示词长度={}", - chatModel.getModelName(), chatModel.getCategory(), prompt.length()); - - try { - // 根据模型类别获取对应的LLM服务实现 - IGraphLLMService llmService = llmServiceFactory.getLLMService(chatModel.getCategory()); - - // 调用LLM进行图谱抽取 - String responseText = llmService.extractGraph(prompt, chatModel); - - log.info("LLM调用成功: model={}, category={}, 响应长度={}", - chatModel.getModelName(), chatModel.getCategory(), responseText.length()); - - return responseText; - - } catch (IllegalArgumentException e) { - // 不支持的模型类别,降级到默认实现 - log.warn("不支持的模型类别: {}, 尝试使用OpenAI兼容模式", chatModel.getCategory()); - - try { - IGraphLLMService openAiService = llmServiceFactory.getLLMService("openai"); - return openAiService.extractGraph(prompt, chatModel); - } catch (Exception fallbackEx) { - log.error("降级调用也失败: {}", fallbackEx.getMessage(), fallbackEx); - throw new RuntimeException("LLM调用失败: " + fallbackEx.getMessage(), fallbackEx); - } - - } catch (Exception e) { - log.error("LLM调用失败: {}", e.getMessage(), e); - throw new RuntimeException("LLM调用失败: " + e.getMessage(), e); - } - } - - /** - * 解析关系强度 - * - * @param strengthStr 强度字符串 - * @return 强度值(0-10) - */ - private Integer parseStrength(String strengthStr) { - try { - // 尝试解析为整数 - int strength = Integer.parseInt(strengthStr); - // 限制在0-10范围内 - return Math.max(0, Math.min(10, strength)); - } catch (NumberFormatException e) { - log.debug("无法解析关系强度: {}, 使用默认值5", strengthStr); - return 5; // 默认中等强度 - } - } - - /** - * 验证实体是否有效 - * 过滤 N/A 以及包含 Neo4j 不支持的特殊字符的实体 - * - * @param name 实体名称 - * @param type 实体类型 - * @return true=无效,false=有效 - */ - private boolean isInvalidEntity(String name, String type) { - // 1. 检查是否为 N/A - if ("N/A".equalsIgnoreCase(name) || "N/A".equalsIgnoreCase(type)) { - return true; - } - - // 2. 检查是否为空或纯空格 - if (StrUtil.isBlank(name) || StrUtil.isBlank(type)) { - return true; - } - - // 3. 检查类型是否包含 Neo4j Label 不支持的字符 - // Neo4j Label 规则:不能包含 / : & | 等特殊字符 - if (type.matches(".*[/:&|\\\\].*")) { - log.warn("⚠️ 实体类型包含非法字符,将被过滤: type={}", type); - return true; - } - - // 4. 检查名称是否过长(Neo4j 建议 < 256) - if (name.length() > 255 || type.length() > 64) { - log.warn("⚠️ 实体名称或类型过长,将被过滤: name.length={}, type.length={}", - name.length(), type.length()); - return true; - } - - return false; - } - - /** - * 根据关系强度计算置信度 - * - * @param strength 关系强度(0-10) - * @return 置信度(0.0-1.0) - */ - private Double calculateConfidence(Integer strength) { - if (strength == null) { - return 0.5; - } - // 将0-10的强度映射到0.3-1.0的置信度 - return 0.3 + (strength / 10.0) * 0.7; - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphInstanceServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphInstanceServiceImpl.java deleted file mode 100644 index 33d7843b..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphInstanceServiceImpl.java +++ /dev/null @@ -1,284 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.domain.bo.graph.GraphInstance; -import org.ruoyi.mapper.graph.GraphInstanceMapper; -import org.ruoyi.service.graph.IGraphInstanceService; -import org.ruoyi.service.graph.IGraphStoreService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 图谱实例服务实现 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphInstanceServiceImpl implements IGraphInstanceService { - - private final GraphInstanceMapper graphInstanceMapper; - private final IGraphStoreService graphStoreService; - - @Override - @Transactional(rollbackFor = Exception.class) - public GraphInstance createInstance(String knowledgeId, String graphName, String config) { - // 检查是否已存在 - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphInstance::getKnowledgeId, knowledgeId); - GraphInstance existing = graphInstanceMapper.selectOne(wrapper); - - if (existing != null) { - log.warn("知识库 {} 已存在图谱实例", knowledgeId); - return existing; - } - - // 创建新实例 - GraphInstance instance = new GraphInstance(); - instance.setGraphUuid(String.valueOf(IdUtil.getSnowflake().nextId())); // UUID - instance.setKnowledgeId(knowledgeId); - instance.setGraphName(StringUtils.isNotBlank(graphName) ? graphName : "知识图谱-" + knowledgeId); - instance.setGraphStatus(0); // 0-未构建(新建时状态为未构建,需手动点击"构建"按钮) - instance.setNodeCount(0); - instance.setRelationshipCount(0); - - // 解析配置 - if (StringUtils.isNotBlank(config)) { - instance.setConfig(config); - } - - graphInstanceMapper.insert(instance); - - // 创建 Neo4j Schema - graphStoreService.createGraphSchema(knowledgeId); - - log.info("创建图谱实例成功: knowledgeId={}, instanceId={}", knowledgeId, instance.getId()); - return instance; - } - - @Override - public GraphInstance getById(Long id) { - return graphInstanceMapper.selectById(id); - } - - @Override - public GraphInstance getByUuid(String graphUuid) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphInstance::getGraphUuid, graphUuid); - return graphInstanceMapper.selectOne(wrapper); - } - - @Override - public boolean updateInstance(GraphInstance instance) { - try { - int rows = graphInstanceMapper.updateById(instance); - log.info("更新图谱实例: id={}, rows={}", instance.getId(), rows); - return rows > 0; - } catch (Exception e) { - log.error("更新图谱实例失败: id={}", instance.getId(), e); - return false; - } - } - - @Override - public List listByKnowledgeId(String knowledgeId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphInstance::getKnowledgeId, knowledgeId); - wrapper.orderByDesc(GraphInstance::getCreateTime); - return graphInstanceMapper.selectList(wrapper); - } - - @Override - public Page queryPage(Page page, String instanceName, String knowledgeId, Integer graphStatus) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - - // 图谱名称模糊查询 - if (StringUtils.isNotBlank(instanceName)) { - wrapper.like(GraphInstance::getGraphName, instanceName.trim()); - } - - // 知识库ID精确查询 - if (StringUtils.isNotBlank(knowledgeId)) { - wrapper.eq(GraphInstance::getKnowledgeId, knowledgeId.trim()); - } - - // 状态精确查询 - if (graphStatus != null) { - wrapper.eq(GraphInstance::getGraphStatus, graphStatus); - } - - // 只查询未删除的记录 - wrapper.eq(GraphInstance::getDelFlag, "0"); - - // 按创建时间倒序 - wrapper.orderByDesc(GraphInstance::getCreateTime); - - return graphInstanceMapper.selectPage(page, wrapper); - } - - @Override - public boolean updateStatus(String graphUuid, Integer status) { - try { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphInstance::getGraphUuid, graphUuid); - wrapper.set(GraphInstance::getGraphStatus, status); - - int rows = graphInstanceMapper.update(null, wrapper); - - log.info("更新图谱状态: graphUuid={}, status={}, rows={}", graphUuid, status, rows); - return rows > 0; - } catch (Exception e) { - log.error("更新图谱状态失败: graphUuid={}, status={}", graphUuid, status, e); - return false; - } - } - - @Override - public boolean updateCounts(String graphUuid, Integer nodeCount, Integer relationshipCount) { - try { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphInstance::getGraphUuid, graphUuid); - - if (nodeCount != null) { - wrapper.set(GraphInstance::getNodeCount, nodeCount); - } - if (relationshipCount != null) { - wrapper.set(GraphInstance::getRelationshipCount, relationshipCount); - } - - int rows = graphInstanceMapper.update(null, wrapper); - - log.info("更新图谱统计: graphUuid={}, nodeCount={}, relationshipCount={}, rows={}", - graphUuid, nodeCount, relationshipCount, rows); - return rows > 0; - } catch (Exception e) { - log.error("更新图谱统计失败: graphUuid={}", graphUuid, e); - return false; - } - } - - @Override - public boolean updateConfig(String graphUuid, String config) { - try { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(GraphInstance::getGraphUuid, graphUuid); - wrapper.set(GraphInstance::getConfig, config); - - int rows = graphInstanceMapper.update(null, wrapper); - - log.info("更新图谱配置: graphUuid={}, rows={}", graphUuid, rows); - return rows > 0; - } catch (Exception e) { - log.error("更新图谱配置失败: graphUuid={}", graphUuid, e); - return false; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean deleteInstance(String graphUuid) { - try { - log.info("🗑️ 开始删除图谱实例及数据,graphUuid: {}", graphUuid); - - // ⭐ 1. 先获取实例信息(获取knowledgeId) - GraphInstance instance = getByUuid(graphUuid); - if (instance == null) { - log.warn("⚠️ 图谱实例不存在: graphUuid={}", graphUuid); - return false; - } - - String knowledgeId = instance.getKnowledgeId(); - - // ⭐ 2. 删除Neo4j中的图数据(通过knowledgeId) - if (StrUtil.isNotBlank(knowledgeId)) { - log.info("删除Neo4j图数据,knowledgeId: {}", knowledgeId); - boolean neo4jDeleted = graphStoreService.deleteByKnowledgeId(knowledgeId); - if (neo4jDeleted) { - log.info("✅ Neo4j图数据删除成功"); - } else { - log.warn("⚠️ Neo4j图数据删除失败(可能是没有数据)"); - } - } else { - log.warn("⚠️ 实例没有关联知识库ID,跳过Neo4j数据删除"); - } - - // 3. 删除MySQL中的实例记录 - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(GraphInstance::getGraphUuid, graphUuid); - int rows = graphInstanceMapper.delete(wrapper); - - log.info("✅ 删除图谱实例成功: graphUuid={}, knowledgeId={}, rows={}", - graphUuid, knowledgeId, rows); - return rows > 0; - } catch (Exception e) { - log.error("❌ 删除图谱实例失败: graphUuid={}", graphUuid, e); - return false; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean deleteInstanceAndData(String graphUuid) { - try { - // 1. 删除 Neo4j 中的图谱数据 - boolean graphDeleted = graphStoreService.deleteGraph(graphUuid); - - // 2. 删除 MySQL 中的实例记录 - boolean instanceDeleted = deleteInstance(graphUuid); - - log.info("删除图谱实例及数据: graphUuid={}, graphDeleted={}, instanceDeleted={}", - graphUuid, graphDeleted, instanceDeleted); - - return graphDeleted && instanceDeleted; - } catch (Exception e) { - log.error("删除图谱实例及数据失败: graphUuid={}", graphUuid, e); - return false; - } - } - - @Override - public Map getStatistics(String graphUuid) { - try { - // 从 Neo4j 获取实时统计 - Map stats = graphStoreService.getGraphStatistics(graphUuid); - - // 更新到 MySQL(异步) - if (stats.containsKey("nodeCount") && stats.containsKey("relationshipCount")) { - updateCounts( - graphUuid, - (Integer) stats.get("nodeCount"), - (Integer) stats.get("relationshipCount") - ); - } - - // 添加实例信息 - GraphInstance instance = getByUuid(graphUuid); - if (instance != null) { - stats.put("graphName", instance.getGraphName()); - stats.put("status", instance.getGraphStatus()); - stats.put("createTime", instance.getCreateTime()); - stats.put("updateTime", instance.getUpdateTime()); - } - - return stats; - } catch (Exception e) { - log.error("获取图谱统计信息失败: graphUuid={}", graphUuid, e); - return new HashMap<>(); - } - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphRAGServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphRAGServiceImpl.java deleted file mode 100644 index 411d6730..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphRAGServiceImpl.java +++ /dev/null @@ -1,462 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.constant.GraphConstants; -import org.ruoyi.domain.bo.graph.GraphEdge; -import org.ruoyi.domain.bo.graph.GraphVertex; -import org.ruoyi.domain.dto.ExtractedEntity; -import org.ruoyi.domain.dto.ExtractedRelation; -import org.ruoyi.domain.dto.GraphExtractionResult; -import org.ruoyi.service.graph.IGraphExtractionService; -import org.ruoyi.service.graph.IGraphRAGService; -import org.ruoyi.service.graph.IGraphStoreService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; - -import java.util.*; - -/** - * GraphRAG服务实现 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphRAGServiceImpl implements IGraphRAGService { - - private final IGraphExtractionService graphExtractionService; - private final IGraphStoreService graphStoreService; - - @Override - public GraphExtractionResult ingestText(String text, String knowledgeId, Map metadata) { - return ingestTextWithModel(text, knowledgeId, metadata, null); - } - - @Override - public GraphExtractionResult ingestTextWithModel(String text, String knowledgeId, Map metadata, String modelName) { - log.info("开始将文本入库到图谱,知识库ID: {}, 模型: {}, 文本长度: {}", - knowledgeId, modelName != null ? modelName : "默认", text.length()); - - try { - // 1. 从文本中抽取实体和关系 - GraphExtractionResult extractionResult; - if (StrUtil.isNotBlank(modelName)) { - extractionResult = graphExtractionService.extractFromTextWithModel(text, modelName); - } else { - extractionResult = graphExtractionService.extractFromText(text); - } - - if (!extractionResult.getSuccess()) { - log.error("实体抽取失败: {}", extractionResult.getErrorMessage()); - return extractionResult; - } - - // 2. 将抽取的实体转换为图节点 - List vertices = convertEntitiesToVertices( - extractionResult.getEntities(), - knowledgeId, - metadata - ); - - // 3. 批量添加节点到Neo4j,并建立实体名称→nodeId的映射 - Map entityNameToNodeIdMap = new HashMap<>(); - if (!vertices.isEmpty()) { - int addedCount = graphStoreService.addVertices(vertices); - log.info("成功添加 {} 个节点到图谱", addedCount); - - // ⭐ 建立映射:实体名称 → nodeId - for (GraphVertex vertex : vertices) { - entityNameToNodeIdMap.put(vertex.getName(), vertex.getNodeId()); - } - log.debug("建立实体名称映射: {} 个实体", entityNameToNodeIdMap.size()); - } - - // 4. 将抽取的关系转换为图边,使用映射填充nodeId - List edges = convertRelationsToEdges( - extractionResult.getRelations(), - knowledgeId, - metadata, - entityNameToNodeIdMap // ⭐ 传入映射 - ); - - // 5. 批量添加关系到Neo4j - if (!edges.isEmpty()) { - int addedCount = graphStoreService.addEdges(edges); - log.info("成功添加 {} 个关系到图谱", addedCount); - } - - return extractionResult; - - } catch (Exception e) { - log.error("文本入库失败", e); - return GraphExtractionResult.builder() - .entities(new ArrayList<>()) - .relations(new ArrayList<>()) - .success(false) - .errorMessage(e.getMessage()) - .build(); - } - } - - @Override - public GraphExtractionResult ingestDocument(String documentText, String knowledgeId, Map metadata) { - return ingestDocumentWithModel(documentText, knowledgeId, metadata, null); - } - - @Override - public GraphExtractionResult ingestDocumentWithModel(String documentText, String knowledgeId, Map metadata, String modelName) { - log.info("开始将文档入库到图谱,知识库ID: {}, 模型: {}, 文档长度: {}", - knowledgeId, modelName != null ? modelName : "默认", documentText.length()); - - // 如果文档较短,直接处理 - if (documentText.length() < GraphConstants.RAG_MAX_SEGMENT_SIZE_IN_TOKENS * 4) { - return ingestTextWithModel(documentText, knowledgeId, metadata, modelName); - } - - // 文档较长,需要分片处理 - List chunks = splitDocument(documentText); - log.info("文档已分割为 {} 个片段", chunks.size()); - - // 合并结果 - List allEntities = new ArrayList<>(); - List allRelations = new ArrayList<>(); - int totalTokenUsed = 0; - - for (int i = 0; i < chunks.size(); i++) { - String chunk = chunks.get(i); - log.debug("处理第 {}/{} 个片段", i + 1, chunks.size()); - - // 为每个片段添加序号元数据 - Map chunkMetadata = new HashMap<>(metadata); - chunkMetadata.put("chunk_index", i); - chunkMetadata.put("total_chunks", chunks.size()); - - GraphExtractionResult result = ingestTextWithModel(chunk, knowledgeId, chunkMetadata, modelName); - - if (result.getSuccess()) { - allEntities.addAll(result.getEntities()); - allRelations.addAll(result.getRelations()); - if (result.getTokenUsed() != null) { - totalTokenUsed += result.getTokenUsed(); - } - } - } - - // 去重实体(基于名称和类型) - List uniqueEntities = deduplicateEntities(allEntities); - log.info("去重后实体数: {} -> {}", allEntities.size(), uniqueEntities.size()); - - return GraphExtractionResult.builder() - .entities(uniqueEntities) - .relations(allRelations) - .tokenUsed(totalTokenUsed) - .success(true) - .build(); - } - - @Override - public String retrieveFromGraph(String query, String knowledgeId, int maxResults) { - log.info("从图谱检索相关内容,查询: {}, 知识库ID: {}", query, knowledgeId); - - try { - // 1. 从查询中抽取关键词(简单分词) - List keywords = extractKeywords(query); - log.debug("提取的关键词: {}", keywords); - - if (keywords.isEmpty()) { - return "未能从查询中提取关键信息"; - } - - // 2. 在图谱中搜索相关实体节点 - List matchedNodes = new ArrayList<>(); - for (String keyword : keywords) { - List nodes = graphStoreService.searchVerticesByName( - keyword, knowledgeId, Math.min(5, maxResults) - ); - matchedNodes.addAll(nodes); - } - - if (matchedNodes.isEmpty()) { - return "图谱中未找到相关实体"; - } - - log.info("找到 {} 个匹配的实体节点", matchedNodes.size()); - - // 3. 去重(按nodeId) - Map uniqueNodes = new HashMap<>(); - for (GraphVertex node : matchedNodes) { - uniqueNodes.putIfAbsent(node.getNodeId(), node); - } - matchedNodes = new ArrayList<>(uniqueNodes.values()); - - // 限制结果数量 - if (matchedNodes.size() > maxResults) { - matchedNodes = matchedNodes.subList(0, maxResults); - } - - // 4. 为每个匹配节点获取邻居,构建子图上下文 - StringBuilder result = new StringBuilder(); - result.append("### 图谱检索结果\n\n"); - result.append(String.format("查询: %s\n", query)); - result.append(String.format("找到 %d 个相关实体:\n\n", matchedNodes.size())); - - for (int i = 0; i < matchedNodes.size(); i++) { - GraphVertex node = matchedNodes.get(i); - result.append(String.format("**%d. %s** (%s)\n", i + 1, node.getName(), node.getLabel())); - - if (StrUtil.isNotBlank(node.getDescription())) { - result.append(String.format(" 描述: %s\n", node.getDescription())); - } - - // 获取邻居节点(1跳) - List neighbors = graphStoreService.getNeighbors( - node.getNodeId(), knowledgeId, 5 - ); - - if (!neighbors.isEmpty()) { - result.append(" 关联实体: "); - List neighborNames = neighbors.stream() - .map(GraphVertex::getName) - .limit(5) - .collect(java.util.stream.Collectors.toList()); - result.append(String.join(", ", neighborNames)); - result.append("\n"); - } - - result.append("\n"); - } - - // 5. 添加统计信息 - result.append("---\n"); - result.append(String.format("总计: %d 个实体节点\n", matchedNodes.size())); - - return result.toString(); - - } catch (Exception e) { - log.error("图谱检索失败", e); - return "检索失败: " + e.getMessage(); - } - } - - /** - * 从查询中提取关键词 - * - * @param query 查询文本 - * @return 关键词列表 - */ - private List extractKeywords(String query) { - List keywords = new ArrayList<>(); - - // 简单的中文分词策略 - // 1. 去除标点符号 - String cleaned = query.replaceAll("[\\p{Punct}\\s]+", " "); - - // 2. 按空格分割 - String[] words = cleaned.split("\\s+"); - - // 3. 过滤停用词和短词 - Set stopWords = new HashSet<>(Arrays.asList( - "的", "了", "和", "是", "在", "我", "有", "个", "这", "那", "为", - "与", "或", "但", "等", "及", "而", "中", "如", "一", "二", "三" - )); - - for (String word : words) { - word = word.trim(); - if (word.length() >= 2 && !stopWords.contains(word)) { - keywords.add(word); - } - } - - // 4. 如果没有提取到关键词,尝试按2-3字切分 - if (keywords.isEmpty() && query.length() >= 2) { - for (int i = 0; i <= query.length() - 2; i++) { - String chunk = query.substring(i, Math.min(i + 3, query.length())); - if (chunk.length() >= 2 && !stopWords.contains(chunk)) { - keywords.add(chunk); - } - } - } - - // 去重并限制数量 - return keywords.stream() - .distinct() - .limit(5) - .collect(java.util.stream.Collectors.toList()); - } - - @Override - public boolean deleteGraphData(String knowledgeId) { - log.info("删除知识库图谱数据,知识库ID: {}", knowledgeId); - - try { - // 删除该知识库的所有节点和关系 - return graphStoreService.deleteByKnowledgeId(knowledgeId); - } catch (Exception e) { - log.error("删除图谱数据失败", e); - return false; - } - } - - /** - * 将抽取的实体转换为图节点 - */ - private List convertEntitiesToVertices( - List entities, - String knowledgeId, - Map metadata) { - - List vertices = new ArrayList<>(); - - for (ExtractedEntity entity : entities) { - GraphVertex vertex = new GraphVertex(); - vertex.setNodeId(IdUtil.simpleUUID()); // 生成唯一ID - vertex.setName(entity.getName()); - vertex.setLabel(entity.getType()); - vertex.setDescription(entity.getDescription()); - vertex.setKnowledgeId(knowledgeId); - vertex.setConfidence(entity.getConfidence() != null ? entity.getConfidence() : 1.0); - - // 添加元数据 - if (metadata != null && !metadata.isEmpty()) { - vertex.setMetadata(metadata); - } - - vertices.add(vertex); - } - - return vertices; - } - - /** - * 将抽取的关系转换为图边 - * - * @param relations 抽取的关系列表 - * @param knowledgeId 知识库ID - * @param metadata 元数据 - * @param entityNameToNodeIdMap 实体名称到节点ID的映射 - * @return 图边列表 - */ - private List convertRelationsToEdges( - List relations, - String knowledgeId, - Map metadata, - Map entityNameToNodeIdMap) { - - List edges = new ArrayList<>(); - int skippedCount = 0; - - for (ExtractedRelation relation : relations) { - // ⭐ 通过实体名称查找对应的nodeId - String sourceNodeId = entityNameToNodeIdMap.get(relation.getSourceEntity()); - String targetNodeId = entityNameToNodeIdMap.get(relation.getTargetEntity()); - - // 如果找不到对应的节点ID,跳过这个关系 - if (sourceNodeId == null || targetNodeId == null) { - log.warn("⚠️ 跳过关系(节点未找到): {} -> {}", - relation.getSourceEntity(), relation.getTargetEntity()); - skippedCount++; - continue; - } - - GraphEdge edge = new GraphEdge(); - edge.setEdgeId(IdUtil.simpleUUID()); - edge.setSourceNodeId(sourceNodeId); // ⭐ 设置源节点ID - edge.setTargetNodeId(targetNodeId); // ⭐ 设置目标节点ID - edge.setSourceName(relation.getSourceEntity()); - edge.setTargetName(relation.getTargetEntity()); - edge.setLabel("RELATED_TO"); // 默认关系类型 - edge.setDescription(relation.getDescription()); - edge.setWeight(relation.getStrength() / 10.0); // 转换为0-1的权重 - edge.setKnowledgeId(knowledgeId); - edge.setConfidence(relation.getConfidence() != null ? relation.getConfidence() : 1.0); - - // 添加元数据 - if (metadata != null && !metadata.isEmpty()) { - edge.setMetadata(metadata); - } - - edges.add(edge); - } - - if (skippedCount > 0) { - log.warn("⚠️ 共跳过 {} 个关系(对应的实体节点未找到)", skippedCount); - } - - return edges; - } - - /** - * 分割文档为多个片段 - */ - private List splitDocument(String text) { - List chunks = new ArrayList<>(); - int chunkSize = GraphConstants.RAG_MAX_SEGMENT_SIZE_IN_TOKENS * 4; // 简单估算字符数 - int overlap = GraphConstants.RAG_SEGMENT_OVERLAP_IN_TOKENS * 4; - - int start = 0; - while (start < text.length()) { - int end = Math.min(start + chunkSize, text.length()); - - // 尝试在句子边界分割 - if (end < text.length()) { - int lastPeriod = text.lastIndexOf('。', end); - int lastNewline = text.lastIndexOf('\n', end); - int boundary = Math.max(lastPeriod, lastNewline); - - if (boundary > start) { - end = boundary + 1; - } - } - - chunks.add(text.substring(start, end)); - - // ⭐ 修复死循环:确保 start 一定会增加 - // 如果已经到达文本末尾,直接退出 - if (end >= text.length()) { - break; - } - - // 计算下一个起始位置,确保至少前进1个字符 - int nextStart = end - overlap; - if (nextStart <= start) { - // 如果 overlap 太大导致无法前进,强制前进到 end - start = end; - } else { - start = nextStart; - } - } - - return chunks; - } - - /** - * 去重实体 - */ - private List deduplicateEntities(List entities) { - Map entityMap = new HashMap<>(); - - for (ExtractedEntity entity : entities) { - String key = entity.getName() + "|" + entity.getType(); - - if (!entityMap.containsKey(key)) { - entityMap.put(key, entity); - } else { - // 如果已存在,保留置信度更高的 - ExtractedEntity existing = entityMap.get(key); - double entityConf = entity.getConfidence() != null ? entity.getConfidence() : 1.0; - double existingConf = existing.getConfidence() != null ? existing.getConfidence() : 1.0; - if (entityConf > existingConf) { - entityMap.put(key, entity); - } - } - } - - return new ArrayList<>(entityMap.values()); - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphStoreServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphStoreServiceImpl.java deleted file mode 100644 index 26d0e4d4..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/GraphStoreServiceImpl.java +++ /dev/null @@ -1,929 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import com.alibaba.fastjson.JSON; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Record; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; -import org.neo4j.driver.types.Node; -import org.neo4j.driver.types.Relationship; -import org.ruoyi.config.GraphProperties; -import org.ruoyi.domain.bo.graph.GraphEdge; -import org.ruoyi.domain.bo.graph.GraphVertex; -import org.ruoyi.service.graph.IGraphStoreService; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; - -import java.util.*; -import java.util.stream.Collectors; - -import static org.neo4j.driver.Values.parameters; - -/** - * 图存储服务实现 - * 负责与 Neo4j 图数据库交互 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Slf4j -@Service -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class GraphStoreServiceImpl implements IGraphStoreService { - - private final Driver neo4jDriver; - private final GraphProperties graphProperties; - - // ==================== 节点操作 ==================== - - @Override - public boolean addVertex(GraphVertex vertex) { - try (Session session = neo4jDriver.session()) { - String cypher = "CREATE (n:" + vertex.getLabel() + " {" + - "id: $id, " + - "name: $name, " + - "description: $description, " + - "knowledgeId: $knowledgeId, " + - "docIds: $docIds, " + - "properties: $properties, " + - "confidence: $confidence" + - "}) RETURN n"; - - Result result = session.run(cypher, parameters( - "id", vertex.getNodeId(), // ⭐ 修复:使用 nodeId 而不是 id - "name", vertex.getName(), - "description", vertex.getDescription(), - "knowledgeId", vertex.getKnowledgeId(), - "docIds", vertex.getDocIds(), - "properties", vertex.getProperties(), - "confidence", vertex.getConfidence() - )); - - return result.hasNext(); - } catch (Exception e) { - log.error("添加节点失败: {}", vertex, e); - return false; - } - } - - @Override - public int addVertices(List vertices) { - if (vertices == null || vertices.isEmpty()) { - return 0; - } - - int successCount = 0; - int batchSize = graphProperties.getBatchSize(); - - try (Session session = neo4jDriver.session()) { - // 分批处理 - for (int i = 0; i < vertices.size(); i += batchSize) { - List batch = vertices.subList( - i, Math.min(i + batchSize, vertices.size()) - ); - - successCount += session.writeTransaction(tx -> { - int count = 0; - for (GraphVertex vertex : batch) { - String cypher = "CREATE (n:" + vertex.getLabel() + " {" + - "id: $id, name: $name, description: $description, " + - "knowledgeId: $knowledgeId, docIds: $docIds, " + - "properties: $properties, confidence: $confidence})"; - - tx.run(cypher, parameters( - "id", vertex.getNodeId(), // ⭐ 修复:使用 nodeId 而不是 id - "name", vertex.getName(), - "description", vertex.getDescription(), - "knowledgeId", vertex.getKnowledgeId(), - "docIds", vertex.getDocIds(), - "properties", vertex.getProperties(), - "confidence", vertex.getConfidence() - )); - count++; - } - return count; - }); - } - } catch (Exception e) { - log.error("批量添加节点失败", e); - } - - return successCount; - } - - @Override - public GraphVertex getVertex(String nodeId, String graphUuid) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (n) WHERE n.id = $nodeId AND n.knowledgeId = $graphUuid RETURN n"; - - Result result = session.run(cypher, parameters( - "nodeId", nodeId, - "graphUuid", graphUuid - )); - - if (result.hasNext()) { - Record record = result.single(); - return nodeToVertex(record.get("n").asNode()); - } - return null; - } catch (Exception e) { - log.error("获取节点失败: nodeId={}, graphUuid={}", nodeId, graphUuid, e); - return null; - } - } - - @Override - public List searchVertices(String graphUuid, String label, Integer limit) { - try (Session session = neo4jDriver.session()) { - StringBuilder cypher = new StringBuilder("MATCH (n"); - if (label != null && !label.isEmpty()) { - cypher.append(":").append(label); - } - cypher.append(") WHERE n.knowledgeId = $graphUuid RETURN n"); - - if (limit != null && limit > 0) { - cypher.append(" LIMIT $limit"); - } - - Map params = new HashMap<>(); - params.put("graphUuid", graphUuid); - if (limit != null && limit > 0) { - params.put("limit", limit); - } - - Result result = session.run(cypher.toString(), params); - - return result.stream() - .map(record -> nodeToVertex(record.get("n").asNode())) - .collect(Collectors.toList()); - } catch (Exception e) { - log.error("搜索节点失败: graphUuid={}, label={}", graphUuid, label, e); - return Collections.emptyList(); - } - } - - @Override - public List searchVerticesByName(String graphUuid, String name) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (n) WHERE n.knowledgeId = $graphUuid AND n.name CONTAINS $name RETURN n"; - - Result result = session.run(cypher, parameters( - "graphUuid", graphUuid, - "name", name - )); - - return result.stream() - .map(record -> nodeToVertex(record.get("n").asNode())) - .collect(Collectors.toList()); - } catch (Exception e) { - log.error("按名称搜索节点失败: graphUuid={}, name={}", graphUuid, name, e); - return Collections.emptyList(); - } - } - - @Override - public List searchVerticesByName(String keyword, String knowledgeId, Integer limit) { - List vertices = new ArrayList<>(); - - try (Session session = neo4jDriver.session()) { - String cypher; - Map params = new HashMap<>(); - params.put("keyword", keyword); - params.put("limit", limit); - - if (knowledgeId != null && !knowledgeId.isEmpty()) { - cypher = "MATCH (n {knowledgeId: $knowledgeId}) " + - "WHERE n.name CONTAINS $keyword " + - "RETURN n LIMIT $limit"; - params.put("knowledgeId", knowledgeId); - } else { - cypher = "MATCH (n) " + - "WHERE n.name CONTAINS $keyword " + - "RETURN n LIMIT $limit"; - } - - Result result = session.run(cypher, params); - - result.stream().forEach(record -> { - Node node = record.get("n").asNode(); - vertices.add(nodeToVertex(node)); - }); - - log.info("搜索到 {} 个节点,关键词: {}", vertices.size(), keyword); - return vertices; - } catch (Exception e) { - log.error("按关键词搜索节点失败: keyword={}, knowledgeId={}", keyword, knowledgeId, e); - return vertices; - } - } - - @Override - public boolean updateVertex(GraphVertex vertex) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (n {id: $id, knowledgeId: $knowledgeId}) " + - "SET n.name = $name, n.description = $description, " + - "n.properties = $properties, n.confidence = $confidence " + - "RETURN n"; - - Result result = session.run(cypher, parameters( - "id", vertex.getId(), - "knowledgeId", vertex.getKnowledgeId(), - "name", vertex.getName(), - "description", vertex.getDescription(), - "properties", vertex.getProperties(), - "confidence", vertex.getConfidence() - )); - - return result.hasNext(); - } catch (Exception e) { - log.error("更新节点失败: {}", vertex, e); - return false; - } - } - - @Override - public boolean deleteVertex(String nodeId, String graphUuid, boolean includeEdges) { - try (Session session = neo4jDriver.session()) { - String cypher; - if (includeEdges) { - cypher = "MATCH (n {id: $nodeId, knowledgeId: $graphUuid}) DETACH DELETE n"; - } else { - cypher = "MATCH (n {id: $nodeId, knowledgeId: $graphUuid}) DELETE n"; - } - - session.run(cypher, parameters( - "nodeId", nodeId, - "graphUuid", graphUuid - )); - - return true; - } catch (Exception e) { - log.error("删除节点失败: nodeId={}, graphUuid={}", nodeId, graphUuid, e); - return false; - } - } - - // ==================== 关系操作 ==================== - - @Override - public boolean addEdge(GraphEdge edge) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (s {id: $startNodeId, knowledgeId: $knowledgeId}) " + - "MATCH (t {id: $endNodeId, knowledgeId: $knowledgeId}) " + - "CREATE (s)-[r:" + edge.getLabel() + " {" + - "id: $id, description: $description, weight: $weight, " + - "docIds: $docIds, properties: $properties, confidence: $confidence" + - "}]->(t) RETURN r"; - - Result result = session.run(cypher, parameters( - "startNodeId", edge.getSourceNodeId(), - "endNodeId", edge.getTargetNodeId(), - "knowledgeId", edge.getKnowledgeId(), - "id", edge.getEdgeId(), - "description", edge.getDescription(), - "weight", edge.getWeight(), - "docIds", edge.getDocIds(), - "properties", edge.getProperties(), - "confidence", edge.getConfidence() - )); - - return result.hasNext(); - } catch (Exception e) { - log.error("添加关系失败: {}", edge, e); - return false; - } - } - - @Override - public int addEdges(List edges) { - if (edges == null || edges.isEmpty()) { - return 0; - } - - log.info("🔄 开始批量添加 {} 个关系到Neo4j", edges.size()); - int successCount = 0; - int failedCount = 0; - int batchSize = graphProperties.getBatchSize(); - - try (Session session = neo4jDriver.session()) { - for (int i = 0; i < edges.size(); i += batchSize) { - List batch = edges.subList( - i, Math.min(i + batchSize, edges.size()) - ); - - int batchIndex = i / batchSize + 1; - log.debug("处理第 {}/{} 批,本批 {} 个关系", - batchIndex, (edges.size() + batchSize - 1) / batchSize, batch.size()); - - successCount += session.writeTransaction(tx -> { - int count = 0; - for (GraphEdge edge : batch) { - try { - String cypher = "MATCH (s {id: $startNodeId, knowledgeId: $knowledgeId}) " + - "MATCH (t {id: $endNodeId, knowledgeId: $knowledgeId}) " + - "CREATE (s)-[r:" + edge.getLabel() + " {" + - "id: $id, knowledgeId: $knowledgeId, description: $description, weight: $weight, " + - "docIds: $docIds, properties: $properties, confidence: $confidence" + - "}]->(t)"; - - Result result = tx.run(cypher, parameters( - "startNodeId", edge.getSourceNodeId(), - "endNodeId", edge.getTargetNodeId(), - "knowledgeId", edge.getKnowledgeId(), - "id", edge.getEdgeId(), - "description", edge.getDescription(), - "weight", edge.getWeight(), - "docIds", edge.getDocIds(), - "properties", edge.getProperties(), - "confidence", edge.getConfidence() - )); - - // ⭐ 检查是否真的创建了关系 - if (result.consume().counters().relationshipsCreated() > 0) { - count++; - } else { - log.warn("⚠️ 关系创建失败(节点未找到): {} -> {} (knowledgeId: {})", - edge.getSourceNodeId(), edge.getTargetNodeId(), edge.getKnowledgeId()); - } - } catch (Exception e) { - log.error("❌ 添加单个关系失败: {} -> {}, 错误: {}", - edge.getSourceNodeId(), edge.getTargetNodeId(), e.getMessage()); - } - } - return count; - }); - } - } catch (Exception e) { - log.error("❌ 批量添加关系失败", e); - } - - failedCount = edges.size() - successCount; - log.info("✅ 关系添加完成: 成功 {}/{}, 失败 {}", successCount, edges.size(), failedCount); - - if (failedCount > 0) { - log.warn("⚠️ 有 {} 个关系添加失败,可能原因:", failedCount); - log.warn(" 1. 源节点或目标节点不存在"); - log.warn(" 2. sourceNodeId/targetNodeId 不匹配"); - log.warn(" 3. knowledgeId 不匹配"); - } - - return successCount; - } - - @Override - public GraphEdge getEdge(String edgeId, String graphUuid) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (s)-[r]->(t) " + - "WHERE r.id = $edgeId AND r.knowledgeId = $graphUuid " + - "RETURN s, r, t"; - - Result result = session.run(cypher, parameters( - "edgeId", edgeId, - "graphUuid", graphUuid - )); - - if (result.hasNext()) { - Record record = result.single(); - return relationshipToEdge( - record.get("s").asNode(), - record.get("r").asRelationship(), - record.get("t").asNode() - ); - } - return null; - } catch (Exception e) { - log.error("获取关系失败: edgeId={}, graphUuid={}", edgeId, graphUuid, e); - return null; - } - } - - @Override - public List searchEdges(String graphUuid, String sourceNodeId, String targetNodeId, Integer limit) { - try (Session session = neo4jDriver.session()) { - StringBuilder cypher = new StringBuilder("MATCH (s)-[r]->(t) WHERE r.knowledgeId = $graphUuid"); - - Map params = new HashMap<>(); - params.put("graphUuid", graphUuid); - - if (sourceNodeId != null && !sourceNodeId.isEmpty()) { - cypher.append(" AND s.id = $sourceNodeId"); - params.put("sourceNodeId", sourceNodeId); - } - - if (targetNodeId != null && !targetNodeId.isEmpty()) { - cypher.append(" AND t.id = $targetNodeId"); - params.put("targetNodeId", targetNodeId); - } - - cypher.append(" RETURN s, r, t"); - - if (limit != null && limit > 0) { - cypher.append(" LIMIT $limit"); - params.put("limit", limit); - } - - Result result = session.run(cypher.toString(), params); - - return result.stream() - .map(record -> relationshipToEdge( - record.get("s").asNode(), - record.get("r").asRelationship(), - record.get("t").asNode() - )) - .collect(Collectors.toList()); - } catch (Exception e) { - log.error("搜索关系失败: graphUuid={}", graphUuid, e); - return Collections.emptyList(); - } - } - - @Override - public List getNodeEdges(String nodeId, String graphUuid, String direction) { - try (Session session = neo4jDriver.session()) { - String cypher; - switch (direction.toUpperCase()) { - case "IN": - cypher = "MATCH (s)-[r]->(t {id: $nodeId, knowledgeId: $graphUuid}) RETURN s, r, t"; - break; - case "OUT": - cypher = "MATCH (s {id: $nodeId, knowledgeId: $graphUuid})-[r]->(t) RETURN s, r, t"; - break; - case "BOTH": - default: - cypher = "MATCH (s)-[r]-(t {id: $nodeId, knowledgeId: $graphUuid}) RETURN s, r, t"; - break; - } - - Result result = session.run(cypher, parameters( - "nodeId", nodeId, - "graphUuid", graphUuid - )); - - return result.stream() - .map(record -> relationshipToEdge( - record.get("s").asNode(), - record.get("r").asRelationship(), - record.get("t").asNode() - )) - .collect(Collectors.toList()); - } catch (Exception e) { - log.error("获取节点关系失败: nodeId={}, graphUuid={}", nodeId, graphUuid, e); - return Collections.emptyList(); - } - } - - @Override - public boolean updateEdge(GraphEdge edge) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH ()-[r {id: $id, knowledgeId: $knowledgeId}]->() " + - "SET r.description = $description, r.weight = $weight, " + - "r.properties = $properties, r.confidence = $confidence " + - "RETURN r"; - - Result result = session.run(cypher, parameters( - "id", edge.getEdgeId(), - "knowledgeId", edge.getKnowledgeId(), - "description", edge.getDescription(), - "weight", edge.getWeight(), - "properties", edge.getProperties(), - "confidence", edge.getConfidence() - )); - - return result.hasNext(); - } catch (Exception e) { - log.error("更新关系失败: {}", edge, e); - return false; - } - } - - @Override - public boolean deleteEdge(String edgeId, String graphUuid) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH ()-[r {id: $edgeId, knowledgeId: $graphUuid}]->() DELETE r"; - - session.run(cypher, parameters( - "edgeId", edgeId, - "graphUuid", graphUuid - )); - - return true; - } catch (Exception e) { - log.error("删除关系失败: edgeId={}, graphUuid={}", edgeId, graphUuid, e); - return false; - } - } - - // ==================== 图谱管理 ==================== - - @Override - public boolean createGraphSchema(String graphUuid) { - try (Session session = neo4jDriver.session()) { - // 创建索引以提高查询性能 - 使用正确的Neo4j 4.x/5.x语法 - session.run("CREATE INDEX entity_id_index IF NOT EXISTS FOR (n:Entity) ON (n.id)"); - session.run("CREATE INDEX entity_knowledge_id_index IF NOT EXISTS FOR (n:Entity) ON (n.knowledgeId)"); - session.run("CREATE INDEX entity_name_index IF NOT EXISTS FOR (n:Entity) ON (n.name)"); - - // 为关系也创建索引 - session.run("CREATE INDEX relation_id_index IF NOT EXISTS FOR ()-[r:RELATION]-() ON (r.id)"); - session.run("CREATE INDEX relation_type_index IF NOT EXISTS FOR ()-[r:RELATION]-() ON (r.type)"); - - log.info("图谱Schema创建成功: graphUuid={}", graphUuid); - return true; - } catch (Exception e) { - log.error("创建图谱Schema失败: graphUuid={}", graphUuid, e); - return false; - } - } - - @Override - public boolean deleteGraph(String graphUuid) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (n {knowledgeId: $graphUuid}) DETACH DELETE n"; - - session.run(cypher, parameters("graphUuid", graphUuid)); - - log.info("图谱数据删除成功: graphUuid={}", graphUuid); - return true; - } catch (Exception e) { - log.error("删除图谱数据失败: graphUuid={}", graphUuid, e); - return false; - } - } - - @Override - public Map getGraphStatistics(String graphUuid) { - Map stats = new HashMap<>(); - - try (Session session = neo4jDriver.session()) { - // 统计节点数 - Result nodeResult = session.run( - "MATCH (n {knowledgeId: $graphUuid}) RETURN count(n) as count", - parameters("graphUuid", graphUuid) - ); - stats.put("nodeCount", nodeResult.single().get("count").asInt()); - - // 统计关系数 - Result relResult = session.run( - "MATCH ()-[r {knowledgeId: $graphUuid}]->() RETURN count(r) as count", - parameters("graphUuid", graphUuid) - ); - stats.put("relationshipCount", relResult.single().get("count").asInt()); - - } catch (Exception e) { - log.error("获取图谱统计信息失败: graphUuid={}", graphUuid, e); - } - - return stats; - } - - // ==================== 高级查询 ==================== - - @Override - public List> findPaths(String sourceNodeId, String targetNodeId, String graphUuid, Integer maxDepth) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH path = (s {id: $sourceNodeId, knowledgeId: $graphUuid})" + - "-[*1.." + (maxDepth != null ? maxDepth : 5) + "]->" + - "(t {id: $targetNodeId, knowledgeId: $graphUuid}) " + - "RETURN nodes(path) as path LIMIT 10"; - - Result result = session.run(cypher, parameters( - "sourceNodeId", sourceNodeId, - "targetNodeId", targetNodeId, - "graphUuid", graphUuid - )); - - List> paths = new ArrayList<>(); - result.stream().forEach(record -> { - List path = record.get("path").asList( - value -> nodeToVertex(value.asNode()) - ); - paths.add(path); - }); - - return paths; - } catch (Exception e) { - log.error("查找路径失败: source={}, target={}, graphUuid={}", sourceNodeId, targetNodeId, graphUuid, e); - return Collections.emptyList(); - } - } - - @Override - public List findNeighbors(String nodeId, String graphUuid, Integer depth) { - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (s {id: $nodeId, knowledgeId: $graphUuid})" + - "-[*1.." + (depth != null ? depth : 1) + "]-(neighbor) " + - "RETURN DISTINCT neighbor"; - - Result result = session.run(cypher, parameters( - "nodeId", nodeId, - "graphUuid", graphUuid - )); - - return result.stream() - .map(record -> nodeToVertex(record.get("neighbor").asNode())) - .collect(Collectors.toList()); - } catch (Exception e) { - log.error("查找邻居节点失败: nodeId={}, graphUuid={}", nodeId, graphUuid, e); - return Collections.emptyList(); - } - } - - @Override - public List> executeCypher(String cypher, Map params) { - try (Session session = neo4jDriver.session()) { - Result result = session.run(cypher, params != null ? params : Collections.emptyMap()); - - return result.stream() - .map(Record::asMap) - .collect(Collectors.toList()); - } catch (Exception e) { - log.error("执行Cypher查询失败: {}", cypher, e); - return Collections.emptyList(); - } - } - - // ==================== 辅助方法 ==================== - - /** - * Neo4j Node 转换为 GraphVertex - */ - private GraphVertex nodeToVertex(Node node) { - GraphVertex vertex = new GraphVertex(); - vertex.setNodeId(node.get("id").asString(null)); - vertex.setLabel(node.labels().iterator().next()); - vertex.setName(node.get("name").asString(null)); - vertex.setDescription(node.get("description").asString(null)); - vertex.setKnowledgeId(node.get("knowledgeId").asString(null)); - vertex.setDocIds(node.get("docIds").asString(null)); - - // 处理 confidence(可能为空) - if (node.containsKey("confidence") && !node.get("confidence").isNull()) { - vertex.setConfidence(node.get("confidence").asDouble()); - } - - // 处理 properties(转换为JSON字符串) - if (node.containsKey("properties") && !node.get("properties").isNull()) { - Map propsMap = node.get("properties").asMap(); - vertex.setProperties(JSON.toJSONString(propsMap)); - } - - return vertex; - } - - /** - * Neo4j Relationship 转换为 GraphEdge - */ - private GraphEdge relationshipToEdge(Node source, Relationship rel, Node target) { - GraphEdge edge = new GraphEdge(); - edge.setEdgeId(rel.get("id").asString(null)); - edge.setLabel(rel.type()); - edge.setSourceNodeId(source.get("id").asString(null)); - edge.setTargetNodeId(target.get("id").asString(null)); - edge.setDescription(rel.get("description").asString(null)); - edge.setKnowledgeId(rel.get("knowledgeId").asString(null)); - edge.setDocIds(rel.get("docIds").asString(null)); - - // 处理 weight(可能为空) - if (rel.containsKey("weight") && !rel.get("weight").isNull()) { - edge.setWeight(rel.get("weight").asDouble()); - } - - // 处理 confidence(可能为空) - if (rel.containsKey("confidence") && !rel.get("confidence").isNull()) { - edge.setConfidence(rel.get("confidence").asDouble()); - } - - // 处理 properties(转换为JSON字符串) - if (rel.containsKey("properties") && !rel.get("properties").isNull()) { - Map propsMap = rel.get("properties").asMap(); - edge.setProperties(JSON.toJSONString(propsMap)); - } - - return edge; - } - - // ==================== 新增的方法实现 ==================== - - @Override - public List queryVerticesByKnowledgeId(String knowledgeId, Integer limit) { - List vertices = new ArrayList<>(); - - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH (n {knowledgeId: $knowledgeId}) " + - "RETURN n LIMIT $limit"; - - Result result = session.run(cypher, parameters( - "knowledgeId", knowledgeId, - "limit", limit - )); - - result.stream().forEach(record -> { - Node node = record.get("n").asNode(); - vertices.add(nodeToVertex(node)); - }); - - log.info("查询到 {} 个节点,知识库ID: {}", vertices.size(), knowledgeId); - return vertices; - } catch (Exception e) { - log.error("查询节点失败", e); - return vertices; - } - } - - @Override - public List queryEdgesByKnowledgeId(String knowledgeId, Integer limit) { - List edges = new ArrayList<>(); - - try (Session session = neo4jDriver.session()) { - // ⭐ 修复:通过节点的 knowledgeId 过滤关系,兼容旧数据 - String cypher = "MATCH (s {knowledgeId: $knowledgeId})-[r]->(t {knowledgeId: $knowledgeId}) " + - "RETURN s, r, t LIMIT $limit"; - - log.info("🔍 开始查询关系 - knowledgeId: {}, limit: {}", knowledgeId, limit); - log.debug("执行Cypher: {}", cypher); - - Result result = session.run(cypher, parameters( - "knowledgeId", knowledgeId, - "limit", limit - )); - - int count = 0; - while (result.hasNext()) { - Record record = result.next(); - Node source = record.get("s").asNode(); - Relationship rel = record.get("r").asRelationship(); - Node target = record.get("t").asNode(); - - // 调试:打印关系详情 - if (count < 3) { // 只打印前3个 - log.debug("关系#{} - 类型: {}, 起点: {} ({}), 终点: {} ({})", - count + 1, - rel.type(), - source.get("name").asString(), - source.get("id").asString(), - target.get("name").asString(), - target.get("id").asString() - ); - } - - edges.add(relationshipToEdge(source, rel, target)); - count++; - } - - log.info("✅ 查询到 {} 个关系,知识库ID: {}", edges.size(), knowledgeId); - return edges; - } catch (Exception e) { - log.error("❌ 查询关系失败 - knowledgeId: {}", knowledgeId, e); - return edges; - } - } - - @Override - public boolean deleteByKnowledgeId(String knowledgeId) { - try (Session session = neo4jDriver.session()) { - log.info("🗑️ 开始删除知识库图谱数据,knowledgeId: {}", knowledgeId); - - // ⭐ 先删除关系(通过节点的knowledgeId过滤,兼容旧数据) - String deleteRelsQuery = "MATCH (s {knowledgeId: $knowledgeId})-[r]->(t {knowledgeId: $knowledgeId}) DELETE r"; - Result relResult = session.run(deleteRelsQuery, parameters("knowledgeId", knowledgeId)); - int deletedRels = relResult.consume().counters().relationshipsDeleted(); - log.info("✅ 删除了 {} 个关系", deletedRels); - - // 再删除节点 - String deleteNodesQuery = "MATCH (n {knowledgeId: $knowledgeId}) DELETE n"; - Result nodeResult = session.run(deleteNodesQuery, parameters("knowledgeId", knowledgeId)); - int deletedNodes = nodeResult.consume().counters().nodesDeleted(); - log.info("✅ 删除了 {} 个节点", deletedNodes); - - log.info("✅ 删除知识库图谱数据成功,knowledgeId: {}, 节点: {}, 关系: {}", - knowledgeId, deletedNodes, deletedRels); - return true; - } catch (Exception e) { - log.error("❌ 删除知识库图谱数据失败,knowledgeId: {}", knowledgeId, e); - return false; - } - } - - @Override - public Map getStatistics(String knowledgeId) { - Map stats = new HashMap<>(); - - try (Session session = neo4jDriver.session()) { - // 统计节点数 - String nodeCountQuery = "MATCH (n {knowledgeId: $knowledgeId}) RETURN count(n) as count"; - Result nodeResult = session.run(nodeCountQuery, parameters("knowledgeId", knowledgeId)); - int nodeCount = 0; - if (nodeResult.hasNext()) { - nodeCount = nodeResult.single().get("count").asInt(); - stats.put("nodeCount", nodeCount); - stats.put("totalNodes", nodeCount); // ⭐ 前端需要的字段 - } - - // ⭐ 统计关系数(通过节点过滤,与查询/删除逻辑一致) - String relCountQuery = "MATCH (s {knowledgeId: $knowledgeId})-[r]->(t {knowledgeId: $knowledgeId}) RETURN count(r) as count"; - Result relResult = session.run(relCountQuery, parameters("knowledgeId", knowledgeId)); - int relCount = 0; - if (relResult.hasNext()) { - relCount = relResult.single().get("count").asInt(); - stats.put("relationshipCount", relCount); - stats.put("totalEdges", relCount); // ⭐ 前端需要的字段 - } - - // 统计节点类型分布 - String labelQuery = "MATCH (n {knowledgeId: $knowledgeId}) " + - "RETURN labels(n)[0] as label, count(*) as count " + - "ORDER BY count DESC LIMIT 10"; - Result labelResult = session.run(labelQuery, parameters("knowledgeId", knowledgeId)); - - Map labelDistribution = new HashMap<>(); - labelResult.stream().forEach(record -> { - String label = record.get("label").asString(); - int count = record.get("count").asInt(); - labelDistribution.put(label, count); - }); - stats.put("labelDistribution", labelDistribution); - stats.put("entityTypes", labelDistribution); // ⭐ 前端需要的字段 - - log.info("📊 获取图谱统计信息: knowledgeId={}, 节点={}, 关系={}, 类型={}", - knowledgeId, nodeCount, relCount, labelDistribution.size()); - return stats; - } catch (Exception e) { - log.error("❌ 获取统计信息失败: knowledgeId={}", knowledgeId, e); - return stats; - } - } - - @Override - public List getNeighbors(String nodeId, String knowledgeId, Integer limit) { - List neighbors = new ArrayList<>(); - - try (Session session = neo4jDriver.session()) { - String cypher; - Map params = new HashMap<>(); - params.put("nodeId", nodeId); - params.put("limit", limit); - - if (knowledgeId != null && !knowledgeId.isEmpty()) { - cypher = "MATCH (n {id: $nodeId, knowledgeId: $knowledgeId})-[]-(neighbor {knowledgeId: $knowledgeId}) " + - "RETURN DISTINCT neighbor LIMIT $limit"; - params.put("knowledgeId", knowledgeId); - } else { - cypher = "MATCH (n {id: $nodeId})-[]-(neighbor) " + - "RETURN DISTINCT neighbor LIMIT $limit"; - } - - Result result = session.run(cypher, params); - - result.stream().forEach(record -> { - Node node = record.get("neighbor").asNode(); - neighbors.add(nodeToVertex(node)); - }); - - log.info("查询到 {} 个邻居节点", neighbors.size()); - return neighbors; - } catch (Exception e) { - log.error("查询邻居节点失败", e); - return neighbors; - } - } - - @Override - public List> findPaths(String startNodeId, String endNodeId, Integer maxDepth) { - List> paths = new ArrayList<>(); - - try (Session session = neo4jDriver.session()) { - String cypher = "MATCH path = (start {id: $startNodeId})-[*1.." + maxDepth + "]-(end {id: $endNodeId}) " + - "RETURN nodes(path) as pathNodes " + - "LIMIT 10"; - - Result result = session.run(cypher, parameters( - "startNodeId", startNodeId, - "endNodeId", endNodeId - )); - - result.stream().forEach(record -> { - List pathNodes = record.get("pathNodes").asList(); - List path = new ArrayList<>(); - - for (Object obj : pathNodes) { - if (obj instanceof Node) { - path.add(nodeToVertex((Node) obj)); - } - } - - if (!path.isEmpty()) { - paths.add(path); - } - }); - - log.info("查询到 {} 条路径", paths.size()); - return paths; - } catch (Exception e) { - log.error("查询路径失败", e); - return paths; - } - } -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/OpenAIGraphLLMServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/OpenAIGraphLLMServiceImpl.java deleted file mode 100644 index 776c781c..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/graph/impl/OpenAIGraphLLMServiceImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.ruoyi.service.graph.impl; - -import dev.langchain4j.model.openai.OpenAiChatModel; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.service.graph.IGraphLLMService; -import org.springframework.stereotype.Service; - - -/** - * OpenAI 图谱LLM服务实现 - * 支持 OpenAI 兼容的模型(GPT-3.5, GPT-4, 自定义OpenAI兼容接口等) - * - * @author ruoyi - * @date 2025-10-11 - */ -@Slf4j -@Service -public class OpenAIGraphLLMServiceImpl implements IGraphLLMService { - - @Override - public String extractGraph(String prompt, ChatModelVo chatModel) { - log.info("OpenAI模型调用: model={}, apiHost={}, 提示词长度={}", - chatModel.getModelName(), chatModel.getApiHost(), prompt.length()); - try { - OpenAiChatModel model = OpenAiChatModel.builder() - .baseUrl(chatModel.getApiHost()) - .apiKey(chatModel.getApiKey()) - .modelName(chatModel.getModelName()) - .build(); - String content = model.chat(prompt); - String responseText = content != null ? content : ""; - log.info("OpenAI模型响应成功:, 响应长度={}", responseText.length()); - return responseText; - } catch (Exception e) { - log.error("OpenAI模型调用失败: {}", e.getMessage(), e); - throw new RuntimeException("OpenAI模型调用失败: " + e.getMessage(), e); - } - } - - @Override - public String getCategory() { - return "openai"; // 对应 ChatModel 表中的 category 字段 - } -} - diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/AbstractVectorStoreStrategy.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/AbstractVectorStoreStrategy.java index cbd34cb8..906c8090 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/AbstractVectorStoreStrategy.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/AbstractVectorStoreStrategy.java @@ -4,6 +4,7 @@ import dev.langchain4j.model.embedding.EmbeddingModel; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.config.VectorStoreProperties; import org.ruoyi.factory.EmbeddingModelFactory; import org.ruoyi.service.vector.VectorStoreService; @@ -22,6 +23,9 @@ public abstract class AbstractVectorStoreStrategy implements VectorStoreService private final EmbeddingModelFactory embeddingModelFactory; + protected final IChatModelService chatModelService; + + /** * 将float数组转换为Float对象数组 */ @@ -37,8 +41,8 @@ public abstract class AbstractVectorStoreStrategy implements VectorStoreService * 获取向量模型 */ @SneakyThrows - protected EmbeddingModel getEmbeddingModel(String modelName, Integer dimension) { - return embeddingModelFactory.createModel(modelName, dimension); + protected EmbeddingModel getEmbeddingModel(String modelName) { + return embeddingModelFactory.createModel(modelName); } /** diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/MilvusVectorStoreStrategy.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/MilvusVectorStoreStrategy.java index 0bd199b2..baf1c612 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/MilvusVectorStoreStrategy.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/MilvusVectorStoreStrategy.java @@ -14,6 +14,8 @@ import io.milvus.param.IndexType; import io.milvus.param.MetricType; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.config.VectorStoreProperties; import org.ruoyi.domain.bo.vector.QueryVectorBo; import org.ruoyi.domain.bo.vector.StoreEmbeddingBo; @@ -31,8 +33,9 @@ import java.util.stream.IntStream; public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy { public MilvusVectorStoreStrategy(VectorStoreProperties vectorStoreProperties, + IChatModelService chatModelService, EmbeddingModelFactory embeddingModelFactory) { - super(vectorStoreProperties, embeddingModelFactory); + super(vectorStoreProperties, embeddingModelFactory, chatModelService); } // 缓存不同集合与 autoFlush 配置的 Milvus 连接 @@ -42,38 +45,27 @@ public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy { * 获取 Milvus Store,支持动态维度 */ private EmbeddingStore getMilvusStore(String collectionName, int dimension, boolean autoFlushOnInsert) { - String key = collectionName + "|" + dimension + "|" + autoFlushOnInsert; - return storeCache.computeIfAbsent(key, k -> - MilvusEmbeddingStore.builder() - .uri(vectorStoreProperties.getMilvus().getUrl()) - .collectionName(collectionName) - .dimension(dimension) - .indexType(IndexType.IVF_FLAT) - .metricType(MetricType.L2) - .autoFlushOnInsert(autoFlushOnInsert) - .idFieldName("id") - .textFieldName("text") - .metadataFieldName("metadata") - .vectorFieldName("vector") - .build() - ); + + return MilvusEmbeddingStore.builder() + .uri(vectorStoreProperties.getMilvus().getUrl()) + .collectionName(collectionName) + .dimension(dimension) + .indexType(IndexType.IVF_FLAT) + .metricType(MetricType.L2) + .autoFlushOnInsert(autoFlushOnInsert) + .idFieldName("id") + .textFieldName("text") + .metadataFieldName("metadata") + .vectorFieldName("vector") + .build(); } /** * 获取 embedding 模型的实际维度 */ private int getModelDimension(String modelName) { - try { - EmbeddingModel model = getEmbeddingModel(modelName, null); - // 使用一个测试文本获取向量维度 - Embedding testEmbedding = model.embed("test").content(); - int dimension = testEmbedding.dimension(); - log.info("Detected embedding model dimension: {} for model: {}", dimension, modelName); - return dimension; - } catch (Exception e) { - log.warn("Failed to detect model dimension for: {}, using default 1024", modelName, e); - return 1024; // 默认使用 1024 (bge-m3 的维度) - } + ChatModelVo modelConfig = chatModelService.selectModelByName(modelName); + return modelConfig.getModelDimension(); } @Override @@ -88,7 +80,7 @@ public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy { @Override public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) { int dimension = getModelDimension(storeEmbeddingBo.getEmbeddingModelName()); - EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName(), dimension); + EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName()); List chunkList = storeEmbeddingBo.getChunkList(); List fidList = storeEmbeddingBo.getFids(); @@ -121,7 +113,7 @@ public class MilvusVectorStoreStrategy extends AbstractVectorStoreStrategy { @Override public List getQueryVector(QueryVectorBo queryVectorBo) { int dimension = getModelDimension(queryVectorBo.getEmbeddingModelName()); - EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName(), dimension); + EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName()); Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content(); String collectionName = vectorStoreProperties.getMilvus().getCollectionname() + queryVectorBo.getKid(); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/WeaviateVectorStoreStrategy.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/WeaviateVectorStoreStrategy.java index 15368140..c62a8470 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/WeaviateVectorStoreStrategy.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/vector/impl/WeaviateVectorStoreStrategy.java @@ -7,6 +7,7 @@ import dev.langchain4j.model.embedding.EmbeddingModel; import io.weaviate.client.WeaviateClient; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.core.exception.ServiceException; import org.ruoyi.config.VectorStoreProperties; import org.ruoyi.domain.bo.vector.QueryVectorBo; @@ -41,8 +42,9 @@ public class WeaviateVectorStoreStrategy extends AbstractVectorStoreStrategy { private WeaviateClient client; public WeaviateVectorStoreStrategy(VectorStoreProperties vectorStoreProperties, + IChatModelService chatModelService, EmbeddingModelFactory embeddingModelFactory) { - super(vectorStoreProperties, embeddingModelFactory); + super(vectorStoreProperties, embeddingModelFactory,chatModelService); } @Override @@ -91,12 +93,12 @@ public class WeaviateVectorStoreStrategy extends AbstractVectorStoreStrategy { @Override public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) { createSchema(storeEmbeddingBo.getKid(), storeEmbeddingBo.getEmbeddingModelName()); - EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName(), null); + EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getEmbeddingModelName()); List chunkList = storeEmbeddingBo.getChunkList(); List fidList = storeEmbeddingBo.getFids(); String kid = storeEmbeddingBo.getKid(); String docId = storeEmbeddingBo.getDocId(); - log.info("向量存储条数记录: " + chunkList.size()); + log.info("向量存储条数记录: {}", chunkList.size()); long startTime = System.currentTimeMillis(); for (int i = 0; i < chunkList.size(); i++) { String text = chunkList.get(i); @@ -123,7 +125,7 @@ public class WeaviateVectorStoreStrategy extends AbstractVectorStoreStrategy { @Override public List getQueryVector(QueryVectorBo queryVectorBo) { createSchema(queryVectorBo.getKid(), queryVectorBo.getEmbeddingModelName()); - EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName(), null); + EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getEmbeddingModelName()); Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content(); float[] vector = queryEmbedding.vector(); List vectorStrings = new ArrayList<>(); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/util/Neo4jTestUtil.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/util/Neo4jTestUtil.java deleted file mode 100644 index 997326e9..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/util/Neo4jTestUtil.java +++ /dev/null @@ -1,369 +0,0 @@ -package org.ruoyi.util; - -import lombok.extern.slf4j.Slf4j; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Record; -import org.neo4j.driver.Result; -import org.neo4j.driver.Session; -import org.neo4j.driver.types.Node; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -import static org.neo4j.driver.Values.parameters; - -/** - * Neo4j连接测试工具类 - * - * @author ruoyi - * @date 2025-09-30 - */ -@Slf4j -@Component -@ConditionalOnProperty(prefix = "knowledge.graph", name = "enabled", havingValue = "true") -public class Neo4jTestUtil { - - private final Driver driver; - - public Neo4jTestUtil(Driver driver) { - this.driver = driver; - } - - /** - * 测试Neo4j连接 - * - * @return 测试结果 - */ - public Map testConnection() { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - // 1. 测试基本连接 - Result pingResult = session.run("RETURN 1 as num"); - if (pingResult.hasNext()) { - Record record = pingResult.single(); - result.put("connection", "SUCCESS"); - result.put("pingResult", record.get("num").asInt()); - } - - // 2. 获取数据库信息 - Result dbInfoResult = session.run("CALL dbms.components() YIELD name, versions, edition"); - if (dbInfoResult.hasNext()) { - Record dbInfo = dbInfoResult.single(); - result.put("neo4jVersion", dbInfo.get("versions").asList().get(0)); - result.put("neo4jEdition", dbInfo.get("edition").asString()); - } - - // 3. 测试节点数量 - Result countResult = session.run("MATCH (n) RETURN count(n) as count"); - if (countResult.hasNext()) { - result.put("totalNodes", countResult.single().get("count").asInt()); - } - - log.info("Neo4j连接测试成功: {}", result); - return result; - - } catch (Exception e) { - log.error("Neo4j连接测试失败", e); - result.put("connection", "FAILED"); - result.put("error", e.getMessage()); - return result; - } - } - - /** - * 创建测试节点 - * - * @param name 节点名称 - * @return 创建的节点信息 - */ - public Map createTestNode(String name) { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - Result queryResult = session.run( - "CREATE (p:TestNode {name: $name, createTime: datetime()}) RETURN p", - parameters("name", name) - ); - - if (queryResult.hasNext()) { - Record record = queryResult.single(); - Node node = record.get("p").asNode(); - - result.put("success", true); - result.put("nodeId", node.elementId()); - result.put("labels", node.labels()); - result.put("properties", node.asMap()); - - log.info("测试节点创建成功: {}", result); - } - - return result; - - } catch (Exception e) { - log.error("创建测试节点失败", e); - result.put("success", false); - result.put("error", e.getMessage()); - return result; - } - } - - /** - * 查询测试节点 - * - * @param name 节点名称 - * @return 节点信息 - */ - public Map queryTestNode(String name) { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - Result queryResult = session.run( - "MATCH (p:TestNode {name: $name}) RETURN p", - parameters("name", name) - ); - - if (queryResult.hasNext()) { - Record record = queryResult.single(); - Node node = record.get("p").asNode(); - - result.put("found", true); - result.put("nodeId", node.elementId()); - result.put("properties", node.asMap()); - } else { - result.put("found", false); - } - - return result; - - } catch (Exception e) { - log.error("查询测试节点失败", e); - result.put("error", e.getMessage()); - return result; - } - } - - /** - * 删除所有测试节点 - * - * @return 删除的节点数量 - */ - public Map deleteAllTestNodes() { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - Result queryResult = session.run( - "MATCH (p:TestNode) DELETE p RETURN count(p) as deleted" - ); - - if (queryResult.hasNext()) { - int deleted = queryResult.single().get("deleted").asInt(); - result.put("success", true); - result.put("deletedCount", deleted); - log.info("删除测试节点数量: {}", deleted); - } - - return result; - - } catch (Exception e) { - log.error("删除测试节点失败", e); - result.put("success", false); - result.put("error", e.getMessage()); - return result; - } - } - - /** - * 创建测试关系 - * - * @param sourceName 源节点名称 - * @param targetName 目标节点名称 - * @return 创建结果 - */ - public Map createTestRelationship(String sourceName, String targetName) { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - // 先创建两个节点 - session.run( - "MERGE (s:TestNode {name: $sourceName}) " + - "MERGE (t:TestNode {name: $targetName})", - parameters("sourceName", sourceName, "targetName", targetName) - ); - - // 创建关系 - Result queryResult = session.run( - "MATCH (s:TestNode {name: $sourceName}) " + - "MATCH (t:TestNode {name: $targetName}) " + - "CREATE (s)-[r:TEST_RELATION {createTime: datetime()}]->(t) " + - "RETURN r", - parameters("sourceName", sourceName, "targetName", targetName) - ); - - if (queryResult.hasNext()) { - result.put("success", true); - result.put("source", sourceName); - result.put("target", targetName); - log.info("测试关系创建成功: {} -> {}", sourceName, targetName); - } - - return result; - - } catch (Exception e) { - log.error("创建测试关系失败", e); - result.put("success", false); - result.put("error", e.getMessage()); - return result; - } - } - - /** - * 获取Neo4j统计信息 - * - * @return 统计信息 - */ - public Map getStatistics() { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - // 节点总数 - Result nodeCountResult = session.run("MATCH (n) RETURN count(n) as count"); - result.put("totalNodes", nodeCountResult.single().get("count").asInt()); - - // 关系总数 - Result relCountResult = session.run("MATCH ()-[r]->() RETURN count(r) as count"); - result.put("totalRelationships", relCountResult.single().get("count").asInt()); - - // 节点标签分布 - Result labelResult = session.run( - "MATCH (n) RETURN labels(n) as label, count(*) as count ORDER BY count DESC LIMIT 10" - ); - Map labelDistribution = new HashMap<>(); - labelResult.stream().forEach(record -> { - String label = record.get("label").asList().toString(); - int count = record.get("count").asInt(); - labelDistribution.put(label, count); - }); - result.put("labelDistribution", labelDistribution); - - log.info("Neo4j统计信息: {}", result); - return result; - - } catch (Exception e) { - log.error("获取统计信息失败", e); - result.put("error", e.getMessage()); - return result; - } - } - - /** - * 调试关系查询 - 详细诊断指定知识库的关系数据 - * - * @param knowledgeId 知识库ID - * @return 调试信息 - */ - public Map debugRelationships(String knowledgeId) { - Map result = new HashMap<>(); - - try (Session session = driver.session()) { - log.info("🔍 开始调试关系查询 - knowledgeId: {}", knowledgeId); - - // 1. 检查该知识库的节点数量 - Result nodeCountResult = session.run( - "MATCH (n {knowledgeId: $knowledgeId}) RETURN count(n) as count", - parameters("knowledgeId", knowledgeId) - ); - int nodeCount = nodeCountResult.single().get("count").asInt(); - result.put("nodeCount", nodeCount); - log.info("✅ 该知识库节点数量: {}", nodeCount); - - // 2. 检查总关系数量(不限制知识库) - Result totalRelResult = session.run("MATCH ()-[r]->() RETURN count(r) as count"); - int totalRelCount = totalRelResult.single().get("count").asInt(); - result.put("totalRelationships", totalRelCount); - log.info("✅ 数据库总关系数量: {}", totalRelCount); - - // 3. 检查带 knowledgeId 的关系数量(旧查询方式) - Result relWithKnowledgeIdResult = session.run( - "MATCH ()-[r {knowledgeId: $knowledgeId}]->() RETURN count(r) as count", - parameters("knowledgeId", knowledgeId) - ); - int relWithKnowledgeId = relWithKnowledgeIdResult.single().get("count").asInt(); - result.put("relationshipsWithKnowledgeId", relWithKnowledgeId); - log.info("✅ 带 knowledgeId 的关系数量: {}", relWithKnowledgeId); - - // 4. 通过节点过滤关系数量(新查询方式) - Result relByNodesResult = session.run( - "MATCH (s {knowledgeId: $knowledgeId})-[r]->(t {knowledgeId: $knowledgeId}) RETURN count(r) as count", - parameters("knowledgeId", knowledgeId) - ); - int relByNodes = relByNodesResult.single().get("count").asInt(); - result.put("relationshipsByNodes", relByNodes); - log.info("✅ 通过节点过滤的关系数量: {}", relByNodes); - - // 5. 采样前5个关系详情(如果有) - if (relByNodes > 0) { - Result sampleResult = session.run( - "MATCH (s {knowledgeId: $knowledgeId})-[r]->(t {knowledgeId: $knowledgeId}) " + - "RETURN s.name as sourceName, s.id as sourceId, type(r) as relType, " + - "t.name as targetName, t.id as targetId, r.knowledgeId as relKnowledgeId " + - "LIMIT 5", - parameters("knowledgeId", knowledgeId) - ); - - java.util.List> samples = new java.util.ArrayList<>(); - sampleResult.stream().forEach(record -> { - Map sample = new HashMap<>(); - sample.put("sourceName", record.get("sourceName").asString()); - sample.put("sourceId", record.get("sourceId").asString()); - sample.put("relationType", record.get("relType").asString()); - sample.put("targetName", record.get("targetName").asString()); - sample.put("targetId", record.get("targetId").asString()); - sample.put("relationshipKnowledgeId", - record.get("relKnowledgeId").isNull() ? null : record.get("relKnowledgeId").asString()); - samples.add(sample); - }); - result.put("sampleRelationships", samples); - log.info("✅ 采样到 {} 个关系", samples.size()); - } - - // 6. 检查是否有孤立节点(没有任何关系的节点) - Result isolatedNodesResult = session.run( - "MATCH (n {knowledgeId: $knowledgeId}) " + - "WHERE NOT (n)-[]-() " + - "RETURN count(n) as count", - parameters("knowledgeId", knowledgeId) - ); - int isolatedNodes = isolatedNodesResult.single().get("count").asInt(); - result.put("isolatedNodes", isolatedNodes); - log.info("✅ 孤立节点数量: {}", isolatedNodes); - - // 7. 诊断结论 - Map diagnosis = new HashMap<>(); - if (nodeCount == 0) { - diagnosis.put("issue", "该知识库没有节点数据"); - diagnosis.put("solution", "请先构建图谱或检查 knowledgeId 是否正确"); - } else if (relByNodes == 0 && totalRelCount > 0) { - diagnosis.put("issue", "数据库中有关系,但该知识库查询不到"); - diagnosis.put("solution", "可能是 knowledgeId 不匹配,检查关系的 knowledgeId 属性"); - } else if (relByNodes == 0) { - diagnosis.put("issue", "该知识库没有关系数据"); - diagnosis.put("solution", "检查 LLM 抽取是否识别到实体关系"); - } else { - diagnosis.put("status", "正常"); - diagnosis.put("message", "关系数据正常,可以正常查询"); - } - result.put("diagnosis", diagnosis); - - log.info("🎯 调试完成: {}", diagnosis); - return result; - - } catch (Exception e) { - log.error("❌ 调试关系查询失败", e); - result.put("error", e.getMessage()); - return result; - } - } -} diff --git a/ruoyi-modules/ruoyi-demo/pom.xml b/ruoyi-modules/ruoyi-demo/pom.xml deleted file mode 100644 index 985bf032..00000000 --- a/ruoyi-modules/ruoyi-demo/pom.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - org.ruoyi - ruoyi-modules - ${revision} - - 4.0.0 - - ruoyi-demo - - - demo模块 - - - - - - - org.ruoyi - ruoyi-common-core - - - - org.ruoyi - ruoyi-common-doc - - - - org.ruoyi - ruoyi-common-sms - - - - org.ruoyi - ruoyi-common-mail - - - - org.ruoyi - ruoyi-common-redis - - - - org.ruoyi - ruoyi-common-idempotent - - - - org.ruoyi - ruoyi-common-mybatis - - - - org.ruoyi - ruoyi-common-log - - - - org.ruoyi - ruoyi-common-excel - - - - org.ruoyi - ruoyi-common-security - - - - org.ruoyi - ruoyi-common-web - - - - org.ruoyi - ruoyi-common-ratelimiter - - - - org.ruoyi - ruoyi-common-translation - - - - org.ruoyi - ruoyi-common-sensitive - - - - org.ruoyi - ruoyi-common-encrypt - - - - org.ruoyi - ruoyi-common-tenant - - - - org.ruoyi - ruoyi-common-websocket - - - - - diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/MailSendController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/MailSendController.java deleted file mode 100644 index 9b4b334a..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/MailSendController.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.ruoyi.demo.controller; - -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.mail.utils.MailUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.util.Arrays; - - -/** - * 邮件发送案例 - * - * @author Michelle.Chung - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/mail") -public class MailSendController { - - /** - * 发送邮件 - * - * @param to 接收人 - * @param subject 标题 - * @param text 内容 - */ - @GetMapping("/sendSimpleMessage") - public R sendSimpleMessage(String to, String subject, String text) { - MailUtils.sendText(to, subject, text); - return R.ok(); - } - - /** - * 发送邮件(带附件) - * - * @param to 接收人 - * @param subject 标题 - * @param text 内容 - */ - @GetMapping("/sendMessageWithAttachment") - public R sendMessageWithAttachment(String to, String subject, String text) { - // 附件路径 禁止前端传递 有任意读取系统文件风险 - MailUtils.sendText(to, subject, text, new File("/xxx/xxx")); - return R.ok(); - } - - /** - * 发送邮件(多附件) - * - * @param to 接收人 - * @param subject 标题 - * @param text 内容 - */ - @GetMapping("/sendMessageWithAttachments") - public R sendMessageWithAttachments(String to, String subject, String text) { - // 附件路径 禁止前端传递 有任意读取系统文件风险 - String[] paths = new String[]{"/xxx/xxx", "/xxx/xxx"}; - File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new); - MailUtils.sendText(to, subject, text, array); - return R.ok(); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisCacheController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisCacheController.java deleted file mode 100644 index cedb7fbd..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisCacheController.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.ruoyi.demo.controller; - -import cn.hutool.core.thread.ThreadUtil; -import org.ruoyi.common.core.constant.CacheNames; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.redis.utils.RedisUtils; -import lombok.RequiredArgsConstructor; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.time.Duration; - -/** - * spring-cache 演示案例 - * - * @author Lion Li - */ -// 类级别 缓存统一配置 -//@CacheConfig(cacheNames = CacheNames.DEMO_CACHE) -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/cache") -public class RedisCacheController { - - /** - * 测试 @Cacheable - *

- * 表示这个方法有了缓存的功能,方法的返回值会被缓存下来 - * 下一次调用该方法前,会去检查是否缓存中已经有值 - * 如果有就直接返回,不调用方法 - * 如果没有,就调用方法,然后把结果缓存起来 - * 这个注解「一般用在查询方法上」 - *

- * 重点说明: 缓存注解严谨与其他筛选数据功能一起使用 - * 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题 - *

- * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 - */ - @Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null") - @GetMapping("/test1") - public R test1(String key, String value) { - return R.ok("操作成功", value); - } - - /** - * 测试 @CachePut - *

- * 加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用 - * 它「通常用在新增或者实时更新方法上」 - *

- * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 - */ - @CachePut(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null") - @GetMapping("/test2") - public R test2(String key, String value) { - return R.ok("操作成功", value); - } - - /** - * 测试 @CacheEvict - *

- * 使用了CacheEvict注解的方法,会清空指定缓存 - * 「一般用在删除的方法上」 - *

- * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 - */ - @CacheEvict(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null") - @GetMapping("/test3") - public R test3(String key, String value) { - return R.ok("操作成功", value); - } - - /** - * 测试设置过期时间 - * 手动设置过期时间10秒 - * 11秒后获取 判断是否相等 - */ - @GetMapping("/test6") - public R test6(String key, String value) { - RedisUtils.setCacheObject(key, value); - boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10)); - System.out.println("***********" + flag); - ThreadUtil.sleep(11 * 1000); - Object obj = RedisUtils.getCacheObject(key); - return R.ok(value.equals(obj)); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisLockController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisLockController.java deleted file mode 100644 index 656caa86..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisLockController.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.ruoyi.demo.controller; - -import cn.hutool.core.thread.ThreadUtil; -import com.baomidou.lock.LockInfo; -import com.baomidou.lock.LockTemplate; -import com.baomidou.lock.annotation.Lock4j; -import com.baomidou.lock.executor.RedissonLockExecutor; -import org.ruoyi.common.core.domain.R; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.time.LocalTime; - - -/** - * 测试分布式锁的样例 - * - * @author shenxinquan - */ -@Slf4j -@RestController -@RequestMapping("/demo/redisLock") -public class RedisLockController { - - @Autowired - private LockTemplate lockTemplate; - - /** - * 测试lock4j 注解 - */ - @Lock4j(keys = {"#key"}) - @GetMapping("/testLock4j") - public R testLock4j(String key, String value) { - System.out.println("start:" + key + ",time:" + LocalTime.now()); - ThreadUtil.sleep(10000); - System.out.println("end :" + key + ",time:" + LocalTime.now()); - return R.ok("操作成功", value); - } - - /** - * 测试lock4j 工具 - */ - @GetMapping("/testLock4jLockTemplate") - public R testLock4jLockTemplate(String key, String value) { - final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class); - if (null == lockInfo) { - throw new RuntimeException("业务处理中,请稍后再试"); - } - // 获取锁成功,处理业务 - try { - ThreadUtil.sleep(8000); - System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName()); - } finally { - //释放锁 - lockTemplate.releaseLock(lockInfo); - } - //结束 - return R.ok("操作成功", value); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisPubSubController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisPubSubController.java deleted file mode 100644 index af35c64d..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisPubSubController.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.redis.utils.RedisUtils; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Redis 发布订阅 演示案例 - * - * @author Lion Li - */ -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/redis/pubsub") -public class RedisPubSubController { - - /** - * 发布消息 - * - * @param key 通道Key - * @param value 发送内容 - */ - @GetMapping("/pub") - public R pub(String key, String value) { - RedisUtils.publish(key, value, consumer -> { - System.out.println("发布通道 => " + key + ", 发送值 => " + value); - }); - return R.ok("操作成功"); - } - - /** - * 订阅消息 - * - * @param key 通道Key - */ - @GetMapping("/sub") - public R sub(String key) { - RedisUtils.subscribe(key, String.class, msg -> { - System.out.println("订阅通道 => " + key + ", 接收值 => " + msg); - }); - return R.ok("操作成功"); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisRateLimiterController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisRateLimiterController.java deleted file mode 100644 index 74705baa..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisRateLimiterController.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.ratelimiter.annotation.RateLimiter; -import org.ruoyi.common.ratelimiter.enums.LimitType; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - - -/** - * 测试分布式限流样例 - * - * @author Lion Li - */ -@Slf4j -@RestController -@RequestMapping("/demo/rateLimiter") -public class RedisRateLimiterController { - - /** - * 测试全局限流 - * 全局影响 - */ - @RateLimiter(count = 2, time = 10) - @GetMapping("/test") - public R test(String value) { - return R.ok("操作成功", value); - } - - /** - * 测试请求IP限流 - * 同一IP请求受影响 - */ - @RateLimiter(count = 2, time = 10, limitType = LimitType.IP) - @GetMapping("/testip") - public R testip(String value) { - return R.ok("操作成功", value); - } - - /** - * 测试集群实例限流 - * 启动两个后端服务互不影响 - */ - @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER) - @GetMapping("/testcluster") - public R testcluster(String value) { - return R.ok("操作成功", value); - } - - /** - * 测试请求IP限流(key基于参数获取) - * 同一IP请求受影响 - * - * 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0} - */ - @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value") - @GetMapping("/testObj") - public R testObj(String value) { - return R.ok("操作成功", value); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/SmsController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/SmsController.java deleted file mode 100644 index 378fb706..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/SmsController.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.ruoyi.demo.controller; - -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.domain.R; -import org.dromara.sms4j.api.SmsBlend; -import org.dromara.sms4j.api.entity.SmsResponse; -import org.dromara.sms4j.core.factory.SmsFactory; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.LinkedHashMap; - -/** - * 短信演示案例 - * 请先阅读文档 否则无法使用 - * - * @author Lion Li - * @version 4.2.0 - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/sms") -public class SmsController { - /** - * 发送短信Aliyun - * - * @param phones 电话号 - * @param templateId 模板ID - */ - @GetMapping("/sendAliyun") - public R sendAliyun(String phones, String templateId) { - LinkedHashMap map = new LinkedHashMap<>(1); - map.put("code", "1234"); - SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); - SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map); - return R.ok(smsResponse); - } - - /** - * 发送短信Tencent - * - * @param phones 电话号 - * @param templateId 模板ID - */ - @GetMapping("/sendTencent") - public R sendTencent(String phones, String templateId) { - LinkedHashMap map = new LinkedHashMap<>(1); -// map.put("2", "测试测试"); - map.put("1", "1234"); - SmsBlend smsBlend = SmsFactory.getSmsBlend("config2"); - SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map); - return R.ok(smsResponse); - } - - /** - * 添加黑名单 - * - * @param phone 手机号 - */ - @GetMapping("/addBlacklist") - public R addBlacklist(String phone){ - SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); - smsBlend.joinInBlacklist(phone); - return R.ok(); - } - - /** - * 移除黑名单 - * - * @param phone 手机号 - */ - @GetMapping("/removeBlacklist") - public R removeBlacklist(String phone){ - SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); - smsBlend.removeFromBlacklist(phone); - return R.ok(); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/Swagger3DemoController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/Swagger3DemoController.java deleted file mode 100644 index 90068379..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/Swagger3DemoController.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -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.multipart.MultipartFile; - -/** - * swagger3 用法示例 - * - * @author Lion Li - */ -@RestController -@RequestMapping("/swagger/demo") -public class Swagger3DemoController { - - /** - * 上传请求 - * 必须使用 @RequestPart 注解标注为文件 - * - * @param file 文件 - */ - @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public R upload(@RequestPart("file") MultipartFile file) { - return R.ok("操作成功", file.getOriginalFilename()); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestBatchController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestBatchController.java deleted file mode 100644 index c9473605..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestBatchController.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.ruoyi.demo.controller; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.demo.domain.TestDemo; -import org.ruoyi.demo.mapper.TestDemoMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.ArrayList; -import java.util.List; - -/** - * 测试批量方法 - * - * @author Lion Li - * @date 2021-05-30 - */ -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/batch") -public class TestBatchController extends BaseController { - - /** - * 为了便于测试 直接引入mapper - */ - private final TestDemoMapper testDemoMapper; - - /** - * 新增批量方法 可完美替代 saveBatch 秒级插入上万数据 (对mysql负荷较大) - *

- * 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度 - */ - @PostMapping("/add") -// @DS("slave") - public R add() { - List list = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { - TestDemo testDemo = new TestDemo(); - testDemo.setOrderNum(-1); - testDemo.setTestKey("批量新增"); - testDemo.setValue("测试新增"); - list.add(testDemo); - } - return toAjax(testDemoMapper.insertBatch(list)); - } - - /** - * 新增或更新 可完美替代 saveOrUpdateBatch 高性能 - *

- * 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度 - */ - @PostMapping("/addOrUpdate") -// @DS("slave") - public R addOrUpdate() { - List list = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { - TestDemo testDemo = new TestDemo(); - testDemo.setOrderNum(-1); - testDemo.setTestKey("批量新增"); - testDemo.setValue("测试新增"); - list.add(testDemo); - } - testDemoMapper.insertBatch(list); - for (int i = 0; i < list.size(); i++) { - TestDemo testDemo = list.get(i); - testDemo.setTestKey("批量新增或修改"); - testDemo.setValue("批量新增或修改"); - if (i % 2 == 0) { - testDemo.setId(null); - } - } - return toAjax(testDemoMapper.insertOrUpdateBatch(list)); - } - - /** - * 删除批量方法 - */ - @DeleteMapping() -// @DS("slave") - public R remove() { - return toAjax(testDemoMapper.delete(new LambdaQueryWrapper() - .eq(TestDemo::getOrderNum, -1L))); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestDemoController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestDemoController.java deleted file mode 100644 index de1f6770..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestDemoController.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.ruoyi.demo.controller; - -import cn.dev33.satoken.annotation.SaCheckPermission; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.utils.MapstructUtils; -import org.ruoyi.common.core.utils.ValidatorUtils; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.core.validate.QueryGroup; -import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.common.idempotent.annotation.RepeatSubmit; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.common.excel.core.ExcelResult; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.common.log.annotation.Log; -import org.ruoyi.common.log.enums.BusinessType; -import org.ruoyi.demo.domain.TestDemo; -import org.ruoyi.demo.domain.bo.TestDemoBo; -import org.ruoyi.demo.domain.bo.TestDemoImportVo; -import org.ruoyi.demo.domain.vo.TestDemoVo; -import org.ruoyi.demo.service.ITestDemoService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -import java.util.*; -import java.util.concurrent.TimeUnit; - -/** - * 测试单表Controller - * - * @author Lion Li - * @date 2021-07-26 - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/demo") -public class TestDemoController extends BaseController { - - private final ITestDemoService testDemoService; - - /** - * 查询测试单表列表 - */ - @SaCheckPermission("demo:demo:list") - @GetMapping("/list") - public TableDataInfo list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) { - return testDemoService.queryPageList(bo, pageQuery); - } - - /** - * 自定义分页查询 - */ - @SaCheckPermission("demo:demo:list") - @GetMapping("/page") - public TableDataInfo page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) { - return testDemoService.customPageList(bo, pageQuery); - } - - /** - * 导入数据 - * - * @param file 导入文件 - */ - @Log(title = "测试单表", businessType = BusinessType.IMPORT) - @SaCheckPermission("demo:demo:import") - @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public R importData(@RequestPart("file") MultipartFile file) throws Exception { - ExcelResult excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true); - List list = MapstructUtils.convert(excelResult.getList(), TestDemo.class); - testDemoService.saveBatch(list); - return R.ok(excelResult.getAnalysis()); - } - - /** - * 导出测试单表列表 - */ - @SaCheckPermission("demo:demo:export") - @Log(title = "测试单表", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(@Validated TestDemoBo bo, HttpServletResponse response) { - List list = testDemoService.queryList(bo); - // 测试雪花id导出 -// for (TestDemoVo vo : list) { -// vo.setId(1234567891234567893L); -// } - ExcelUtil.exportExcel(list, "测试单表", TestDemoVo.class, response); - } - - /** - * 获取测试单表详细信息 - * - * @param id 测试ID - */ - @SaCheckPermission("demo:demo:query") - @GetMapping("/{id}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable("id") Long id) { - return R.ok(testDemoService.queryById(id)); - } - - /** - * 新增测试单表 - */ - @SaCheckPermission("demo:demo:add") - @Log(title = "测试单表", businessType = BusinessType.INSERT) - @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "{repeat.submit.message}") - @PostMapping() - public R add(@RequestBody TestDemoBo bo) { - // 使用校验工具对标 @Validated(AddGroup.class) 注解 - // 用于在非 Controller 的地方校验对象 - ValidatorUtils.validate(bo, AddGroup.class); - return toAjax(testDemoService.insertByBo(bo)); - } - - /** - * 修改测试单表 - */ - @SaCheckPermission("demo:demo:edit") - @Log(title = "测试单表", businessType = BusinessType.UPDATE) - @RepeatSubmit - @PutMapping() - public R edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) { - return toAjax(testDemoService.updateByBo(bo)); - } - - /** - * 删除测试单表 - * - * @param ids 测试ID串 - */ - @SaCheckPermission("demo:demo:remove") - @Log(title = "测试单表", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Long[] ids) { - return toAjax(testDemoService.deleteWithValidByIds(Arrays.asList(ids), true)); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestEncryptController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestEncryptController.java deleted file mode 100644 index 9897ce15..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestEncryptController.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.ruoyi.demo.domain.TestDemoEncrypt; -import org.ruoyi.demo.mapper.TestDemoEncryptMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.HashMap; -import java.util.Map; - - -/** - * 测试数据库加解密功能 - * - * @author Lion Li - */ -@Validated -@RestController -@RequestMapping("/demo/encrypt") -public class TestEncryptController { - - @Autowired - private TestDemoEncryptMapper mapper; - @Value("${mybatis-encryptor.enable}") - private Boolean encryptEnable; - - /** - * 测试数据库加解密 - * - * @param key 测试key - * @param value 测试value - */ - @GetMapping() - public R> test(String key, String value) { - if (!encryptEnable) { - throw new RuntimeException("加密功能未开启!"); - } - Map map = new HashMap<>(2); - TestDemoEncrypt demo = new TestDemoEncrypt(); - demo.setTestKey(key); - demo.setValue(value); - mapper.insert(demo); - map.put("加密", demo); - TestDemoEncrypt testDemo = mapper.selectById(demo.getId()); - map.put("解密", testDemo); - return R.ok(map); - } - - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestExcelController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestExcelController.java deleted file mode 100644 index 405cf029..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestExcelController.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.ruoyi.demo.controller; - -import cn.hutool.core.collection.CollUtil; -import jakarta.servlet.http.HttpServletResponse; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.excel.core.ExcelResult; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.demo.domain.vo.ExportDemoVo; -import org.ruoyi.demo.listener.ExportDemoListener; -import org.ruoyi.demo.service.IExportExcelService; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 测试Excel功能 - * - * @author Lion Li - */ -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/excel") -public class TestExcelController { - - private final IExportExcelService exportExcelService; - - /** - * 单列表多数据 - */ - @GetMapping("/exportTemplateOne") - public void exportTemplateOne(HttpServletResponse response) { - Map map = new HashMap<>(); - map.put("title", "单列表多数据"); - map.put("test1", "数据测试1"); - map.put("test2", "数据测试2"); - map.put("test3", "数据测试3"); - map.put("test4", "数据测试4"); - map.put("testTest", "666"); - List list = new ArrayList<>(); - list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4")); - list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8")); - list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12")); - ExcelUtil.exportTemplate(CollUtil.newArrayList(map, list), "单列表.xlsx", "excel/单列表.xlsx", response); - } - - /** - * 多列表多数据 - */ - @GetMapping("/exportTemplateMuliti") - public void exportTemplateMuliti(HttpServletResponse response) { - Map map = new HashMap<>(); - map.put("title1", "标题1"); - map.put("title2", "标题2"); - map.put("title3", "标题3"); - map.put("title4", "标题4"); - map.put("author", "Lion Li"); - List list1 = new ArrayList<>(); - list1.add(new TestObj1("list1测试1", "list1测试2", "list1测试3")); - list1.add(new TestObj1("list1测试4", "list1测试5", "list1测试6")); - list1.add(new TestObj1("list1测试7", "list1测试8", "list1测试9")); - List list2 = new ArrayList<>(); - list2.add(new TestObj1("list2测试1", "list2测试2", "list2测试3")); - list2.add(new TestObj1("list2测试4", "list2测试5", "list2测试6")); - List list3 = new ArrayList<>(); - list3.add(new TestObj1("list3测试1", "list3测试2", "list3测试3")); - List list4 = new ArrayList<>(); - list4.add(new TestObj1("list4测试1", "list4测试2", "list4测试3")); - list4.add(new TestObj1("list4测试4", "list4测试5", "list4测试6")); - list4.add(new TestObj1("list4测试7", "list4测试8", "list4测试9")); - list4.add(new TestObj1("list4测试10", "list4测试11", "list4测试12")); - Map multiListMap = new HashMap<>(); - multiListMap.put("map", map); - multiListMap.put("data1", list1); - multiListMap.put("data2", list2); - multiListMap.put("data3", list3); - multiListMap.put("data4", list4); - ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response); - } - - /** - * 导出下拉框 - * - * @param response / - */ - @GetMapping("/exportWithOptions") - public void exportWithOptions(HttpServletResponse response) { - exportExcelService.exportWithOptions(response); - } - - /** - * 自定义导出 - * - * @param response / - */ - @GetMapping("/customExport") - public void customExport(HttpServletResponse response) throws IOException { - exportExcelService.customExport(response); - } - - /** - * 多个sheet导出 - */ - @GetMapping("/exportTemplateMultiSheet") - public void exportTemplateMultiSheet(HttpServletResponse response) { - List list1 = new ArrayList<>(); - list1.add(new TestObj1("list1测试1", "list1测试2", "list1测试3")); - list1.add(new TestObj1("list1测试4", "list1测试5", "list1测试6")); - List list2 = new ArrayList<>(); - list2.add(new TestObj1("list2测试1", "list2测试2", "list2测试3")); - list2.add(new TestObj1("list2测试4", "list2测试5", "list2测试6")); - List list3 = new ArrayList<>(); - list3.add(new TestObj1("list3测试1", "list3测试2", "list3测试3")); - list3.add(new TestObj1("list3测试4", "list3测试5", "list3测试6")); - List list4 = new ArrayList<>(); - list4.add(new TestObj1("list4测试1", "list4测试2", "list4测试3")); - list4.add(new TestObj1("list4测试4", "list4测试5", "list4测试6")); - - List> list = new ArrayList<>(); - Map sheetMap1 = new HashMap<>(); - sheetMap1.put("data1", list1); - Map sheetMap2 = new HashMap<>(); - sheetMap2.put("data2", list2); - Map sheetMap3 = new HashMap<>(); - sheetMap3.put("data3", list3); - Map sheetMap4 = new HashMap<>(); - sheetMap4.put("data4", list4); - - list.add(sheetMap1); - list.add(sheetMap2); - list.add(sheetMap3); - list.add(sheetMap4); - ExcelUtil.exportTemplateMultiSheet(list, "多sheet列表", "excel/多sheet列表.xlsx", response); - } - - /** - * 导入表格 - */ - @PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public List importWithOptions(@RequestPart("file") MultipartFile file) throws Exception { - // 处理解析结果 - ExcelResult excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener()); - return excelResult.getList(); - } - - @Data - @AllArgsConstructor - static class TestObj1 { - private String test1; - private String test2; - private String test3; - } - - @Data - @AllArgsConstructor - static class TestObj { - private String name; - private String list1; - private String list2; - private String list3; - private String list4; - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestI18nController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestI18nController.java deleted file mode 100644 index 4b2919fc..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestI18nController.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.utils.MessageUtils; -import lombok.Data; -import org.hibernate.validator.constraints.Range; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - - -/** - * 测试国际化 - * - * @author Lion Li - */ -@Validated -@RestController -@RequestMapping("/demo/i18n") -public class TestI18nController { - - /** - * 通过code获取国际化内容 - * code为 messages.properties 中的 key - *

- * 测试使用 user.register.success - * - * @param code 国际化code - */ - @GetMapping() - public R get(String code) { - return R.ok(MessageUtils.message(code)); - } - - /** - * Validator 校验国际化 - * 不传值 分别查看异常返回 - *

- * 测试使用 not.null - */ - @GetMapping("/test1") - public R test1(@NotBlank(message = "{not.null}") String str) { - return R.ok(str); - } - - /** - * Bean 校验国际化 - * 不传值 分别查看异常返回 - *

- * 测试使用 not.null - */ - @GetMapping("/test2") - public R test2(@Validated TestI18nBo bo) { - return R.ok(bo); - } - - @Data - public static class TestI18nBo { - - @NotBlank(message = "{not.null}") - private String name; - - @NotNull(message = "{not.null}") - @Range(min = 0, max = 100, message = "{length.not.valid}") - private Integer age; - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestSensitiveController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestSensitiveController.java deleted file mode 100644 index 8028ffcb..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestSensitiveController.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.common.sensitive.annotation.Sensitive; -import org.ruoyi.common.sensitive.core.SensitiveStrategy; -import lombok.Data; -import org.ruoyi.common.sensitive.core.SensitiveService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 测试数据脱敏控制器 - *

- * 默认管理员不过滤 - * 需自行根据业务重写实现 - * - * @author Lion Li - * @version 3.6.0 - * @see SensitiveService - */ -@RestController -@RequestMapping("/demo/sensitive") -public class TestSensitiveController extends BaseController { - - /** - * 测试数据脱敏 - */ - @GetMapping("/test") - public R test() { - TestSensitive testSensitive = new TestSensitive(); - testSensitive.setIdCard("210397198608215431"); - testSensitive.setPhone("17640125371"); - testSensitive.setAddress("北京市朝阳区某某四合院1203室"); - testSensitive.setEmail("17640125371@163.com"); - testSensitive.setBankCard("6226456952351452853"); - return R.ok(testSensitive); - } - - @Data - static class TestSensitive { - - /** - * 身份证 - */ - @Sensitive(strategy = SensitiveStrategy.ID_CARD) - private String idCard; - - /** - * 电话 - */ - @Sensitive(strategy = SensitiveStrategy.PHONE, roleKey = "common") - private String phone; - - /** - * 地址 - */ - @Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query") - private String address; - - /** - * 邮箱 - */ - @Sensitive(strategy = SensitiveStrategy.EMAIL, roleKey = "common", perms = "system:user:query1") - private String email; - - /** - * 银行卡 - */ - @Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleKey = "common1", perms = "system:user:query") - private String bankCard; - - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestTreeController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestTreeController.java deleted file mode 100644 index feca147a..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestTreeController.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.ruoyi.demo.controller; - -import cn.dev33.satoken.annotation.SaCheckPermission; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.core.validate.QueryGroup; -import org.ruoyi.common.web.core.BaseController; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.common.idempotent.annotation.RepeatSubmit; -import org.ruoyi.common.log.annotation.Log; -import org.ruoyi.common.log.enums.BusinessType; -import org.ruoyi.demo.domain.bo.TestTreeBo; -import org.ruoyi.demo.domain.vo.TestTreeVo; -import org.ruoyi.demo.service.ITestTreeService; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.Arrays; -import java.util.List; - -/** - * 测试树表Controller - * - * @author Lion Li - * @date 2021-07-26 - */ -@Validated -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/tree") -public class TestTreeController extends BaseController { - - private final ITestTreeService testTreeService; - - /** - * 查询测试树表列表 - */ - @SaCheckPermission("demo:tree:list") - @GetMapping("/list") - public R> list(@Validated(QueryGroup.class) TestTreeBo bo) { - List list = testTreeService.queryList(bo); - return R.ok(list); - } - - /** - * 导出测试树表列表 - */ - @SaCheckPermission("demo:tree:export") - @Log(title = "测试树表", businessType = BusinessType.EXPORT) - @GetMapping("/export") - public void export(@Validated TestTreeBo bo, HttpServletResponse response) { - List list = testTreeService.queryList(bo); - ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response); - } - - /** - * 获取测试树表详细信息 - * - * @param id 测试树ID - */ - @SaCheckPermission("demo:tree:query") - @GetMapping("/{id}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable("id") Long id) { - return R.ok(testTreeService.queryById(id)); - } - - /** - * 新增测试树表 - */ - @SaCheckPermission("demo:tree:add") - @Log(title = "测试树表", businessType = BusinessType.INSERT) - @RepeatSubmit - @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) { - return toAjax(testTreeService.insertByBo(bo)); - } - - /** - * 修改测试树表 - */ - @SaCheckPermission("demo:tree:edit") - @Log(title = "测试树表", businessType = BusinessType.UPDATE) - @RepeatSubmit - @PutMapping() - public R edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) { - return toAjax(testTreeService.updateByBo(bo)); - } - - /** - * 删除测试树表 - * - * @param ids 测试树ID串 - */ - @SaCheckPermission("demo:tree:remove") - @Log(title = "测试树表", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Long[] ids) { - return toAjax(testTreeService.deleteWithValidByIds(Arrays.asList(ids), true)); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/WebSocketController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/WebSocketController.java deleted file mode 100644 index f458d72f..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/WebSocketController.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.ruoyi.demo.controller; - -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.websocket.dto.WebSocketMessageDto; -import org.ruoyi.common.websocket.utils.WebSocketUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * WebSocket 演示案例 - * - * @author zendwang - */ -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/websocket") -@Slf4j -public class WebSocketController { - - /** - * 发布消息 - * - * @param dto 发送内容 - */ - @GetMapping("/send") - public R send(WebSocketMessageDto dto) throws InterruptedException { - WebSocketUtils.publishMessage(dto); - return R.ok("操作成功"); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/package-info.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/package-info.java deleted file mode 100644 index df052926..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.ruoyi.demo.controller; diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/BoundedQueueController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/BoundedQueueController.java deleted file mode 100644 index 9740cb74..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/BoundedQueueController.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.ruoyi.demo.controller.queue; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.redis.utils.QueueUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 有界队列 演示案例 - *

- * 轻量级队列 重量级数据量 请使用 MQ - *

- * 集群测试通过 同一个数据只会被消费一次 做好事务补偿 - * 集群测试流程 在其中一台发送数据 两端分别调用获取接口 一次获取一条 - * - * @author Lion Li - * @version 3.6.0 - * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用 - */ -@Deprecated -@Slf4j -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/queue/bounded") -public class BoundedQueueController { - - - /** - * 添加队列数据 - * - * @param queueName 队列名 - * @param capacity 容量 - */ - @GetMapping("/add") - public R add(String queueName, int capacity) { - // 用完了一定要销毁 否则会一直存在 - boolean b = QueueUtils.destroyBoundedQueue(queueName); - log.info("通道: {} , 删除: {}", queueName, b); - // 初始化设置一次即可 - if (QueueUtils.trySetBoundedQueueCapacity(queueName, capacity)) { - log.info("通道: {} , 设置容量: {}", queueName, capacity); - } else { - log.info("通道: {} , 设置容量失败", queueName); - return R.fail("操作失败"); - } - for (int i = 0; i < 11; i++) { - String data = "data-" + i; - boolean flag = QueueUtils.addBoundedQueueObject(queueName, data); - if (flag == false) { - log.info("通道: {} , 发送数据: {} 失败, 通道已满", queueName, data); - } else { - log.info("通道: {} , 发送数据: {}", queueName, data); - } - } - return R.ok("操作成功"); - } - - /** - * 删除队列数据 - * - * @param queueName 队列名 - */ - @GetMapping("/remove") - public R remove(String queueName) { - String data = "data-" + 5; - if (QueueUtils.removeBoundedQueueObject(queueName, data)) { - log.info("通道: {} , 删除数据: {}", queueName, data); - } else { - return R.fail("操作失败"); - } - return R.ok("操作成功"); - } - - /** - * 获取队列数据 - * - * @param queueName 队列名 - */ - @GetMapping("/get") - public R get(String queueName) { - String data; - do { - data = QueueUtils.getBoundedQueueObject(queueName); - log.info("通道: {} , 获取数据: {}", queueName, data); - } while (data != null); - return R.ok("操作成功"); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/DelayedQueueController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/DelayedQueueController.java deleted file mode 100644 index e489a60c..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/DelayedQueueController.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.ruoyi.demo.controller.queue; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.redis.utils.QueueUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -/** - * 延迟队列 演示案例 - *

- * 轻量级队列 重量级数据量 请使用 MQ - * 例如: 创建订单30分钟后过期处理 - *

- * 集群测试通过 同一个数据只会被消费一次 做好事务补偿 - * 集群测试流程 两台集群分别开启订阅 在其中一台发送数据 观察接收消息的规律 - * - * @author Lion Li - * @version 3.6.0 - * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用 - */ -@Deprecated -@Slf4j -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/queue/delayed") -public class DelayedQueueController { - - /** - * 订阅队列 - * - * @param queueName 队列名 - */ - @GetMapping("/subscribe") - public R subscribe(String queueName) { - log.info("通道: {} 监听中......", queueName); - // 项目初始化设置一次即可 - QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> { - // 观察接收时间 - log.info("通道: {}, 收到数据: {}", queueName, orderNum); - return CompletableFuture.runAsync(() -> { - // 异步处理数据逻辑 不要在上方处理业务逻辑 - log.info("数据处理: {}", orderNum); - }); - }, true); - return R.ok("操作成功"); - } - - /** - * 添加队列数据 - * - * @param queueName 队列名 - * @param orderNum 订单号 - * @param time 延迟时间(秒) - */ - @GetMapping("/add") - public R add(String queueName, String orderNum, Long time) { - QueueUtils.addDelayedQueueObject(queueName, orderNum, time, TimeUnit.SECONDS); - // 观察发送时间 - log.info("通道: {} , 发送数据: {}", queueName, orderNum); - return R.ok("操作成功"); - } - - /** - * 删除队列数据 - * - * @param queueName 队列名 - * @param orderNum 订单号 - */ - @GetMapping("/remove") - public R remove(String queueName, String orderNum) { - if (QueueUtils.removeDelayedQueueObject(queueName, orderNum)) { - log.info("通道: {} , 删除数据: {}", queueName, orderNum); - } else { - return R.fail("操作失败"); - } - return R.ok("操作成功"); - } - - /** - * 销毁队列 - * - * @param queueName 队列名 - */ - @GetMapping("/destroy") - public R destroy(String queueName) { - // 用完了一定要销毁 否则会一直存在 - QueueUtils.destroyDelayedQueue(queueName); - return R.ok("操作成功"); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityDemo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityDemo.java deleted file mode 100644 index c6152a6a..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityDemo.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ruoyi.demo.controller.queue; - -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 实体类 注意不允许使用内部类 否则会找不到类 - * - * @author Lion Li - * @version 3.6.0 - * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用 - */ -@Deprecated -@Data -@NoArgsConstructor -public class PriorityDemo implements Comparable { - private String name; - private Integer orderNum; - - @Override - public int compareTo(PriorityDemo other) { - return Integer.compare(getOrderNum(), other.getOrderNum()); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityQueueController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityQueueController.java deleted file mode 100644 index c9f11ab6..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityQueueController.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.ruoyi.demo.controller.queue; - -import cn.hutool.core.util.RandomUtil; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.redis.utils.QueueUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 优先队列 演示案例 - *

- * 轻量级队列 重量级数据量 请使用 MQ - *

- * 集群测试通过 同一个消息只会被消费一次 做好事务补偿 - * 集群测试流程 在其中一台发送数据 两端分别调用获取接口 一次获取一条 - * - * @author Lion Li - * @version 3.6.0 - * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用 - */ -@Deprecated -@Slf4j -@RequiredArgsConstructor -@RestController -@RequestMapping("/demo/queue/priority") -public class PriorityQueueController { - - /** - * 添加队列数据 - * - * @param queueName 队列名 - */ - @GetMapping("/add") - public R add(String queueName) { - // 用完了一定要销毁 否则会一直存在 - boolean b = QueueUtils.destroyPriorityQueue(queueName); - log.info("通道: {} , 删除: {}", queueName, b); - - for (int i = 0; i < 10; i++) { - int randomNum = RandomUtil.randomInt(10); - PriorityDemo data = new PriorityDemo(); - data.setName("data-" + i); - data.setOrderNum(randomNum); - if (QueueUtils.addPriorityQueueObject(queueName, data)) { - log.info("通道: {} , 发送数据: {}", queueName, data); - } else { - log.info("通道: {} , 发送数据: {}, 发送失败", queueName, data); - } - } - return R.ok("操作成功"); - } - - /** - * 删除队列数据 - * - * @param queueName 队列名 - * @param name 对象名 - * @param orderNum 排序号 - */ - @GetMapping("/remove") - public R remove(String queueName, String name, Integer orderNum) { - PriorityDemo data = new PriorityDemo(); - data.setName(name); - data.setOrderNum(orderNum); - if (QueueUtils.removePriorityQueueObject(queueName, data)) { - log.info("通道: {} , 删除数据: {}", queueName, data); - } else { - return R.fail("操作失败"); - } - return R.ok("操作成功"); - } - - /** - * 获取队列数据 - * - * @param queueName 队列名 - */ - @GetMapping("/get") - public R get(String queueName) { - PriorityDemo data; - do { - data = QueueUtils.getPriorityQueueObject(queueName); - log.info("通道: {} , 获取数据: {}", queueName, data); - } while (data != null); - return R.ok("操作成功"); - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemo.java deleted file mode 100644 index 550f9c94..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemo.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.ruoyi.demo.domain; - -import com.baomidou.mybatisplus.annotation.*; -import org.ruoyi.common.tenant.core.TenantEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.io.Serial; - -/** - * 测试单表对象 test_demo - * - * @author Lion Li - * @date 2021-07-26 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("test_demo") -public class TestDemo extends TenantEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "id") - private Long id; - - /** - * 部门id - */ - private Long deptId; - - /** - * 用户id - */ - private Long userId; - - /** - * 排序号 - */ - @OrderBy(asc = false, sort = 1) - private Integer orderNum; - - /** - * key键 - */ - private String testKey; - - /** - * 值 - */ - private String value; - - /** - * 版本 - */ - @Version - private Long version; - - /** - * 删除标志 - */ - @TableLogic - private Long delFlag; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemoEncrypt.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemoEncrypt.java deleted file mode 100644 index 75a4f43d..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemoEncrypt.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.ruoyi.demo.domain; - -import com.baomidou.mybatisplus.annotation.TableName; -import org.ruoyi.common.encrypt.annotation.EncryptField; -import org.ruoyi.common.encrypt.enumd.AlgorithmType; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("test_demo") -public class TestDemoEncrypt extends TestDemo { - - /** - * key键 - */ - // @EncryptField(algorithm=AlgorithmType.SM2, privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgZSlOvw8FBiH+aFJWLYZP/VRjg9wjfRarTkGBZd/T3N+gCgYIKoEcz1UBgi2hRANCAAR5DGuQwJqkxnbCsP+iPSDoHWIF4RwcR5EsSvT8QPxO1wRkR2IhCkzvRb32x2CUgJFdvoqVqfApFDPZzShqzBwX", publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEeQxrkMCapMZ2wrD/oj0g6B1iBeEcHEeRLEr0/ED8TtcEZEdiIQpM70W99sdglICRXb6KlanwKRQz2c0oaswcFw==") - @EncryptField(algorithm = AlgorithmType.RSA, privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANBBEeueWlXlkkj2+WY5l+IWe42d8b5K28g+G/CFKC/yYAEHtqGlCsBOrb+YBkG9mPzmuYA/n9k0NFIc8E8yY5vZQaroyFBrTTWEzG9RY2f7Y3svVyybs6jpXSUs4xff8abo7wL1Y/wUaeatTViamxYnyTvdTmLm3d+JjRij68rxAgMBAAECgYAB0TnhXraSopwIVRfmboea1b0upl+BUdTJcmci412UjrKr5aE695ZLPkXbFXijVu7HJlyyv94NVUdaMACV7Ku/S2RuNB70M7YJm8rAjHFC3/i2ZeIM60h1Ziy4QKv0XM3pRATlDCDNhC1WUrtQCQSgU8kcp6eUUppruOqDzcY04QJBAPm9+sBP9CwDRgy3e5+V8aZtJkwDstb0lVVV/KY890cydVxiCwvX3fqVnxKMlb+x0YtH0sb9v+71xvK2lGobaRECQQDVePU6r/cCEfpc+nkWF6osAH1f8Mux3rYv2DoBGvaPzV2BGfsLed4neRfCwWNCKvGPCdW+L0xMJg8+RwaoBUPhAkAT5kViqXxFPYWJYd1h2+rDXhMdH3ZSlm6HvDBDdrwlWinr0Iwcx3iSjPV93uHXwm118aUj4fg3LDJMCKxOwBxhAkByrQXfvwOMYygBprRBf/j0plazoWFrbd6lGR0f1uI5IfNnFRPdeFw1DEINZ2Hw+6zEUF44SqRMC+4IYJNc02dBAkBCgy7RvfyV/A7N6kKXxTHauY0v6XwSSvpeKtRJkbIcRWOdIYvaHO9L7cklj3vIEdwjSUp9K4VTBYYlmAz1xh03", publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQQRHrnlpV5ZJI9vlmOZfiFnuNnfG+StvIPhvwhSgv8mABB7ahpQrATq2/mAZBvZj85rmAP5/ZNDRSHPBPMmOb2UGq6MhQa001hMxvUWNn+2N7L1csm7Oo6V0lLOMX3/Gm6O8C9WP8FGnmrU1YmpsWJ8k73U5i5t3fiY0Yo+vK8QIDAQAB") - private String testKey; - - /** - * 值 - */ - // @EncryptField // 什么也不写走默认yml配置 - // @EncryptField(algorithm = AlgorithmType.SM4, password = "10rfylhtccpuyke5") - @EncryptField(algorithm = AlgorithmType.AES, password = "10rfylhtccpuyke5") - private String value; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestTree.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestTree.java deleted file mode 100644 index af687a74..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestTree.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ruoyi.demo.domain; - -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableLogic; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.annotation.Version; -import org.ruoyi.common.tenant.core.TenantEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.io.Serial; - -/** - * 测试树表对象 test_tree - * - * @author Lion Li - * @date 2021-07-26 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("test_tree") -public class TestTree extends TenantEntity { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "id") - private Long id; - - /** - * 父ID - */ - private Long parentId; - - /** - * 部门id - */ - private Long deptId; - - /** - * 用户id - */ - private Long userId; - - /** - * 树节点名 - */ - private String treeName; - - /** - * 版本 - */ - @Version - private Long version; - - /** - * 删除标志 - */ - @TableLogic - private Long delFlag; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoBo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoBo.java deleted file mode 100644 index ae35d0cd..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoBo.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.ruoyi.demo.domain.bo; - -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; -import org.ruoyi.demo.domain.TestDemo; -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 测试单表业务对象 test_demo - * - * @author Lion Li - * @date 2021-07-26 - */ - -@Data -@EqualsAndHashCode(callSuper = true) -@AutoMapper(target = TestDemo.class, reverseConvertGenerate = false) -public class TestDemoBo extends BaseEntity { - - /** - * 主键 - */ - @NotNull(message = "主键不能为空", groups = {EditGroup.class}) - private Long id; - - /** - * 部门id - */ - @NotNull(message = "部门id不能为空", groups = {AddGroup.class, EditGroup.class}) - private Long deptId; - - /** - * 用户id - */ - @NotNull(message = "用户id不能为空", groups = {AddGroup.class, EditGroup.class}) - private Long userId; - - /** - * 排序号 - */ - @NotNull(message = "排序号不能为空", groups = {AddGroup.class, EditGroup.class}) - private Integer orderNum; - - /** - * key键 - */ - @NotBlank(message = "key键不能为空", groups = {AddGroup.class, EditGroup.class}) - private String testKey; - - /** - * 值 - */ - @NotBlank(message = "值不能为空", groups = {AddGroup.class, EditGroup.class}) - private String value; - - /** - * 版本 - */ - private Long version; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoImportVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoImportVo.java deleted file mode 100644 index d140bac8..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoImportVo.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.ruoyi.demo.domain.bo; - -import cn.idev.excel.annotation.ExcelProperty; -import lombok.Data; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 测试单表业务对象 test_demo - * - * @author Lion Li - * @date 2021-07-26 - */ -@Data -public class TestDemoImportVo { - - /** - * 部门id - */ - @NotNull(message = "部门id不能为空") - @ExcelProperty(value = "部门id") - private Long deptId; - - /** - * 用户id - */ - @NotNull(message = "用户id不能为空") - @ExcelProperty(value = "用户id") - private Long userId; - - /** - * 排序号 - */ - @NotNull(message = "排序号不能为空") - @ExcelProperty(value = "排序号") - private Long orderNum; - - /** - * key键 - */ - @NotBlank(message = "key键不能为空") - @ExcelProperty(value = "key键") - private String testKey; - - /** - * 值 - */ - @NotBlank(message = "值不能为空") - @ExcelProperty(value = "值") - private String value; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestTreeBo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestTreeBo.java deleted file mode 100644 index f719011b..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestTreeBo.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.ruoyi.demo.domain.bo; - -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.mybatis.core.domain.BaseEntity; -import org.ruoyi.demo.domain.TestTree; -import io.github.linpeilie.annotations.AutoMapper; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 测试树表业务对象 test_tree - * - * @author Lion Li - * @date 2021-07-26 - */ - -@Data -@EqualsAndHashCode(callSuper = true) -@AutoMapper(target = TestTree.class, reverseConvertGenerate = false) -public class TestTreeBo extends BaseEntity { - - /** - * 主键 - */ - @NotNull(message = "主键不能为空", groups = {EditGroup.class}) - private Long id; - - /** - * 父ID - */ - private Long parentId; - - /** - * 部门id - */ - @NotNull(message = "部门id不能为空", groups = {AddGroup.class, EditGroup.class}) - private Long deptId; - - /** - * 用户id - */ - @NotNull(message = "用户id不能为空", groups = {AddGroup.class, EditGroup.class}) - private Long userId; - - /** - * 树节点名 - */ - @NotBlank(message = "树节点名不能为空", groups = {AddGroup.class, EditGroup.class}) - private String treeName; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/package-info.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/package-info.java deleted file mode 100644 index f7d47105..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.ruoyi.demo.domain; diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/ExportDemoVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/ExportDemoVo.java deleted file mode 100644 index 964e59d8..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/ExportDemoVo.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.ruoyi.demo.domain.vo; - -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.ruoyi.common.core.enums.UserStatus; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.excel.annotation.ExcelDictFormat; -import org.ruoyi.common.excel.annotation.ExcelEnumFormat; -import org.ruoyi.common.excel.convert.ExcelDictConvert; -import org.ruoyi.common.excel.convert.ExcelEnumConvert; - -import java.io.Serial; -import java.io.Serializable; - -/** - * 带有下拉选的Excel导出 - * - * @author Emil.Zhang - */ -@Data -@ExcelIgnoreUnannotated -@AllArgsConstructor -@NoArgsConstructor -public class ExportDemoVo implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 用户昵称 - */ - @ExcelProperty(value = "用户名", index = 0) - @NotEmpty(message = "用户名不能为空", groups = AddGroup.class) - private String nickName; - - /** - * 用户类型 - *

- * 使用ExcelEnumFormat注解需要进行下拉选的部分 - */ - @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class) - @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info") - @NotEmpty(message = "用户类型不能为空", groups = AddGroup.class) - private String userStatus; - - /** - * 性别 - *

- * 使用ExcelDictFormat注解需要进行下拉选的部分 - */ - @ExcelProperty(value = "性别", index = 2, converter = ExcelDictConvert.class) - @ExcelDictFormat(dictType = "sys_user_sex") - @NotEmpty(message = "性别不能为空", groups = AddGroup.class) - private String gender; - - /** - * 手机号 - */ - @ExcelProperty(value = "手机号", index = 3) - @NotEmpty(message = "手机号不能为空", groups = AddGroup.class) - private String phoneNumber; - - /** - * Email - */ - @ExcelProperty(value = "Email", index = 4) - @NotEmpty(message = "Email不能为空", groups = AddGroup.class) - private String email; - - /** - * 省 - *

- * 级联下拉,仅判断是否选了 - */ - @ExcelProperty(value = "省", index = 5) - @NotNull(message = "省不能为空", groups = AddGroup.class) - private String province; - - /** - * 数据库中的省ID - *

- * 处理完毕后再判断是否市正确的值 - */ - @NotNull(message = "请勿手动输入", groups = EditGroup.class) - private Integer provinceId; - - /** - * 市 - *

- * 级联下拉 - */ - @ExcelProperty(value = "市", index = 6) - @NotNull(message = "市不能为空", groups = AddGroup.class) - private String city; - - /** - * 数据库中的市ID - */ - @NotNull(message = "请勿手动输入", groups = EditGroup.class) - private Integer cityId; - - /** - * 县 - *

- * 级联下拉 - */ - @ExcelProperty(value = "县", index = 7) - @NotNull(message = "县不能为空", groups = AddGroup.class) - private String area; - - /** - * 数据库中的县ID - */ - @NotNull(message = "请勿手动输入", groups = EditGroup.class) - private Integer areaId; -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestDemoVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestDemoVo.java deleted file mode 100644 index 06b4b745..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestDemoVo.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.ruoyi.demo.domain.vo; - -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; -import cn.idev.excel.annotation.format.DateTimeFormat; -import org.ruoyi.common.excel.annotation.ExcelNotation; -import org.ruoyi.common.excel.annotation.ExcelRequired; -import org.ruoyi.common.translation.annotation.Translation; -import org.ruoyi.common.translation.constant.TransConstant; -import org.ruoyi.demo.domain.TestDemo; -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - - -/** - * 测试单表视图对象 test_demo - * - * @author Lion Li - * @date 2021-07-26 - */ -@Data -@ExcelIgnoreUnannotated -@AutoMapper(target = TestDemo.class) -public class TestDemoVo implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @ExcelProperty(value = "主键") - private Long id; - - /** - * 部门id - */ - @ExcelRequired - @ExcelProperty(value = "部门id") - private Long deptId; - - /** - * 用户id - */ - @ExcelRequired - @ExcelProperty(value = "用户id", index = 5) - private Long userId; - - /** - * 排序号 - */ - @ExcelRequired - @ExcelProperty(value = "排序号") - private Integer orderNum; - - /** - * key键 - */ - @ExcelNotation(value = "测试key") - @ExcelProperty(value = "key键") - private String testKey; - - /** - * 值 - */ - @ExcelNotation(value = "测试value") - @ExcelProperty(value = "值") - private String value; - - /** - * 创建时间 - */ - @ExcelRequired - @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - @ExcelProperty(value = "创建时间") - private Date createTime; - - /** - * 创建人 - */ - @ExcelProperty(value = "创建人") - private Long createBy; - - /** - * 创建人账号 - */ - @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") - @ExcelProperty(value = "创建人账号") - private String createByName; - - /** - * 更新时间 - */ - @ExcelProperty(value = "更新时间") - private Date updateTime; - - /** - * 更新人 - */ - @ExcelProperty(value = "更新人") - private Long updateBy; - - /** - * 更新人账号 - */ - @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy") - @ExcelProperty(value = "更新人账号") - private String updateByName; - - /** - * 版本 - */ - private Long version; - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestTreeVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestTreeVo.java deleted file mode 100644 index 77220d7f..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestTreeVo.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.ruoyi.demo.domain.vo; - -import cn.idev.excel.annotation.ExcelIgnoreUnannotated; -import cn.idev.excel.annotation.ExcelProperty; -import org.ruoyi.demo.domain.TestTree; -import io.github.linpeilie.annotations.AutoMapper; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - - -/** - * 测试树表视图对象 test_tree - * - * @author Lion Li - * @date 2021-07-26 - */ -@Data -@ExcelIgnoreUnannotated -@AutoMapper(target = TestTree.class) -public class TestTreeVo implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - private Long id; - - /** - * 父id - */ - @ExcelProperty(value = "父id") - private Long parentId; - - /** - * 部门id - */ - @ExcelProperty(value = "部门id") - private Long deptId; - - /** - * 用户id - */ - @ExcelProperty(value = "用户id") - private Long userId; - - /** - * 树节点名 - */ - @ExcelProperty(value = "树节点名") - private String treeName; - - /** - * 创建时间 - */ - @ExcelProperty(value = "创建时间") - private Date createTime; - - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/listener/ExportDemoListener.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/listener/ExportDemoListener.java deleted file mode 100644 index 381ba6e3..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/listener/ExportDemoListener.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.ruoyi.demo.listener; - -import cn.hutool.core.util.NumberUtil; -import cn.idev.excel.context.AnalysisContext; -import org.ruoyi.common.core.utils.ValidatorUtils; -import org.ruoyi.common.core.validate.AddGroup; -import org.ruoyi.common.core.validate.EditGroup; -import org.ruoyi.common.excel.core.DefaultExcelListener; -import org.ruoyi.common.excel.core.DropDownOptions; -import org.ruoyi.demo.domain.vo.ExportDemoVo; - -import java.util.List; - -/** - * Excel带下拉框的解析处理器 - * - * @author Emil.Zhang - */ -public class ExportDemoListener extends DefaultExcelListener { - - public ExportDemoListener() { - // 显示使用构造函数,否则将导致空指针 - super(true); - } - - @Override - public void invoke(ExportDemoVo data, AnalysisContext context) { - // 先校验必填 - ValidatorUtils.validate(data, AddGroup.class); - - // 处理级联下拉的部分 - String province = data.getProvince(); - String city = data.getCity(); - String area = data.getArea(); - // 本行用户选择的省 - List thisRowSelectedProvinceOption = DropDownOptions.analyzeOptionValue(province); - if (thisRowSelectedProvinceOption.size() == 2) { - String provinceIdStr = thisRowSelectedProvinceOption.get(1); - if (NumberUtil.isNumber(provinceIdStr)) { - // 严格要求数据的话可以在这里做与数据库相关的判断 - // 例如判断省信息是否在数据库中存在等,建议结合RedisCache做缓存10s,减少数据库调用 - data.setProvinceId(Integer.parseInt(provinceIdStr)); - } - } - // 本行用户选择的市 - List thisRowSelectedCityOption = DropDownOptions.analyzeOptionValue(city); - if (thisRowSelectedCityOption.size() == 2) { - String cityIdStr = thisRowSelectedCityOption.get(1); - if (NumberUtil.isNumber(cityIdStr)) { - data.setCityId(Integer.parseInt(cityIdStr)); - } - } - // 本行用户选择的县 - List thisRowSelectedAreaOption = DropDownOptions.analyzeOptionValue(area); - if (thisRowSelectedAreaOption.size() == 2) { - String areaIdStr = thisRowSelectedAreaOption.get(1); - if (NumberUtil.isNumber(areaIdStr)) { - data.setAreaId(Integer.parseInt(areaIdStr)); - } - } - - // 处理完毕以后判断是否符合规则 - ValidatorUtils.validate(data, EditGroup.class); - - // 添加到处理结果中 - getExcelResult().getList().add(data); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoEncryptMapper.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoEncryptMapper.java deleted file mode 100644 index 8b5eb968..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoEncryptMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ruoyi.demo.mapper; - -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; -import org.ruoyi.demo.domain.TestDemoEncrypt; - -/** - * 测试加密功能 - * - * @author Lion Li - */ -public interface TestDemoEncryptMapper extends BaseMapperPlus { - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoMapper.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoMapper.java deleted file mode 100644 index 0e5e191a..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoMapper.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.ruoyi.demo.mapper; - -import com.baomidou.mybatisplus.core.conditions.Wrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.core.toolkit.Constants; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import org.apache.ibatis.annotations.Param; -import org.ruoyi.common.mybatis.annotation.DataColumn; -import org.ruoyi.common.mybatis.annotation.DataPermission; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; -import org.ruoyi.demo.domain.TestDemo; -import org.ruoyi.demo.domain.vo.TestDemoVo; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; - -/** - * 测试单表Mapper接口 - * - * @author Lion Li - * @date 2021-07-26 - */ -public interface TestDemoMapper extends BaseMapperPlus { - - @DataPermission({ - @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") - }) - Page customPageList(@Param("page") Page page, @Param("ew") Wrapper wrapper); - - @Override - @DataPermission({ - @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") - }) - default

> P selectVoPage(IPage page, Wrapper wrapper) { - return selectVoPage(page, wrapper, this.currentVoClass()); - } - - @Override - @DataPermission({ - @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") - }) - default List selectVoList(Wrapper wrapper) { - return selectVoList(wrapper, this.currentVoClass()); - } - - @Override - @DataPermission(value = { - @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") - }, joinStr = "AND") - List selectByIds(@Param(Constants.COLL) Collection idList); - - @Override - @DataPermission({ - @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") - }) - int updateById(@Param(Constants.ENTITY) TestDemo entity); - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestTreeMapper.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestTreeMapper.java deleted file mode 100644 index d6b59b95..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestTreeMapper.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ruoyi.demo.mapper; - -import org.ruoyi.common.mybatis.annotation.DataColumn; -import org.ruoyi.common.mybatis.annotation.DataPermission; -import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; -import org.ruoyi.demo.domain.TestTree; -import org.ruoyi.demo.domain.vo.TestTreeVo; - -/** - * 测试树表Mapper接口 - * - * @author Lion Li - * @date 2021-07-26 - */ -@DataPermission({ - @DataColumn(key = "deptName", value = "dept_id"), - @DataColumn(key = "userName", value = "user_id") -}) -public interface TestTreeMapper extends BaseMapperPlus { - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/package-info.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/package-info.java deleted file mode 100644 index 8c89e45d..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.ruoyi.demo.mapper; diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/IExportExcelService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/IExportExcelService.java deleted file mode 100644 index 46813988..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/IExportExcelService.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.ruoyi.demo.service; - -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - -/** - * 导出下拉框Excel示例 - * - * @author Emil.Zhang - */ -public interface IExportExcelService { - - /** - * 导出下拉框 - * - * @param response / - */ - void exportWithOptions(HttpServletResponse response); - - /** - * 自定义导出 - * - * @param response / - */ - void customExport(HttpServletResponse response) throws IOException; -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestDemoService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestDemoService.java deleted file mode 100644 index 1a4d7154..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestDemoService.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.ruoyi.demo.service; - -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.demo.domain.TestDemo; -import org.ruoyi.demo.domain.bo.TestDemoBo; -import org.ruoyi.demo.domain.vo.TestDemoVo; - -import java.util.Collection; -import java.util.List; - -/** - * 测试单表Service接口 - * - * @author Lion Li - * @date 2021-07-26 - */ -public interface ITestDemoService { - - /** - * 查询单个 - * - * @return - */ - TestDemoVo queryById(Long id); - - /** - * 查询列表 - */ - TableDataInfo queryPageList(TestDemoBo bo, PageQuery pageQuery); - - /** - * 自定义分页查询 - */ - TableDataInfo customPageList(TestDemoBo bo, PageQuery pageQuery); - - /** - * 查询列表 - */ - List queryList(TestDemoBo bo); - - /** - * 根据新增业务对象插入测试单表 - * - * @param bo 测试单表新增业务对象 - * @return - */ - Boolean insertByBo(TestDemoBo bo); - - /** - * 根据编辑业务对象修改测试单表 - * - * @param bo 测试单表编辑业务对象 - * @return - */ - Boolean updateByBo(TestDemoBo bo); - - /** - * 校验并删除数据 - * - * @param ids 主键集合 - * @param isValid 是否校验,true-删除前校验,false-不校验 - * @return - */ - Boolean deleteWithValidByIds(Collection ids, Boolean isValid); - - /** - * 批量保存 - */ - Boolean saveBatch(List list); -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestTreeService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestTreeService.java deleted file mode 100644 index 6336fc18..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestTreeService.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.ruoyi.demo.service; - -import org.ruoyi.demo.domain.bo.TestTreeBo; -import org.ruoyi.demo.domain.vo.TestTreeVo; - -import java.util.Collection; -import java.util.List; - -/** - * 测试树表Service接口 - * - * @author Lion Li - * @date 2021-07-26 - */ -public interface ITestTreeService { - /** - * 查询单个 - * - * @return - */ - TestTreeVo queryById(Long id); - - /** - * 查询列表 - */ - List queryList(TestTreeBo bo); - - /** - * 根据新增业务对象插入测试树表 - * - * @param bo 测试树表新增业务对象 - * @return - */ - Boolean insertByBo(TestTreeBo bo); - - /** - * 根据编辑业务对象修改测试树表 - * - * @param bo 测试树表编辑业务对象 - * @return - */ - Boolean updateByBo(TestTreeBo bo); - - /** - * 校验并删除数据 - * - * @param ids 主键集合 - * @param isValid 是否校验,true-删除前校验,false-不校验 - * @return - */ - Boolean deleteWithValidByIds(Collection ids, Boolean isValid); -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/ExportExcelServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/ExportExcelServiceImpl.java deleted file mode 100644 index 541e09a1..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/ExportExcelServiceImpl.java +++ /dev/null @@ -1,297 +0,0 @@ -package org.ruoyi.demo.service.impl; - -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.idev.excel.write.metadata.WriteSheet; -import jakarta.servlet.http.HttpServletResponse; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.constant.SystemConstants; -import org.ruoyi.common.core.utils.StreamUtils; -import org.ruoyi.common.core.utils.file.FileUtils; -import org.ruoyi.common.excel.core.DropDownOptions; -import org.ruoyi.common.excel.utils.ExcelUtil; -import org.ruoyi.common.excel.utils.ExcelWriterWrapper; -import org.ruoyi.demo.domain.vo.ExportDemoVo; -import org.ruoyi.demo.service.IExportExcelService; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * 导出下拉框Excel示例 - * - * @author Emil.Zhang - */ -@Service -@RequiredArgsConstructor -public class ExportExcelServiceImpl implements IExportExcelService { - - @Override - public void exportWithOptions(HttpServletResponse response) { - // 创建表格数据,业务中一般通过数据库查询 - List excelDataList = new ArrayList<>(); - for (int i = 0; i < 3; i++) { - // 模拟数据库中的一条数据 - ExportDemoVo everyRowData = new ExportDemoVo(); - everyRowData.setNickName("用户-" + i); - everyRowData.setUserStatus(SystemConstants.NORMAL); - everyRowData.setGender("1"); - everyRowData.setPhoneNumber(String.format("175%08d", i)); - everyRowData.setEmail(String.format("175%08d", i) + "@163.com"); - everyRowData.setProvinceId(i); - everyRowData.setCityId(i); - everyRowData.setAreaId(i); - excelDataList.add(everyRowData); - } - - // 通过@ExcelIgnoreUnannotated配合@ExcelProperty合理显示需要的列 - // 并通过@DropDown注解指定下拉值,或者通过创建ExcelOptions来指定下拉框 - // 使用ExcelOptions时建议指定列index,防止出现下拉列解析不对齐 - - // 首先从数据库中查询下拉框内的可选项 - // 这里模拟查询结果 - List provinceList = getProvinceList(), - cityList = getCityList(provinceList), - areaList = getAreaList(cityList); - int provinceIndex = 5, cityIndex = 6, areaIndex = 7; - - DropDownOptions provinceToCity = DropDownOptions.buildLinkedOptions( - provinceList, - provinceIndex, - cityList, - cityIndex, - DemoCityData::getId, - DemoCityData::getPid, - everyOptions -> DropDownOptions.createOptionValue( - everyOptions.getName(), - everyOptions.getId() - ) - ); - - DropDownOptions cityToArea = DropDownOptions.buildLinkedOptions( - cityList, - cityIndex, - areaList, - areaIndex, - DemoCityData::getId, - DemoCityData::getPid, - everyOptions -> DropDownOptions.createOptionValue( - everyOptions.getName(), - everyOptions.getId() - ) - ); - - // 把所有的下拉框存储 - List options = new ArrayList<>(); - options.add(provinceToCity); - options.add(cityToArea); - - // 到此为止所有的下拉框可选项已全部配置完毕 - - // 接下来需要将Excel中的展示数据转换为对应的下拉选 - List outList = StreamUtils.toList(excelDataList, everyRowData -> { - // 只需要处理没有使用@ExcelDictFormat注解的下拉框 - // 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值 - everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId())); - everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId())); - everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId())); - return everyRowData; - }); - - ExcelUtil.exportExcel(outList, "下拉框示例", ExportDemoVo.class, response, options); - } - - private String buildOptions(List cityDataList, Integer id) { - Map> groupByIdMap = - cityDataList.stream().collect(Collectors.groupingBy(DemoCityData::getId)); - if (groupByIdMap.containsKey(id)) { - DemoCityData demoCityData = groupByIdMap.get(id).get(0); - return DropDownOptions.createOptionValue(demoCityData.getName(), demoCityData.getId()); - } else { - return StrUtil.EMPTY; - } - } - - /** - * 模拟查询数据库操作 - * - * @return / - */ - private List getProvinceList() { - List provinceList = new ArrayList<>(); - - // 实际业务中一般采用数据库读取的形式,这里直接拼接创建 - provinceList.add(new DemoCityData(0, null, "P100000")); - provinceList.add(new DemoCityData(1, null, "P200000")); - provinceList.add(new DemoCityData(2, null, "P300000")); - - return provinceList; - } - - /** - * 模拟查找数据库操作,需要连带查询出省的数据 - * - * @param provinceList 模拟的父省数据 - * @return / - */ - private List getCityList(List provinceList) { - List cityList = new ArrayList<>(); - - // 实际业务中一般采用数据库读取的形式,这里直接拼接创建 - cityList.add(new DemoCityData(0, 0, "C110000")); - cityList.add(new DemoCityData(1, 0, "C120000")); - cityList.add(new DemoCityData(2, 1, "C210000")); - cityList.add(new DemoCityData(3, 1, "C220000")); - cityList.add(new DemoCityData(4, 1, "C230000")); - - selectParentData(provinceList, cityList); - - return cityList; - } - - /** - * 模拟查找数据库操作,需要连带查询出市的数据 - * - * @param cityList 模拟的父市数据 - * @return / - */ - private List getAreaList(List cityList) { - List areaList = new ArrayList<>(); - - int minCount = 500; - int maxCount = 10000; - - // 实际业务中一般采用数据库读取的形式,这里直接拼接创建 - for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) { - areaList.add(new DemoCityData(areaList.size(), 0, String.format("A11%04d", i))); - } - - for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) { - areaList.add(new DemoCityData(areaList.size(), 1, String.format("A12%04d", i))); - } - - for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) { - areaList.add(new DemoCityData(areaList.size(), 2, String.format("A21%04d", i))); - } - - for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) { - areaList.add(new DemoCityData(areaList.size(), 3, String.format("A22%04d", i))); - } - - for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) { - areaList.add(new DemoCityData(areaList.size(), 4, String.format("A23%04d", i))); - } - - selectParentData(cityList, areaList); - - return areaList; - } - - /** - * 模拟数据库的查询父数据操作 - * - * @param parentList / - * @param sonList / - */ - private void selectParentData(List parentList, List sonList) { - Map> parentGroupByIdMap = - parentList.stream().collect(Collectors.groupingBy(DemoCityData::getId)); - - sonList.forEach(everySon -> { - if (parentGroupByIdMap.containsKey(everySon.getPid())) { - everySon.setPData(parentGroupByIdMap.get(everySon.getPid()).get(0)); - } - }); - } - - /** - * 模拟的数据库省市县 - */ - @Data - private static class DemoCityData { - /** - * 数据库id字段 - */ - private Integer id; - /** - * 数据库pid字段 - */ - private Integer pid; - /** - * 数据库name字段 - */ - private String name; - /** - * MyBatisPlus连带查询父数据 - */ - private DemoCityData pData; - - public DemoCityData(Integer id, Integer pid, String name) { - this.id = id; - this.pid = pid; - this.name = name; - } - } - - - @Override - public void customExport(HttpServletResponse response) throws IOException { - String filename = ExcelUtil.encodingFilename("自定义导出"); - FileUtils.setAttachmentResponseHeader(response, filename); - response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); - - ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> { - // 创建表格数据,业务中一般通过数据库查询 - List excelDataList = new ArrayList<>(); - for (int i = 0; i < 30; i++) { - // 模拟数据库中的一条数据 - ExportDemoVo everyRowData = new ExportDemoVo(); - everyRowData.setNickName("用户-" + i); - everyRowData.setUserStatus(SystemConstants.NORMAL); - everyRowData.setGender("1"); - everyRowData.setPhoneNumber(String.format("175%08d", i)); - everyRowData.setEmail(String.format("175%08d", i) + "@163.com"); - everyRowData.setProvinceId(i); - everyRowData.setCityId(i); - everyRowData.setAreaId(i); - excelDataList.add(everyRowData); - } - - // 创建表格 - WriteSheet sheet = ExcelWriterWrapper.sheetBuilder("自定义导出demo") - // 合并单元格 - // .registerWriteHandler(new CellMergeStrategy(excelDataList, true)) - .build(); - - - wrapper.write(excelDataList, sheet); - - List excelDataList2 = new ArrayList<>(); - for (int i = 0; i < 20; i++) { - int index = 1000 + i; - // 模拟数据库中的一条数据 - ExportDemoVo everyRowData = new ExportDemoVo(); - everyRowData.setNickName("用户-" + index); - everyRowData.setUserStatus(SystemConstants.NORMAL); - everyRowData.setGender("1"); - everyRowData.setPhoneNumber(String.format("175%08d", index)); - everyRowData.setEmail(String.format("175%08d", index) + "@163.com"); - everyRowData.setProvinceId(index); - everyRowData.setCityId(index); - everyRowData.setAreaId(index); - excelDataList2.add(everyRowData); - } - - wrapper.write(excelDataList2, sheet); - - // 或者在同一个excel中创建多个表格 - // WriteSheet sheet2 = ExcelWriterWrapper.sheetBuilder("自定义导出demo2").build(); - // wrapper.write(excelDataList2, sheet2); - }); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestDemoServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestDemoServiceImpl.java deleted file mode 100644 index 3f310762..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestDemoServiceImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.ruoyi.demo.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.RequiredArgsConstructor; -import org.ruoyi.common.core.exception.ServiceException; -import org.ruoyi.common.core.utils.MapstructUtils; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.common.mybatis.core.page.PageQuery; -import org.ruoyi.common.mybatis.core.page.TableDataInfo; -import org.ruoyi.demo.domain.TestDemo; -import org.ruoyi.demo.domain.bo.TestDemoBo; -import org.ruoyi.demo.domain.vo.TestDemoVo; -import org.ruoyi.demo.mapper.TestDemoMapper; -import org.ruoyi.demo.service.ITestDemoService; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * 测试单表Service业务层处理 - * - * @author Lion Li - * @date 2021-07-26 - */ -@RequiredArgsConstructor -@Service -public class TestDemoServiceImpl implements ITestDemoService { - - private final TestDemoMapper baseMapper; - - @Override - public TestDemoVo queryById(Long id) { - return baseMapper.selectVoById(id); - } - - @Override - public TableDataInfo queryPageList(TestDemoBo bo, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); - return TableDataInfo.build(result); - } - - /** - * 自定义分页查询 - */ - @Override - public TableDataInfo customPageList(TestDemoBo bo, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - Page result = baseMapper.customPageList(pageQuery.build(), lqw); - return TableDataInfo.build(result); - } - - @Override - public List queryList(TestDemoBo bo) { - return baseMapper.selectVoList(buildQueryWrapper(bo)); - } - - private LambdaQueryWrapper buildQueryWrapper(TestDemoBo bo) { - Map params = bo.getParams(); - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey()); - lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue()); - lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, - TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); - lqw.orderByAsc(TestDemo::getId); - return lqw; - } - - @Override - public Boolean insertByBo(TestDemoBo bo) { - TestDemo add = MapstructUtils.convert(bo, TestDemo.class); - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setId(add.getId()); - } - return flag; - } - - @Override - public Boolean updateByBo(TestDemoBo bo) { - TestDemo update = MapstructUtils.convert(bo, TestDemo.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; - } - - /** - * 保存前的数据校验 - * - * @param entity 实体类数据 - */ - private void validEntityBeforeSave(TestDemo entity) { - //TODO 做一些数据校验,如唯一约束 - } - - @Override - public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if (isValid) { - // 做一些业务上的校验,判断是否需要校验 - List list = baseMapper.selectByIds(ids); - if (list.size() != ids.size()) { - throw new ServiceException("您没有删除权限!"); - } - } - return baseMapper.deleteByIds(ids) > 0; - } - - @Override - public Boolean saveBatch(List list) { - return baseMapper.insertBatch(list); - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestTreeServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestTreeServiceImpl.java deleted file mode 100644 index 2d0cd0a3..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestTreeServiceImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.ruoyi.demo.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import org.ruoyi.common.core.utils.MapstructUtils; -import org.ruoyi.common.core.utils.StringUtils; -import org.ruoyi.demo.domain.TestTree; -import org.ruoyi.demo.domain.bo.TestTreeBo; -import org.ruoyi.demo.domain.vo.TestTreeVo; -import org.ruoyi.demo.mapper.TestTreeMapper; -import org.ruoyi.demo.service.ITestTreeService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * 测试树表Service业务层处理 - * - * @author Lion Li - * @date 2021-07-26 - */ -// @DS("slave") // 切换从库查询 -@RequiredArgsConstructor -@Service -public class TestTreeServiceImpl implements ITestTreeService { - - private final TestTreeMapper baseMapper; - - @Override - public TestTreeVo queryById(Long id) { - return baseMapper.selectVoById(id); - } - - // @DS("slave") // 切换从库查询 - @Override - public List queryList(TestTreeBo bo) { - LambdaQueryWrapper lqw = buildQueryWrapper(bo); - return baseMapper.selectVoList(lqw); - } - - private LambdaQueryWrapper buildQueryWrapper(TestTreeBo bo) { - Map params = bo.getParams(); - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName()); - lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, - TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); - lqw.orderByAsc(TestTree::getId); - return lqw; - } - - @Override - public Boolean insertByBo(TestTreeBo bo) { - TestTree add = MapstructUtils.convert(bo, TestTree.class); - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setId(add.getId()); - } - return flag; - } - - @Override - public Boolean updateByBo(TestTreeBo bo) { - TestTree update = MapstructUtils.convert(bo, TestTree.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; - } - - /** - * 保存前的数据校验 - * - * @param entity 实体类数据 - */ - private void validEntityBeforeSave(TestTree entity) { - //TODO 做一些数据校验,如唯一约束 - } - - @Override - public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if (isValid) { - //TODO 做一些业务上的校验,判断是否需要校验 - } - return baseMapper.deleteByIds(ids) > 0; - } -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/package-info.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/package-info.java deleted file mode 100644 index 1f0abbaf..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.ruoyi.demo.service.impl; diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/package-info.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/package-info.java deleted file mode 100644 index 41088fdc..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.ruoyi.demo.service; diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/excel/单列表.xlsx b/ruoyi-modules/ruoyi-demo/src/main/resources/excel/单列表.xlsx deleted file mode 100644 index 0f7347d6..00000000 Binary files a/ruoyi-modules/ruoyi-demo/src/main/resources/excel/单列表.xlsx and /dev/null differ diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多sheet列表.xlsx b/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多sheet列表.xlsx deleted file mode 100644 index 5277f2ea..00000000 Binary files a/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多sheet列表.xlsx and /dev/null differ diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多列表.xlsx b/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多列表.xlsx deleted file mode 100644 index c7d11dcc..00000000 Binary files a/ruoyi-modules/ruoyi-demo/src/main/resources/excel/多列表.xlsx and /dev/null differ diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml deleted file mode 100644 index 6d00b20e..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml deleted file mode 100644 index 46b0e366..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md deleted file mode 100644 index c938b1e5..00000000 --- a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/package-info.md +++ /dev/null @@ -1,3 +0,0 @@ -java包使用 `.` 分割 resource 目录使用 `/` 分割 -
-此文件目的 防止文件夹粘连找不到 `xml` 文件 \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-job/pom.xml b/ruoyi-modules/ruoyi-job/pom.xml deleted file mode 100644 index f4be36da..00000000 --- a/ruoyi-modules/ruoyi-job/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - org.ruoyi - ruoyi-modules - ${revision} - - 4.0.0 - jar - ruoyi-job - - - 任务调度 - - - - - - - org.ruoyi - ruoyi-common-json - - - - org.ruoyi - ruoyi-common-job - - - - - - diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/entity/BillDto.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/entity/BillDto.java deleted file mode 100644 index 7258b03f..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/entity/BillDto.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.ruoyi.job.entity; - -import lombok.Data; - -import java.math.BigDecimal; - -@Data -public class BillDto { - - /** - * 账单ID - */ - private Long billId; - - /** - * 账单渠道 - */ - private String billChannel; - - /** - * 账单日期 - */ - private String billDate; - - /** - * 账单金额 - */ - private BigDecimal billAmount; - -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/package-info.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/package-info.java deleted file mode 100644 index 4729790f..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.ruoyi.job; diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/AlipayBillTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/AlipayBillTask.java deleted file mode 100644 index c61bc02c..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/AlipayBillTask.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.ruoyi.common.json.utils.JsonUtils; -import org.ruoyi.job.entity.BillDto; -import org.springframework.stereotype.Component; - -import java.math.BigDecimal; - -/** - * DAG工作流任务-模拟支付宝账单任务 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "alipayBillTask") -public class AlipayBillTask { - - public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException { - BillDto billDto = new BillDto(); - billDto.setBillId(23456789L); - billDto.setBillChannel("alipay"); - // 设置清算日期 - String settlementDate = (String) jobArgs.getWfContext().get("settlementDate"); - if (StrUtil.equals(settlementDate, "sysdate")) { - settlementDate = DateUtil.today(); - } - billDto.setBillDate(settlementDate); - billDto.setBillAmount(new BigDecimal("2345.67")); - // 把billDto对象放入上下文进行传递 - jobArgs.appendContext("alipay", JsonUtils.toJsonString(billDto)); - SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext()); - return ExecuteResult.success(billDto); - } - -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/SummaryBillTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/SummaryBillTask.java deleted file mode 100644 index 49b991d1..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/SummaryBillTask.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.util.StrUtil; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.ruoyi.common.json.utils.JsonUtils; -import org.ruoyi.job.entity.BillDto; -import org.springframework.stereotype.Component; - -import java.math.BigDecimal; - -/** - * DAG工作流任务-模拟汇总账单任务 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "summaryBillTask") -public class SummaryBillTask { - - public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException { - // 获得微信账单 - BigDecimal wechatAmount = BigDecimal.valueOf(0); - String wechat = (String) jobArgs.getWfContext("wechat"); - if (StrUtil.isNotBlank(wechat)) { - BillDto wechatBillDto = JsonUtils.parseObject(wechat, BillDto.class); - wechatAmount = wechatBillDto.getBillAmount(); - } - // 获得支付宝账单 - BigDecimal alipayAmount = BigDecimal.valueOf(0); - String alipay = (String) jobArgs.getWfContext("alipay"); - if (StrUtil.isNotBlank(alipay)) { - BillDto alipayBillDto = JsonUtils.parseObject(alipay, BillDto.class); - alipayAmount = alipayBillDto.getBillAmount(); - } - // 汇总账单 - BigDecimal totalAmount = wechatAmount.add(alipayAmount); - SnailJobLog.REMOTE.info("总金额: {}", totalAmount); - return ExecuteResult.success(totalAmount); - } - -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestAnnoJobExecutor.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestAnnoJobExecutor.java deleted file mode 100644 index 7530c304..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestAnnoJobExecutor.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.ruoyi.job.snailjob; - -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.common.core.util.JsonUtil; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.springframework.stereotype.Component; - -/** - * 正常任务 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "testJobExecutor") -public class TestAnnoJobExecutor { - - public ExecuteResult jobExecute(JobArgs jobArgs) { - SnailJobLog.LOCAL.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs)); - SnailJobLog.REMOTE.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs)); - return ExecuteResult.success("测试成功"); - } -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestBroadcastJob.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestBroadcastJob.java deleted file mode 100644 index fe794c19..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestBroadcastJob.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.util.RandomUtil; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -/** - * 广播任务 - * - * - * @author 老马 - */ -@Slf4j -@Component -@JobExecutor(name = "testBroadcastJob") -public class TestBroadcastJob { - - @Value("${snail-job.port}") - private int clientPort; - - public ExecuteResult jobExecute(JobArgs jobArgs) { - int randomInt = RandomUtil.randomInt(100); - log.info("随机数: {}", randomInt); - SnailJobLog.REMOTE.info("随机数: {},客户端端口:{}", randomInt, clientPort); - if (randomInt < 50) { - throw new RuntimeException("随机数小于50,收集日志任务执行失败"); - } - // 获得jobArgs 中传入的相加的两个数 - return ExecuteResult.success("随机数大于50,收集日志任务执行成功"); - } - -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestClassJobExecutor.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestClassJobExecutor.java deleted file mode 100644 index 433d13a5..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestClassJobExecutor.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.ruoyi.job.snailjob; - -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.springframework.stereotype.Component; - -/** - * @author opensnail - * @date 2024-05-17 - */ -@Component -public class TestClassJobExecutor extends AbstractJobExecutor { - - @Override - protected ExecuteResult doJobExecute(JobArgs jobArgs) { - return ExecuteResult.success("TestJobExecutor测试成功"); - } -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestMapJobAnnotation.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestMapJobAnnotation.java deleted file mode 100644 index 6b0f28d7..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestMapJobAnnotation.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.extra.spring.SpringUtil; -import com.aizuda.snailjob.client.job.core.MapHandler; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.annotation.MapExecutor; -import com.aizuda.snailjob.client.job.core.dto.MapArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * Map任务 动态分配 只分片不关注结果 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "testMapJobAnnotation") -public class TestMapJobAnnotation { - - @MapExecutor - public ExecuteResult doJobMapExecute(MapArgs mapArgs, MapHandler mapHandler) { - // 生成1~200数值并分片 - int partitionSize = 50; - List> partition = IntStream.rangeClosed(1, 200) - .boxed() - .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize)) - .values() - .stream() - .toList(); - SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port")); - return mapHandler.doMap(partition, "doCalc"); - } - - @MapExecutor(taskName = "doCalc") - public ExecuteResult doCalc(MapArgs mapArgs) { - List sourceList = (List) mapArgs.getMapResult(); - // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal - int partitionTotal = sourceList.stream().mapToInt(i -> i).sum(); - // 打印日志到服务器 - ThreadUtil.sleep(3, TimeUnit.SECONDS); - SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal); - return ExecuteResult.success(partitionTotal); - } - -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestMapReduceAnnotation1.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestMapReduceAnnotation1.java deleted file mode 100644 index 8f5042f5..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestMapReduceAnnotation1.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.extra.spring.SpringUtil; -import com.aizuda.snailjob.client.job.core.MapHandler; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.annotation.MapExecutor; -import com.aizuda.snailjob.client.job.core.annotation.ReduceExecutor; -import com.aizuda.snailjob.client.job.core.dto.MapArgs; -import com.aizuda.snailjob.client.job.core.dto.ReduceArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * MapReduce任务 动态分配 分片后合并结果 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "testMapReduceAnnotation1") -public class TestMapReduceAnnotation1 { - - @MapExecutor - public ExecuteResult rootMapExecute(MapArgs mapArgs, MapHandler mapHandler) { - int partitionSize = 50; - List> partition = IntStream.rangeClosed(1, 200) - .boxed() - .collect(Collectors.groupingBy(i -> (i - 1) / partitionSize)) - .values() - .stream() - .toList(); - SnailJobLog.REMOTE.info("端口:{}完成分配任务", SpringUtil.getProperty("server.port")); - return mapHandler.doMap(partition, "doCalc"); - } - - @MapExecutor(taskName = "doCalc") - public ExecuteResult doCalc(MapArgs mapArgs) { - List sourceList = (List) mapArgs.getMapResult(); - // 遍历sourceList的每一个元素,计算出一个累加值partitionTotal - int partitionTotal = sourceList.stream().mapToInt(i -> i).sum(); - // 打印日志到服务器 - ThreadUtil.sleep(3, TimeUnit.SECONDS); - SnailJobLog.REMOTE.info("端口:{},partitionTotal:{}", SpringUtil.getProperty("server.port"), partitionTotal); - return ExecuteResult.success(partitionTotal); - } - - @ReduceExecutor - public ExecuteResult reduceExecute(ReduceArgs reduceArgs) { - int reduceTotal = reduceArgs.getMapResult().stream().mapToInt(i -> Integer.parseInt((String) i)).sum(); - SnailJobLog.REMOTE.info("端口:{},reduceTotal:{}", SpringUtil.getProperty("server.port"), reduceTotal); - return ExecuteResult.success(reduceTotal); - } -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestStaticShardingJob.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestStaticShardingJob.java deleted file mode 100644 index e0c96662..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/TestStaticShardingJob.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.convert.Convert; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.springframework.stereotype.Component; - -/** - * 静态分片 根据服务端任务参数分片 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "testStaticShardingJob") -public class TestStaticShardingJob { - - public ExecuteResult jobExecute(JobArgs jobArgs) { - String jobParams = Convert.toStr(jobArgs.getJobParams()); - SnailJobLog.LOCAL.info("开始执行分片任务,参数:{}", jobParams); - // 获得jobArgs 中传入的开始id和结束id - String[] split = jobParams.split(","); - Long fromId = Long.parseLong(split[0]); - Long toId = Long.parseLong(split[1]); - // 模拟数据库操作,对范围id,进行加密处理 - try { - SnailJobLog.REMOTE.info("开始对id范围:{}进行加密处理", fromId + "-" + toId); - Thread.sleep(3000); - SnailJobLog.REMOTE.info("对id范围:{}进行加密处理完成", fromId + "-" + toId); - } catch (InterruptedException e) { - return ExecuteResult.failure("任务执行失败"); - } - return ExecuteResult.success("执行分片任务完成"); - } -} diff --git a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/WechatBillTask.java b/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/WechatBillTask.java deleted file mode 100644 index 5e3e6a42..00000000 --- a/ruoyi-modules/ruoyi-job/src/main/java/org/ruoyi/job/snailjob/WechatBillTask.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.ruoyi.job.snailjob; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; -import com.aizuda.snailjob.client.job.core.dto.JobArgs; -import com.aizuda.snailjob.common.log.SnailJobLog; -import com.aizuda.snailjob.model.dto.ExecuteResult; -import org.ruoyi.common.json.utils.JsonUtils; -import org.ruoyi.job.entity.BillDto; -import org.springframework.stereotype.Component; - -import java.math.BigDecimal; - -/** - * DAG工作流任务-模拟微信账单任务 - * - * - * @author 老马 - */ -@Component -@JobExecutor(name = "wechatBillTask") -public class WechatBillTask { - - public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException { - BillDto billDto = new BillDto(); - billDto.setBillId(123456789L); - billDto.setBillChannel("wechat"); - // 从上下文中获得清算日期并设置,如果上下文中清算日期 - // 是sysdate设置为当前日期;否则取管理页面设置的值 - String settlementDate = (String) jobArgs.getWfContext().get("settlementDate"); - if (StrUtil.equals(settlementDate, "sysdate")) { - settlementDate = DateUtil.today(); - } - billDto.setBillDate(settlementDate); - billDto.setBillAmount(new BigDecimal("1234.56")); - // 把billDto对象放入上下文进行传递 - jobArgs.appendContext("wechat", JsonUtils.toJsonString(billDto)); - SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext()); - return ExecuteResult.success(billDto); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/pom.xml b/ruoyi-modules/ruoyi-wechat/pom.xml deleted file mode 100644 index c16f24d5..00000000 --- a/ruoyi-modules/ruoyi-wechat/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - org.ruoyi - ruoyi-modules - ${revision} - ../pom.xml - - - ruoyi-wechat - - - 17 - 17 - UTF-8 - - - - - org.ruoyi - ruoyi-common-web - - - - org.ruoyi - ruoyi-common-core - - - - com.alibaba - fastjson - - - - - com.github.binarywang - weixin-java-cp - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - - - - - diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/AbstractBuilder.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/AbstractBuilder.java deleted file mode 100644 index 4ee22cb8..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/AbstractBuilder.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ruoyi.builder; - -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; - -/** - * @author Binary Wang - */ -public abstract class AbstractBuilder { - public abstract WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, WxCpService service); -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/ImageBuilder.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/ImageBuilder.java deleted file mode 100644 index bd618c59..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/ImageBuilder.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ruoyi.builder; - -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutImageMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; - -/** - * @author Binary Wang - */ -public class ImageBuilder extends AbstractBuilder { - - @Override - public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, - WxCpService service) { - - WxCpXmlOutImageMessage m = WxCpXmlOutMessage.IMAGE().mediaId(content) - .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName()) - .build(); - - return m; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/TextBuilder.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/TextBuilder.java deleted file mode 100644 index c750acfd..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/builder/TextBuilder.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.ruoyi.builder; - -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage; - -/** - * @author Binary Wang - */ -public class TextBuilder extends AbstractBuilder { - - @Override - public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, - WxCpService service) { - WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content(content) - .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName()) - .build(); - return m; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpConfiguration.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpConfiguration.java deleted file mode 100644 index 0399785a..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpConfiguration.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.ruoyi.config; - - -import com.google.common.collect.Maps; -import jakarta.annotation.PostConstruct; -import lombok.val; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; -import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl; -import me.chanjar.weixin.cp.constant.WxCpConsts; -import me.chanjar.weixin.cp.message.WxCpMessageHandler; -import me.chanjar.weixin.cp.message.WxCpMessageRouter; -import org.ruoyi.handler.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import java.util.Map; -import java.util.stream.Collectors; - -/** - * 单实例配置 - * - * @author Binary Wang - */ -@Configuration -@EnableConfigurationProperties(WxCpProperties.class) -public class WxCpConfiguration { - private static Map routers = Maps.newHashMap(); - private static Map cpServices = Maps.newHashMap(); - private final LogHandler logHandler; - private final UnsubscribeHandler unsubscribeHandler; - private NullHandler nullHandler; - private LocationHandler locationHandler; - private MenuHandler menuHandler; - private MsgHandler msgHandler; - private SubscribeHandler subscribeHandler; - private WxCpProperties properties; - - @Autowired - public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler, - MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler, - SubscribeHandler subscribeHandler, WxCpProperties properties) { - this.logHandler = logHandler; - this.nullHandler = nullHandler; - this.locationHandler = locationHandler; - this.menuHandler = menuHandler; - this.msgHandler = msgHandler; - this.unsubscribeHandler = unsubscribeHandler; - this.subscribeHandler = subscribeHandler; - this.properties = properties; - } - - - public static Map getRouters() { - return routers; - } - - public static WxCpService getCpService(Integer agentId) { - return cpServices.get(agentId); - } - - @PostConstruct - public void initServices() { - cpServices = this.properties.getAppConfigs().stream().map(a -> { - val configStorage = new WxCpDefaultConfigImpl(); - configStorage.setCorpId(this.properties.getCorpId()); - configStorage.setAgentId(a.getAgentId()); - configStorage.setCorpSecret(a.getSecret()); - configStorage.setToken(a.getToken()); - configStorage.setAesKey(a.getAesKey()); - val service = new WxCpServiceImpl(); - service.setWxCpConfigStorage(configStorage); - routers.put(a.getAgentId(), this.newRouter(service)); - return service; - }).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getAgentId(), a -> a)); - } - - private WxCpMessageRouter newRouter(WxCpService wxCpService) { - final val newRouter = new WxCpMessageRouter(wxCpService); - - // 记录所有事件的日志 (异步执行) - newRouter.rule().handler(this.logHandler).next(); - - // 自定义菜单事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end(); - - // 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end(); - - // 关注事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler) - .end(); - - // 取消关注事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.UNSUBSCRIBE) - .handler((WxCpMessageHandler) this.unsubscribeHandler).end(); - - // 上报地理位置事件 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.LOCATION).handler(this.locationHandler) - .end(); - - // 接收地理位置消息 - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION) - .handler(this.locationHandler).end(); - - // 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展) - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxConsts.EventType.SCAN).handler((WxCpMessageHandler) this.nullHandler).end(); - - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxCpConsts.EventType.CHANGE_CONTACT).handler(new ContactChangeHandler()).end(); - - newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) - .event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end(); - - // 默认 - newRouter.rule().async(false).handler(this.msgHandler).end(); - - return newRouter; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java deleted file mode 100644 index b58b6b74..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.ruoyi.config; - -import lombok.Data; -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.List; - -/** - * @author Binary Wang - */ -@Data -@ConfigurationProperties(prefix = "wechat.cp") -public class WxCpProperties { - /** - * 设置企业微信的corpId - */ - private String corpId; - - private List appConfigs; - - @Getter - @Setter - public static class AppConfig { - /** - * 设置企业微信应用的AgentId - */ - private Integer agentId; - - /** - * 设置企业微信应用的Secret - */ - private String secret; - - /** - * 设置企业微信应用的token - */ - private String token; - - /** - * 设置企业微信应用的EncodingAESKey - */ - private String aesKey; - - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java deleted file mode 100644 index 5f17cb52..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.ruoyi.controller; - - -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.ruoyi.service.WeixinUserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -/** - * 企业微信应用 - * - * @author ageerle - * @date 2025-05-03 - */ -@Slf4j -@RestController -public class WeixinServerController { - - @Autowired - private WeixinUserService weixinUserService; - - @GetMapping(value = "/weixin/check") - public String weixinCheck(HttpServletRequest request) { - String signature = request.getParameter("signature"); - String timestamp = request.getParameter("timestamp"); - String nonce = request.getParameter("nonce"); - String echostr = request.getParameter("echostr"); - - if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) { - return ""; - } - weixinUserService.checkSignature(signature, timestamp, nonce); - return echostr; - } - - @PostMapping(value = "/weixin/check") - public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature, - @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) { - - log.debug("requestBody:{}", requestBody); - log.debug("signature:{}", signature); - log.debug("timestamp:{}", timestamp); - log.debug("nonce:{}", nonce); - - weixinUserService.checkSignature(signature, timestamp, nonce); - return weixinUserService.handleWeixinMsg(requestBody); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java deleted file mode 100644 index 8dca18ef..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.ruoyi.controller; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.ruoyi.common.core.domain.R; -import org.ruoyi.common.core.domain.dto.VisitorLoginUserDto; -import org.ruoyi.common.core.service.UserLoginService; -import org.ruoyi.domin.WeixinQrCode; -import org.ruoyi.util.WeixinApiUtil; -import org.ruoyi.util.WeixinQrCodeCacheUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 微信公众号登录 - * - * @author ageerle - * @date 2025-05-03 - */ -@Slf4j -@RestController -public class WeixinUserController { - - @Autowired - private WeixinApiUtil weixinApiUtil; - - @Autowired - private UserLoginService userLoginService; - - @GetMapping(value = "/user/qrcode") - public R getQrCode() { - WeixinQrCode qrCode = weixinApiUtil.getQrCode(); - qrCode.setUrl(null); - qrCode.setExpireSeconds(null); - return R.ok(qrCode); - } - - /** - * 校验是否扫描完成 - * 完成,返回 JWT - * 未完成,返回 check failed - */ - @GetMapping(value = "/user/login/qrcode") - public R userLogin(String ticket,String clientId) { - String openId = WeixinQrCodeCacheUtil.get(ticket); - if (StringUtils.isNotEmpty(openId)) { - log.info("login success,open id:{}", openId); - VisitorLoginUserDto loginBody = userLoginService.mpLogin(openId,clientId); - return R.ok(loginBody); - } - log.info("login error,ticket:{}", ticket); - return R.fail("check failed"); - } -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java deleted file mode 100644 index f34f9980..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.ruoyi.controller; - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; -import org.apache.commons.lang3.StringUtils; -import org.ruoyi.common.json.utils.JsonUtils; -import org.ruoyi.config.WxCpConfiguration; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.*; - -/** - * 微信公众号登录校验 - * - * @author ageerle - * @date 2025-05-03 - */ -@RestController -@RequestMapping("/wx/cp") -@Slf4j -public class WxPortalController { - - @Value("${wechat.cp.appConfigs[0].agentId}") - private Integer agentId; - - - @GetMapping(produces = "text/plain;charset=utf-8") - public String authGet( - @RequestParam(name = "msg_signature", required = false) String signature, - @RequestParam(name = "timestamp", required = false) String timestamp, - @RequestParam(name = "nonce", required = false) String nonce, - @RequestParam(name = "echostr", required = false) String echostr) { - log.info("\n接收到来自微信服务器的认证消息:signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]", - signature, timestamp, nonce, echostr); - - if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { - throw new IllegalArgumentException("请求参数非法,请核实!"); - } - - final WxCpService wxCpService = WxCpConfiguration.getCpService(agentId); - if (wxCpService == null) { - throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId)); - } - - if (wxCpService.checkSignature(signature, timestamp, nonce, echostr)) { - return new WxCpCryptUtil(wxCpService.getWxCpConfigStorage()).decrypt(echostr); - } - - return "非法请求"; - } - - @PostMapping(produces = "application/xml; charset=UTF-8") - public String post( - @RequestBody String requestBody, - @RequestParam("msg_signature") String signature, - @RequestParam("timestamp") String timestamp, - @RequestParam("nonce") String nonce) { - log.info("\n接收微信请求:[signature=[{}], timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", - signature, timestamp, nonce, requestBody); - - final WxCpService wxCpService = WxCpConfiguration.getCpService(1000002); - WxCpXmlMessage inMessage = WxCpXmlMessage.fromEncryptedXml(requestBody, wxCpService.getWxCpConfigStorage(), - timestamp, nonce, signature); - log.debug("\n消息解密后内容为:\n{} ", JsonUtils.toJsonString(inMessage)); - WxCpXmlOutMessage outMessage = this.route(1000002, inMessage); - if (outMessage == null) { - return ""; - } - - String out = outMessage.toEncryptedXml(wxCpService.getWxCpConfigStorage()); - log.debug("\n组装回复信息:{}", out); - return out; - } - - private WxCpXmlOutMessage route(Integer agentId, WxCpXmlMessage message) { - try { - return WxCpConfiguration.getRouters().get(agentId).route(message); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - - return null; - } - - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java deleted file mode 100644 index adc7d300..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.ruoyi.domin; - -import lombok.Data; - -@Data -public class ReceiveMessage { - /** - * 消息ID 64位 - */ - String msgId; - /** - * 开发者微信号 - */ - private String toUserName; - /** - * 发送方账号(一个openid) - */ - private String fromUserName; - /** - * 消息创建时间(整形) - */ - private String createTime; - /** - * 消息类型 - */ - private String msgType; - /** - * 文本消息内容 - */ - private String content; - /** - * 消息的数据ID 消息来自文章才有 - */ - private String msgDataId; - /** - * 多图文时第几篇文章,从1开始 消息如果来自文章才有 - */ - private String idx; - /** - * 订阅事件 subscribe 订阅 unsbscribe 取消订阅 - */ - private String event; - /** - * 扫码 - ticket - */ - private String ticket; - - public String getReplyTextMsg(String msg) { - String xml = "\n" - + " \n" - + " \n" - + " " + System.currentTimeMillis() + "\n" - + " \n" - + " \n" - + " "; - return xml; - } -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java deleted file mode 100644 index 0363b3bf..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.ruoyi.domin; - -import lombok.Data; - -/** - * @author https://www.wdbyte.com - */ -@Data -public class WeixinQrCode { - - private String ticket; - private Long expireSeconds; - private String url; - private String qrCodeUrl; -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/AbstractHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/AbstractHandler.java deleted file mode 100644 index 7ec2b0ce..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/AbstractHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.ruoyi.handler; - -import me.chanjar.weixin.cp.message.WxCpMessageHandler; - -/** - * @author Binary Wang - */ -public abstract class AbstractHandler implements WxCpMessageHandler { -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/ContactChangeHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/ContactChangeHandler.java deleted file mode 100644 index c4b65851..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/ContactChangeHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.ruoyi.handler; - - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.ruoyi.builder.TextBuilder; -import org.ruoyi.common.json.utils.JsonUtils; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * 通讯录变更事件处理器. - * - * @author Binary Wang - */ -@Slf4j -@Component -public class ContactChangeHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - String content = "收到通讯录变更事件,内容:" + JsonUtils.toJsonString(wxMessage); - log.info(content); - - return new TextBuilder().build(content, wxMessage, cpService); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/EnterAgentHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/EnterAgentHandler.java deleted file mode 100644 index c98fe68a..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/EnterAgentHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.ruoyi.handler; - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; - -import java.util.Map; - -/** - *

- *
- * Created by Binary Wang on 2018/8/27.
- * 
- * - * @author Binary Wang - */ -@Slf4j -public class EnterAgentHandler extends AbstractHandler { - private static final int TEST_AGENT = 1000002; - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException { - // do something - return null; - } -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/LocationHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/LocationHandler.java deleted file mode 100644 index 6aaa3ec7..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/LocationHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.ruoyi.handler; - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.ruoyi.builder.TextBuilder; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Slf4j -@Component -public class LocationHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.LOCATION)) { - //TODO 接收处理用户发送的地理位置消息 - try { - String content = "感谢反馈,您的的地理位置已收到!"; - return new TextBuilder().build(content, wxMessage, null); - } catch (Exception e) { - log.error("位置消息接收处理失败", e); - return null; - } - } - - //上报地理位置事件 - log.info("\n上报地理位置,纬度 : {}\n经度 : {}\n精度 : {}", - wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision())); - - //TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 - - return null; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/LogHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/LogHandler.java deleted file mode 100644 index 80e03ef9..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/LogHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ruoyi.handler; - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.ruoyi.common.json.utils.JsonUtils; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Slf4j -@Component -public class LogHandler extends AbstractHandler { - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - log.info("\n接收到请求消息,内容:{}", JsonUtils.toJsonString(wxMessage)); - return null; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/MenuHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/MenuHandler.java deleted file mode 100644 index 0b8be34b..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/MenuHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.ruoyi.handler; - -import me.chanjar.weixin.common.api.WxConsts.MenuButtonType; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Component -public class MenuHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - - String msg = String.format("type:%s, event:%s, key:%s", - wxMessage.getMsgType(), wxMessage.getEvent(), - wxMessage.getEventKey()); - if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) { - return null; - } - - return WxCpXmlOutMessage.TEXT().content(msg) - .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName()) - .build(); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/MsgHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/MsgHandler.java deleted file mode 100644 index 9914eedb..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/MsgHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.ruoyi.handler; - -import lombok.RequiredArgsConstructor; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.ruoyi.builder.TextBuilder; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Component -@RequiredArgsConstructor -public class MsgHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - final String msgType = wxMessage.getMsgType(); - if (msgType == null) { - // 如果msgType没有,就自己根据具体报文内容做处理 - } - - if (!msgType.equals(WxConsts.XmlMsgType.EVENT)) { - //TODO 可以选择将消息保存到本地 - } - //TODO 组装回复消息 - return new TextBuilder().build("你好", wxMessage, cpService); - - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/NullHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/NullHandler.java deleted file mode 100644 index b7e8d54a..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/NullHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.ruoyi.handler; - -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Component -public class NullHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - return null; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/ScanHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/ScanHandler.java deleted file mode 100644 index 4d9a5025..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/ScanHandler.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.ruoyi.handler; - -/** - * @author Binary Wang - */ -public abstract class ScanHandler extends AbstractHandler { - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/SubscribeHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/SubscribeHandler.java deleted file mode 100644 index c5e42fb4..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/SubscribeHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.ruoyi.handler; - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpUser; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.ruoyi.builder.TextBuilder; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Slf4j -@Component -public class SubscribeHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) throws WxErrorException { - - log.info("新关注用户 OPENID: " + wxMessage.getFromUserName()); - - // 获取微信用户基本信息 - WxCpUser userWxInfo = cpService.getUserService().getById(wxMessage.getFromUserName()); - - if (userWxInfo != null) { - // TODO 可以添加关注用户到本地 - } - - WxCpXmlOutMessage responseResult = null; - try { - responseResult = handleSpecial(wxMessage); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - - if (responseResult != null) { - return responseResult; - } - - try { - return new TextBuilder().build("感谢关注", wxMessage, cpService); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - - return null; - } - - /** - * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 - */ - private WxCpXmlOutMessage handleSpecial(WxCpXmlMessage wxMessage) { - //TODO - return null; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/UnsubscribeHandler.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/UnsubscribeHandler.java deleted file mode 100644 index 77933460..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/handler/UnsubscribeHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.ruoyi.handler; - -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * @author Binary Wang - */ -@Slf4j -@Component -public class UnsubscribeHandler extends AbstractHandler { - - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService cpService, - WxSessionManager sessionManager) { - String openId = wxMessage.getFromUserName(); - log.info("取消关注用户 OPENID: " + openId); - // TODO 可以更新本地数据库为取消关注状态 - return null; - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/IChatVxService.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/IChatVxService.java deleted file mode 100644 index 43d7e62e..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/IChatVxService.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.ruoyi.service; - -/** - * 企业微信聊天管理Service接口 - * - * @author ageerle - * @date 2025-04-08 - */ -public interface IChatVxService { - - - /** - * 企业微信应用回复 - * - * @param prompt 提示词 - * @return 回复内容 - */ - String chat(String prompt); - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java deleted file mode 100644 index c9996791..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ruoyi.service; - - -public interface WeixinUserService { - - void checkSignature(String signature, String timestamp, String nonce); - - String handleWeixinMsg(String body); - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java deleted file mode 100644 index 81ef6979..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ruoyi.service.impl; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.digest.DigestUtils; -import org.ruoyi.domin.ReceiveMessage; -import org.ruoyi.service.WeixinUserService; -import org.ruoyi.util.WeixinMsgUtil; -import org.ruoyi.util.WeixinQrCodeCacheUtil; -import org.springframework.stereotype.Service; - -import java.util.Arrays; - - -@Slf4j -@Service -public class WeixinUserServiceImpl implements WeixinUserService { - - private String token = "panda"; - - @Override - public void checkSignature(String signature, String timestamp, String nonce) { - String[] arr = new String[]{token, timestamp, nonce}; - Arrays.sort(arr); - StringBuilder content = new StringBuilder(); - for (String str : arr) { - content.append(str); - } - String tmpStr = DigestUtils.sha1Hex(content.toString()); - if (tmpStr.equals(signature)) { - log.info("check success"); - return; - } - log.error("check fail"); - throw new RuntimeException("check fail"); - } - - @Override - public String handleWeixinMsg(String requestBody) { - ReceiveMessage receiveMessage = WeixinMsgUtil.msgToReceiveMessage(requestBody); - // 扫码登录 - if (WeixinMsgUtil.isScanQrCode(receiveMessage)) { - return handleScanLogin(receiveMessage); - } - // 关注 - if (WeixinMsgUtil.isEventAndSubscribe(receiveMessage)) { - return receiveMessage.getReplyTextMsg("感谢您的关注!"); - } - return receiveMessage.getReplyTextMsg("收到(自动回复)"); - } - - /** - * 处理扫码登录 - * - * @param receiveMessage - * @return - */ - private String handleScanLogin(ReceiveMessage receiveMessage) { - String qrCodeTicket = WeixinMsgUtil.getQrCodeTicket(receiveMessage); - if (WeixinQrCodeCacheUtil.get(qrCodeTicket) == null) { - String openId = receiveMessage.getFromUserName(); - WeixinQrCodeCacheUtil.put(qrCodeTicket, openId); - } - return receiveMessage.getReplyTextMsg("你已成功登录!"); - } -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java deleted file mode 100644 index 6bbf917f..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.ruoyi.util; - -import org.apache.commons.lang3.RandomStringUtils; - -import java.util.UUID; - -/** - * @author https://www.wdbyte.com - */ -public class KeyUtils { - - public synchronized static String key6() { - return RandomStringUtils.randomAlphanumeric(6); - } - - public synchronized static String key16() { - return RandomStringUtils.randomAlphanumeric(16); - } - - public static String uuid32() { - return UUID.randomUUID().toString().replace("-", ""); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java deleted file mode 100644 index ceef7554..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.ruoyi.util; - -import cn.hutool.http.HttpUtil; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.common.core.service.ConfigService; -import org.ruoyi.domin.WeixinQrCode; -import org.springframework.stereotype.Component; - -import java.net.URI; -import java.time.LocalDateTime; - -/** - * @author https://www.wdbyte.com - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class WeixinApiUtil { - - private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="; - private static String ACCESS_TOKEN = null; - private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null; - /** - * 二维码 Ticket 过期时间 - */ - private static int QR_CODE_TICKET_TIMEOUT = 10 * 60; - private final ConfigService configService; - - /** - * 获取 access token - * - * @return - */ - public synchronized String getAccessToken() { - if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) { - return ACCESS_TOKEN; - } - String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + getKey("appid") + "&secret=" - + getKey("secret"); - String result = HttpUtil.get(api); - JSONObject jsonObject = JSON.parseObject(result); - ACCESS_TOKEN = jsonObject.getString("access_token"); - ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10); - return ACCESS_TOKEN; - } - - /** - * 获取二维码 Ticket - *

- * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html - * - * @return - */ - public WeixinQrCode getQrCode() { - String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken(); - String jsonBody = String.format("{\n" - + " \"expire_seconds\": %d,\n" - + " \"action_name\": \"QR_STR_SCENE\",\n" - + " \"action_info\": {\n" - + " \"scene\": {\n" - + " \"scene_str\": \"%s\"\n" - + " }\n" - + " }\n" - + "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32()); - String result = HttpUtil.post(api, jsonBody); - log.info("get qr code params:{}", jsonBody); - log.info("get qr code result:{}", result); - WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class); - weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString()); - return weixinQrCode; - } - - public String getKey(String key) { - return configService.getConfigValue("wechat",key); - } -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java deleted file mode 100644 index e1fd520c..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.ruoyi.util; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import org.apache.commons.lang3.StringUtils; -import org.ruoyi.domin.ReceiveMessage; - -/** - * @author https://www.wdbyte.com - */ -public class WeixinMsgUtil { - - // 事件-关注 - private static String EVENT_SUBSCRIBE = "subscribe"; - - /** - * 微信消息转对象 - * - * @param xml - * @return - */ - public static ReceiveMessage msgToReceiveMessage(String xml) { - JSONObject jsonObject = JSON.parseObject(XmlUtil.xml2json(xml)); - ReceiveMessage receiveMessage = new ReceiveMessage(); - receiveMessage.setToUserName(jsonObject.getString("ToUserName")); - receiveMessage.setFromUserName(jsonObject.getString("FromUserName")); - receiveMessage.setCreateTime(jsonObject.getString("CreateTime")); - receiveMessage.setMsgType(jsonObject.getString("MsgType")); - receiveMessage.setContent(jsonObject.getString("Content")); - receiveMessage.setMsgId(jsonObject.getString("MsgId")); - receiveMessage.setEvent(jsonObject.getString("Event")); - receiveMessage.setTicket(jsonObject.getString("Ticket")); - return receiveMessage; - } - - /** - * 是否是订阅事件 - * - * @param receiveMessage - * @return - */ - public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) { - return StringUtils.equals(receiveMessage.getEvent(), EVENT_SUBSCRIBE); - } - - /** - * 是否是二维码扫描事件 - * - * @param receiveMessage - * @return - */ - public static boolean isScanQrCode(ReceiveMessage receiveMessage) { - return StringUtils.isNotEmpty(receiveMessage.getTicket()); - } - - /** - * 获取扫描的二维码 Ticket - * - * @param receiveMessage - * @return - */ - public static String getQrCodeTicket(ReceiveMessage receiveMessage) { - return receiveMessage.getTicket(); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java deleted file mode 100644 index aaebd1e9..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.ruoyi.util; - -import java.util.LinkedHashMap; - -/** - * 微信二维码缓存工具类 - * - * @author https://www.wdbyte.com - */ -public class WeixinQrCodeCacheUtil { - private static long MAX_CACHE_SIZE = 10000; - private static LinkedHashMap QR_CODE_TICKET_MAP = new LinkedHashMap<>(); - - /** - * 增加一个 Ticket - * 首次 put:value 为 "" - * 再次 put: value 有 openId,若openId已经存在,则已被扫码 - * - * @param key - * @param value - */ - public synchronized static void put(String key, String value) { - QR_CODE_TICKET_MAP.put(key, value); - if (QR_CODE_TICKET_MAP.size() > MAX_CACHE_SIZE) { - String first = QR_CODE_TICKET_MAP.keySet().stream().findFirst().get(); - QR_CODE_TICKET_MAP.remove(first); - } - } - - public synchronized static String get(String key) { - return QR_CODE_TICKET_MAP.remove(key); - } - -} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java deleted file mode 100644 index d6e58f69..00000000 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.ruoyi.util; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author https://www.wdbyte.com - */ -@Slf4j -public class XmlUtil { - - public static String xml2json(String requestBody) { - requestBody = StringUtils.trim(requestBody); - XmlMapper xmlMapper = new XmlMapper(); - JsonNode node = null; - try { - node = xmlMapper.readTree(requestBody.getBytes()); - ObjectMapper jsonMapper = new ObjectMapper(); - return jsonMapper.writeValueAsString(node); - } catch (Exception e) { - log.error("xml 2 json error,msg:" + e.getMessage(), e); - } - return null; - } -}