From 8a03ede7dcbd298e3ba6e29a0b675003e9074fc5 Mon Sep 17 00:00:00 2001
From: wangys <3401275564@qq.com>
Date: Mon, 15 Dec 2025 15:26:05 +0800
Subject: [PATCH] =?UTF-8?q?ai=E6=A8=A1=E5=9D=97=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docker/nginx/conf.d/dify.conf | 51 +
.../database/postgres/sql/createTableAI.sql | 166 +++
.../postgres/sql/createTableAgent.sql | 306 -----
.../database/postgres/sql/createTableAll.sql | 156 ---
.../database/postgres/sql/createTableFile.sql | 51 +-
.../postgres/sql/createTableKnowledge.sql | 153 ---
.../.bin/database/postgres/sql/initAll.sql | 7 +-
.../database/postgres/sql/initDataConfig.sql | 17 +
.../org/xyzh/agent/client/DifyClient.java | 5 -
urbanLifelineServ/{agent => ai}/pom.xml | 15 +-
.../src/main/java/org/xyzh/ai}/AgentApp.java | 2 +-
.../org/xyzh/ai/client/DifyApiClient.java | 1031 +++++++++++++++++
.../ai/client/callback/StreamCallback.java | 44 +
.../org/xyzh/ai/client/dto/ChatRequest.java | 75 ++
.../org/xyzh/ai/client/dto/ChatResponse.java | 121 ++
.../client/dto/ConversationListResponse.java | 50 +
.../ai/client/dto/DatasetCreateRequest.java | 55 +
.../ai/client/dto/DatasetCreateResponse.java | 55 +
.../ai/client/dto/DatasetDetailResponse.java | 82 ++
.../ai/client/dto/DatasetListResponse.java | 61 +
.../ai/client/dto/DatasetUpdateRequest.java | 50 +
.../ai/client/dto/DocumentListResponse.java | 96 ++
.../ai/client/dto/DocumentStatusResponse.java | 95 ++
.../ai/client/dto/DocumentUploadRequest.java | 95 ++
.../ai/client/dto/DocumentUploadResponse.java | 145 +++
.../ai/client/dto/EmbeddingModelResponse.java | 144 +++
.../ai/client/dto/MessageHistoryResponse.java | 94 ++
.../ai/client/dto/RerankModelResponse.java | 56 +
.../xyzh/ai/client/dto/RetrievalModel.java | 76 ++
.../xyzh/ai/client/dto/RetrievalRequest.java | 33 +
.../xyzh/ai/client/dto/RetrievalResponse.java | 88 ++
.../java/org/xyzh/ai/config/DifyConfig.java | 170 +++
.../org/xyzh/ai/exception/DifyException.java | 42 +
.../src/main/resources/application.yml | 0
.../src/main/resources/log4j2.xml | 0
.../xyzh/api/agent/dto/KnowledgeBaseDTO.java | 51 -
.../xyzh/api/agent/dto/KnowledgeChunkDTO.java | 48 -
.../api/agent/dto/KnowledgeDocumentDTO.java | 67 --
.../xyzh/api/agent/vo/KnowledgeBaseVO.java | 93 --
.../xyzh/api/agent/vo/KnowledgeChunkVO.java | 68 --
.../api/agent/vo/KnowledgeDocumentVO.java | 114 --
.../apis/{api-agent => api-ai}/pom.xml | 4 +-
.../java/org/xyzh/api/ai}/AgentService.java | 2 +-
.../org/xyzh/api/ai/dto/DifyFileInfo.java | 99 ++
.../java/org/xyzh/api/ai/dto/PromptCard.java | 14 +
.../java/org/xyzh/api/ai/dto/TbAgent.java | 41 +
.../main/java/org/xyzh/api/ai/dto/TbChat.java | 25 +
.../org/xyzh/api/ai/dto/TbChatMessage.java | 30 +
.../java/org/xyzh/api/ai/dto/TbKnowledge.java | 68 ++
.../org/xyzh/api/ai/dto/TbKnowledgeFile.java | 28 +
.../main/java/org/xyzh/api/ai/vo/AgentVO.java | 12 +
.../org/xyzh/api/file/dto/TbSysFileDTO.java | 6 +
urbanLifelineServ/apis/pom.xml | 2 +-
.../utils/json/StringArrayTypeHandler.java | 59 +
urbanLifelineServ/pom.xml | 2 +-
urbanLifelineServ/知识库逻辑变更.md | 2 +
56 files changed, 3403 insertions(+), 1119 deletions(-)
create mode 100644 docker/nginx/conf.d/dify.conf
create mode 100644 urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql
delete mode 100644 urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql
delete mode 100644 urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql
delete mode 100644 urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql
delete mode 100644 urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java
rename urbanLifelineServ/{agent => ai}/pom.xml (59%)
rename urbanLifelineServ/{agent/src/main/java/org/xyzh/agent => ai/src/main/java/org/xyzh/ai}/AgentApp.java (97%)
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/MessageHistoryResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RerankModelResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalModel.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalRequest.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalResponse.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java
create mode 100644 urbanLifelineServ/ai/src/main/java/org/xyzh/ai/exception/DifyException.java
rename urbanLifelineServ/{agent => ai}/src/main/resources/application.yml (100%)
rename urbanLifelineServ/{agent => ai}/src/main/resources/log4j2.xml (100%)
delete mode 100644 urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeBaseDTO.java
delete mode 100644 urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeChunkDTO.java
delete mode 100644 urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeDocumentDTO.java
delete mode 100644 urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeBaseVO.java
delete mode 100644 urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeChunkVO.java
delete mode 100644 urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeDocumentVO.java
rename urbanLifelineServ/apis/{api-agent => api-ai}/pom.xml (89%)
rename urbanLifelineServ/apis/{api-agent/src/main/java/org/xyzh/api/agent => api-ai/src/main/java/org/xyzh/api/ai}/AgentService.java (78%)
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/DifyFileInfo.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/PromptCard.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbAgent.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChat.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChatMessage.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledge.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledgeFile.java
create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/AgentVO.java
create mode 100644 urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/json/StringArrayTypeHandler.java
create mode 100644 urbanLifelineServ/知识库逻辑变更.md
diff --git a/docker/nginx/conf.d/dify.conf b/docker/nginx/conf.d/dify.conf
new file mode 100644
index 00000000..bef3c96c
--- /dev/null
+++ b/docker/nginx/conf.d/dify.conf
@@ -0,0 +1,51 @@
+# Dify 服务代理配置
+
+# Dify Web 前端
+location /dify/ {
+ proxy_pass http://dify-web:3000/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ # 支持 WebSocket
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ # 超时配置
+ proxy_connect_timeout 60s;
+ proxy_send_timeout 60s;
+ proxy_read_timeout 60s;
+}
+
+# Dify API
+location /dify/api/ {
+ proxy_pass http://dify-api:5001/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ # 支持 WebSocket (SSE)
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ # 超时配置(AI 响应可能较慢)
+ proxy_connect_timeout 300s;
+ proxy_send_timeout 300s;
+ proxy_read_timeout 300s;
+
+ # 禁用缓冲(支持流式响应)
+ proxy_buffering off;
+}
+
+# Dify App (公开应用)
+location /dify/app/ {
+ proxy_pass http://dify-web:3000/app/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+}
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql
new file mode 100644
index 00000000..dd7eb252
--- /dev/null
+++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql
@@ -0,0 +1,166 @@
+CREATE SCHEMA IF NOT EXISTS ai;
+
+-- AI智能体配置
+DROP TABLE IF EXISTS ai.tb_agent CASCADE;
+CREATE TABLE ai.tb_agent(
+ optsn VARCHAR(50) NOT NULL, -- 流水号
+ agent_id VARCHAR(50) NOT NULL, -- 智能体ID
+ name VARCHAR(50) NOT NULL, -- 智能体名称
+ description VARCHAR(500) DEFAULT NULL, -- 智能体描述
+ link VARCHAR(500) DEFAULT NULL, -- 智能体url
+ api_key VARCHAR(500) NOT NULL, -- dify智能体APIKEY
+ introduce VARCHAR(500) NOT NULL, -- 引导词
+ prompt_cards JSONB DEFAULT '[]'::jsonb, -- 提示卡片数组 [{file_id:'', prompt:''}]
+ category VARCHAR(50) NOT NULL, -- 分类
+ creator VARCHAR(50) DEFAULT NULL, -- 创建者
+ updater VARCHAR(50) DEFAULT NULL, -- 更新者
+ create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
+ update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
+ delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
+ deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
+ PRIMARY KEY (agent_id),
+ UNIQUE (optsn),
+ UNIQUE (api_key)
+);
+
+-- AI智能体对话
+DROP TABLE IF EXISTS ai.tb_chat CASCADE;
+CREATE TABLE ai.tb_chat(
+ optsn VARCHAR(50) NOT NULL, -- 流水号
+ chat_id VARCHAR(50) NOT NULL, -- 对话ID
+ agent_id VARCHAR(50) NOT NULL, -- 智能体ID
+ user_id VARCHAR(50) NOT NULL, -- 用户ID
+ title VARCHAR(500) NOT NULL, -- 对话标题
+ create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
+ update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
+ delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
+ deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
+ PRIMARY KEY (chat_id),
+ UNIQUE (optsn)
+);
+
+-- AI智能体对话消息
+DROP TABLE IF EXISTS ai.tb_chat_message CASCADE;
+CREATE TABLE ai.tb_chat_message(
+ optsn VARCHAR(50) NOT NULL, -- 流水号
+ message_id VARCHAR(50) NOT NULL, -- 消息ID
+ chat_id VARCHAR(50) NOT NULL, -- 对话ID
+ role VARCHAR(50) NOT NULL, -- 角色:user-用户/assistant-智能体/recipient-客服
+ content TEXT NOT NULL, -- 消息内容
+ files VARCHAR(50)[] DEFAULT NULL, -- 文件id数组
+ create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
+ update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
+ delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
+ deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
+ PRIMARY KEY (message_id),
+ UNIQUE (optsn)
+);
+
+
+-- 知识库配置 bidding和workcase2个服务使用
+DROP TABLE IF EXISTS ai.tb_knowledge CASCADE;
+CREATE TABLE ai.tb_knowledge(
+ -- 知识库dify相关配置
+ optsn VARCHAR(50) NOT NULL, -- 流水号
+ knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
+ title VARCHAR(255) NOT NULL, -- 知识库标题
+ avatar VARCHAR(255) DEFAULT NULL, -- 知识库头像
+ description VARCHAR(500) DEFAULT NULL, -- 知识库描述
+ dify_dataset_id VARCHAR(100) DEFAULT NULL, -- Dify知识库ID(Dataset ID)
+ dify_indexing_technique VARCHAR(50) DEFAULT 'high_quality', -- Dify索引方式(high_quality/economy)
+ embedding_model VARCHAR(100) DEFAULT NULL, -- 向量模型名称
+ embedding_model_provider VARCHAR(100) DEFAULT NULL, -- 向量模型提供商
+ rerank_model VARCHAR(100) DEFAULT NULL, -- Rerank模型名称
+ rerank_model_provider VARCHAR(100) DEFAULT NULL, -- Rerank模型提供商
+ reranking_enable BOOLEAN DEFAULT false, -- 是否启用Rerank
+ retrieval_top_k INTEGER DEFAULT 2, -- 检索Top K(返回前K个结果)
+ retrieval_score_threshold DECIMAL(3,2) DEFAULT 0.00, -- 检索分数阈值(0.00-1.00)
+ document_count INTEGER DEFAULT 0, -- 文档数量
+ total_chunks INTEGER DEFAULT 0, -- 总分段数
+ -- 下面是服务使用
+ service VARCHAR(50) DEFAULT NULL, -- 所属服务 workcase、bidding
+ project_id VARCHAR(50) DEFAULT NULL, -- bidding所属项目ID
+ category VARCHAR(50) DEFAULT NULL, -- 所属分类 workcase 内部知识库、外部知识库
+ creator VARCHAR(50) NOT NULL, -- 创建者(用户ID)
+ dept_path VARCHAR(50) DEFAULT NULL, -- 创建者部门路径
+ updater VARCHAR(50) DEFAULT NULL, -- 更新者
+ create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
+ update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
+ delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
+ deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
+ PRIMARY KEY (optsn),
+ UNIQUE (knowledge_id),
+ UNIQUE (dify_dataset_id)
+);
+
+-- 知识库配置表字段注释
+COMMENT ON TABLE ai.tb_knowledge IS '知识库配置表';
+COMMENT ON COLUMN ai.tb_knowledge.optsn IS '流水号';
+COMMENT ON COLUMN ai.tb_knowledge.knowledge_id IS '知识库ID';
+COMMENT ON COLUMN ai.tb_knowledge.title IS '知识库标题';
+COMMENT ON COLUMN ai.tb_knowledge.avatar IS '知识库头像';
+COMMENT ON COLUMN ai.tb_knowledge.description IS '知识库描述';
+COMMENT ON COLUMN ai.tb_knowledge.dify_dataset_id IS 'Dify知识库ID(Dataset ID)';
+COMMENT ON COLUMN ai.tb_knowledge.dify_indexing_technique IS 'Dify索引方式(high_quality/economy)';
+COMMENT ON COLUMN ai.tb_knowledge.embedding_model IS '向量模型名称';
+COMMENT ON COLUMN ai.tb_knowledge.embedding_model_provider IS '向量模型提供商';
+COMMENT ON COLUMN ai.tb_knowledge.rerank_model IS 'Rerank模型名称';
+COMMENT ON COLUMN ai.tb_knowledge.rerank_model_provider IS 'Rerank模型提供商';
+COMMENT ON COLUMN ai.tb_knowledge.reranking_enable IS '是否启用Rerank';
+COMMENT ON COLUMN ai.tb_knowledge.retrieval_top_k IS '检索Top K(返回前K个结果)';
+COMMENT ON COLUMN ai.tb_knowledge.retrieval_score_threshold IS '检索分数阈值(0.00-1.00)';
+COMMENT ON COLUMN ai.tb_knowledge.document_count IS '文档数量';
+COMMENT ON COLUMN ai.tb_knowledge.total_chunks IS '总分段数';
+COMMENT ON COLUMN ai.tb_knowledge.service IS '所属服务 workcase、bidding';
+COMMENT ON COLUMN ai.tb_knowledge.project_id IS 'bidding所属项目ID';
+COMMENT ON COLUMN ai.tb_knowledge.category IS '所属分类 workcase 内部知识库、外部知识库';
+COMMENT ON COLUMN ai.tb_knowledge.creator IS '创建者(用户ID)';
+COMMENT ON COLUMN ai.tb_knowledge.dept_path IS '创建者部门路径';
+COMMENT ON COLUMN ai.tb_knowledge.updater IS '更新者';
+COMMENT ON COLUMN ai.tb_knowledge.create_time IS '创建时间';
+COMMENT ON COLUMN ai.tb_knowledge.update_time IS '更新时间';
+COMMENT ON COLUMN ai.tb_knowledge.delete_time IS '删除时间';
+COMMENT ON COLUMN ai.tb_knowledge.deleted IS '是否删除';
+-- bidding知识库根据project等变化
+-- workcase知识库固定8个
+-- workcase外部知识库:4个知识库:
+-- 1. 设备操作指南
+-- 2. 常见故障解决方案
+-- 3. 三包外服务政策
+-- 4. 配件咨询话术
+-- workcase内部知识库:4个知识库:
+-- 1. 技术维修手册
+-- 2. 产品参数明细
+-- 3. 内部服务流程规范
+-- 4. 客户服务话术模板
+
+-- 知识库文件 文件上传dify知识库,对dify内的文件修改不生成新版本, 只有重新上传才生成新版本
+DROP TABLE IF EXISTS ai.tb_knowledge_file CASCADE;
+CREATE TABLE ai.tb_knowledge_file(
+ optsn VARCHAR(50) NOT NULL, -- 流水号
+ knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
+ file_root_id VARCHAR(50) NOT NULL, -- 文件根ID
+ file_id VARCHAR(50) NOT NULL, -- 文件ID
+ dify_file_id VARCHAR(50) NOT NULL, -- dify文件ID
+ version VARCHAR(50) NOT NULL, -- 文件版本
+ create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
+ update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
+ delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
+ deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
+ PRIMARY KEY (optsn),
+ UNIQUE (knowledge_id, file_id)
+);
+
+-- 知识库文件表字段注释
+COMMENT ON TABLE ai.tb_knowledge_file IS '知识库文件表';
+COMMENT ON COLUMN ai.tb_knowledge_file.optsn IS '流水号';
+COMMENT ON COLUMN ai.tb_knowledge_file.knowledge_id IS '知识库ID';
+COMMENT ON COLUMN ai.tb_knowledge_file.file_root_id IS '文件根ID';
+COMMENT ON COLUMN ai.tb_knowledge_file.file_id IS '文件ID';
+COMMENT ON COLUMN ai.tb_knowledge_file.dify_file_id IS 'dify文件ID';
+COMMENT ON COLUMN ai.tb_knowledge_file.version IS '文件版本';
+COMMENT ON COLUMN ai.tb_knowledge_file.create_time IS '创建时间';
+COMMENT ON COLUMN ai.tb_knowledge_file.update_time IS '更新时间';
+COMMENT ON COLUMN ai.tb_knowledge_file.delete_time IS '删除时间';
+COMMENT ON COLUMN ai.tb_knowledge_file.deleted IS '是否删除';
+
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql
deleted file mode 100644
index ac8897b4..00000000
--- a/urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql
+++ /dev/null
@@ -1,306 +0,0 @@
--- -- =============================
--- -- 智能体管理和平台基础设施模块
--- -- 支持:智能体广场、API集成管理、智能体运维监控
--- -- =============================
--- CREATE SCHEMA IF NOT EXISTS agent;
-
--- -- 智能体定义表
--- DROP TABLE IF EXISTS agent.tb_agent CASCADE;
--- CREATE TABLE agent.tb_agent (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- agent_id VARCHAR(50) NOT NULL, -- 智能体ID
--- agent_code VARCHAR(100) NOT NULL, -- 智能体编码(唯一标识)
--- agent_name VARCHAR(255) NOT NULL, -- 智能体名称
--- agent_type VARCHAR(50) NOT NULL, -- 智能体类型:bidding-招投标/customer_service-客服/knowledge_assistant-知识助手/custom-自定义
--- display_name VARCHAR(255) NOT NULL, -- 展示名称
--- description TEXT, -- 智能体描述
--- icon VARCHAR(500), -- 图标URL
--- banner VARCHAR(500), -- Banner图URL
--- version VARCHAR(20) DEFAULT '1.0.0', -- 版本号
--- model_provider VARCHAR(50), -- 模型提供商:openai/anthropic/baidu/aliyun/custom
--- model_name VARCHAR(100), -- 模型名称
--- model_config JSONB, -- 模型配置(温度、最大tokens等)
--- prompt_template TEXT, -- 提示词模板
--- system_prompt TEXT, -- 系统提示词
--- capabilities TEXT[], -- 能力列表
--- access_level VARCHAR(20) DEFAULT 'private', -- 访问级别:public-公开/private-私有/internal-内部
--- is_published BOOLEAN DEFAULT false, -- 是否发布到智能体广场
--- usage_count INTEGER DEFAULT 0, -- 使用次数
--- rating DECIMAL(3,2) DEFAULT 0, -- 评分(0-5)
--- rating_count INTEGER DEFAULT 0, -- 评分人数
--- tags TEXT[], -- 标签数组
--- category VARCHAR(100), -- 分类
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- owner_user_id VARCHAR(50), -- 所有者用户ID
--- status VARCHAR(20) DEFAULT 'active', -- 状态:active-激活/inactive-停用/under_maintenance-维护中
--- creator VARCHAR(50) DEFAULT NULL, -- 创建者
--- updater VARCHAR(50) DEFAULT NULL, -- 更新者
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
--- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
--- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
--- PRIMARY KEY (agent_id),
--- UNIQUE (optsn),
--- UNIQUE (agent_code)
--- );
-
--- CREATE INDEX idx_agent_type ON agent.tb_agent(agent_type) WHERE deleted = false;
--- CREATE INDEX idx_agent_published ON agent.tb_agent(is_published) WHERE deleted = false AND is_published = true;
--- CREATE INDEX idx_agent_category ON agent.tb_agent(category) WHERE deleted = false;
-
--- COMMENT ON TABLE agent.tb_agent IS '智能体定义表';
--- COMMENT ON COLUMN agent.tb_agent.agent_type IS '智能体类型:bidding/customer_service/knowledge_assistant/custom';
-
--- -- 智能体会话表
--- DROP TABLE IF EXISTS agent.tb_agent_session CASCADE;
--- CREATE TABLE agent.tb_agent_session (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- session_id VARCHAR(50) NOT NULL, -- 会话ID
--- agent_id VARCHAR(50) NOT NULL, -- 智能体ID
--- user_id VARCHAR(50) NOT NULL, -- 用户ID
--- session_type VARCHAR(30) DEFAULT 'chat', -- 会话类型:chat-对话/task-任务/workflow-工作流
--- session_name VARCHAR(255), -- 会话名称
--- context JSONB, -- 会话上下文
--- session_status VARCHAR(20) DEFAULT 'active', -- 会话状态:active-活跃/paused-暂停/ended-结束
--- start_time TIMESTAMPTZ DEFAULT now(), -- 开始时间
--- end_time TIMESTAMPTZ, -- 结束时间
--- message_count INTEGER DEFAULT 0, -- 消息数量
--- token_usage INTEGER DEFAULT 0, -- Token使用量
--- cost DECIMAL(10,4) DEFAULT 0, -- 成本
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- creator VARCHAR(50) DEFAULT NULL, -- 创建者
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
--- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
--- PRIMARY KEY (session_id),
--- UNIQUE (optsn),
--- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
--- );
-
--- CREATE INDEX idx_session_agent ON agent.tb_agent_session(agent_id) WHERE deleted = false;
--- CREATE INDEX idx_session_user ON agent.tb_agent_session(user_id) WHERE deleted = false;
-
--- COMMENT ON TABLE agent.tb_agent_session IS '智能体会话表';
-
--- -- 智能体消息表
--- DROP TABLE IF EXISTS agent.tb_agent_message CASCADE;
--- CREATE TABLE agent.tb_agent_message (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- message_id VARCHAR(50) NOT NULL, -- 消息ID
--- session_id VARCHAR(50) NOT NULL, -- 会话ID
--- agent_id VARCHAR(50) NOT NULL, -- 智能体ID
--- role VARCHAR(20) NOT NULL, -- 角色:user-用户/assistant-助手/system-系统/function-函数
--- content TEXT, -- 消息内容
--- content_type VARCHAR(30) DEFAULT 'text', -- 内容类型:text-文本/image-图片/file-文件/structured-结构化数据
--- function_call JSONB, -- 函数调用(JSON格式)
--- function_response JSONB, -- 函数响应
--- token_count INTEGER, -- Token数量
--- model_name VARCHAR(100), -- 使用的模型
--- kb_references JSONB, -- 知识库引用(JSON数组)
--- metadata JSONB, -- 消息元数据
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
--- PRIMARY KEY (message_id),
--- UNIQUE (optsn),
--- FOREIGN KEY (session_id) REFERENCES agent.tb_agent_session(session_id),
--- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
--- );
-
--- CREATE INDEX idx_msg_session ON agent.tb_agent_message(session_id, create_time) WHERE deleted = false;
--- CREATE INDEX idx_msg_agent ON agent.tb_agent_message(agent_id) WHERE deleted = false;
-
--- COMMENT ON TABLE agent.tb_agent_message IS '智能体消息表';
-
--- -- 智能体工具表
--- DROP TABLE IF EXISTS agent.tb_agent_tool CASCADE;
--- CREATE TABLE agent.tb_agent_tool (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- tool_id VARCHAR(50) NOT NULL, -- 工具ID
--- tool_code VARCHAR(100) NOT NULL, -- 工具编码
--- tool_name VARCHAR(255) NOT NULL, -- 工具名称
--- tool_type VARCHAR(50) NOT NULL, -- 工具类型:api-API调用/function-函数/plugin-插件/integration-集成
--- description TEXT, -- 工具描述
--- function_schema JSONB, -- 函数Schema(OpenAI function calling格式)
--- api_endpoint VARCHAR(500), -- API端点
--- api_method VARCHAR(10), -- API方法:GET/POST/PUT/DELETE
--- api_headers JSONB, -- API请求头
--- auth_type VARCHAR(30), -- 认证类型:none/api_key/oauth2/bearer
--- auth_config JSONB, -- 认证配置(加密存储)
--- request_template TEXT, -- 请求模板
--- response_template TEXT, -- 响应模板
--- timeout_seconds INTEGER DEFAULT 30, -- 超时时间(秒)
--- retry_count INTEGER DEFAULT 3, -- 重试次数
--- is_enabled BOOLEAN DEFAULT true, -- 是否启用
--- usage_count INTEGER DEFAULT 0, -- 使用次数
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- creator VARCHAR(50) DEFAULT NULL, -- 创建者
--- updater VARCHAR(50) DEFAULT NULL, -- 更新者
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
--- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
--- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
--- PRIMARY KEY (tool_id),
--- UNIQUE (optsn),
--- UNIQUE (tool_code)
--- );
-
--- CREATE INDEX idx_tool_type ON agent.tb_agent_tool(tool_type) WHERE deleted = false;
-
--- COMMENT ON TABLE agent.tb_agent_tool IS '智能体工具表';
-
--- -- API集成注册表
--- DROP TABLE IF EXISTS agent.tb_api_integration CASCADE;
--- CREATE TABLE agent.tb_api_integration (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- integration_id VARCHAR(50) NOT NULL, -- 集成ID
--- integration_name VARCHAR(255) NOT NULL, -- 集成名称
--- integration_type VARCHAR(50) NOT NULL, -- 集成类型:rest_api/soap/graphql/webhook/mq
--- provider VARCHAR(100), -- 提供商
--- base_url VARCHAR(500), -- 基础URL
--- version VARCHAR(20), -- API版本
--- auth_type VARCHAR(30) DEFAULT 'none', -- 认证类型
--- auth_config JSONB, -- 认证配置(加密存储)
--- endpoints JSONB, -- 端点列表(JSON数组)
--- rate_limit INTEGER, -- 速率限制(请求/秒)
--- timeout_seconds INTEGER DEFAULT 30, -- 超时时间
--- retry_config JSONB, -- 重试配置
--- health_check_url VARCHAR(500), -- 健康检查URL
--- health_status VARCHAR(20) DEFAULT 'unknown', -- 健康状态:healthy-健康/unhealthy-不健康/unknown-未知
--- last_health_check TIMESTAMPTZ, -- 最后健康检查时间
--- documentation_url VARCHAR(500), -- 文档URL
--- is_enabled BOOLEAN DEFAULT true, -- 是否启用
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- creator VARCHAR(50) DEFAULT NULL, -- 创建者
--- updater VARCHAR(50) DEFAULT NULL, -- 更新者
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
--- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
--- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
--- PRIMARY KEY (integration_id),
--- UNIQUE (optsn)
--- );
-
--- CREATE INDEX idx_integration_type ON agent.tb_api_integration(integration_type) WHERE deleted = false;
--- CREATE INDEX idx_integration_health ON agent.tb_api_integration(health_status) WHERE deleted = false;
-
--- COMMENT ON TABLE agent.tb_api_integration IS 'API集成注册表';
-
--- -- API调用日志表
--- DROP TABLE IF EXISTS agent.tb_api_call_log CASCADE;
--- CREATE TABLE agent.tb_api_call_log (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- log_id VARCHAR(50) NOT NULL, -- 日志ID
--- integration_id VARCHAR(50), -- 集成ID
--- tool_id VARCHAR(50), -- 工具ID
--- agent_id VARCHAR(50), -- 智能体ID
--- session_id VARCHAR(50), -- 会话ID
--- user_id VARCHAR(50), -- 用户ID
--- endpoint VARCHAR(500) NOT NULL, -- 请求端点
--- method VARCHAR(10) NOT NULL, -- 请求方法
--- request_headers JSONB, -- 请求头
--- request_body TEXT, -- 请求体
--- response_status INTEGER, -- 响应状态码
--- response_headers JSONB, -- 响应头
--- response_body TEXT, -- 响应体
--- duration_ms INTEGER, -- 请求耗时(毫秒)
--- is_success BOOLEAN, -- 是否成功
--- error_message TEXT, -- 错误信息
--- retry_count INTEGER DEFAULT 0, -- 重试次数
--- ip_address VARCHAR(45), -- IP地址
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- PRIMARY KEY (log_id),
--- UNIQUE (optsn)
--- );
-
--- CREATE INDEX idx_api_log_integration ON agent.tb_api_call_log(integration_id, create_time DESC);
--- CREATE INDEX idx_api_log_agent ON agent.tb_api_call_log(agent_id, create_time DESC);
--- CREATE INDEX idx_api_log_status ON agent.tb_api_call_log(is_success, create_time DESC);
-
--- COMMENT ON TABLE agent.tb_api_call_log IS 'API调用日志表';
-
--- -- 智能体监控指标表
--- DROP TABLE IF EXISTS agent.tb_agent_metrics CASCADE;
--- CREATE TABLE agent.tb_agent_metrics (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- metric_id VARCHAR(50) NOT NULL, -- 指标ID
--- agent_id VARCHAR(50) NOT NULL, -- 智能体ID
--- metric_date DATE NOT NULL, -- 指标日期
--- metric_hour INTEGER, -- 指标小时(0-23)
--- total_sessions INTEGER DEFAULT 0, -- 总会话数
--- active_sessions INTEGER DEFAULT 0, -- 活跃会话数
--- total_messages INTEGER DEFAULT 0, -- 总消息数
--- total_tokens BIGINT DEFAULT 0, -- 总Token数
--- total_cost DECIMAL(10,4) DEFAULT 0, -- 总成本
--- avg_response_time INTEGER, -- 平均响应时间(毫秒)
--- success_rate DECIMAL(5,4), -- 成功率
--- error_count INTEGER DEFAULT 0, -- 错误次数
--- api_call_count INTEGER DEFAULT 0, -- API调用次数
--- avg_rating DECIMAL(3,2), -- 平均评分
--- rating_count INTEGER DEFAULT 0, -- 评分数量
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
--- PRIMARY KEY (metric_id),
--- UNIQUE (optsn),
--- UNIQUE (agent_id, metric_date, metric_hour),
--- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
--- );
-
--- CREATE INDEX idx_metrics_agent_date ON agent.tb_agent_metrics(agent_id, metric_date DESC);
-
--- COMMENT ON TABLE agent.tb_agent_metrics IS '智能体监控指标表';
-
--- -- 智能体异常日志表
--- DROP TABLE IF EXISTS agent.tb_agent_error_log CASCADE;
--- CREATE TABLE agent.tb_agent_error_log (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- log_id VARCHAR(50) NOT NULL, -- 日志ID
--- agent_id VARCHAR(50) NOT NULL, -- 智能体ID
--- session_id VARCHAR(50), -- 会话ID
--- error_type VARCHAR(50) NOT NULL, -- 错误类型:model_error-模型错误/api_error-API错误/timeout-超时/rate_limit-限流/other-其他
--- error_code VARCHAR(50), -- 错误代码
--- error_message TEXT NOT NULL, -- 错误信息
--- stack_trace TEXT, -- 堆栈跟踪
--- request_context JSONB, -- 请求上下文
--- severity VARCHAR(20) DEFAULT 'error', -- 严重级别:critical-致命/error-错误/warning-警告
--- is_resolved BOOLEAN DEFAULT false, -- 是否已解决
--- resolution_notes TEXT, -- 解决方案备注
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- resolve_time TIMESTAMPTZ, -- 解决时间
--- PRIMARY KEY (log_id),
--- UNIQUE (optsn),
--- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
--- );
-
--- CREATE INDEX idx_error_agent ON agent.tb_agent_error_log(agent_id, create_time DESC);
--- CREATE INDEX idx_error_severity ON agent.tb_agent_error_log(severity) WHERE is_resolved = false;
-
--- COMMENT ON TABLE agent.tb_agent_error_log IS '智能体异常日志表';
-
--- -- 智能体评价表
--- DROP TABLE IF EXISTS agent.tb_agent_rating CASCADE;
--- CREATE TABLE agent.tb_agent_rating (
--- optsn VARCHAR(50) NOT NULL, -- 流水号
--- rating_id VARCHAR(50) NOT NULL, -- 评价ID
--- agent_id VARCHAR(50) NOT NULL, -- 智能体ID
--- session_id VARCHAR(50), -- 会话ID
--- user_id VARCHAR(50) NOT NULL, -- 用户ID
--- rating INTEGER NOT NULL, -- 评分(1-5星)
--- dimensions JSONB, -- 分维度评分(准确性、速度、友好度等)
--- feedback TEXT, -- 评价反馈
--- tags TEXT[], -- 标签
--- is_anonymous BOOLEAN DEFAULT false, -- 是否匿名
--- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
--- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
--- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
--- PRIMARY KEY (rating_id),
--- UNIQUE (optsn),
--- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
--- );
-
--- CREATE INDEX idx_rating_agent ON agent.tb_agent_rating(agent_id) WHERE deleted = false;
-
--- COMMENT ON TABLE agent.tb_agent_rating IS '智能体评价表';
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql
deleted file mode 100644
index 62abb2bc..00000000
--- a/urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql
+++ /dev/null
@@ -1,156 +0,0 @@
- -- =============================
--- 泰豪电源AI数智化平台 - 完整数据库初始化脚本
--- 包含所有业务模块的表结构
--- =============================
-
--- 安装必要的扩展
-CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID生成
-CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 全文搜索
-CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- GIN索引支持
--- CREATE EXTENSION IF NOT EXISTS "vector"; -- pgvector向量检索(需要单独安装)
-
--- =============================
--- 1. 系统基础模块(权限、用户、部门、角色)
--- =============================
-\i createTablePermission.sql
-\i createTableUser.sql
-
--- =============================
--- 2. 文件管理模块
--- =============================
-\i createTableFile.sql
-
--- =============================
--- 3. 消息通知模块
--- =============================
-\i createTableMessage.sql
-
--- =============================
--- 4. 日志模块
--- =============================
-\i createTableLog.sql
-
--- =============================
--- 5. 配置管理模块
--- =============================
-\i createTableConfig.sql
-
--- =============================
--- 6. 知识库管理模块
--- =============================
-\i createTableKnowledge.sql
-
--- =============================
--- 7. 招投标智能体业务模块
--- =============================
-\i createTableBidding.sql
-
--- =============================
--- 8. 智能客服系统业务模块
--- =============================
-\i createTableCustomerService.sql
-
--- =============================
--- 9. 智能体管理和平台基础设施模块
--- =============================
-\i createTableAgent.sql
-
--- =============================
--- 创建视图
--- =============================
-
--- 用户完整信息视图
-CREATE OR REPLACE VIEW sys.v_user_full_info AS
-SELECT
- u.user_id,
- u.email,
- u.phone,
- u.wechat_id,
- u.status,
- ui.avatar,
- ui.full_name,
- ui.gender,
- ui.level,
- u.create_time,
- u.update_time
-FROM sys.tb_sys_user u
-LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id
-WHERE u.deleted = false AND (ui.deleted = false OR ui.deleted IS NULL);
-
--- 用户角色权限视图
-CREATE OR REPLACE VIEW sys.v_user_role_permission AS
-SELECT DISTINCT
- ur.user_id,
- r.role_id,
- r.name AS role_name,
- p.permission_id,
- p.code AS permission_code,
- p.name AS permission_name,
- m.module_id,
- m.name AS module_name
-FROM sys.tb_sys_user_role ur
-JOIN sys.tb_sys_role r ON ur.role_id = r.role_id
-JOIN sys.tb_sys_role_permission rp ON r.role_id = rp.role_id
-JOIN sys.tb_sys_permission p ON rp.permission_id = p.permission_id
-LEFT JOIN sys.tb_sys_module m ON p.module_id = m.module_id
-WHERE ur.deleted = false
- AND r.deleted = false
- AND rp.deleted = false
- AND p.deleted = false;
-
--- 智能体使用统计视图
-CREATE OR REPLACE VIEW agent.v_agent_usage_stats AS
-SELECT
- a.agent_id,
- a.agent_name,
- a.agent_type,
- COUNT(DISTINCT s.session_id) AS total_sessions,
- COUNT(DISTINCT s.user_id) AS unique_users,
- SUM(s.message_count) AS total_messages,
- AVG(s.token_usage) AS avg_token_usage,
- AVG(r.rating) AS avg_rating,
- COUNT(r.rating_id) AS rating_count,
- MAX(s.start_time) AS last_used_time
-FROM agent.tb_agent a
-LEFT JOIN agent.tb_agent_session s ON a.agent_id = s.agent_id AND s.deleted = false
-LEFT JOIN agent.tb_agent_rating r ON a.agent_id = r.agent_id AND r.deleted = false
-WHERE a.deleted = false
-GROUP BY a.agent_id, a.agent_name, a.agent_type;
-
--- 客服工单统计视图
-CREATE OR REPLACE VIEW customer_service.v_ticket_stats AS
-SELECT
- t.ticket_status,
- t.priority,
- t.ticket_type,
- COUNT(*) AS ticket_count,
- AVG(EXTRACT(EPOCH FROM (t.resolution_time - t.create_time))/3600) AS avg_resolution_hours,
- AVG(t.customer_rating) AS avg_rating,
- COUNT(CASE WHEN t.is_overdue THEN 1 END) AS overdue_count
-FROM customer_service.tb_ticket t
-WHERE t.deleted = false
-GROUP BY t.ticket_status, t.priority, t.ticket_type;
-
--- 招投标项目统计视图
-CREATE OR REPLACE VIEW bidding.v_project_stats AS
-SELECT
- p.project_status,
- p.project_type,
- COUNT(*) AS project_count,
- SUM(p.budget_amount) AS total_budget,
- SUM(CASE WHEN p.winning_status = 'won' THEN 1 ELSE 0 END) AS won_count,
- SUM(CASE WHEN p.winning_status = 'won' THEN p.winning_amount ELSE 0 END) AS total_won_amount,
- AVG(CASE WHEN p.winning_status = 'won' THEN p.winning_amount / NULLIF(p.budget_amount, 0) ELSE NULL END) AS avg_win_rate
-FROM bidding.tb_bidding_project p
-WHERE p.deleted = false
-GROUP BY p.project_status, p.project_type;
-
-COMMENT ON VIEW sys.v_user_full_info IS '用户完整信息视图';
-COMMENT ON VIEW sys.v_user_role_permission IS '用户角色权限视图';
-COMMENT ON VIEW agent.v_agent_usage_stats IS '智能体使用统计视图';
-COMMENT ON VIEW customer_service.v_ticket_stats IS '客服工单统计视图';
-COMMENT ON VIEW bidding.v_project_stats IS '招投标项目统计视图';
-
--- =============================
--- 数据库初始化完成
--- =============================
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql
index d308c57d..b1bc95ac 100644
--- a/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql
+++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql
@@ -15,6 +15,8 @@ CREATE TABLE file.tb_sys_file (
-- TbSysFileDTO 特有字段
file_id VARCHAR(50) NOT NULL, -- 文件ID (主键)
+ file_root_id VARCHAR(50) DEFAULT NULL, -- 文件根ID
+ version VARCHAR(50) DEFAULT NULL, -- 文件版本
name VARCHAR(255) NOT NULL, -- 文件名
path VARCHAR(500) NOT NULL, -- 文件路径
size BIGINT NOT NULL, -- 文件大小(字节)
@@ -50,6 +52,8 @@ COMMENT ON COLUMN file.tb_sys_file.deleted IS '是否已删除(0-未删除,1
-- TbSysFileDTO 特有字段注释
COMMENT ON COLUMN file.tb_sys_file.file_id IS '文件ID (主键)';
+COMMENT ON COLUMN file.tb_sys_file.file_root_id IS '文件根ID';
+COMMENT ON COLUMN file.tb_sys_file.version IS '文件版本';
COMMENT ON COLUMN file.tb_sys_file.name IS '文件名';
COMMENT ON COLUMN file.tb_sys_file.path IS '文件路径';
COMMENT ON COLUMN file.tb_sys_file.size IS '文件大小(字节)';
@@ -72,49 +76,4 @@ CREATE INDEX idx_file_uploader ON file.tb_sys_file(uploader) WHERE deleted = 0;
CREATE INDEX idx_file_bucket ON file.tb_sys_file(bucket_name) WHERE deleted = 0;
CREATE INDEX idx_file_status ON file.tb_sys_file(status) WHERE deleted = 0;
CREATE INDEX idx_file_create_time ON file.tb_sys_file(create_time) WHERE deleted = 0;
-CREATE INDEX idx_file_md5 ON file.tb_sys_file(md5_hash) WHERE deleted = 0;
-
--- =============================
--- 文件关联表
--- =============================
-DROP TABLE IF EXISTS file.tb_file_relation CASCADE;
-CREATE TABLE file.tb_file_relation (
- optsn VARCHAR(50) NOT NULL,
- relation_id VARCHAR(50) NOT NULL,
- file_id VARCHAR(50) NOT NULL,
- object_type VARCHAR(50) NOT NULL, -- 对象类型:bidding_project/ticket/document等
- object_id VARCHAR(50) NOT NULL, -- 对象ID
- relation_type VARCHAR(30) DEFAULT 'attachment', -- 关联类型:attachment-附件/avatar-头像/banner-横幅
- order_num INTEGER DEFAULT 0, -- 排序号
- service_type VARCHAR(50), -- 服务类型(继承自object_type)
- creator VARCHAR(50) DEFAULT NULL, -- 创建者
- updater VARCHAR(50) DEFAULT NULL, -- 更新者
- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
- PRIMARY KEY (relation_id),
- UNIQUE (optsn),
- FOREIGN KEY (file_id) REFERENCES file.tb_sys_file(file_id)
-);
-
-COMMENT ON TABLE file.tb_file_relation IS '文件关联表';
-COMMENT ON COLUMN file.tb_file_relation.optsn IS '流水号';
-COMMENT ON COLUMN file.tb_file_relation.relation_id IS '关联ID';
-COMMENT ON COLUMN file.tb_file_relation.file_id IS '文件ID';
-COMMENT ON COLUMN file.tb_file_relation.object_type IS '对象类型:bidding_project/ticket/document等';
-COMMENT ON COLUMN file.tb_file_relation.object_id IS '对象ID';
-COMMENT ON COLUMN file.tb_file_relation.relation_type IS '关联类型:attachment-附件/avatar-头像/banner-横幅';
-COMMENT ON COLUMN file.tb_file_relation.order_num IS '排序号';
-COMMENT ON COLUMN file.tb_file_relation.service_type IS '服务类型(继承自object_type)';
-COMMENT ON COLUMN file.tb_file_relation.creator IS '创建者';
-COMMENT ON COLUMN file.tb_file_relation.updater IS '更新者';
-COMMENT ON COLUMN file.tb_file_relation.create_time IS '创建时间';
-COMMENT ON COLUMN file.tb_file_relation.update_time IS '更新时间';
-COMMENT ON COLUMN file.tb_file_relation.delete_time IS '删除时间';
-COMMENT ON COLUMN file.tb_file_relation.deleted IS '是否删除';
-
--- 文件关联表索引
-CREATE INDEX idx_file_relation_object ON file.tb_file_relation(object_type, object_id) WHERE deleted = false;
-CREATE INDEX idx_file_relation_file ON file.tb_file_relation(file_id) WHERE deleted = false;
-CREATE INDEX idx_file_relation_service ON file.tb_file_relation(service_type) WHERE deleted = false;
\ No newline at end of file
+CREATE INDEX idx_file_md5 ON file.tb_sys_file(md5_hash) WHERE deleted = 0;
\ No newline at end of file
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql
deleted file mode 100644
index a56b25da..00000000
--- a/urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql
+++ /dev/null
@@ -1,153 +0,0 @@
--- =============================
--- 知识库管理模块
--- 支持:招投标知识库、客服知识库、企业内部知识库
--- =============================
-CREATE SCHEMA IF NOT EXISTS knowledge;
-
--- 知识库表(多租户知识库定义)
-DROP TABLE IF EXISTS knowledge.tb_knowledge_base CASCADE;
-CREATE TABLE knowledge.tb_knowledge_base (
- optsn VARCHAR(50) NOT NULL, -- 流水号
- agent_id VARCHAR(50) NOT NULL, -- 智能体id
- knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
- name VARCHAR(255) NOT NULL, -- 知识库名称
- kb_type VARCHAR(50) NOT NULL, -- 知识库类型:bidding-招投标/customer_service-客服/internal-内部协同
- access_level VARCHAR(20) NOT NULL DEFAULT 'private', -- 访问级别:public-公开/private-私有/internal-内部
- description TEXT, -- 知识库描述
- storage_path VARCHAR(500), -- 存储路径
- version VARCHAR(20) DEFAULT '1.0', -- 当前版本号
- config JSONB DEFAULT NULL, -- 知识库配置(JSON格式:索引配置、检索参数等)
- service_type VARCHAR(50), -- 服务类型(bidding/customer_service/internal,冗余kb_type便于查询)
- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
- status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-激活/inactive-停用/archived-归档
- creator VARCHAR(50) DEFAULT NULL, -- 创建者
- updater VARCHAR(50) DEFAULT NULL, -- 更新者
- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
- PRIMARY KEY (knowledge_id),
- UNIQUE (optsn)
-);
-
-CREATE INDEX idx_kb_type ON knowledge.tb_knowledge_base(kb_type) WHERE deleted = false;
-CREATE INDEX idx_kb_service ON knowledge.tb_knowledge_base(service_type) WHERE deleted = false;
-CREATE INDEX idx_kb_dept_path ON knowledge.tb_knowledge_base(dept_path) WHERE deleted = false;
-
-COMMENT ON TABLE knowledge.tb_knowledge_base IS '知识库表';
-COMMENT ON COLUMN knowledge.tb_knowledge_base.kb_type IS '知识库类型:bidding-招投标/customer_service-客服/internal-内部协同';
-COMMENT ON COLUMN knowledge.tb_knowledge_base.service_type IS '服务类型(冗余存储便于快速过滤,与kb_type保持一致)';
-COMMENT ON COLUMN knowledge.tb_knowledge_base.access_level IS '访问级别:public-公开/private-私有/internal-内部';
-
--- 知识文档表
-DROP TABLE IF EXISTS knowledge.tb_knowledge_document CASCADE;
-CREATE TABLE knowledge.tb_knowledge_document (
- optsn VARCHAR(50) NOT NULL, -- 流水号
- doc_id VARCHAR(50) NOT NULL, -- 文档ID
- knowledge_id VARCHAR(50) NOT NULL, -- 所属知识库ID
- title VARCHAR(500) NOT NULL, -- 文档标题
- doc_type VARCHAR(50) NOT NULL, -- 文档类型:text-文本/pdf/word/excel/image/video
- category VARCHAR(100), -- 文档分类(自动或手动分类)
- content TEXT, -- 文档内容(文本类型)
- content_summary TEXT, -- 内容摘要(AI生成)
- file_id VARCHAR(50), -- 关联文件表ID
- file_path VARCHAR(500), -- 文件路径
- file_size BIGINT, -- 文件大小(字节)
- mime_type VARCHAR(100), -- MIME类型
- version INTEGER DEFAULT 1, -- 文档版本号(仅做记录)
- root_doc_id VARCHAR(50), -- 根文档ID(版本组标识,保留用于整体版本管理)
- tags TEXT[], -- 文档标签数组
- keywords TEXT[], -- 关键词数组(AI提取)
- embedding_status VARCHAR(20) DEFAULT 'pending', -- 向量化状态:pending-待处理/processing-处理中/completed-完成/failed-失败
- embedding_model VARCHAR(100), -- 使用的向量化模型
- chunk_count INTEGER DEFAULT 0, -- 切片数量
- metadata JSONB DEFAULT NULL, -- 文档元数据(JSON格式)
- source_url VARCHAR(500), -- 来源URL
- service_type VARCHAR(50), -- 服务类型(继承自知识库)
- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
- status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-激活/inactive-停用/archived-归档
- creator VARCHAR(50) DEFAULT NULL, -- 创建者
- updater VARCHAR(50) DEFAULT NULL, -- 更新者
- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
- PRIMARY KEY (doc_id),
- UNIQUE (optsn)
-);
-
-CREATE INDEX idx_doc_kb ON knowledge.tb_knowledge_document(knowledge_id) WHERE deleted = false;
-CREATE INDEX idx_doc_service ON knowledge.tb_knowledge_document(service_type) WHERE deleted = false;
-CREATE INDEX idx_doc_category ON knowledge.tb_knowledge_document(category) WHERE deleted = false;
-CREATE INDEX idx_doc_embedding_status ON knowledge.tb_knowledge_document(embedding_status) WHERE deleted = false;
-CREATE INDEX idx_doc_tags ON knowledge.tb_knowledge_document USING GIN(tags) WHERE deleted = false;
-CREATE INDEX idx_doc_root ON knowledge.tb_knowledge_document(root_doc_id) WHERE deleted = false;
-
-COMMENT ON TABLE knowledge.tb_knowledge_document IS '知识文档表(文档级元数据,版本控制在chunk级别)';
-COMMENT ON COLUMN knowledge.tb_knowledge_document.service_type IS '服务类型(从知识库继承,用于服务间隔离)';
-COMMENT ON COLUMN knowledge.tb_knowledge_document.version IS '文档版本号(仅做记录,实际版本控制在chunk级别)';
-COMMENT ON COLUMN knowledge.tb_knowledge_document.root_doc_id IS '根文档ID(用于文档整体版本管理,可选)';
-COMMENT ON COLUMN knowledge.tb_knowledge_document.embedding_status IS '向量化状态:pending/processing/completed/failed';
-
--- 知识文档片段表(用于RAG检索)
-DROP TABLE IF EXISTS knowledge.tb_knowledge_chunk CASCADE;
-CREATE TABLE knowledge.tb_knowledge_chunk (
- optsn VARCHAR(50) NOT NULL, -- 流水号
- chunk_id VARCHAR(50) NOT NULL, -- 片段ID
- doc_id VARCHAR(50) NOT NULL, -- 所属文档ID
- knowledge_id VARCHAR(50) NOT NULL, -- 所属知识库ID
- chunk_index INTEGER NOT NULL, -- 片段索引(在文档中的顺序)
- content TEXT NOT NULL, -- 片段内容
- content_length INTEGER, -- 内容长度
- embedding vector(1536), -- 向量嵌入(假设使用OpenAI 1536维)
- chunk_type VARCHAR(20) DEFAULT 'text', -- 片段类型:text-文本/table-表格/image-图片
- version INTEGER DEFAULT 1, -- chunk版本号(每次修改自动+1)
- root_chunk_id VARCHAR(50), -- 根chunk ID(同一chunk的不同版本共享此ID)
- is_current BOOLEAN DEFAULT true, -- 是否当前使用的版本
- position_info JSONB, -- 位置信息(页码、坐标等)
- metadata JSONB, -- 片段元数据
- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
- creator VARCHAR(50) DEFAULT NULL, -- 创建者
- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
- PRIMARY KEY (chunk_id),
- UNIQUE (optsn)
-);
-
-CREATE INDEX idx_chunk_doc ON knowledge.tb_knowledge_chunk(doc_id) WHERE deleted = false;
-CREATE INDEX idx_chunk_kb ON knowledge.tb_knowledge_chunk(knowledge_id) WHERE deleted = false;
-CREATE INDEX idx_chunk_root_current ON knowledge.tb_knowledge_chunk(root_chunk_id, is_current) WHERE deleted = false;
-CREATE INDEX idx_chunk_current ON knowledge.tb_knowledge_chunk(is_current) WHERE deleted = false AND is_current = true;
--- 向量检索索引(需要安装pgvector扩展,建议只索引当前版本)
--- CREATE INDEX idx_chunk_embedding ON knowledge.tb_knowledge_chunk USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100) WHERE deleted = false AND is_current = true;
-
-COMMENT ON TABLE knowledge.tb_knowledge_chunk IS '知识文档片段表(RAG检索基本单位,支持chunk级版本控制)';
-COMMENT ON COLUMN knowledge.tb_knowledge_chunk.version IS 'chunk版本号(整数,每次修改自动+1)';
-COMMENT ON COLUMN knowledge.tb_knowledge_chunk.root_chunk_id IS '根chunk ID(同一chunk的所有版本共享此ID,首次创建时等于chunk_id)';
-COMMENT ON COLUMN knowledge.tb_knowledge_chunk.is_current IS '是否当前使用的版本(每个root_chunk_id只有一个is_current=true,RAG检索时只使用当前版本)';
-COMMENT ON COLUMN knowledge.tb_knowledge_chunk.embedding IS '向量嵌入(需要pgvector扩展,建议只为is_current=true的chunk生成)';
-
--- 知识访问日志表
-DROP TABLE IF EXISTS knowledge.tb_knowledge_access_log CASCADE;
-CREATE TABLE knowledge.tb_knowledge_access_log (
- optsn VARCHAR(50) NOT NULL, -- 流水号
- log_id VARCHAR(50) NOT NULL, -- 日志ID
- knowledge_id VARCHAR(50), -- 知识库ID
- doc_id VARCHAR(50), -- 文档ID
- user_id VARCHAR(50) NOT NULL, -- 用户ID
- access_type VARCHAR(20) NOT NULL, -- 访问类型:view-查看/download-下载/search-搜索/edit-编辑
- query_text TEXT, -- 搜索查询文本
- result_count INTEGER, -- 搜索结果数量
- ip_address VARCHAR(45), -- IP地址
- user_agent TEXT, -- 用户代理
- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
- PRIMARY KEY (log_id),
- UNIQUE (optsn)
-);
-
-CREATE INDEX idx_access_log_user ON knowledge.tb_knowledge_access_log(user_id, create_time DESC);
-CREATE INDEX idx_access_log_kb ON knowledge.tb_knowledge_access_log(knowledge_id, create_time DESC);
-
-COMMENT ON TABLE knowledge.tb_knowledge_access_log IS '知识访问日志表';
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql b/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql
index c0d81b5b..2e8b5549 100644
--- a/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql
+++ b/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql
@@ -24,8 +24,8 @@
-- 5. 配置管理模块
\i createTableConfig.sql
--- 6. 知识库管理模块
-\i createTableKnowledge.sql
+-- 6. AI模块 智能体+知识库
+\i createTableAI.sql
-- 7. 招投标业务模块
\i createTableBidding.sql
@@ -33,9 +33,6 @@
-- 8. 智能客服业务模块
\i createTableWorkcase.sql
--- 9. 智能体模块(暂不启用)
--- \i createTableAgent.sql
-
-- =============================
-- 第二阶段:初始化基础数据
-- =============================
diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql b/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql
index 48963205..a50b8327 100644
--- a/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql
+++ b/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql
@@ -62,6 +62,23 @@ INSERT INTO config.tb_sys_config (
('CFG-0416', 'cfg_sms_tpl_register', 'sms.templateCode.register','注册验证码模板', 'SMS_491985030', 'String', 'input', '注册验证码模板编码', NULL, NULL, 'notify', 'mod_message', 130, 1, '注册验证码短信模板', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0417', 'cfg_sms_timeout', 'sms.timeout', '请求超时时间', '30000', 'INTEGER', 'input', '请求超时时间(毫秒)', NULL, NULL, 'notify', 'mod_message', 140, 1, 'API请求超时时间(5000-60000)', 'system', NULL, NULL, now(), NULL, NULL, false),
+-- Dify AI 配置
+-- Dify 基础配置
+('CFG-0450', 'cfg_dify_api_base', 'dify.apiBaseUrl', 'Dify API地址', 'http://localhost:8000/v1', 'String', 'input', 'Dify API基础地址', NULL, NULL, 'dify', 'mod_agent', 10, 1, 'Dify服务的API基础地址,如 http://localhost/dify/api', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0452', 'cfg_dify_knowledge_key','dify.knowledgeApiKey', '知识库API密钥', 'dataset-nupqKP4LONpzdXmGthIrbjeJ', 'String', 'password', '知识库API密钥', NULL, NULL, 'dify', 'mod_agent', 30, 1, '用于访问Dify知识库的API密钥', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0453', 'cfg_dify_timeout', 'dify.timeout', '请求超时时间', '60', 'INTEGER', 'input', '请求超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 40, 1, 'API请求的超时时间(10-600秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0454', 'cfg_dify_conn_timeout','dify.connectTimeout', '连接超时时间', '10', 'INTEGER', 'input', '连接超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 50, 1, 'API连接的超时时间(5-60秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0455', 'cfg_dify_read_timeout','dify.readTimeout', '读取超时时间', '60', 'INTEGER', 'input', '读取超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 60, 1, 'API读取响应的超时时间(10-600秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0456', 'cfg_dify_stream_timeout','dify.streamTimeout', '流式响应超时时间', '300', 'INTEGER', 'input', '流式响应超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 70, 1, '流式API响应的超时时间(30-1800秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
+
+-- Dify 上传配置
+('CFG-0460', 'cfg_dify_upload_types','dify.upload.allowedTypes','允许的文件类型', 'pdf,txt,docx,doc,md,html,htm,xlsx,xls,csv', 'String', 'textarea', '上传文件允许的类型', NULL, NULL, 'dify', 'mod_agent', 80, 1, '支持上传的文件类型列表,逗号分隔', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0461', 'cfg_dify_upload_max', 'dify.upload.maxSize', '最大文件大小', '50', 'INTEGER', 'input', '最大文件大小(MB)', NULL, NULL, 'dify', 'mod_agent', 90, 1, '单个文件上传的最大大小限制(1-500MB)', 'system', NULL, NULL, now(), NULL, NULL, false),
+
+-- Dify 知识库配置
+('CFG-0470', 'cfg_dify_index_tech', 'dify.dataset.defaultIndexingTechnique','默认索引方式', 'high_quality', 'String', 'select', '默认索引方式', NULL, '["high_quality", "economy"]'::json, 'dify', 'mod_agent', 100, 1, '知识库文档的默认索引方式:high_quality(高质量)或 economy(经济)', 'system', NULL, NULL, now(), NULL, NULL, false),
+('CFG-0471', 'cfg_dify_embed_model', 'dify.dataset.defaultEmbeddingModel','默认Embedding模型', 'text-embedding-ada-002', 'String', 'input', '默认Embedding模型', NULL, NULL, 'dify', 'mod_agent', 110, 1, '知识库使用的默认Embedding模型名称', 'system', NULL, NULL, now(), NULL, NULL, false),
+
-- 日志与审计
('CFG-0501', 'cfg_log_level', 'log.level', '日志级别', 'INFO', 'String', 'select', '系统日志级别', NULL, '["DEBUG", "INFO", "WARN", "ERROR"]'::json, 'log', 'mod_system', 10, 0, 'DEBUG/INFO/WARN/ERROR', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0502', 'cfg_audit_retention', 'audit.retentionDays', '审计日志保留', '90', 'INTEGER', 'input', '审计日志保留天数', NULL, NULL, 'log', 'mod_system', 20, 0, '合规按需调整', 'system', NULL, NULL, now(), NULL, NULL, false),
diff --git a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java b/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java
deleted file mode 100644
index eeaa0bc1..00000000
--- a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.xyzh.agent.client;
-
-public class DifyClient {
-
-}
diff --git a/urbanLifelineServ/agent/pom.xml b/urbanLifelineServ/ai/pom.xml
similarity index 59%
rename from urbanLifelineServ/agent/pom.xml
rename to urbanLifelineServ/ai/pom.xml
index d4827c29..7f1f1db1 100644
--- a/urbanLifelineServ/agent/pom.xml
+++ b/urbanLifelineServ/ai/pom.xml
@@ -10,12 +10,25 @@
org.xyzh
- agent
+ ai
1.0.0
21
21
+
+
+ org.xyzh.apis
+ api-ai
+ ${urban-lifeline.version}
+
+
+ org.xyzh.apis
+ api-system
+ ${urban-lifeline.version}
+
+
+
\ No newline at end of file
diff --git a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/AgentApp.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/AgentApp.java
similarity index 97%
rename from urbanLifelineServ/agent/src/main/java/org/xyzh/agent/AgentApp.java
rename to urbanLifelineServ/ai/src/main/java/org/xyzh/ai/AgentApp.java
index 906de32a..2854e8b2 100644
--- a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/AgentApp.java
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/AgentApp.java
@@ -1,4 +1,4 @@
-package org.xyzh.agent;
+package org.xyzh.ai;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.slf4j.Logger;
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java
new file mode 100644
index 00000000..ee62b8e3
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java
@@ -0,0 +1,1031 @@
+package org.xyzh.ai.client;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+
+import jakarta.annotation.PostConstruct;
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.xyzh.ai.client.dto.*;
+import org.xyzh.ai.client.callback.StreamCallback;
+import org.xyzh.ai.config.DifyConfig;
+import org.xyzh.ai.exception.DifyException;
+import org.xyzh.api.ai.dto.DifyFileInfo;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @description Dify API客户端 - 封装所有Dify平台HTTP调用
+ * @filename DifyApiClient.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-12-15
+ */
+@Component
+public class DifyApiClient {
+
+ private static final Logger logger = LoggerFactory.getLogger(DifyApiClient.class);
+
+ @Autowired
+ private DifyConfig difyConfig;
+
+ private OkHttpClient httpClient;
+ private OkHttpClient streamHttpClient;
+
+ @PostConstruct
+ public void init() {
+ // 普通请求客户端
+ this.httpClient = new OkHttpClient.Builder()
+ .connectTimeout(difyConfig.getConnectTimeout(), TimeUnit.SECONDS)
+ .readTimeout(difyConfig.getReadTimeout(), TimeUnit.SECONDS)
+ .writeTimeout(difyConfig.getTimeout(), TimeUnit.SECONDS)
+ .retryOnConnectionFailure(true)
+ .build();
+
+ // 流式请求客户端(更长的读取超时)
+ this.streamHttpClient = new OkHttpClient.Builder()
+ .connectTimeout(difyConfig.getConnectTimeout(), TimeUnit.SECONDS)
+ .readTimeout(difyConfig.getStreamTimeout(), TimeUnit.SECONDS)
+ .writeTimeout(difyConfig.getTimeout(), TimeUnit.SECONDS)
+ .retryOnConnectionFailure(false) // 流式不重试
+ .build();
+
+ logger.info("DifyApiClient初始化完成,API地址: {}", difyConfig.getApiBaseUrl());
+ }
+
+ // ===================== 知识库管理 API =====================
+
+ /**
+ * 创建知识库(Dataset)
+ */
+ public DatasetCreateResponse createDataset(DatasetCreateRequest request) {
+ String url = difyConfig.getFullApiUrl("/datasets");
+
+ try {
+ String jsonBody = JSON.toJSONString(request);
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("创建知识库失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("创建知识库失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, DatasetCreateResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("创建知识库异常", e);
+ throw new DifyException("创建知识库异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 查询知识库列表
+ */
+ public DatasetListResponse listDatasets(int page, int limit) {
+ String url = difyConfig.getFullApiUrl("/datasets?page=" + page + "&limit=" + limit);
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("查询知识库列表失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("查询知识库列表失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, DatasetListResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("查询知识库列表异常", e);
+ throw new DifyException("查询知识库列表异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 查询知识库详情
+ */
+ public DatasetDetailResponse getDatasetDetail(String datasetId) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId);
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("查询知识库详情失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("查询知识库详情失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, DatasetDetailResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("查询知识库详情异常", e);
+ throw new DifyException("查询知识库详情异常: " + e.getMessage(), e);
+ }
+ }
+
+
+ /**
+ * 更新知识库
+ */
+ public void updateDataset(String datasetId, DatasetUpdateRequest request) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId);
+
+ try {
+ String jsonBody = JSON.toJSONString(request);
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .header("Content-Type", "application/json")
+ .patch(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ if (!response.isSuccessful()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+ logger.error("更新知识库失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("更新知识库失败: " + responseBody);
+ }
+ logger.info("知识库更新成功: {}", datasetId);
+ }
+ } catch (IOException e) {
+ logger.error("更新知识库异常", e);
+ throw new DifyException("更新知识库异常: " + e.getMessage(), e);
+ }
+ }
+ /**
+ * 删除知识库
+ */
+ public void deleteDataset(String datasetId) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId);
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .delete()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ if (!response.isSuccessful()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+ logger.error("删除知识库失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("删除知识库失败: " + responseBody);
+ }
+ logger.info("知识库删除成功: {}", datasetId);
+ }
+ } catch (IOException e) {
+ logger.error("删除知识库异常", e);
+ throw new DifyException("删除知识库异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 对话文件上传 API =====================
+
+ /**
+ * 上传文件用于对话(图文多模态)
+ * @param file 文件
+ * @param originalFilename 原始文件名
+ * @param user 用户标识
+ * @param apiKey API密钥
+ * @return 文件信息(包含id、name、size等)
+ */
+ public DifyFileInfo uploadFileForChat(File file, String originalFilename, String user, String apiKey) {
+ String url = difyConfig.getFullApiUrl("/files/upload");
+
+ try {
+ MultipartBody.Builder bodyBuilder = new MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("file", originalFilename,
+ RequestBody.create(file, MediaType.parse("application/octet-stream")))
+ .addFormDataPart("user", user);
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .post(bodyBuilder.build())
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("上传对话文件失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("上传对话文件失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, DifyFileInfo.class);
+ }
+ } catch (IOException e) {
+ logger.error("上传对话文件异常", e);
+ throw new DifyException("上传对话文件异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 文档管理 API =====================
+
+ /**
+ * 上传文档到知识库(通过文件)
+ * 根据 Dify API 文档: POST /datasets/{dataset_id}/document/create-by-file
+ */
+ public DocumentUploadResponse uploadDocumentByFile(
+ String datasetId,
+ File file,
+ String originalFilename,
+ DocumentUploadRequest uploadRequest) {
+
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/document/create-by-file");
+
+ try {
+ // 构建 data JSON 字符串(包含所有元数据)
+ Map dataMap = new HashMap<>();
+ if (uploadRequest.getName() != null) {
+ dataMap.put("name", uploadRequest.getName());
+ }
+ if (uploadRequest.getIndexingTechnique() != null) {
+ dataMap.put("indexing_technique", uploadRequest.getIndexingTechnique());
+ }
+
+ // process_rule 是必填字段,如果没有提供则使用默认配置
+ if (uploadRequest.getProcessRule() != null) {
+ dataMap.put("process_rule", uploadRequest.getProcessRule());
+ } else {
+ // 默认分段规则
+ Map defaultProcessRule = new HashMap<>();
+ defaultProcessRule.put("mode", "automatic");
+ dataMap.put("process_rule", defaultProcessRule);
+ }
+
+ // 默认设置文档形式和语言
+ dataMap.put("doc_form", "text_model");
+ dataMap.put("doc_language", "Chinese");
+
+ String dataJson = JSON.toJSONString(dataMap);
+ logger.info("上传文档到知识库: datasetId={}, file={}, data={}", datasetId, originalFilename, dataJson);
+
+ // 构建 multipart/form-data 请求体
+ MultipartBody.Builder bodyBuilder = new MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("file", originalFilename,
+ RequestBody.create(file, MediaType.parse("application/octet-stream")))
+ .addFormDataPart("data", dataJson);
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .post(bodyBuilder.build())
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("上传文档失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("上传文档失败: " + responseBody);
+ }
+
+ logger.info("文档上传成功: datasetId={}, file={}", datasetId, originalFilename);
+ return JSON.parseObject(responseBody, DocumentUploadResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("上传文档异常", e);
+ throw new DifyException("上传文档异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 查询文档处理状态
+ */
+ public DocumentStatusResponse getDocumentStatus(String datasetId, String batchId) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents/" + batchId + "/indexing-status");
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("查询文档状态失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("查询文档状态失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, DocumentStatusResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("查询文档状态异常", e);
+ throw new DifyException("查询文档状态异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 查询知识库文档列表
+ */
+ public DocumentListResponse listDocuments(String datasetId, int page, int limit) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents?page=" + page + "&limit=" + limit);
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("查询文档列表失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("查询文档列表失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, DocumentListResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("查询文档列表异常", e);
+ throw new DifyException("查询文档列表异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 删除文档
+ */
+ public void deleteDocument(String datasetId, String documentId) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents/" + documentId);
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .delete()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ if (!response.isSuccessful()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+ logger.error("删除文档失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("删除文档失败: " + responseBody);
+ }
+ logger.info("文档删除成功: {}", documentId);
+ }
+ } catch (IOException e) {
+ logger.error("删除文档异常", e);
+ throw new DifyException("删除文档异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 知识库检索 API =====================
+
+ /**
+ * 从知识库检索相关内容
+ */
+ public RetrievalResponse retrieveFromDataset(String datasetId, RetrievalRequest request) {
+ String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/retrieve");
+
+ try {
+ String jsonBody = JSON.toJSONString(request);
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("知识库检索失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("知识库检索失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, RetrievalResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("知识库检索异常", e);
+ throw new DifyException("知识库检索异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 对话 API =====================
+
+ /**
+ * 流式对话(SSE)
+ */
+ public void streamChat(ChatRequest request, String apiKey, StreamCallback callback) {
+ String url = difyConfig.getFullApiUrl("/chat-messages");
+
+ try {
+ // 设置为流式模式
+ request.setResponseMode("streaming");
+
+ String jsonBody = JSON.toJSONString(request);
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ streamHttpClient.newCall(httpRequest).enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ if (!response.isSuccessful()) {
+ String errorBody = response.body() != null ? response.body().string() : "";
+ callback.onError(new DifyException("流式对话失败: " + errorBody));
+ return;
+ }
+
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(response.body().byteStream()))) {
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("data: ")) {
+ String data = line.substring(6).trim();
+
+ if ("[DONE]".equals(data)) {
+ callback.onComplete();
+ break;
+ }
+
+ if (!data.isEmpty()) {
+ // 使用Fastjson2解析SSE数据
+ JSONObject jsonNode = JSON.parseObject(data);
+ String event = jsonNode.containsKey("event") ? jsonNode.getString("event") : "";
+
+ // 转发所有事件到回调(包含完整数据)
+ callback.onEvent(event, data);
+
+ switch (event) {
+ case "message":
+ case "agent_message":
+ // 消息内容
+ if (jsonNode.containsKey("answer")) {
+ callback.onMessage(jsonNode.getString("answer"));
+ }
+ break;
+ case "message_end":
+ // 消息结束,提取元数据
+ callback.onMessageEnd(data);
+ break;
+ case "error":
+ // 错误事件
+ String errorMsg = jsonNode.containsKey("message") ?
+ jsonNode.getString("message") : "未知错误";
+ callback.onError(new DifyException(errorMsg));
+ return;
+ // 其他事件(workflow_started、node_started、node_finished等)
+ // 已通过onEvent转发,这里不需要额外处理
+ }
+ }
+ }
+ }
+ // 流正常读取完毕,如果没有收到 [DONE] 标记,也当作完成
+ logger.info("SSE流读取完毕");
+ callback.onComplete();
+ } catch (java.io.EOFException e) {
+ // EOFException 通常表示流正常结束(Dify可能没有发送[DONE]标记)
+ logger.info("SSE流已结束(EOF)");
+ callback.onComplete();
+ } catch (Exception e) {
+ logger.error("流式响应处理异常", e);
+ callback.onError(e);
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, IOException e) {
+ logger.error("流式对话请求失败", e);
+ callback.onError(e);
+ }
+ });
+
+ } catch (Exception e) {
+ logger.error("流式对话异常", e);
+ callback.onError(e);
+ }
+ }
+
+ /**
+ * 阻塞式对话(非流式)
+ */
+ public ChatResponse blockingChat(ChatRequest request, String apiKey) {
+ String url = difyConfig.getFullApiUrl("/chat-messages");
+
+ try {
+ // 设置为阻塞模式
+ request.setResponseMode("blocking");
+
+ String jsonBody = JSON.toJSONString(request);
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("阻塞式对话失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("阻塞式对话失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, ChatResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("阻塞式对话异常", e);
+ throw new DifyException("阻塞式对话异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 停止对话生成
+ */
+ public void stopChatMessage(String taskId, String userId, String apiKey) {
+ String url = difyConfig.getFullApiUrl("/chat-messages/" + taskId + "/stop");
+
+ try {
+ String jsonBody = JSON.toJSONString(new StopRequest(userId));
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ if (!response.isSuccessful()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+ logger.error("停止对话失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("停止对话失败: " + responseBody);
+ }
+ logger.info("对话停止成功: {}", taskId);
+ }
+ } catch (IOException e) {
+ logger.error("停止对话异常", e);
+ throw new DifyException("停止对话异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 提交消息反馈
+ * @param messageId Dify消息ID
+ * @param rating 评分(like/dislike/null)
+ * @param userId 用户ID
+ * @param apiKey API密钥
+ */
+ public void submitMessageFeedback(String messageId, String rating, String userId, String feedback, String apiKey) {
+ String url = difyConfig.getFullApiUrl("/messages/" + messageId + "/feedbacks");
+
+ try {
+ FeedbackRequest feedbackRequest = new FeedbackRequest(rating, userId, feedback);
+ String jsonBody = JSON.toJSONString(feedbackRequest);
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("提交消息反馈失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("提交消息反馈失败: " + responseBody);
+ }
+ if (responseBody!="success") {
+ logger.error("提交消息反馈失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("提交消息反馈失败: " + responseBody);
+ }
+ logger.info("消息反馈提交成功: {} - {}", messageId, rating);
+ }
+ } catch (IOException e) {
+ logger.error("提交消息反馈异常", e);
+ throw new DifyException("提交消息反馈异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 对话历史 API =====================
+
+ /**
+ * 获取对话历史消息
+ */
+ public MessageHistoryResponse getMessageHistory(
+ String conversationId,
+ String userId,
+ String firstId,
+ Integer limit,
+ String apiKey) {
+
+ StringBuilder urlBuilder = new StringBuilder(difyConfig.getFullApiUrl("/messages"));
+ urlBuilder.append("?user=").append(userId);
+
+ if (conversationId != null && !conversationId.isEmpty()) {
+ urlBuilder.append("&conversation_id=").append(conversationId);
+ }
+ if (firstId != null && !firstId.isEmpty()) {
+ urlBuilder.append("&first_id=").append(firstId);
+ }
+ if (limit != null) {
+ urlBuilder.append("&limit=").append(limit);
+ }
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(urlBuilder.toString())
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("获取对话历史失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("获取对话历史失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, MessageHistoryResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("获取对话历史异常", e);
+ throw new DifyException("获取对话历史异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 获取对话列表
+ */
+ public ConversationListResponse getConversations(
+ String userId,
+ String lastId,
+ Integer limit,
+ String apiKey) {
+
+ StringBuilder urlBuilder = new StringBuilder(difyConfig.getFullApiUrl("/conversations"));
+ urlBuilder.append("?user=").append(userId);
+
+ if (lastId != null && !lastId.isEmpty()) {
+ urlBuilder.append("&last_id=").append(lastId);
+ }
+ if (limit != null) {
+ urlBuilder.append("&limit=").append(limit);
+ }
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(urlBuilder.toString())
+ .header("Authorization", "Bearer " + getApiKey(apiKey))
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("获取对话列表失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("获取对话列表失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, ConversationListResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("获取对话列表异常", e);
+ throw new DifyException("获取对话列表异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 通用 HTTP 方法(用于代理转发)=====================
+
+ /**
+ * 通用 GET 请求
+ * @param path API路径
+ * @param apiKey API密钥(为null时使用知识库API Key)
+ * @return JSON响应字符串
+ */
+ public String get(String path, String apiKey) {
+ String url = difyConfig.getFullApiUrl(path);
+
+ try {
+ // 如果apiKey为null,使用知识库API Key(因为通用方法主要用于知识库相关操作)
+ String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey();
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + actualApiKey)
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("GET请求失败: {} - {} - {}", url, response.code(), responseBody);
+ throw new DifyException("GET请求失败[" + response.code() + "]: " + responseBody);
+ }
+
+ return responseBody;
+ }
+ } catch (IOException e) {
+ logger.error("GET请求异常: {}", url, e);
+ throw new DifyException("GET请求异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 通用 POST 请求
+ * @param path API路径
+ * @param requestBody 请求体(JSON字符串或Map)
+ * @param apiKey API密钥(为null时使用知识库API Key)
+ * @return JSON响应字符串
+ */
+ public String post(String path, Object requestBody, String apiKey) {
+ String url = difyConfig.getFullApiUrl(path);
+
+ try {
+ // 如果apiKey为null,使用知识库API Key
+ String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey();
+
+ String jsonBody = requestBody instanceof String ?
+ (String) requestBody : JSON.toJSONString(requestBody);
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + actualApiKey)
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("POST请求失败: {} - {} - {}", url, response.code(), responseBody);
+ throw new DifyException("POST请求失败[" + response.code() + "]: " + responseBody);
+ }
+
+ return responseBody;
+ }
+ } catch (IOException e) {
+ logger.error("POST请求异常: {}", url, e);
+ throw new DifyException("POST请求异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 通用 PATCH 请求
+ * @param path API路径
+ * @param requestBody 请求体(JSON字符串或Map)
+ * @param apiKey API密钥(为null时使用知识库API Key)
+ * @return JSON响应字符串
+ */
+ public String patch(String path, Object requestBody, String apiKey) {
+ String url = difyConfig.getFullApiUrl(path);
+
+ try {
+ // 如果apiKey为null,使用知识库API Key
+ String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey();
+
+ String jsonBody = requestBody instanceof String ?
+ (String) requestBody : JSON.toJSONString(requestBody);
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + actualApiKey)
+ .header("Content-Type", "application/json")
+ .patch(RequestBody.create(jsonBody, MediaType.parse("application/json")))
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("PATCH请求失败: {} - {} - {}", url, response.code(), responseBody);
+ throw new DifyException("PATCH请求失败[" + response.code() + "]: " + responseBody);
+ }
+
+ return responseBody;
+ }
+ } catch (IOException e) {
+ logger.error("PATCH请求异常: {}", url, e);
+ throw new DifyException("PATCH请求异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 通用 DELETE 请求
+ * @param path API路径
+ * @param apiKey API密钥(为null时使用知识库API Key)
+ * @return JSON响应字符串
+ */
+ public String delete(String path, String apiKey) {
+ String url = difyConfig.getFullApiUrl(path);
+
+ try {
+ // 如果apiKey为null,使用知识库API Key
+ String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey();
+
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + actualApiKey)
+ .delete()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("DELETE请求失败: {} - {} - {}", url, response.code(), responseBody);
+ throw new DifyException("DELETE请求失败[" + response.code() + "]: " + responseBody);
+ }
+
+ return responseBody;
+ }
+ } catch (IOException e) {
+ logger.error("DELETE请求异常: {}", url, e);
+ throw new DifyException("DELETE请求异常: " + e.getMessage(), e);
+ }
+ }
+
+ // ===================== 工具方法 =====================
+
+ /**
+ * 获取API密钥(优先使用传入的密钥,否则使用配置的默认密钥)
+ * 用于智能体相关的API
+ */
+ private String getApiKey(String apiKey) {
+ if (apiKey != null && !apiKey.trim().isEmpty()) {
+ return apiKey;
+ }
+ throw new DifyException("未配置Dify API密钥");
+ }
+
+ /**
+ * 获取知识库API密钥(统一使用配置中的knowledgeApiKey)
+ * 用于知识库相关的API
+ */
+ private String getKnowledgeApiKey() {
+ if (difyConfig.getKnowledgeApiKey() != null && !difyConfig.getKnowledgeApiKey().trim().isEmpty()) {
+ return difyConfig.getKnowledgeApiKey();
+ }
+ throw new DifyException("未配置Dify知识库API密钥");
+ }
+
+ /**
+ * 停止请求的内部类
+ */
+ private static class StopRequest {
+ private String user;
+
+ public StopRequest(String user) {
+ this.user = user;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+ }
+
+ /**
+ * 反馈请求的内部类
+ */
+ private static class FeedbackRequest {
+ private String rating;
+ private String user;
+
+ private String feedback;
+
+ public FeedbackRequest(String rating, String user, String feedback) {
+ this.rating = rating;
+ this.user = user;
+ this.feedback = feedback;
+ }
+
+ public String getRating() {
+ return rating;
+ }
+
+ public void setRating(String rating) {
+ this.rating = rating;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getFeedback() {
+ return feedback;
+ }
+
+ public void setFeedback(String feedback) {
+ this.feedback = feedback;
+ }
+ }
+
+ // ===================== 模型管理 API =====================
+
+ /**
+ * 获取可用的嵌入模型列表
+ */
+ public EmbeddingModelResponse getAvailableEmbeddingModels() {
+ String url = difyConfig.getFullApiUrl("/workspaces/current/models/model-types/text-embedding");
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("获取嵌入模型列表失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("获取嵌入模型列表失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, EmbeddingModelResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("获取嵌入模型列表异常", e);
+ throw new DifyException("获取嵌入模型列表异常: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 获取可用的Rerank模型列表
+ */
+ public RerankModelResponse getAvailableRerankModels() {
+ String url = difyConfig.getFullApiUrl("/workspaces/current/models/model-types/rerank");
+
+ try {
+ Request httpRequest = new Request.Builder()
+ .url(url)
+ .header("Authorization", "Bearer " + getKnowledgeApiKey())
+ .get()
+ .build();
+
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
+ String responseBody = response.body() != null ? response.body().string() : "";
+
+ if (!response.isSuccessful()) {
+ logger.error("获取Rerank模型列表失败: {} - {}", response.code(), responseBody);
+ throw new DifyException("获取Rerank模型列表失败: " + responseBody);
+ }
+
+ return JSON.parseObject(responseBody, RerankModelResponse.class);
+ }
+ } catch (IOException e) {
+ logger.error("获取Rerank模型列表异常", e);
+ throw new DifyException("获取Rerank模型列表异常: " + e.getMessage(), e);
+ }
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java
new file mode 100644
index 00000000..f2c47744
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java
@@ -0,0 +1,44 @@
+package org.xyzh.ai.client.callback;
+
+/**
+ * @description 流式响应回调接口
+ * @filename StreamCallback.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+public interface StreamCallback {
+
+ /**
+ * 接收到消息片段
+ * @param message 消息内容
+ */
+ void onMessage(String message);
+
+ /**
+ * 消息结束(包含元数据)
+ * @param metadata JSON格式的元数据
+ */
+ void onMessageEnd(String metadata);
+
+ /**
+ * 接收到Dify原始事件(用于转发完整事件数据)
+ * @param eventType 事件类型(如workflow_started、node_started等)
+ * @param eventData 完整的事件JSON数据
+ */
+ default void onEvent(String eventType, String eventData) {
+ // 默认实现:不处理
+ }
+
+ /**
+ * 流式响应完成
+ */
+ void onComplete();
+
+ /**
+ * 发生错误
+ * @param error 错误对象
+ */
+ void onError(Throwable error);
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java
new file mode 100644
index 00000000..9a685d39
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java
@@ -0,0 +1,75 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+import java.util.List;
+import java.util.Map;
+import org.xyzh.api.ai.dto.DifyFileInfo;
+
+/**
+ * @description 对话请求
+ * @filename ChatRequest.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-12-15
+ */
+@Data
+public class ChatRequest {
+
+ /**
+ * 输入变量
+ */
+ private Map inputs;
+
+ /**
+ * 用户问题
+ */
+ private String query;
+
+ /**
+ * 响应模式:streaming(流式)、blocking(阻塞)
+ */
+ @JSONField(name = "response_mode")
+ private String responseMode = "streaming";
+
+ /**
+ * 对话ID(继续对话时传入)
+ */
+ @JSONField(name = "conversation_id")
+ private String conversationId;
+
+ /**
+ * 用户标识
+ */
+ private String user;
+
+ /**
+ * 上传的文件列表
+ */
+ private List files;
+
+ /**
+ * 自动生成标题
+ */
+ @JSONField(name = "auto_generate_name")
+ private Boolean autoGenerateName = true;
+
+ /**
+ * 指定的数据集ID列表(知识库检索)
+ */
+ @JSONField(name = "dataset_ids")
+ private List datasetIds;
+
+ /**
+ * 温度参数(0.0-1.0)
+ */
+ private Double temperature;
+
+ /**
+ * 最大token数
+ */
+ @JSONField(name = "max_tokens")
+ private Integer maxTokens;
+
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java
new file mode 100644
index 00000000..8b166668
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java
@@ -0,0 +1,121 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @description 对话响应(阻塞模式)
+ * @filename ChatResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class ChatResponse {
+
+ /**
+ * 消息ID
+ */
+ @JSONField(name = "message_id")
+ private String messageId;
+
+ /**
+ * 对话ID
+ */
+ @JSONField(name = "conversation_id")
+ private String conversationId;
+
+ /**
+ * 模式
+ */
+ private String mode;
+
+ /**
+ * 回答内容
+ */
+ private String answer;
+
+ /**
+ * 元数据
+ */
+ private Map metadata;
+
+ /**
+ * 创建时间
+ */
+ @JSONField(name = "created_at")
+ private Long createdAt;
+
+ /**
+ * Token使用情况
+ */
+ private Usage usage;
+
+ /**
+ * 检索信息
+ */
+ @JSONField(name = "retrieval_info")
+ private List retrievalInfo;
+
+ @Data
+ public static class Usage {
+ @JSONField(name = "prompt_tokens")
+ private Integer promptTokens;
+
+ @JSONField(name = "prompt_unit_price")
+ private String promptUnitPrice;
+
+ @JSONField(name = "prompt_price_unit")
+ private String promptPriceUnit;
+
+ @JSONField(name = "prompt_price")
+ private String promptPrice;
+
+ @JSONField(name = "completion_tokens")
+ private Integer completionTokens;
+
+ @JSONField(name = "completion_unit_price")
+ private String completionUnitPrice;
+
+ @JSONField(name = "completion_price_unit")
+ private String completionPriceUnit;
+
+ @JSONField(name = "completion_price")
+ private String completionPrice;
+
+ @JSONField(name = "total_tokens")
+ private Integer totalTokens;
+
+ @JSONField(name = "total_price")
+ private String totalPrice;
+
+ private String currency;
+
+ private Double latency;
+ }
+
+ @Data
+ public static class RetrievalInfo {
+ @JSONField(name = "dataset_id")
+ private String datasetId;
+
+ @JSONField(name = "dataset_name")
+ private String datasetName;
+
+ @JSONField(name = "document_id")
+ private String documentId;
+
+ @JSONField(name = "document_name")
+ private String documentName;
+
+ @JSONField(name = "segment_id")
+ private String segmentId;
+
+ private Double score;
+
+ private String content;
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java
new file mode 100644
index 00000000..f177cfd7
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java
@@ -0,0 +1,50 @@
+package org.xyzh.ai.client.dto;
+
+import lombok.Data;
+import java.util.List;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+
+/**
+ * @description 对话列表响应
+ * @filename ConversationListResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class ConversationListResponse {
+
+ private Integer limit;
+
+ @JSONField(name = "has_more")
+ private Boolean hasMore;
+
+ private List data;
+
+ @Data
+ public static class ConversationInfo {
+ private String id;
+
+ private String name;
+
+ private List inputs;
+
+ private String status;
+
+ private String introduction;
+
+ @JSONField(name = "created_at")
+ private Long createdAt;
+
+ @JSONField(name = "updated_at")
+ private Long updatedAt;
+ }
+
+ @Data
+ public static class InputInfo {
+ private String key;
+ private String value;
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java
new file mode 100644
index 00000000..c408cbf3
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java
@@ -0,0 +1,55 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @description 创建知识库请求
+ * @filename DatasetCreateRequest.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DatasetCreateRequest {
+
+ /**
+ * 知识库名称
+ */
+ private String name;
+
+ /**
+ * 知识库描述
+ */
+ private String description;
+
+ /**
+ * 索引方式:high_quality(高质量)、economy(经济)
+ */
+ @JSONField(name = "indexing_technique")
+ private String indexingTechnique = "high_quality";
+
+ /**
+ * Embedding模型
+ */
+ @JSONField(name = "embedding_model")
+ private String embeddingModel;
+
+ /**
+ * Embedding模型提供商
+ */
+ @JSONField(name = "embedding_model_provider")
+ private String embeddingModelProvider;
+
+ /**
+ * 检索模型配置(包含 Rerank、Top K、Score 阈值等)
+ */
+ @JSONField(name = "retrieval_model")
+ private RetrievalModel retrievalModel;
+
+ /**
+ * 权限:only_me(仅自己)、all_team_members(团队所有成员)
+ */
+ private String permission = "only_me";
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java
new file mode 100644
index 00000000..8f3b3f40
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java
@@ -0,0 +1,55 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @description 创建知识库响应
+ * @filename DatasetCreateResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DatasetCreateResponse {
+
+ /**
+ * 知识库ID
+ */
+ private String id;
+
+ /**
+ * 知识库名称
+ */
+ private String name;
+
+ /**
+ * 描述
+ */
+ private String description;
+
+ /**
+ * 索引方式
+ */
+ @JSONField(name = "indexing_technique")
+ private String indexingTechnique;
+
+ /**
+ * Embedding模型
+ */
+ @JSONField(name = "embedding_model")
+ private String embeddingModel;
+
+ /**
+ * 创建时间
+ */
+ @JSONField(name = "created_at")
+ private Long createdAt;
+
+ /**
+ * 创建人
+ */
+ @JSONField(name = "created_by")
+ private String createdBy;
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java
new file mode 100644
index 00000000..33c2f947
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java
@@ -0,0 +1,82 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @description 知识库详情响应
+ * @filename DatasetDetailResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DatasetDetailResponse {
+
+ private String id;
+
+ private String name;
+
+ private String description;
+
+ @JSONField(name = "indexing_technique")
+ private String indexingTechnique;
+
+ @JSONField(name = "embedding_model")
+ private String embeddingModel;
+
+ @JSONField(name = "embedding_model_provider")
+ private String embeddingModelProvider;
+
+ @JSONField(name = "embedding_available")
+ private Boolean embeddingAvailable;
+
+ @JSONField(name = "retrieval_model_dict")
+ private RetrievalModelDict retrievalModelDict;
+
+ @JSONField(name = "document_count")
+ private Integer documentCount;
+
+ @JSONField(name = "word_count")
+ private Integer wordCount;
+
+ @JSONField(name = "app_count")
+ private Integer appCount;
+
+ @JSONField(name = "created_by")
+ private String createdBy;
+
+ @JSONField(name = "created_at")
+ private Long createdAt;
+
+ @JSONField(name = "updated_at")
+ private Long updatedAt;
+
+ @Data
+ public static class RetrievalModelDict {
+ @JSONField(name = "search_method")
+ private String searchMethod;
+
+ @JSONField(name = "reranking_enable")
+ private Boolean rerankingEnable;
+
+ @JSONField(name = "reranking_model")
+ private RerankingModel rerankingModel;
+
+ @JSONField(name = "top_k")
+ private Integer topK;
+
+ @JSONField(name = "score_threshold_enabled")
+ private Boolean scoreThresholdEnabled;
+ }
+
+ @Data
+ public static class RerankingModel {
+ @JSONField(name = "reranking_provider_name")
+ private String rerankingProviderName;
+
+ @JSONField(name = "reranking_model_name")
+ private String rerankingModelName;
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java
new file mode 100644
index 00000000..6ef4f821
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java
@@ -0,0 +1,61 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+import java.util.List;
+
+/**
+ * @description 知识库列表响应
+ * @filename DatasetListResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DatasetListResponse {
+
+ /**
+ * 知识库列表
+ */
+ private List data;
+
+ /**
+ * 是否有更多
+ */
+ @JSONField(name = "has_more")
+ private Boolean hasMore;
+
+ /**
+ * 分页限制
+ */
+ private Integer limit;
+
+ /**
+ * 总数
+ */
+ private Integer total;
+
+ /**
+ * 当前页
+ */
+ private Integer page;
+
+ @Data
+ public static class DatasetInfo {
+ private String id;
+ private String name;
+ private String description;
+ private String permission;
+ @JSONField(name = "document_count")
+ private Integer documentCount;
+ @JSONField(name = "word_count")
+ private Integer wordCount;
+ @JSONField(name = "created_by")
+ private String createdBy;
+ @JSONField(name = "created_at")
+ private Long createdAt;
+ @JSONField(name = "updated_at")
+ private Long updatedAt;
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java
new file mode 100644
index 00000000..cd639539
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java
@@ -0,0 +1,50 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @description Dify知识库更新请求
+ * @filename DatasetUpdateRequest.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DatasetUpdateRequest {
+
+ /**
+ * 知识库名称
+ */
+ private String name;
+
+ /**
+ * 知识库描述
+ */
+ private String description;
+
+ /**
+ * 索引方式(high_quality/economy)
+ */
+ @JSONField(name = "indexing_technique")
+ private String indexingTechnique;
+
+ /**
+ * Embedding模型
+ */
+ @JSONField(name = "embedding_model")
+ private String embeddingModel;
+
+ /**
+ * Embedding模型提供商
+ */
+ @JSONField(name = "embedding_model_provider")
+ private String embeddingModelProvider;
+
+ /**
+ * 检索模型配置(包含 Rerank、Top K、Score 阈值等)
+ */
+ @JSONField(name = "retrieval_model")
+ private RetrievalModel retrievalModel;
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java
new file mode 100644
index 00000000..cb4c9c5a
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java
@@ -0,0 +1,96 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @description Dify文档列表响应
+ * @filename DocumentListResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-07
+ */
+@Data
+public class DocumentListResponse {
+
+ private List data;
+
+ @JSONField(name = "has_more")
+ private Boolean hasMore;
+
+ private Integer limit;
+
+ private Integer total;
+
+ private Integer page;
+
+ /**
+ * 文档信息
+ */
+ @Data
+ public static class Document {
+ private String id;
+
+ private Integer position;
+
+ @JSONField(name = "data_source_type")
+ private String dataSourceType;
+
+ @JSONField(name = "data_source_info")
+ private DataSourceInfo dataSourceInfo;
+
+ @JSONField(name = "dataset_process_rule_id")
+ private String datasetProcessRuleId;
+
+ private String name;
+
+ @JSONField(name = "created_from")
+ private String createdFrom;
+
+ @JSONField(name = "created_by")
+ private String createdBy;
+
+ @JSONField(name = "created_at")
+ private Long createdAt;
+
+ private Integer tokens;
+
+ @JSONField(name = "indexing_status")
+ private String indexingStatus;
+
+ private String error;
+
+ private Boolean enabled;
+
+ @JSONField(name = "disabled_at")
+ private Long disabledAt;
+
+ @JSONField(name = "disabled_by")
+ private String disabledBy;
+
+ private Boolean archived;
+
+ @JSONField(name = "display_status")
+ private String displayStatus;
+
+ @JSONField(name = "word_count")
+ private Integer wordCount;
+
+ @JSONField(name = "hit_count")
+ private Integer hitCount;
+
+ @JSONField(name = "doc_form")
+ private String docForm;
+ }
+
+ /**
+ * 数据源信息
+ */
+ @Data
+ public static class DataSourceInfo {
+ @JSONField(name = "upload_file_id")
+ private String uploadFileId;
+ }
+}
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java
new file mode 100644
index 00000000..fa8ac9c2
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java
@@ -0,0 +1,95 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+import java.util.List;
+
+/**
+ * @description 文档处理状态响应
+ * @filename DocumentStatusResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DocumentStatusResponse {
+
+ /**
+ * 文档列表
+ */
+ private List data;
+
+ @Data
+ public static class DocumentStatus {
+ /**
+ * 文档ID
+ */
+ private String id;
+
+ /**
+ * 索引状态:waiting、parsing、cleaning、splitting、indexing、completed、error
+ */
+ @JSONField(name = "indexing_status")
+ private String indexingStatus;
+
+ /**
+ * 处理开始时间
+ */
+ @JSONField(name = "processing_started_at")
+ private Long processingStartedAt;
+
+ /**
+ * 解析完成时间
+ */
+ @JSONField(name = "parsing_completed_at")
+ private Long parsingCompletedAt;
+
+ /**
+ * 清洗完成时间
+ */
+ @JSONField(name = "cleaning_completed_at")
+ private Long cleaningCompletedAt;
+
+ /**
+ * 分割完成时间
+ */
+ @JSONField(name = "splitting_completed_at")
+ private Long splittingCompletedAt;
+
+ /**
+ * 完成时间
+ */
+ @JSONField(name = "completed_at")
+ private Long completedAt;
+
+ /**
+ * 暂停时间
+ */
+ @JSONField(name = "paused_at")
+ private Long pausedAt;
+
+ /**
+ * 错误信息
+ */
+ private String error;
+
+ /**
+ * 停止时间
+ */
+ @JSONField(name = "stopped_at")
+ private Long stoppedAt;
+
+ /**
+ * 分段数量
+ */
+ @JSONField(name = "completed_segments")
+ private Integer completedSegments;
+
+ /**
+ * 总分段数
+ */
+ @JSONField(name = "total_segments")
+ private Integer totalSegments;
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java
new file mode 100644
index 00000000..aca27ee9
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java
@@ -0,0 +1,95 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @description 文档上传请求
+ * @filename DocumentUploadRequest.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DocumentUploadRequest {
+
+ /**
+ * 文档名称
+ */
+ private String name;
+
+ /**
+ * 索引方式
+ */
+ @JSONField(name = "indexing_technique")
+ private String indexingTechnique;
+
+ /**
+ * 处理规则
+ */
+ @JSONField(name = "process_rule")
+ private ProcessRule processRule;
+
+ @Data
+ public static class ProcessRule {
+ /**
+ * 分段模式:automatic(自动)、custom(自定义)
+ */
+ private String mode = "automatic";
+
+ /**
+ * 预处理规则
+ */
+ private Rules rules;
+
+ @Data
+ public static class Rules {
+ /**
+ * 自动分段配置
+ */
+ @JSONField(name = "pre_processing_rules")
+ private PreProcessingRules preProcessingRules;
+
+ /**
+ * 分段配置
+ */
+ private Segmentation segmentation;
+ }
+
+ @Data
+ public static class PreProcessingRules {
+ /**
+ * 移除额外空格
+ */
+ @JSONField(name = "remove_extra_spaces")
+ private Boolean removeExtraSpaces = true;
+
+ /**
+ * 移除URL和邮箱
+ */
+ @JSONField(name = "remove_urls_emails")
+ private Boolean removeUrlsEmails = false;
+ }
+
+ @Data
+ public static class Segmentation {
+ /**
+ * 分隔符
+ */
+ private String separator = "\\n";
+
+ /**
+ * 最大分段长度
+ */
+ @JSONField(name = "max_tokens")
+ private Integer maxTokens = 1000;
+
+ /**
+ * 分段重叠长度
+ */
+ @JSONField(name = "chunk_overlap")
+ private Integer chunkOverlap = 50;
+ }
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java
new file mode 100644
index 00000000..b46e4bc4
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java
@@ -0,0 +1,145 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * @description 文档上传响应(根据 Dify API 返回结构)
+ * @filename DocumentUploadResponse.java
+ * @author AI Assistant
+ * @copyright xyzh
+ * @since 2025-11-04
+ */
+@Data
+public class DocumentUploadResponse {
+
+ /**
+ * 文档详细信息
+ */
+ private Document document;
+
+ /**
+ * 批次ID(用于查询处理状态)
+ */
+ private String batch;
+
+ /**
+ * 文档详细信息
+ */
+ @Data
+ public static class Document {
+ /**
+ * 文档ID
+ */
+ private String id;
+
+ /**
+ * 文档名称
+ */
+ private String name;
+
+ /**
+ * 位置(序号)
+ */
+ private Integer position;
+
+ /**
+ * 数据源类型
+ */
+ @JSONField(name = "data_source_type")
+ private String dataSourceType;
+
+ /**
+ * 数据源信息
+ */
+ @JSONField(name = "data_source_info")
+ private Object dataSourceInfo;
+
+ /**
+ * 数据集处理规则ID
+ */
+ @JSONField(name = "dataset_process_rule_id")
+ private String datasetProcessRuleId;
+
+ /**
+ * 创建来源
+ */
+ @JSONField(name = "created_from")
+ private String createdFrom;
+
+ /**
+ * 创建人
+ */
+ @JSONField(name = "created_by")
+ private String createdBy;
+
+ /**
+ * 创建时间(时间戳)
+ */
+ @JSONField(name = "created_at")
+ private Long createdAt;
+
+ /**
+ * Token数量
+ */
+ private Integer tokens;
+
+ /**
+ * 索引状态
+ */
+ @JSONField(name = "indexing_status")
+ private String indexingStatus;
+
+ /**
+ * 错误信息
+ */
+ private String error;
+
+ /**
+ * 是否启用
+ */
+ private Boolean enabled;
+
+ /**
+ * 禁用时间
+ */
+ @JSONField(name = "disabled_at")
+ private Long disabledAt;
+
+ /**
+ * 禁用人
+ */
+ @JSONField(name = "disabled_by")
+ private String disabledBy;
+
+ /**
+ * 是否归档
+ */
+ private Boolean archived;
+
+ /**
+ * 显示状态
+ */
+ @JSONField(name = "display_status")
+ private String displayStatus;
+
+ /**
+ * 字数
+ */
+ @JSONField(name = "word_count")
+ private Integer wordCount;
+
+ /**
+ * 命中次数
+ */
+ @JSONField(name = "hit_count")
+ private Integer hitCount;
+
+ /**
+ * 文档形式
+ */
+ @JSONField(name = "doc_form")
+ private String docForm;
+ }
+}
+
diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java
new file mode 100644
index 00000000..0b1ebdaa
--- /dev/null
+++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java
@@ -0,0 +1,144 @@
+package org.xyzh.ai.client.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @description Dify嵌入模型响应
+ * @filename EmbeddingModelResponse.java
+ * @author AI Assistant
+ * @since 2025-11-06
+ */
+@Data
+public class EmbeddingModelResponse {
+
+ /**
+ * 模型提供商列表
+ */
+ @JSONField(name = "data")
+ private List data;
+
+ /**
+ * 模型提供商
+ */
+ @Data
+ public static class ModelProvider {
+ /**
+ * 提供商标识
+ */
+ @JSONField(name = "provider")
+ private String provider;
+
+ /**
+ * 提供商标签
+ */
+ @JSONField(name = "label")
+ private Map label;
+
+ /**
+ * 小图标
+ */
+ @JSONField(name = "icon_small")
+ private Map iconSmall;
+
+ /**
+ * 大图标
+ */
+ @JSONField(name = "icon_large")
+ private Map iconLarge;
+
+ /**
+ * 状态
+ */
+ @JSONField(name = "status")
+ private String status;
+
+ /**
+ * 模型列表
+ */
+ @JSONField(name = "models")
+ private List models;
+ }
+
+ /**
+ * 模型详情
+ */
+ @Data
+ public static class Model {
+ /**
+ * 模型名称
+ */
+ @JSONField(name = "model")
+ private String model;
+
+ /**
+ * 模型标签
+ */
+ @JSONField(name = "label")
+ private Map label;
+
+ /**
+ * 模型类型
+ */
+ @JSONField(name = "model_type")
+ private String modelType;
+
+ /**
+ * 特性列表
+ */
+ @JSONField(name = "features")
+ private List