This commit is contained in:
2025-12-18 16:48:45 +08:00
parent b97f0da746
commit 41cbe2bd54
80 changed files with 5434 additions and 351 deletions

View File

@@ -9,6 +9,7 @@ CREATE TABLE ai.tb_agent(
description VARCHAR(500) DEFAULT NULL, -- 智能体描述 description VARCHAR(500) DEFAULT NULL, -- 智能体描述
link VARCHAR(500) DEFAULT NULL, -- 智能体url link VARCHAR(500) DEFAULT NULL, -- 智能体url
api_key VARCHAR(500) NOT NULL, -- dify智能体APIKEY api_key VARCHAR(500) NOT NULL, -- dify智能体APIKEY
outer BOOLEAN DEFAULT false, -- 是否是对外智能体,未登录可用
introduce VARCHAR(500) NOT NULL, -- 引导词 introduce VARCHAR(500) NOT NULL, -- 引导词
prompt_cards JSONB DEFAULT '[]'::jsonb, -- 提示卡片数组 [{file_id:'', prompt:''}] prompt_cards JSONB DEFAULT '[]'::jsonb, -- 提示卡片数组 [{file_id:'', prompt:''}]
category VARCHAR(50) NOT NULL, -- 分类 category VARCHAR(50) NOT NULL, -- 分类
@@ -45,8 +46,9 @@ DROP TABLE IF EXISTS ai.tb_chat_message CASCADE;
CREATE TABLE ai.tb_chat_message( CREATE TABLE ai.tb_chat_message(
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
message_id VARCHAR(50) NOT NULL, -- 消息ID message_id VARCHAR(50) NOT NULL, -- 消息ID
dify_message_id VARCHAR(100) DEFAULT NULL, -- Dify消息ID
chat_id VARCHAR(50) NOT NULL, -- 对话ID chat_id VARCHAR(50) NOT NULL, -- 对话ID
role VARCHAR(50) NOT NULL, -- 角色user-用户/assistant-智能体/recipient-来客 role VARCHAR(50) NOT NULL, -- 角色user-用户/ai-智能体/recipient-来客
content TEXT NOT NULL, -- 消息内容 content TEXT NOT NULL, -- 消息内容
files VARCHAR(50)[] DEFAULT NULL, -- 文件id数组 files VARCHAR(50)[] DEFAULT NULL, -- 文件id数组
comment VARCHAR(50) DEFAULT NULL, -- 评价 comment VARCHAR(50) DEFAULT NULL, -- 评价
@@ -144,7 +146,7 @@ CREATE TABLE ai.tb_knowledge_file(
file_root_id VARCHAR(50) NOT NULL, -- 文件根ID file_root_id VARCHAR(50) NOT NULL, -- 文件根ID
file_id VARCHAR(50) NOT NULL, -- 文件ID file_id VARCHAR(50) NOT NULL, -- 文件ID
dify_file_id VARCHAR(50) NOT NULL, -- dify文件ID dify_file_id VARCHAR(50) NOT NULL, -- dify文件ID
version VARCHAR(50) NOT NULL, -- 文件版本 version INTEGER NOT NULL DEFAULT 1, -- 文件版本
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间

View File

@@ -64,7 +64,7 @@ CREATE TABLE bidding.tb_bidding_document (
file_path VARCHAR(500), -- 文件路径 file_path VARCHAR(500), -- 文件路径
file_size BIGINT, -- 文件大小 file_size BIGINT, -- 文件大小
mime_type VARCHAR(100), -- MIME类型 mime_type VARCHAR(100), -- MIME类型
version VARCHAR(20) DEFAULT '1.0', -- 版本号 version INTEGER DEFAULT 1, -- 版本号
language VARCHAR(20) DEFAULT 'zh-CN', -- 语言 language VARCHAR(20) DEFAULT 'zh-CN', -- 语言
page_count INTEGER, -- 页数 page_count INTEGER, -- 页数
parse_status VARCHAR(30) DEFAULT 'pending', -- 解析状态pending-待解析/parsing-解析中/completed-已完成/failed-失败 parse_status VARCHAR(30) DEFAULT 'pending', -- 解析状态pending-待解析/parsing-解析中/completed-已完成/failed-失败
@@ -145,7 +145,7 @@ CREATE TABLE bidding.tb_bid_response (
generation_status VARCHAR(30) DEFAULT 'draft', -- 生成状态draft-草稿/reviewing-审核中/approved-已批准/rejected-已拒绝/submitted-已提交 generation_status VARCHAR(30) DEFAULT 'draft', -- 生成状态draft-草稿/reviewing-审核中/approved-已批准/rejected-已拒绝/submitted-已提交
file_id VARCHAR(50), -- 生成的文件ID file_id VARCHAR(50), -- 生成的文件ID
file_path VARCHAR(500), -- 文件路径 file_path VARCHAR(500), -- 文件路径
version VARCHAR(20) DEFAULT '1.0', -- 版本号 version INTEGER DEFAULT 1, -- 版本号
parent_version_id VARCHAR(50), -- 父版本ID parent_version_id VARCHAR(50), -- 父版本ID
review_comments TEXT, -- 审核意见 review_comments TEXT, -- 审核意见
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径

View File

@@ -16,7 +16,7 @@ CREATE TABLE file.tb_sys_file (
-- TbSysFileDTO 特有字段 -- TbSysFileDTO 特有字段
file_id VARCHAR(50) NOT NULL, -- 文件ID (主键) file_id VARCHAR(50) NOT NULL, -- 文件ID (主键)
file_root_id VARCHAR(50) DEFAULT NULL, -- 文件根ID file_root_id VARCHAR(50) DEFAULT NULL, -- 文件根ID
version VARCHAR(50) DEFAULT NULL, -- 文件版本 version INTEGER DEFAULT 1, -- 文件版本
name VARCHAR(255) NOT NULL, -- 文件名 name VARCHAR(255) NOT NULL, -- 文件名
path VARCHAR(500) NOT NULL, -- 文件路径 path VARCHAR(500) NOT NULL, -- 文件路径
size BIGINT NOT NULL, -- 文件大小(字节) size BIGINT NOT NULL, -- 文件大小(字节)

View File

@@ -1,15 +1,16 @@
CREATE SCHEMA IF NOT EXISTS workcase; CREATE SCHEMA IF NOT EXISTS workcase;
-- 系统外部人员(来客)管理 用于给系统外人员创建id -- 系统外部人员(来客)管理 用于给系统外人员创建id
DROP TABLE IF EXISTS workcase.tb_receptionist CASCADE; DROP TABLE IF EXISTS sys.tb_guest CASCADE;
CREATE TABLE workcase.tb_receptionist( CREATE TABLE sys.tb_guest(
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
user_id VARCHAR(50) NOT NULL, -- 来客ID user_id VARCHAR(50) NOT NULL, -- 来客ID
name VARCHAR(50) NOT NULL, -- 姓名 name VARCHAR(50) NOT NULL, -- 姓名
phone VARCHAR(50) NOT NULL, -- 电话 phone VARCHAR(50) DEFAULT NULL, -- 电话
email VARCHAR(50) NOT NULL, -- 邮箱 email VARCHAR(50) DEFAULT NULL, -- 邮箱
wechat_id VARCHAR(50) NOT NULL, -- 微信号 wechat_id VARCHAR(50) DEFAULT NULL, -- 微信号
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
update_time TIMESTAMPTZ DEFAULT NULL, -- 创建时间
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (user_id), PRIMARY KEY (user_id),

View File

@@ -38,158 +38,107 @@ INSERT INTO sys.tb_sys_role (
-- 3. 初始化系统模块 -- 3. 初始化系统模块
-- ============================= -- =============================
INSERT INTO sys.tb_sys_module ( INSERT INTO sys.tb_sys_module (
optsn, module_id, name, description, optsn, module_id, name, description, creator, dept_path, create_time, deleted
creator, dept_path, create_time, deleted
) VALUES ) VALUES
('MODULE-0001', 'module_system', '系统管理', '用户、角色、权限、部门管理', ('MODULE-0001', 'module_system', '系统管理', '用户、角色、权限、部门管理', 'system', NULL, now(), false),
'system', NULL, now(), false), ('MODULE-0002', 'module_file', '文件管理', '文件上传、下载、关联管理', 'system', NULL, now(), false),
('MODULE-0003', 'module_message', '消息通知', '消息发送、接收、模板管理', 'system', NULL, now(), false),
('MODULE-0002', 'module_file', '文件管理', '文件上传、下载、关联管理', ('MODULE-0004', 'module_config', '配置管理', '系统配置参数管理', 'system', NULL, now(), false),
'system', NULL, now(), false), ('MODULE-0008', 'module_agent', '智能体', '智能体管理', 'system', NULL, now(), false),
('MODULE-0005', 'module_knowledge', '知识库', '知识文档管理', 'system', NULL, now(), false),
('MODULE-0003', 'module_message', '消息通知', '消息发送、接收、模板管理', ('MODULE-0006', 'module_bidding', '招投标', '招投标业务管理', 'system', NULL, now(), false),
'system', NULL, now(), false), ('MODULE-0007', 'module_workcase', '智能客服', '客服工单管理', 'system', NULL, now(), false);
('MODULE-0004', 'module_config', '配置管理', '系统配置参数管理',
'system', NULL, now(), false),
('MODULE-0005', 'module_knowledge', '知识库', '知识文档管理',
'system', NULL, now(), false),
('MODULE-0006', 'module_bidding', '招投标', '招投标业务管理',
'system', NULL, now(), false),
('MODULE-0007', 'module_workcase', '智能客服', '客服工单管理',
'system', NULL, now(), false);
-- ============================= -- =============================
-- 4. 初始化系统权限 -- 4. 初始化系统权限
-- ============================= -- =============================
INSERT INTO sys.tb_sys_permission ( INSERT INTO sys.tb_sys_permission (
optsn, permission_id, name, code, description, module_id, optsn, permission_id, name, code, description, module_id, status, creator, dept_path, create_time, deleted
status, creator, dept_path, create_time, deleted
) VALUES ) VALUES
-- 系统管理模块权限 -- 系统管理模块权限
('PERM-0001', 'perm_user_view', '用户查看', 'system:user:view', '查看用户列表和详情', 'module_system', ('PERM-0001', 'perm_user_view', '用户查看', 'system:user:view', '查看用户列表和详情', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0002', 'perm_user_create', '用户创建', 'system:user:create', '创建新用户', 'module_system', true, 'system', NULL, now(), false),
('PERM-0002', 'perm_user_create', '用户创建', 'system:user:create', '创建新用户', 'module_system', ('PERM-0003', 'perm_user_edit', '用户编辑', 'system:user:edit', '编辑用户信息', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0004', 'perm_user_delete', '用户删除', 'system:user:delete', '删除用户', 'module_system', true, 'system', NULL, now(), false),
('PERM-0003', 'perm_user_edit', '用户编辑', 'system:user:edit', '编辑用户信息', 'module_system', ('PERM-0011', 'perm_role_view', '角色查看', 'system:role:view', '查看角色列表和详情', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0012', 'perm_role_create', '角色创建', 'system:role:create', '创建新角色', 'module_system', true, 'system', NULL, now(), false),
('PERM-0004', 'perm_user_delete', '用户删除', 'system:user:delete', '删除用户', 'module_system', ('PERM-0013', 'perm_role_edit', '角色编辑', 'system:role:edit', '编辑角色信息', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0014', 'perm_role_delete', '角色删除', 'system:role:delete', '删除角色', 'module_system', true, 'system', NULL, now(), false),
('PERM-0021', 'perm_dept_view', '部门查看', 'system:dept:view', '查看部门列表和详情', 'module_system', true, 'system', NULL, now(), false),
('PERM-0011', 'perm_role_view', '角色查看', 'system:role:view', '查看角色列表和详情', 'module_system', ('PERM-0022', 'perm_dept_create', '部门创建', 'system:dept:create', '创建新部门', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0023', 'perm_dept_edit', '部门编辑', 'system:dept:edit', '编辑部门信息', 'module_system', true, 'system', NULL, now(), false),
('PERM-0012', 'perm_role_create', '角色创建', 'system:role:create', '创建新角色', 'module_system', ('PERM-0024', 'perm_dept_delete', '部门删除', 'system:dept:delete', '删除部门', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0031', 'perm_permission_view', '权限查看', 'system:permission:view', '查看权限列表', 'module_system', true, 'system', NULL, now(), false),
('PERM-0013', 'perm_role_edit', '角色编辑', 'system:role:edit', '编辑角色信息', 'module_system', ('PERM-0032', 'perm_permission_manage', '权限管理', 'system:permission:manage', '管理权限配置', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false),
('PERM-0014', 'perm_role_delete', '角色删除', 'system:role:delete', '删除角色', 'module_system',
true, 'system', NULL, now(), false),
('PERM-0021', 'perm_dept_view', '部门查看', 'system:dept:view', '查看部门列表和详情', 'module_system',
true, 'system', NULL, now(), false),
('PERM-0022', 'perm_dept_create', '部门创建', 'system:dept:create', '创建新部门', 'module_system',
true, 'system', NULL, now(), false),
('PERM-0023', 'perm_dept_edit', '部门编辑', 'system:dept:edit', '编辑部门信息', 'module_system',
true, 'system', NULL, now(), false),
('PERM-0024', 'perm_dept_delete', '部门删除', 'system:dept:delete', '删除部门', 'module_system',
true, 'system', NULL, now(), false),
('PERM-0031', 'perm_permission_view', '权限查看', 'system:permission:view', '查看权限列表', 'module_system',
true, 'system', NULL, now(), false),
('PERM-0032', 'perm_permission_manage', '权限管理', 'system:permission:manage', '管理权限配置', 'module_system',
true, 'system', NULL, now(), false),
-- 系统管理模块导出权限 -- 系统管理模块导出权限
('PERM-0041', 'perm_user_export', '用户导出', 'system:user:export', '导出用户数据', 'module_system', ('PERM-0041', 'perm_user_export', '用户导出', 'system:user:export', '导出用户数据', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0042', 'perm_role_export', '角色导出', 'system:role:export', '导出角色数据', 'module_system', true, 'system', NULL, now(), false),
('PERM-0042', 'perm_role_export', '角色导出', 'system:role:export', '导出角色数据', 'module_system', ('PERM-0043', 'perm_dept_export', '部门导出', 'system:dept:export', '导出部门数据', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false),
('PERM-0043', 'perm_dept_export', '部门导出', 'system:dept:export', '导出部门数据', 'module_system',
true, 'system', NULL, now(), false),
-- 文件管理模块权限 -- 文件管理模块权限
('PERM-0101', 'perm_file_view', '文件查看', 'file:file:view', '查看文件列表', 'module_file', ('PERM-0101', 'perm_file_view', '文件查看', 'file:file:view', '查看文件列表', 'module_file', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0102', 'perm_file_upload', '文件上传', 'file:file:upload', '上传文件', 'module_file', true, 'system', NULL, now(), false),
('PERM-0102', 'perm_file_upload', '文件上传', 'file:file:upload', '上传文件', 'module_file', ('PERM-0103', 'perm_file_download', '文件下载', 'file:file:download', '下载文件', 'module_file', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0104', 'perm_file_delete', '文件删除', 'file:file:delete', '删除文件', 'module_file', true, 'system', NULL, now(), false),
('PERM-0103', 'perm_file_download', '文件下载', 'file:file:download', '下载文件', 'module_file', ('PERM-0105', 'perm_file_export', '文件导出', 'file:file:export', '导出文件列表数据', 'module_file', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), -- 智能体权限
('PERM-0104', 'perm_file_delete', '文件删除', 'file:file:delete', '删除文件', 'module_file', ('PERM-0120', 'perm_ai_create', '智能体创建', 'ai:agent:create', '创建智能体', 'module_agent', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0121', 'perm_ai_update', '智能体更新', 'ai:agent:update', '更新智能体', 'module_agent', true, 'system', NULL, now(), false),
('PERM-0105', 'perm_file_export', '文件导出', 'file:file:export', '导出文件列表数据', 'module_file', ('PERM-0122', 'perm_ai_delete', '智能体删除', 'ai:agent:delete', '删除智能体', 'module_agent', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0123', 'perm_ai_view', '智能体查询', 'ai:agent:view', '查询智能体', 'module_agent', true, 'system', NULL, now(), false),
-- 智能体对话权限 没有,因为所有人都可以
-- Dify代理功能权限知识库分段管理
('PERM-0130', 'perm_dify_segment_view', '分段查看', 'ai:dify:segment:view', '查看文档分段列表', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0131', 'perm_dify_segment_create', '分段创建', 'ai:dify:segment:create', '创建文档分段', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0132', 'perm_dify_segment_update', '分段更新', 'ai:dify:segment:update', '更新文档分段', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0133', 'perm_dify_segment_delete', '分段删除', 'ai:dify:segment:delete', '删除文档分段', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0134', 'perm_dify_document_status', '文档状态管理', 'ai:dify:document:status', '更新文档状态(启用/禁用/归档)', 'module_knowledge', true, 'system', NULL, now(), false),
-- 知识库管理权限
('PERM-0140', 'perm_knowledge_create', '知识库创建', 'ai:knowledge:create', '创建知识库', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0141', 'perm_knowledge_update', '知识库更新', 'ai:knowledge:update', '更新知识库', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0142', 'perm_knowledge_delete', '知识库删除', 'ai:knowledge:delete', '删除知识库', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0143', 'perm_knowledge_view', '知识库查看', 'ai:knowledge:view', '查看知识库列表和详情', 'module_knowledge', true, 'system', NULL, now(), false),
-- 知识库文件管理权限
('PERM-0150', 'perm_knowledge_file_upload', '知识库文件上传', 'ai:knowledge:file:upload', '上传文件到知识库', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0151', 'perm_knowledge_file_update', '知识库文件更新', 'ai:knowledge:file:update', '更新知识库文件信息', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0152', 'perm_knowledge_file_delete', '知识库文件删除', 'ai:knowledge:file:delete', '删除知识库文件', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0153', 'perm_knowledge_file_view', '知识库文件查看', 'ai:knowledge:file:view', '查看知识库文件历史', 'module_knowledge', true, 'system', NULL, now(), false),
-- 消息通知模块权限 -- 消息通知模块权限
('PERM-0201', 'perm_message_view', '消息查看', 'message:message:view', '查看消息列表', 'module_message', ('PERM-0201', 'perm_message_view', '消息查看', 'message:message:view', '查看消息列表', 'module_message', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0202', 'perm_message_send', '消息发送', 'message:message:send', '发送消息通知', 'module_message', true, 'system', NULL, now(), false),
('PERM-0202', 'perm_message_send', '消息发送', 'message:message:send', '发送消息通知', 'module_message', ('PERM-0203', 'perm_message_manage', '消息管理', 'message:message:manage', '管理消息模板和配置', 'module_message', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0204', 'perm_message_export', '消息导出', 'message:message:export', '导出消息数据', 'module_message', true, 'system', NULL, now(), false),
('PERM-0203', 'perm_message_manage', '消息管理', 'message:message:manage', '管理消息模板和配置', 'module_message',
true, 'system', NULL, now(), false),
('PERM-0204', 'perm_message_export', '消息导出', 'message:message:export', '导出消息数据', 'module_message',
true, 'system', NULL, now(), false),
-- 配置管理模块权限 -- 配置管理模块权限
('PERM-0301', 'perm_config_view', '配置查看', 'config:config:view', '查看系统配置', 'module_config', ('PERM-0301', 'perm_config_view', '配置查看', 'config:config:view', '查看系统配置', 'module_config', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0302', 'perm_config_edit', '配置编辑', 'config:config:edit', '修改系统配置', 'module_config', true, 'system', NULL, now(), false),
('PERM-0302', 'perm_config_edit', '配置编辑', 'config:config:edit', '修改系统配置', 'module_config', ('PERM-0303', 'perm_config_export', '配置导出', 'config:config:export', '导出系统配置数据', 'module_config', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false),
('PERM-0303', 'perm_config_export', '配置导出', 'config:config:export', '导出系统配置数据', 'module_config',
true, 'system', NULL, now(), false),
-- 日志模块权限 -- 日志模块权限
('PERM-0401', 'perm_log_view', '日志查看', 'log:log:view', '查看系统日志', 'module_system', ('PERM-0401', 'perm_log_view', '日志查看', 'log:log:view', '查看系统日志', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0402', 'perm_log_export', '日志导出', 'log:log:export', '导出系统日志数据', 'module_system', true, 'system', NULL, now(), false),
('PERM-0402', 'perm_log_export', '日志导出', 'log:log:export', '导出系统日志数据', 'module_system',
true, 'system', NULL, now(), false),
-- 平台基础菜单访问权限(所有登录用户都有) -- 平台基础菜单访问权限(所有登录用户都有)
('PERM-0501', 'perm_platform_home', '工作台访问', 'platform:home:view', '访问平台工作台', 'module_system', ('PERM-0501', 'perm_platform_home', '工作台访问', 'platform:home:view', '访问平台工作台', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0502', 'perm_platform_chat', 'AI助手访问', 'platform:chat:view', '访问AI助手', 'module_system', true, 'system', NULL, now(), false),
('PERM-0502', 'perm_platform_chat', 'AI助手访问', 'platform:chat:view', '访问AI助手', 'module_system', ('PERM-0503', 'perm_platform_bidding', '招标助手访问', 'platform:bidding:view', '访问招标助手iframe', 'module_bidding', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0504', 'perm_platform_workcase', '泰豪小电访问', 'platform:workcase:view', '访问泰豪小电客服iframe', 'module_workcase', true, 'system', NULL, now(), false),
('PERM-0503', 'perm_platform_bidding', '招标助手访问', 'platform:bidding:view', '访问招标助手iframe', 'module_bidding', ('PERM-0505', 'perm_platform_workflow', '智能体编排访问', 'platform:workflow:view', '访问智能体编排iframe', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false),
('PERM-0504', 'perm_platform_workcase', '泰豪小电访问', 'platform:workcase:view', '访问泰豪小电客服iframe', 'module_workcase',
true, 'system', NULL, now(), false),
('PERM-0505', 'perm_platform_workflow', '智能体编排访问', 'platform:workflow:view', '访问智能体编排iframe', 'module_system',
true, 'system', NULL, now(), false),
-- Platform 管理后台功能权限 -- Platform 管理后台功能权限
('PERM-0601', 'perm_platform_admin', '平台管理后台', 'platform:admin:view', '访问平台管理后台', 'module_system', ('PERM-0601', 'perm_platform_admin', '平台管理后台', 'platform:admin:view', '访问平台管理后台', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0602', 'perm_platform_admin_overview', '平台数据概览', 'platform:admin:overview', '访问平台数据概览', 'module_system', true, 'system', NULL, now(), false),
('PERM-0602', 'perm_platform_admin_overview', '平台数据概览', 'platform:admin:overview', '访问平台数据概览', 'module_system', ('PERM-0603', 'perm_platform_admin_user', '平台用户管理', 'platform:admin:user', '访问平台用户管理', 'module_system', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0604', 'perm_platform_admin_knowledge', '平台知识库', 'platform:admin:knowledge', '访问平台知识库', 'module_knowledge', true, 'system', NULL, now(), false),
('PERM-0603', 'perm_platform_admin_user', '平台用户管理', 'platform:admin:user', '访问平台用户管理', 'module_system', ('PERM-0605', 'perm_platform_admin_config', '平台系统配置', 'platform:admin:config', '访问平台系统配置', 'module_config', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false),
('PERM-0604', 'perm_platform_admin_knowledge', '平台知识库', 'platform:admin:knowledge', '访问平台知识库', 'module_knowledge',
true, 'system', NULL, now(), false),
('PERM-0605', 'perm_platform_admin_config', '平台系统配置', 'platform:admin:config', '访问平台系统配置', 'module_config',
true, 'system', NULL, now(), false),
-- Bidding 管理后台功能权限 -- Bidding 管理后台功能权限
('PERM-0611', 'perm_bidding_admin', '招标管理后台', 'bidding:admin:view', '访问招标管理后台', 'module_bidding', ('PERM-0611', 'perm_bidding_admin', '招标管理后台', 'bidding:admin:view', '访问招标管理后台', 'module_bidding', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false),
-- Workcase 管理后台功能权限 -- Workcase 管理后台功能权限
('PERM-0621', 'perm_workcase_admin', '客服管理后台', 'workcase:admin:view', '访问客服管理后台', 'module_workcase', ('PERM-0621', 'perm_workcase_admin', '客服管理后台', 'workcase:admin:view', '访问客服管理后台', 'module_workcase', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0622', 'perm_workcase_overview', '数据概览', 'workcase:overview:view', '访问泰豪小电数据概览', 'module_workcase', true, 'system', NULL, now(), false),
('PERM-0622', 'perm_workcase_overview', '数据概览', 'workcase:overview:view', '访问泰豪小电数据概览', 'module_workcase', ('PERM-0623', 'perm_workcase_knowledge', '知识库管理', 'workcase:knowledge:view', '访问知识库管理', 'module_workcase', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0624', 'perm_workcase_tickets', '工单管理', 'workcase:tickets:view', '访问工单管理', 'module_workcase', true, 'system', NULL, now(), false),
('PERM-0623', 'perm_workcase_knowledge', '知识库管理', 'workcase:knowledge:view', '访问知识库管理', 'module_workcase', ('PERM-0625', 'perm_workcase_conversation', '对话数据', 'workcase:conversation:view', '访问对话数据管理', 'module_workcase', true, 'system', NULL, now(), false),
true, 'system', NULL, now(), false), ('PERM-0626', 'perm_workcase_agent', '智能体管理', 'workcase:agent:view', '访问智能体管理', 'module_workcase', true, 'system', NULL, now(), false),
('PERM-0624', 'perm_workcase_tickets', '工单管理', 'workcase:tickets:view', '访问工单管理', 'module_workcase', ('PERM-0627', 'perm_workcase_log', '日志管理', 'workcase:log:view', '访问日志管理', 'module_workcase', true, 'system', NULL, now(), false);
true, 'system', NULL, now(), false),
('PERM-0625', 'perm_workcase_conversation', '对话数据', 'workcase:conversation:view', '访问对话数据管理', 'module_workcase',
true, 'system', NULL, now(), false),
('PERM-0626', 'perm_workcase_agent', '智能体管理', 'workcase:agent:view', '访问智能体管理', 'module_workcase',
true, 'system', NULL, now(), false),
('PERM-0627', 'perm_workcase_log', '日志管理', 'workcase:log:view', '访问日志管理', 'module_workcase',
true, 'system', NULL, now(), false);
-- ============================= -- =============================
-- 5. 初始化视图(菜单) -- 5. 初始化视图(菜单)
-- ============================= -- =============================

View File

@@ -24,4 +24,5 @@ hs_err_pid*
.data .data
.idea .idea
*/target */target
*/*/target */*/target
# example/*

View File

@@ -3,4 +3,5 @@ trigger: manual
--- ---
1. 有BaseDTO基类,DTO\VO在api模块下面 1. 有BaseDTO基类,DTO\VO在api模块下面
2. 用Dubbo注册和引用服务 2. 用Dubbo注册和引用服务
3. 当个端Insert语句对应sql表的必填项才生成有默认值也是if生成其他应该if条件生成

View File

@@ -0,0 +1,51 @@
# 智能体创建
说明: 本服务只对智能体进行对话的转发不能对dify智能体进行修改
1. 智能体创建用户上传tb_agent需要的字段进行智能体创建
# 智能体更新
1. 更新数据库和redis缓存
2. 注意加锁,避免并发时,其他线程用了错误数据
# 智能体删除
软删除本服务的智能体数据
# 智能体对话
1. 校验智能体是否可以用
2. 用户会先对1个智能体创建一个会话
3. 用户先发起对话预处理请求传入会话携带的对象数据到redis中产生一个临时的消息id
4. 真正发起对话数据请求时会传入3个必须参数1智能体id2会话id3本次消息id
5. 优先从redis缓存中获取agentid对应的agent配置没有找数据库。这里要进行双检加锁避免智能体修改、重复加载等问题
6. 从redis中获取消息id真正的数据包含各种数据对象和fileid等等内容
7. 和dify构建sse流式对话并回应前端的sse流式对话。 不要乱生成无用的data事件直接返回dify的事件和数据
8. 前端会自动处理dify的事件
# 对话评价
1. 用户对对话进行评价评价会存储到tb_chat_message的comment字段中
2. 调用dify代理服务更新dify智能体本消息的评价
# 知识库创建
1. 用户上传tb_knowledge需要的字段进行知识库创建
# 知识库文件上传
1. 用户上传文件到本服务存储到文件表和minio中并生成version版本
2. 上传文件到dify知识库dify返回上传成功后插入tb_knowledge_file表并更新version版本否则提示用户
# 知识库更新文件
1. 用户上传文件到本服务并生成version版本存储到文件表和minio中
2. 上传文件到dify知识库dify返回上传成功后插入tb_knowledge_file表并更新version版本否则提示用户
3. 删除dify旧的version的文件document来控制版本
4. 本服务中旧的文件对象不会删除本条新的file对象会有file_root_id指向原始第一个file对象的file_id用来进行版本展示但dify知识库实际只有1个最新的文件
# 知识库删除文件
1. 软删除本服务服务中tb_knowledge_file的该文件所有版本
2. 真删除dify本文件
# 知识库创建、更新、删除文件分段
1. 直接调用dify代理服务实现不存储到本服务也不生成新的version版本
# 知识库删除
1. 软删除本服务服务中tb_knowledge的该知识库
2. 真删除dify知识库

View File

@@ -28,7 +28,51 @@
<artifactId>api-system</artifactId> <artifactId>api-system</artifactId>
<version>${urban-lifeline.version}</version> <version>${urban-lifeline.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-file</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-utils</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -13,7 +13,6 @@ import org.xyzh.ai.client.dto.*;
import org.xyzh.ai.client.callback.StreamCallback; import org.xyzh.ai.client.callback.StreamCallback;
import org.xyzh.ai.config.DifyConfig; import org.xyzh.ai.config.DifyConfig;
import org.xyzh.ai.exception.DifyException; import org.xyzh.ai.exception.DifyException;
import org.xyzh.api.ai.dto.DifyFileInfo;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -483,11 +482,11 @@ public class DifyApiClient {
} }
if (!data.isEmpty()) { if (!data.isEmpty()) {
// 使用Fastjson2解析SSE数据 // 解析SSE数据
JSONObject jsonNode = JSON.parseObject(data); JSONObject jsonNode = JSON.parseObject(data);
String event = jsonNode.containsKey("event") ? jsonNode.getString("event") : ""; String event = jsonNode.containsKey("event") ? jsonNode.getString("event") : "";
// 转发所有事件回调(包含完整数据 // 转发事件回调(用于前端SSE转发
callback.onEvent(event, data); callback.onEvent(event, data);
switch (event) { switch (event) {

View File

@@ -0,0 +1,97 @@
package org.xyzh.ai.client.dto;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* Dify文件上传响应DTO
* 对应 Dify API /files/upload 的响应结构
*/
@Data
public class DifyFileInfo {
/**
* 文件IDDify返回
*/
private String id;
/**
* 文件名
*/
private String name;
/**
* 文件大小(字节)
*/
private Integer size;
/**
* 文件扩展名
*/
private String extension;
/**
* 文件MIME类型
*/
@JSONField(name="mime_type")
private String mimeType;
/**
* 上传人ID
*/
@JSONField(name="created_by")
private String createdBy;
/**
* 上传时间(时间戳)
*/
@JSONField(name="created_at")
private Long createdAt;
/**
* 预览URL
*/
@JSONField(name="preview_url")
private String previewUrl;
/**
* 源文件URL
*/
@JSONField(name="source_url")
private String sourceUrl;
/**
* 文件类型image、document、audio、video、file
*/
private String type;
/**
* 传输方式remote_url、local_file
*/
@JSONField(name="transfer_method")
private String transferMethod;
/**
* 文件URL或ID
*/
private String url;
/**
* 本地文件上传ID
*/
@JSONField(name="upload_file_id")
private String uploadFileId;
/**
* 系统文件ID
*/
@JSONField(name="sys_file_id")
private String sysFileId;
/**
* 文件路径(从系统文件表获取)
*/
@JSONField(name="file_path")
private String filePath;
}

View File

@@ -0,0 +1,147 @@
package org.xyzh.ai.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.xyzh.ai.service.impl.AgentServiceImpl;
import org.xyzh.api.ai.dto.TbAgent;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.utils.validation.ValidationResult;
import org.xyzh.common.utils.validation.ValidationParam;
import org.xyzh.common.utils.validation.ValidationUtils;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
/**
* @description 智能体控制器
* @filename AgentController.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Slf4j
@Validated
@RestController
@RequestMapping("/ai/agent")
public class AgentController {
@Autowired
private AgentServiceImpl agentService;
/**
* @description 创建智能体
* @param agent
* @author yslg
* @since 2025-12-17
*/
@PostMapping
@PreAuthorize("@ss.hasPermi('ai:agent:create')")
public ResultDomain<TbAgent> createAgent(@RequestBody TbAgent agent) {
log.info("创建智能体: name={}", agent.getName());
// 参数校验
ValidationResult result = ValidationUtils.validate(agent, Arrays.asList(
ValidationUtils.requiredString("name", "智能体名称", 1, 100),
ValidationUtils.requiredString("apiKey", "API密钥", 1, 100),
ValidationUtils.requiredString("link", "智能体url",10,500)
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
return agentService.addAgent(agent);
}
/**
* @description 更新智能体
* @param agent
* @author yslg
* @since 2025-12-17
*/
@PutMapping
@PreAuthorize("@ss.hasPermi('ai:agent:update')")
public ResultDomain<TbAgent> updateAgent(@RequestBody TbAgent agent) {
log.info("更新智能体: agentId={}", agent.getAgentId());
// 参数校验
ValidationResult result = ValidationUtils.validate(agent, Arrays.asList(
ValidationUtils.requiredString("agentId", "智能体ID"),
ValidationUtils.requiredString("name", "智能体名称", 1, 100),
ValidationUtils.requiredString("apiKey", "API密钥", 1, 100),
ValidationUtils.requiredString("link", "智能体url",10,500)
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
return agentService.updateAgent(agent);
}
/**
* @description 删除智能体
* @param agentId
* @author yslg
* @since 2025-12-17
*/
@DeleteMapping("/{agentId}")
@PreAuthorize("@ss.hasPermi('ai:agent:delete')")
public ResultDomain<TbAgent> deleteAgent(@PathVariable("agentId") @NotNull String agentId) {
log.info("删除智能体: agentId={}", agentId);
TbAgent agent = new TbAgent();
agent.setAgentId(agentId);
return agentService.deleteAgent(agent);
}
/**
* @description 获取智能体详情
* @param agentId
* @author yslg
* @since 2025-12-17
*/
@GetMapping("/{agentId}")
@PreAuthorize("@ss.hasPermi('ai:agent:view')")
public ResultDomain<TbAgent> getAgent(@PathVariable("agentId") @NotNull String agentId) {
log.info("获取智能体: agentId={}", agentId);
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
if (agentResult.getSuccess() && agentResult.getData() != null) {
return ResultDomain.success("查询成功", agentResult.getData());
}
return ResultDomain.failure("智能体不存在"+agentResult.getMessage());
}
/**
* @description 分页查询智能体
* @param pageRequest
* @author yslg
* @since 2025-12-17
*/
@PostMapping("/page")
@PreAuthorize("@ss.hasPermi('ai:agent:view')")
public ResultDomain<TbAgent> getAgentPage(@RequestBody PageRequest<TbAgent> pageRequest) {
log.info("分页查询智能体");
// 参数校验(支持嵌套属性路径)
ValidationResult result = ValidationUtils.validate(pageRequest, Arrays.asList(
ValidationParam.builder().fieldName("pageParam").fieldLabel("分页参数").required().build(),
ValidationUtils.requiredNumber("pageParam.page", "页码", 1, null),
ValidationUtils.requiredNumber("pageParam.pageSize", "每页条数", 1, 100)
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
return agentService.getAgentPage(pageRequest);
}
/**
* @description 获取智能体列表
* @param tbAgent
* @author yslg
* @since 2025-12-17
*/
@GetMapping("/list")
@PreAuthorize("@ss.hasPermi('ai:agent:view')")
public ResultDomain<TbAgent> getAgentList(TbAgent tbAgent) {
log.info("获取智能体列表");
return agentService.getAgentList(tbAgent);
}
}

View File

@@ -0,0 +1,305 @@
package org.xyzh.ai.controller;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.xyzh.api.ai.dto.ChatPrepareData;
import org.xyzh.api.ai.dto.TbChat;
import org.xyzh.api.ai.dto.TbChatMessage;
import org.xyzh.api.ai.service.AIFileUploadService;
import org.xyzh.api.ai.service.AgentChatService;
import org.xyzh.common.auth.utils.LoginUtil;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.utils.NonUtils;
import org.xyzh.common.utils.validation.ValidationParam;
import org.xyzh.common.utils.validation.ValidationResult;
import org.xyzh.common.utils.validation.ValidationUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
/**
* @description 智能体对话控制器 所有接口开放
* @filename ChatController.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Slf4j
@RestController
@Validated
@RequestMapping("/ai/chat")
public class ChatController {
@Autowired
private AgentChatService chatService;
@Autowired
private AIFileUploadService fileUploadService;
// ====================== 会话管理 ======================
/**
* @description 创建会话
* @param
* @author yslg
* @since 2025-12-17
*/
@PostMapping("/conversation")
public ResultDomain<TbChat> createChat(@RequestBody TbChat chat, @RequestHeader("Authorization") String token) {
ValidationResult result = ValidationUtils.validate(chat, Arrays.asList(
ValidationUtils.requiredString("agentId", "智能体id", 1, 100),
ValidationUtils.requiredString("title", "对话标题", 1, 100),
ValidationUtils.requiredString("userId", "用户id", 1, 100)
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
// 默认来客如果token不为空且token对应的用户存在说明是员工
chat.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
chat.setUserType(true);
}
}
log.info("创建会话: agentId={}, title={}, userId={}, userType={}", chat.getAgentId(), chat.getTitle(), chat.getUserId());
return chatService.createChat(chat);
}
/**
* @description 更新对话
* @param
* @author yslg
* @since 2025-12-17
*/
@PutMapping("/conversation")
public ResultDomain<TbChat> updateChat(@RequestBody TbChat chat, @RequestHeader("Authorization") String token) {
ValidationResult result = ValidationUtils.validate(chat, Arrays.asList(
ValidationUtils.requiredString("agentId", "智能体id", 1, 100),
ValidationUtils.requiredString("title", "对话标题", 1, 100),
ValidationUtils.requiredString("userId", "用户id", 1, 100),
ValidationParam.builder().fieldName("userType").fieldLabel("用户类型").required().fieldType(Boolean.class).build()
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
chat.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
chat.setUserType(true);
}
}
log.info("更新会话: chatId={}", chat.getChatId());
return chatService.updateChat(chat);
}
/**
* @description 删除对话
* @param
* @author yslg
* @since 2025-12-17
*/
@DeleteMapping("/conversation")
public ResultDomain<TbChat> deleteChat(@RequestBody TbChat chat, @RequestHeader("Authorization") String token) {
chat.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
chat.setUserType(true);
}
}
log.info("删除会话: chatId={}", chat.getChatId());
return chatService.deleteChat(chat);
}
/**
* @description 获取对话列表
* @param
* @author yslg
* @since 2025-12-17
*/
@GetMapping("/conversations")
public ResultDomain<TbChat> getChatList(@RequestBody TbChat filter, @RequestHeader("Authorization") String token) {
log.info("获取会话列表: agentId={}", filter.getAgentId());
filter.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
filter.setUserType(true);
}
}
return chatService.getChatList(filter);
}
// ====================== 消息管理 ======================
/**
* @description 获取对话消息列表
* @param
* @author yslg
* @since 2025-12-17
*/
@PostMapping("/messages")
public ResultDomain<TbChatMessage> getMessageList(@RequestBody TbChat filter, @RequestHeader("Authorization") String token) {
ValidationResult result = ValidationUtils.validate(filter, Arrays.asList(
ValidationUtils.requiredString("agentId", "智能体id",10,50),
ValidationUtils.requiredString("chatId", "对话Id", 10, 50),
ValidationUtils.requiredString("userId", "用户Id")
));
filter.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
filter.setUserType(true);
}
}
log.info("获取消息列表: chatId={}", filter.getChatId());
return chatService.getChatMessageList(filter);
}
// ====================== 流式对话 ======================
/**
* @description 准备流式对话会话数据
* @param chatPrepareData
* @author yslg
* @since 2025-12-17
*/
@PostMapping("/stream/prepare")
public ResultDomain<String> prepareStreamChat(@RequestBody ChatPrepareData chatPrepareData, @RequestHeader("Authorization") String token) {
ValidationResult result = ValidationUtils.validate(chatPrepareData, Arrays.asList(
ValidationUtils.requiredString("agentId", "智能体Id", 1, 50),
ValidationUtils.requiredString("chatId", "会话Id", 1, 50),
ValidationUtils.requiredString("query", "用户问题"),
ValidationUtils.requiredString("userId", "用户Id", 1, 100)
));
if(!result.isValid()){
return ResultDomain.failure(result.getAllErrors());
}
// 设置用户类型
chatPrepareData.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
chatPrepareData.setUserType(true);
}
}
log.info("准备流式对话: agentId={}, chatId={}, query={}", chatPrepareData.getAgentId(), chatPrepareData.getChatId(), chatPrepareData.getQuery());
return chatService.prepareChatMessageSession(chatPrepareData);
}
/**
* @description 进行流式对话
* @param
* @author yslg
* @since 2025-12-17
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamChat(@RequestParam("sessionId") String sessionId) {
if(NonUtils.isEmpty(sessionId)){
SseEmitter emitter = new SseEmitter(300000L);
try {
emitter.send(SseEmitter.event().name("error").data("{\"message\":\"会话不存在\"}"));
} catch (IOException e) {
log.error("发送错误事件失败", e);
}finally {
emitter.complete();
}
return emitter;
}
log.info("建立SSE连接: sessionId={}", sessionId);
return chatService.streamChatMessageWithSse(sessionId);
}
/**
* @description 停止会话
* @param
* @author yslg
* @since 2025-12-17
*/
@PostMapping("/stop")
public ResultDomain<Boolean> stopChat(@RequestBody Map<String, String> params, @RequestHeader("Authorization") String token) {
ValidationResult result = ValidationUtils.validateMap(params, Arrays.asList(
ValidationUtils.requiredString("taskId", "任务ID"),
ValidationUtils.requiredString("agentId", "智能体ID"),
ValidationUtils.requiredString("userId", "用户ID")
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
TbChat filter = new TbChat();
filter.setAgentId(params.get("agentId"));
filter.setUserId(params.get("userId"));
filter.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
filter.setUserType(true);
}
}
log.info("停止对话: taskId={}", params.get("taskId"));
return chatService.stopChatMessageByTaskId(filter, params.get("taskId"));
}
/**
* @description 评价消息
* @param
* @author yslg
* @since 2025-12-17
*/
@PostMapping("/comment")
public ResultDomain<Boolean> commentMessage(@RequestBody Map<String, String> params, @RequestHeader("Authorization") String token) {
ValidationResult result = ValidationUtils.validateMap(params, Arrays.asList(
ValidationUtils.requiredString("agentId", "智能体ID"),
ValidationUtils.requiredString("chatId", "对话ID"),
ValidationUtils.requiredString("messageId", "消息ID"),
ValidationUtils.requiredString("comment", "评价"),
ValidationUtils.requiredString("userId", "用户ID")
));
if (!result.isValid()) {
return ResultDomain.failure(result.getAllErrors());
}
TbChat filter = new TbChat();
filter.setAgentId(params.get("agentId"));
filter.setChatId(params.get("chatId"));
filter.setUserId(params.get("userId"));
filter.setUserType(false);
if(NonUtils.isNotEmpty(token)){
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (NonUtils.isNotEmpty(loginDomain)) {
filter.setUserType(true);
}
}
log.info("评价消息: messageId={}, comment={}", params.get("messageId"), params.get("comment"));
return chatService.commentChatMessage(filter, params.get("messageId"), params.get("comment"));
}
// ====================== 文件上传 ======================
/**
* @description 上传文件用于对话(图文多模态)
* @param file 文件
* @param agentId 智能体ID
* @author yslg
* @since 2025-12-17
*/
@PostMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResultDomain<Map<String, Object>> uploadFileForChat(
@RequestPart("file") @NotNull MultipartFile file,
@RequestPart("agentId") @NotNull String agentId) {
log.info("上传对话文件: agentId={}, fileName={}", agentId, file.getOriginalFilename());
return fileUploadService.uploadFileForChat(file, agentId);
}
}

View File

@@ -0,0 +1,350 @@
package org.xyzh.ai.controller;
import com.alibaba.fastjson2.JSONObject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.xyzh.ai.service.impl.DifyProxyServiceImpl;
import org.xyzh.ai.service.impl.KnowledgeServiceImpl;
import org.xyzh.api.ai.dto.TbKnowledge;
import org.xyzh.api.ai.dto.TbKnowledgeFile;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.utils.validation.ValidationResult;
import org.xyzh.common.utils.validation.ValidationUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* @description 知识库控制器
* @filename KnowledgeController.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@RestController
@Validated
@RequestMapping("/ai/knowledge")
public class KnowledgeController {
private static final Logger logger = LoggerFactory.getLogger(KnowledgeController.class);
@Autowired
private KnowledgeServiceImpl knowledgeService;
@Autowired
private DifyProxyServiceImpl difyProxyService;
// ====================== 知识库管理 ======================
/**
* @description 创建知识库基础信息包含dify知识库各种参数的配置
* @param knowledge
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
@PostMapping
public ResultDomain<TbKnowledge> createKnowledge(@RequestBody TbKnowledge knowledge) {
ValidationResult result = ValidationUtils.validate(knowledge, Arrays.asList(
ValidationUtils.requiredString("title", "知识库标题", 1, 50),
ValidationUtils.inSet("difyIndexingTechnique", "Dify索引方式high_quality/economy" , true, new HashSet<>(Arrays.asList("high_quality", "economy"))),
ValidationUtils.inSet("category", "所属分类 workcase 内部知识库、外部知识库", true, new HashSet<>(Arrays.asList("default", "内部知识库", "外部知识库")))
));
if(!result.isValid()){
return ResultDomain.failure(result.getAllErrors());
}
logger.info("创建知识库: title={}", knowledge.getTitle());
return knowledgeService.createKnowledge(knowledge, "PUBLIC", null, null);
}
/**
* @description 更新知识库包含dify知识库各种参数的配置
* @param knowledge
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
@PutMapping
public ResultDomain<TbKnowledge> updateKnowledge(@RequestBody @Valid TbKnowledge knowledge) {
ValidationResult result = ValidationUtils.validate(knowledge, Arrays.asList(
ValidationUtils.requiredString("title", "知识库标题", 1, 50),
ValidationUtils.inSet("difyIndexingTechnique", "Dify索引方式high_quality/economy" , true, new HashSet<>(Arrays.asList("high_quality", "economy"))),
ValidationUtils.inSet("category", "所属分类 workcase 内部知识库、外部知识库", true, new HashSet<>(Arrays.asList("default", "内部知识库", "外部知识库")))
));
if(!result.isValid()){
return ResultDomain.failure(result.getAllErrors());
}
logger.info("更新知识库: knowledgeId={}", knowledge.getKnowledgeId());
return knowledgeService.updateKnowledge(knowledge);
}
/**
* @description 删除知识库同时删除dify知识库
* @param knowledgeId
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
@DeleteMapping("/{knowledgeId}")
public ResultDomain<Boolean> deleteKnowledge(@PathVariable("knowledgeId") @NotBlank String knowledgeId) {
logger.info("删除知识库: knowledgeId={}", knowledgeId);
return knowledgeService.deleteKnowledge(knowledgeId);
}
/**
* @description 获取知识库详情包含dify知识库各种参数的配置
* @param knowledgeId
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
@GetMapping("/{knowledgeId}")
public ResultDomain<TbKnowledge> getKnowledge(@PathVariable("knowledgeId") @NotBlank String knowledgeId) {
logger.info("获取知识库: knowledgeId={}", knowledgeId);
return knowledgeService.getKnowledgeById(knowledgeId);
}
/**
* @description 查询知识库列表
* @param filter
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
@PostMapping("/list")
public ResultDomain<TbKnowledge> listKnowledges(@RequestBody(required = false) TbKnowledge filter) {
logger.info("查询知识库列表");
return knowledgeService.listKnowledges(filter);
}
/**
* @description 分页查询知识库
* @param pageRequest
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
@PostMapping("/page")
public ResultDomain<TbKnowledge> pageKnowledges(@RequestBody @Valid PageRequest<TbKnowledge> pageRequest) {
logger.info("分页查询知识库");
return knowledgeService.pageKnowledges(pageRequest.getFilter(), pageRequest.getPageParam());
}
/**
* @description 获取知识库统计信息
* @param knowledgeId
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
@GetMapping("/{knowledgeId}/stats")
public ResultDomain<TbKnowledge> getKnowledgeStats(@PathVariable("knowledgeId") @NotBlank String knowledgeId) {
logger.info("获取知识库统计: knowledgeId={}", knowledgeId);
return knowledgeService.getKnowledgeStats(knowledgeId);
}
// ====================== 文件管理 ======================
/**
* @description 获取知识库文档列表
* @param knowledgeId 知识库id
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:view')")
@GetMapping("/{knowledgeId}/documents")
public ResultDomain<Map<String, Object>> getDocumentList(
@PathVariable("knowledgeId") @NotBlank String knowledgeId,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "limit", defaultValue = "20") Integer limit) {
logger.info("获取文档列表: knowledgeId={}", knowledgeId);
return knowledgeService.getDocumentList(knowledgeId, page, limit);
}
/**
* @description 上传文件到知识库同步到Dify
* @param file 上传文件
* @param knowledgeId 知识库id
* @param indexingTechnique 索引方式
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:upload')")
@PostMapping(name = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResultDomain<TbKnowledgeFile> uploadToKnowledge(
@RequestParam("file") @NotNull MultipartFile file,
@RequestParam("knowledgeId") @NotBlank String knowledgeId,
@RequestParam(value = "indexingTechnique", required = false) String indexingTechnique) {
logger.info("上传知识库文件: knowledgeId={}, fileName={}", knowledgeId, file.getOriginalFilename());
return knowledgeService.uploadKnowledgeFile(knowledgeId, file, indexingTechnique);
}
/**
* @description 批量上传文件到知识库
* @param files 文件数组
* @param knowledgeId 知识库id
* @param indexingTechnique 索引方式
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:upload')")
@PostMapping(name = "/file/batch-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResultDomain<TbKnowledgeFile> batchUploadToKnowledge(
@RequestParam("files") @NotEmpty List<MultipartFile> files,
@RequestParam("knowledgeId") @NotBlank String knowledgeId,
@RequestParam(value = "indexingTechnique", required = false) String indexingTechnique) {
logger.info("批量上传知识库文件: knowledgeId={}, fileCount={}", knowledgeId, files.size());
return knowledgeService.batchUploadKnowledgeFile(knowledgeId, files, indexingTechnique);
}
/**
* 上传新文件来更新知识库文件注意fileRootId一致生成新id
* @param file 上传文件
* @param knowledgeId 知识库id
* @param fileRootId 文件Rootid多version下一致
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:update')")
@PutMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResultDomain<TbKnowledgeFile> updateFile(
@RequestParam("file") @NotNull MultipartFile file,
@RequestParam("knowledgeId") @NotBlank String knowledgeId,
@RequestParam("fileRootId") @NotBlank String fileRootId) {
logger.info("更新知识库文件: knowledgeId={}, fileName={}", knowledgeId, file.getOriginalFilename());
return knowledgeService.updateKnowledgeFileVersion(knowledgeId, file, fileRootId);
}
/**
* 删除知识库文件同时删除Dify文档所有filtRootId的文件一起软删除
* @param fileId 文件id
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:delete')")
@DeleteMapping("/file/{fileId}")
public ResultDomain<Boolean> deleteFile(@PathVariable("fileId") @NotBlank String fileId) {
logger.info("删除知识库文件: fileId={}", fileId);
return knowledgeService.deleteKnowledgeFileById(fileId);
}
/**
* 获取文件历史版本获取fileRootId下所有version
* @param fileRootId 文件id
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:view')")
@GetMapping("/file/{fileId}/history")
public ResultDomain<TbKnowledgeFile> getFileHistory(@PathVariable("fileId") @NotBlank String fileRootId) {
logger.info("获取文件历史: fileRootId={}", fileRootId);
return knowledgeService.getKnowledgeFileHistory(fileRootId);
}
// ====================== 文档分段管理 ======================
/**
* @description 获取文档分段列表
* @param datasetId Dify数据集ID
* @param documentId Dify文档ID
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:dify:segment:view')")
@GetMapping("/datasets/{datasetId}/documents/{documentId}/segments")
public ResultDomain<JSONObject> getDocumentSegments(
@PathVariable("datasetId") @NotBlank String datasetId,
@PathVariable("documentId") @NotBlank String documentId) {
logger.info("获取文档分段: datasetId={}, documentId={}", datasetId, documentId);
return difyProxyService.getDocumentSegments(datasetId, documentId);
}
/**
* @description 创建文档分段
* @param datasetId Dify数据集ID
* @param documentId Dify文档ID
* @param requestBody 分段内容
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:dify:segment:create')")
@PostMapping("/datasets/{datasetId}/documents/{documentId}/segments")
public ResultDomain<String> createSegment(
@PathVariable("datasetId") @NotBlank String datasetId,
@PathVariable("documentId") @NotBlank String documentId,
@RequestBody Map<String, Object> requestBody) {
logger.info("创建文档分段: datasetId={}, documentId={}", datasetId, documentId);
return difyProxyService.createSegment(datasetId, documentId, requestBody);
}
/**
* @description 更新文档分段
* @param datasetId Dify数据集ID
* @param documentId Dify文档ID
* @param segmentId Dify分段ID
* @param requestBody 分段内容
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:dify:segment:update')")
@PatchMapping("/datasets/{datasetId}/documents/{documentId}/segments/{segmentId}")
public ResultDomain<String> updateSegment(
@PathVariable("datasetId") @NotBlank String datasetId,
@PathVariable("documentId") @NotBlank String documentId,
@PathVariable("segmentId") @NotBlank String segmentId,
@RequestBody Map<String, Object> requestBody) {
logger.info("更新文档分段: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
return difyProxyService.updateSegment(datasetId, documentId, segmentId, requestBody);
}
/**
* @description 删除文档分段
* @param datasetId Dify数据集ID
* @param documentId Dify文档ID
* @param segmentId Dify分段ID
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:dify:segment:delete')")
@DeleteMapping("/datasets/{datasetId}/documents/{documentId}/segments/{segmentId}")
public ResultDomain<String> deleteSegment(
@PathVariable("datasetId") @NotBlank String datasetId,
@PathVariable("documentId") @NotBlank String documentId,
@PathVariable("segmentId") @NotBlank String segmentId) {
logger.info("删除文档分段: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
return difyProxyService.deleteSegment(datasetId, documentId, segmentId);
}
// ====================== 文档状态管理 ======================
/**
* @description 更新文档状态(启用/禁用/归档)
* @param datasetId Dify数据集ID
* @param action 操作类型: enable/disable/archive/un_archive
* @param requestBody 请求体
* @author yslg
* @since 2025-12-18
*/
@PreAuthorize("@ss.hasPermission('ai:dify:document:status')")
@PatchMapping("/datasets/{datasetId}/documents/{action}/status")
public ResultDomain<String> updateDocumentStatus(
@PathVariable("datasetId") @NotBlank String datasetId,
@PathVariable("action") @NotBlank String action,
@RequestBody Map<String, Object> requestBody) {
logger.info("更新文档状态: datasetId={}, action={}", datasetId, action);
return difyProxyService.updateDocumentStatus(datasetId, action, requestBody);
}
}

View File

@@ -0,0 +1,64 @@
package org.xyzh.ai.handler;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.xyzh.api.ai.dto.PromptCard;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* @description 提示卡片列表类型处理器JSONB <-> List<PromptCard>
* @filename PromptCardsTypeHandler.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@MappedTypes({List.class})
public class PromptCardsTypeHandler extends BaseTypeHandler<List<PromptCard>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<PromptCard> parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null || parameter.isEmpty()) {
ps.setString(i, "[]");
} else {
ps.setString(i, JSON.toJSONString(parameter));
}
}
@Override
public List<PromptCard> getNullableResult(ResultSet rs, String columnName) throws SQLException {
String jsonString = rs.getString(columnName);
return parseToList(jsonString);
}
@Override
public List<PromptCard> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String jsonString = rs.getString(columnIndex);
return parseToList(jsonString);
}
@Override
public List<PromptCard> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String jsonString = cs.getString(columnIndex);
return parseToList(jsonString);
}
private List<PromptCard> parseToList(String jsonString) {
if (jsonString == null || jsonString.trim().isEmpty()) {
return new ArrayList<>();
}
try {
return JSON.parseObject(jsonString, new TypeReference<List<PromptCard>>() {});
} catch (Exception e) {
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,70 @@
package org.xyzh.ai.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.api.ai.dto.TbAgent;
import org.xyzh.common.core.page.PageParam;
import java.util.List;
/**
* @description 智能体数据访问层
* @filename AgentMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Mapper
public interface TbAgentMapper {
/**
* 插入智能体
*/
int insertAgent(TbAgent agent);
/**
* 更新智能体只更新非null字段
*/
int updateAgent(TbAgent agent);
/**
* 逻辑删除智能体
*/
int deleteAgent(TbAgent agent);
/**
* 根据ID查询智能体
*/
TbAgent selectAgentById(@Param("agentId") String agentId);
/**
* 根据ApiKey查询智能体
*/
TbAgent selectAgentByApiKey(@Param("apiKey") String apiKey);
/**
* 查询智能体列表
*/
List<TbAgent> selectAgentList(@Param("filter") TbAgent filter);
/**
* 分页查询智能体
*/
List<TbAgent> selectAgentPage(
@Param("filter") TbAgent filter,
@Param("pageParam") PageParam pageParam
);
/**
* 统计智能体数量
*/
long countAgents(@Param("filter") TbAgent filter);
/**
* 根据名称检查是否存在
*/
int countByName(
@Param("name") String name,
@Param("excludeId") String excludeId
);
}

View File

@@ -0,0 +1,60 @@
package org.xyzh.ai.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.api.ai.dto.TbChat;
import org.xyzh.common.core.page.PageParam;
import java.util.List;
/**
* @description 对话数据访问层
* @filename ChatMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Mapper
public interface TbChatMapper {
/**
* 插入对话
*/
int insertChat(TbChat chat);
/**
* 更新对话
*/
int updateChat(TbChat chat);
/**
* 逻辑删除对话
*/
int deleteChat(TbChat chat);
/**
* 根据ID查询对话
*/
TbChat selectChatById(@Param("chatId") String chatId);
/**
* 根据智能体ID和用户ID查询对话列表
*/
List<TbChat> selectChatList(
@Param("agentId") String agentId,
@Param("userId") String userId
);
/**
* 分页查询对话
*/
List<TbChat> selectChatPage(
@Param("filter") TbChat filter,
@Param("pageParam") PageParam pageParam
);
/**
* 统计对话数量
*/
long countChats(@Param("filter") TbChat filter);
}

View File

@@ -0,0 +1,62 @@
package org.xyzh.ai.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.api.ai.dto.TbChatMessage;
import org.xyzh.common.core.page.PageParam;
import java.util.List;
/**
* @description 对话消息数据访问层
* @filename ChatMessageMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Mapper
public interface TbChatMessageMapper {
/**
* 插入消息
*/
int insertChatMessage(TbChatMessage message);
/**
* 更新消息
*/
int updateChatMessage(TbChatMessage message);
/**
* 逻辑删除消息
*/
int deleteChatMessage(TbChatMessage message);
/**
* 根据ID查询消息
*/
TbChatMessage selectMessageById(@Param("messageId") String messageId);
/**
* 根据对话ID查询消息列表
*/
List<TbChatMessage> selectMessagesByChatId(@Param("chatId") String chatId);
/**
* 分页查询消息
*/
List<TbChatMessage> selectMessagePage(
@Param("chatId") String chatId,
@Param("pageParam") PageParam pageParam
);
/**
* 统计消息数量
*/
long countMessages(@Param("chatId") String chatId);
/**
* 批量删除消息根据对话ID
*/
int deleteMessagesByChatId(@Param("chatId") String chatId);
}

View File

@@ -0,0 +1,78 @@
package org.xyzh.ai.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.api.ai.dto.TbKnowledgeFile;
import org.xyzh.common.core.page.PageParam;
import java.util.List;
/**
* @description 知识库文件数据访问层
* @filename KnowledgeFileMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Mapper
public interface TbKnowledgeFileMapper {
/**
* 插入知识库文件
*/
int insertKnowledgeFile(TbKnowledgeFile file);
/**
* 更新知识库文件
*/
int updateKnowledgeFile(TbKnowledgeFile file);
/**
* 逻辑删除知识库文件
*/
int deleteKnowledgeFile(TbKnowledgeFile file);
/**
* 根据知识库ID和文件ID查询
*/
TbKnowledgeFile selectKnowledgeFile(
@Param("knowledgeId") String knowledgeId,
@Param("fileId") String fileId
);
/**
* 根据知识库ID查询文件列表
*/
List<TbKnowledgeFile> selectFilesByKnowledgeId(@Param("knowledgeId") String knowledgeId);
/**
* 根据文件根ID查询所有版本
*/
List<TbKnowledgeFile> selectFileVersions(@Param("fileRootId") String fileRootId);
/**
* 分页查询知识库文件
*/
List<TbKnowledgeFile> selectFilePage(
@Param("knowledgeId") String knowledgeId,
@Param("pageParam") PageParam pageParam
);
/**
* 统计知识库文件数量
*/
long countFiles(@Param("knowledgeId") String knowledgeId);
/**
* 批量删除知识库文件根据文件根ID
*/
int deleteFilesByRootId(@Param("fileRootId") String fileRootId);
/**
* 获取文件最新版本号
*/
Integer selectLatestVersion(
@Param("knowledgeId") String knowledgeId,
@Param("fileRootId") String fileRootId
);
}

View File

@@ -0,0 +1,62 @@
package org.xyzh.ai.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.api.ai.dto.TbKnowledge;
import org.xyzh.common.core.page.PageParam;
import java.util.List;
/**
* @description 知识库数据访问层
* @filename KnowledgeMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Mapper
public interface TbKnowledgeMapper {
/**
* 插入知识库
*/
int insertKnowledge(TbKnowledge knowledge);
/**
* 更新知识库
*/
int updateKnowledge(TbKnowledge knowledge);
/**
* 逻辑删除知识库
*/
int deleteKnowledge(TbKnowledge knowledge);
/**
* 根据ID查询知识库
*/
TbKnowledge selectKnowledgeById(@Param("knowledgeId") String knowledgeId);
/**
* 根据DifyDatasetId查询知识库
*/
TbKnowledge selectKnowledgeByDifyId(@Param("difyDatasetId") String difyDatasetId);
/**
* 查询知识库列表
*/
List<TbKnowledge> selectKnowledgeList(@Param("filter") TbKnowledge filter);
/**
* 分页查询知识库
*/
List<TbKnowledge> selectKnowledgePage(
@Param("filter") TbKnowledge filter,
@Param("pageParam") PageParam pageParam
);
/**
* 统计知识库数量
*/
long countKnowledges(@Param("filter") TbKnowledge filter);
}

View File

@@ -0,0 +1,207 @@
package org.xyzh.ai.service.impl;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.xyzh.ai.client.DifyApiClient;
import org.xyzh.ai.client.dto.DifyFileInfo;
import org.xyzh.ai.client.dto.DocumentUploadRequest;
import org.xyzh.ai.client.dto.DocumentUploadResponse;
import org.xyzh.api.ai.dto.TbAgent;
import org.xyzh.api.ai.service.AIFileUploadService;
import org.xyzh.common.auth.utils.LoginUtil;
import org.xyzh.common.core.domain.ResultDomain;
import java.io.File;
import java.util.List;
import java.util.Map;
/**
* @description AI文件上传服务实现只负责与Dify交互不处理minio和数据库
* @filename AIFileUploadServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@DubboService(version = "1.0.0", group = "ai", timeout = 30000, retries = 0)
public class AIFileUploadServiceImpl implements AIFileUploadService {
private static final Logger logger = LoggerFactory.getLogger(AIFileUploadServiceImpl.class);
@Autowired
private DifyApiClient difyApiClient;
@Autowired
private AgentServiceImpl agentService;
// ============================ 对话文件管理 ============================
@Override
public ResultDomain<Map<String, Object>> uploadFileForChat(MultipartFile file, String agentId) {
// 1. 参数校验
if (file == null || file.isEmpty()) {
return ResultDomain.failure("文件不能为空");
}
if (!StringUtils.hasText(agentId)) {
return ResultDomain.failure("智能体ID不能为空");
}
// 2. 获取智能体API Key
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
if (!agentResult.getSuccess() || agentResult.getData() == null) {
return ResultDomain.failure("智能体不存在");
}
TbAgent agent = agentResult.getData();
File tempFile = null;
try {
// 3. 将MultipartFile转换为临时File
tempFile = File.createTempFile("upload_", "_" + file.getOriginalFilename());
file.transferTo(tempFile);
// 4. 获取当前用户
String userId = LoginUtil.getCurrentUserId();
if (!StringUtils.hasText(userId)) {
userId = "anonymous";
}
// 5. 上传到Dify
DifyFileInfo difyFile = difyApiClient.uploadFileForChat(tempFile, file.getOriginalFilename(), userId, agent.getApiKey());
if (difyFile != null && StringUtils.hasText(difyFile.getId())) {
logger.info("上传对话文件成功: agentId={}, fileId={}", agentId, difyFile.getId());
Map<String, Object> result = new java.util.HashMap<>();
result.put("id", difyFile.getId());
result.put("name", difyFile.getName());
result.put("size", difyFile.getSize());
result.put("type", difyFile.getType());
result.put("upload_file_id", difyFile.getUploadFileId());
return ResultDomain.success("上传成功", result);
}
return ResultDomain.failure("上传文件失败");
} catch (Exception e) {
logger.error("上传对话文件异常: {}", e.getMessage(), e);
return ResultDomain.failure("上传文件异常: " + e.getMessage());
} finally {
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}
}
}
// ============================ 知识库Dify文档管理 ============================
/**
* @description 上传文件到Dify知识库只负责Dify上传不处理minio和数据库
* @param difyDatasetId Dify知识库ID
* @param file 文件
* @param fileName 文件名
* @param indexingTechnique 索引方式
* @return ResultDomain<String> Dify文档ID
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<String> uploadFileToDify(String difyDatasetId, File file, String fileName, String indexingTechnique) {
if (!StringUtils.hasText(difyDatasetId)) {
return ResultDomain.failure("Dify知识库ID不能为空");
}
if (file == null || !file.exists()) {
return ResultDomain.failure("文件不能为空");
}
try {
DocumentUploadRequest uploadRequest = new DocumentUploadRequest();
uploadRequest.setName(fileName);
if (StringUtils.hasText(indexingTechnique)) {
uploadRequest.setIndexingTechnique(indexingTechnique);
}
DocumentUploadResponse uploadResponse = difyApiClient.uploadDocumentByFile(
difyDatasetId,
file,
fileName,
uploadRequest
);
if (uploadResponse != null && uploadResponse.getDocument() != null) {
String difyDocumentId = uploadResponse.getDocument().getId();
logger.info("上传文件到Dify成功: difyDatasetId={}, difyDocumentId={}", difyDatasetId, difyDocumentId);
return ResultDomain.success("上传成功", difyDocumentId);
}
return ResultDomain.failure("上传文件到Dify失败");
} catch (Exception e) {
logger.error("上传文件到Dify异常: {}", e.getMessage(), e);
return ResultDomain.failure("上传文件到Dify异常: " + e.getMessage());
}
}
/**
* @description 从Dify知识库删除文档
* @param difyDatasetId Dify知识库ID
* @param difyDocumentId Dify文档ID
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Boolean> deleteFileFromDify(String difyDatasetId, String difyDocumentId) {
if (!StringUtils.hasText(difyDatasetId)) {
return ResultDomain.failure("Dify知识库ID不能为空");
}
if (!StringUtils.hasText(difyDocumentId)) {
return ResultDomain.failure("Dify文档ID不能为空");
}
try {
difyApiClient.deleteDocument(difyDatasetId, difyDocumentId);
logger.info("从Dify删除文档成功: difyDatasetId={}, difyDocumentId={}", difyDatasetId, difyDocumentId);
return ResultDomain.success("删除成功", true);
} catch (Exception e) {
logger.error("从Dify删除文档异常: {}", e.getMessage(), e);
return ResultDomain.failure("从Dify删除文档异常: " + e.getMessage());
}
}
/**
* @description 批量从Dify知识库删除文档
* @param difyDatasetId Dify知识库ID
* @param difyDocumentIds Dify文档ID列表
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Boolean> batchDeleteFilesFromDify(String difyDatasetId, List<String> difyDocumentIds) {
if (!StringUtils.hasText(difyDatasetId)) {
return ResultDomain.failure("Dify知识库ID不能为空");
}
if (difyDocumentIds == null || difyDocumentIds.isEmpty()) {
return ResultDomain.failure("Dify文档ID列表不能为空");
}
int successCount = 0;
int failCount = 0;
for (String difyDocumentId : difyDocumentIds) {
if (StringUtils.hasText(difyDocumentId)) {
try {
difyApiClient.deleteDocument(difyDatasetId, difyDocumentId);
successCount++;
} catch (Exception e) {
logger.warn("批量删除Dify文档失败: difyDocumentId={}, error={}", difyDocumentId, e.getMessage());
failCount++;
}
}
}
logger.info("批量从Dify删除文档完成: 成功={}, 失败={}", successCount, failCount);
if (failCount == 0) {
return ResultDomain.success("批量删除成功", true);
} else if (successCount == 0) {
return ResultDomain.failure("批量删除全部失败");
} else {
return ResultDomain.success("部分删除成功", true);
}
}
}

View File

@@ -0,0 +1,476 @@
package org.xyzh.ai.service.impl;
import com.alibaba.fastjson2.JSONObject;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.xyzh.ai.client.DifyApiClient;
import org.xyzh.ai.client.callback.StreamCallback;
import org.xyzh.ai.client.dto.ChatRequest;
import org.xyzh.ai.mapper.TbChatMapper;
import org.xyzh.ai.mapper.TbChatMessageMapper;
import org.xyzh.api.ai.dto.ChatPrepareData;
import org.xyzh.api.ai.dto.DifyFileInfo;
import org.xyzh.api.ai.dto.TbAgent;
import org.xyzh.api.ai.dto.TbChat;
import org.xyzh.api.ai.dto.TbChatMessage;
import org.xyzh.api.ai.service.AgentChatService;
import org.xyzh.api.ai.service.AgentService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.redis.service.RedisService;
import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.auth.utils.LoginUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @description 智能体对话服务实现
* @filename AgentChatServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@DubboService(
version = "1.0.0",
group = "ai",
timeout = 3000,
retries = 0
)
public class AgentChatServiceImpl implements AgentChatService {
private static final Logger logger = LoggerFactory.getLogger(AgentChatServiceImpl.class);
private static final String CHAT_SESSION_PREFIX = "ai:chat:session:";
private static final long SESSION_TTL = 5 * 60;
@Autowired
private TbChatMapper chatMapper;
@Autowired
private TbChatMessageMapper chatMessageMapper;
@DubboReference
private AgentService agentService;
@Autowired
private DifyApiClient difyApiClient;
@Autowired
private RedisService redisService;
/**
* @description 根据 userType 获取用户ID
* @param chat 会话信息(包含 userId 和 userType
* @return 真实的系统用户ID
*/
private String getUserIdByType(TbChat chat) {
if (!chat.getUserType()) {
// 来客userType=false直接返回传入的 userId已经是真正的系统 userId
return chat.getUserId();
} else {
// 员工userType=true从登录信息获取 userId
return LoginUtil.getCurrentUserId();
}
}
/**
* @description 判断智能体是否是outer
* @param agentId 智能体ID
* @return true-是outerfalse-不是outer
*/
private Boolean isOuterAgent(String agentId){
// 智能体必须是outer
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
if(!agentResult.getSuccess()|| agentResult.getData() == null || !agentResult.getData().getOuter()){
return false;
}
return true;
}
// ====================== 智能体会话管理 ======================
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbChat> createChat(TbChat chat) {
// 如果是来客userType=false校验智能体是否是 outer
if(!chat.getUserType()){
if(!isOuterAgent(chat.getAgentId())){
return ResultDomain.failure("智能体不可用");
}
}
// 设置chat来客传入的 userId 已经是真正的系统 userId
chat.setOptsn(IdUtil.getOptsn());
chat.setChatId(IdUtil.generateID());
chatMapper.insertChat(chat);
return ResultDomain.success("创建成功", chat);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbChat> updateChat(TbChat chat) {
// 1. 校验会话
TbChat chat2 = chatMapper.selectChatById(chat.getChatId());
if (chat2 == null) {
return ResultDomain.failure("会话不存在");
}
// 判断agent是否是outer
if(!isOuterAgent(chat.getAgentId())){
return ResultDomain.failure("智能体不可用");
}
// 2. 获取用户ID并校验权限
String userId = getUserIdByType(chat);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
if (!chat2.getUserId().equals(userId)) {
return ResultDomain.failure("无权修改此会话");
}
// 3. 更新会话
TbChat update = new TbChat();
update.setChatId(chat.getChatId());
update.setTitle(chat.getTitle());
int rows = chatMapper.updateChat(update);
if (rows > 0) {
return ResultDomain.success("更新会话成功", update);
}
return ResultDomain.failure("更新会话失败");
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbChat> deleteChat(TbChat filter) {
// 1. 校验会话
TbChat chat = chatMapper.selectChatById(filter.getChatId());
if (chat == null) {
return ResultDomain.failure("会话不存在");
}
// 判断agent是否是outer
if(!isOuterAgent(chat.getAgentId())){
return ResultDomain.failure("智能体不可用");
}
// 2. 获取用户ID并校验权限
String userId = getUserIdByType(filter);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
if (!chat.getUserId().equals(userId)) {
return ResultDomain.failure("无权删除此会话");
}
// 3. 删除会话消息
chatMessageMapper.deleteMessagesByChatId(filter.getChatId());
// 4. 删除会话
TbChat delete = new TbChat();
delete.setChatId(filter.getChatId());
int rows = chatMapper.deleteChat(delete);
if (rows > 0) {
logger.info("删除会话成功: chatId={}", filter.getChatId());
return ResultDomain.success("删除会话成功", chat);
}
return ResultDomain.failure("删除会话失败");
}
@Override
public ResultDomain<TbChat> getChatList(TbChat filter) {
// 判断agent是否是outer
if(!isOuterAgent(filter.getAgentId())){
return ResultDomain.failure("智能体不可用");
}
// 获取用户ID
String userId = getUserIdByType(filter);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
List<TbChat> chatList = chatMapper.selectChatList(filter.getAgentId(), userId);
return ResultDomain.success("查询成功", chatList);
}
// ====================== 智能体聊天管理 ======================
@Override
public ResultDomain<TbChatMessage> getChatMessageList(TbChat filter) {
// 1. 校验会话
TbChat chat = chatMapper.selectChatById(filter.getChatId());
if (chat == null) {
return ResultDomain.failure("会话不存在");
}
// 判断agent是否是outer
if(!isOuterAgent(chat.getAgentId())){
return ResultDomain.failure("智能体不可用");
}
// 2. 获取用户ID并校验权限
String userId = getUserIdByType(filter);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
if (!chat.getUserId().equals(userId)) {
return ResultDomain.failure("无权查看此会话");
}
List<TbChatMessage> messages = chatMessageMapper.selectMessagesByChatId(filter.getChatId());
return ResultDomain.success("查询成功", messages);
}
@Override
public ResultDomain<String> prepareChatMessageSession(ChatPrepareData prepareData) {
String agentId = prepareData.getAgentId();
String chatId = prepareData.getChatId();
String query = prepareData.getQuery();
// 1. 校验智能体
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
if (!agentResult.getSuccess() || agentResult.getData() == null || !agentResult.getData().getOuter()) {
return ResultDomain.failure("智能体不存在或不可用");
}
TbAgent agent = agentResult.getData();
// 2. 获取用户ID根据userType处理来客/员工)
TbChat chatFilter = new TbChat();
chatFilter.setAgentId(agentId);
chatFilter.setUserId(prepareData.getUserId());
chatFilter.setUserType(prepareData.getUserType());
String userId = getUserIdByType(chatFilter);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
// 3. 校验会话
if (StringUtils.hasText(chatId)) {
TbChat chat = chatMapper.selectChatById(chatId);
if (chat == null) {
return ResultDomain.failure("会话不存在");
}
if (!chat.getUserId().equals(userId)) {
return ResultDomain.failure("无权操作此会话");
}
}
// 4. 生成临时消息IDsessionId
String sessionId = IdUtil.getSnowflakeId();
// 5. 存储会话数据到Redis
Map<String, Object> sessionData = new HashMap<>();
sessionData.put("agentId", agentId);
sessionData.put("chatId", chatId);
sessionData.put("query", query);
sessionData.put("userId", userId);
sessionData.put("filesData", prepareData.getFiles());
sessionData.put("apiKey", agent.getApiKey());
String cacheKey = CHAT_SESSION_PREFIX + sessionId;
redisService.set(cacheKey, sessionData, SESSION_TTL, TimeUnit.SECONDS);
logger.info("准备对话会话: sessionId={}, agentId={}", sessionId, agentId);
return ResultDomain.success("准备成功", sessionId);
}
@Override
public SseEmitter streamChatMessageWithSse(String sessionId) {
SseEmitter emitter = new SseEmitter(300000L);
// 1. 从Redis获取会话数据
String cacheKey = CHAT_SESSION_PREFIX + sessionId;
@SuppressWarnings("unchecked")
Map<String, Object> sessionData = (Map<String, Object>) redisService.get(cacheKey);
if (sessionData == null) {
try {
emitter.send(SseEmitter.event().name("error").data("{\"message\":\"会话已过期\"}"));
emitter.complete();
} catch (IOException e) {
logger.error("发送错误事件失败", e);
}
return emitter;
}
// 2. 解析会话数据
String agentId = (String) sessionData.get("agentId");
String chatId = (String) sessionData.get("chatId");
String query = (String) sessionData.get("query");
String userId = (String) sessionData.get("userId");
String apiKey = (String) sessionData.get("apiKey");
@SuppressWarnings("unchecked")
List<DifyFileInfo> filesData = (List<DifyFileInfo>) sessionData.get("filesData");
// 3. 删除已使用的会话数据
redisService.delete(cacheKey);
// 4. 保存用户消息
String userMessageId = IdUtil.getSnowflakeId();
TbChatMessage userMessage = new TbChatMessage();
userMessage.setOptsn(IdUtil.getOptsn());
userMessage.setMessageId(userMessageId);
userMessage.setChatId(chatId);
userMessage.setRole("user");
userMessage.setContent(query);
chatMessageMapper.insertChatMessage(userMessage);
// 5. 构建Dify请求
ChatRequest chatRequest = new ChatRequest();
chatRequest.setQuery(query);
chatRequest.setUser(userId);
chatRequest.setResponseMode("streaming");
if (filesData != null && !filesData.isEmpty()) {
chatRequest.setFiles(filesData);
}
// 6. 准备AI消息记录
String aiMessageId = IdUtil.getSnowflakeId();
StringBuilder aiContent = new StringBuilder();
// 7. 发起流式请求
difyApiClient.streamChat(chatRequest, apiKey, new StreamCallback() {
@Override
public void onEvent(String event, String data) {
try {
// 使用SseEmitter标准格式发送event: xxx\ndata: xxx
emitter.send(SseEmitter.event().name(event).data(data));
} catch (IOException e) {
logger.error("发送SSE事件失败: event={}", event, e);
}
}
@Override
public void onMessage(String content) {
aiContent.append(content);
}
@Override
public void onMessageEnd(String data) {
// 从message_end事件中提取difyMessageId
String difyMessageId = null;
try {
JSONObject json = JSONObject.parseObject(data);
if (json != null && json.containsKey("message_id")) {
difyMessageId = json.getString("message_id");
}
} catch (Exception e) {
logger.warn("解析difyMessageId失败: {}", e.getMessage());
}
// 保存AI回复消息
TbChatMessage aiMessage = new TbChatMessage();
aiMessage.setOptsn(IdUtil.getOptsn());
aiMessage.setMessageId(aiMessageId);
aiMessage.setDifyMessageId(difyMessageId);
aiMessage.setChatId(chatId);
aiMessage.setRole("ai");
aiMessage.setContent(aiContent.toString());
chatMessageMapper.insertChatMessage(aiMessage);
logger.info("对话完成: chatId={}, aiMessageId={}, difyMessageId={}", chatId, aiMessageId, difyMessageId);
}
@Override
public void onComplete() {
try {
emitter.complete();
} catch (Exception e) {
logger.error("完成SSE失败", e);
}
}
@Override
public void onError(Throwable throwable) {
logger.error("流式对话异常", throwable);
try {
JSONObject errorData = new JSONObject();
errorData.put("message", throwable.getMessage());
emitter.send(SseEmitter.event().name("error").data(errorData.toJSONString()));
emitter.complete();
} catch (IOException e) {
logger.error("发送错误事件失败", e);
}
}
});
return emitter;
}
@Override
public ResultDomain<Boolean> stopChatMessageByTaskId(TbChat filter, String taskId) {
// 1. 获取智能体
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(filter.getAgentId());
if (!agentResult.getSuccess() || agentResult.getData() == null || !agentResult.getData().getOuter()) {
return ResultDomain.failure("智能体不存在");
}
TbAgent agent = agentResult.getData();
// 2. 获取用户ID
String userId = getUserIdByType(filter);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
try {
// 3. 调用Dify停止接口
difyApiClient.stopChatMessage(taskId, userId, agent.getApiKey());
logger.info("停止对话成功: taskId={}", taskId);
return ResultDomain.success("停止成功", true);
} catch (Exception e) {
logger.error("停止对话失败: taskId={}", taskId, e);
return ResultDomain.failure("停止对话失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<Boolean> commentChatMessage(TbChat filter, String messageId, String comment) {
// 1. 校验消息
TbChatMessage message = chatMessageMapper.selectMessageById(messageId);
if (message == null) {
return ResultDomain.failure("消息不存在");
}
// 2. 获取用户ID
String userId = getUserIdByType(filter);
if (userId == null) {
return ResultDomain.failure("用户信息获取失败");
}
// 3. 更新评价
TbChatMessage update = new TbChatMessage();
update.setMessageId(messageId);
update.setComment(comment);
int rows = chatMessageMapper.updateChatMessage(update);
if (rows > 0) {
// 4. 同步到Dify转换评价格式使用difyMessageId
if (StringUtils.hasText(message.getDifyMessageId())) {
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(filter.getAgentId());
if (agentResult.getSuccess() && agentResult.getData() != null) {
TbAgent agent = agentResult.getData();
String rating = "like".equals(comment) ? "like" : ("dislike".equals(comment) ? "dislike" : null);
try {
difyApiClient.submitMessageFeedback(message.getDifyMessageId(), rating, userId, comment, agent.getApiKey());
} catch (Exception e) {
logger.warn("同步评价到Dify失败: difyMessageId={}", message.getDifyMessageId(), e);
}
}
}
logger.info("评价消息成功: messageId={}, comment={}", messageId, comment);
return ResultDomain.success("评价成功", true);
}
return ResultDomain.failure("评价失败");
}
}

View File

@@ -0,0 +1,253 @@
package org.xyzh.ai.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.xyzh.ai.mapper.TbAgentMapper;
import org.xyzh.api.ai.dto.TbAgent;
import org.xyzh.api.ai.service.AgentService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.redis.service.RedisService;
import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.auth.utils.LoginUtil;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
/**
* @description 智能体服务实现
* @filename AgentServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@DubboService(version = "1.0.0", group = "ai", timeout = 3000, retries = 0)
public class AgentServiceImpl implements AgentService {
private static final Logger logger = LoggerFactory.getLogger(AgentServiceImpl.class);
private static final String AGENT_CACHE_PREFIX = "ai:agent:";
private static final long AGENT_CACHE_TTL = 24 * 60 * 60;
private final ReentrantLock agentLock = new ReentrantLock();
@Autowired
private TbAgentMapper agentMapper;
@Autowired
private RedisService redisService;
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbAgent> addAgent(TbAgent tbAgent) {
// 1. 参数校验
if (!StringUtils.hasText(tbAgent.getName())) {
return ResultDomain.failure("智能体名称不能为空");
}
if (!StringUtils.hasText(tbAgent.getApiKey())) {
return ResultDomain.failure("智能体API Key不能为空");
}
if (!StringUtils.hasText(tbAgent.getIntroduce())) {
return ResultDomain.failure("引导词不能为空");
}
if (!StringUtils.hasText(tbAgent.getCategory())) {
return ResultDomain.failure("分类不能为空");
}
// 2. 检查名称是否重复
int count = agentMapper.countByName(tbAgent.getName(), null);
if (count > 0) {
return ResultDomain.failure("智能体名称已存在");
}
// 3. 检查ApiKey是否重复
TbAgent existAgent = agentMapper.selectAgentByApiKey(tbAgent.getApiKey());
if (existAgent != null) {
return ResultDomain.failure("该API Key已被使用");
}
// 4. 生成ID和流水号
tbAgent.setOptsn(IdUtil.getOptsn());
tbAgent.setAgentId(IdUtil.getSnowflakeId());
// 5. 设置创建者
String userId = LoginUtil.getCurrentUserId();
if (StringUtils.hasText(userId)) {
tbAgent.setCreator(userId);
}
// 6. 插入数据库
int rows = agentMapper.insertAgent(tbAgent);
if (rows > 0) {
logger.info("创建智能体成功: agentId={}, name={}", tbAgent.getAgentId(), tbAgent.getName());
return ResultDomain.success("创建智能体成功", tbAgent);
}
return ResultDomain.failure("创建智能体失败");
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbAgent> updateAgent(TbAgent tbAgent) {
// 1. 参数校验
if (!StringUtils.hasText(tbAgent.getAgentId())) {
return ResultDomain.failure("智能体ID不能为空");
}
// 2. 检查是否存在
TbAgent existAgent = agentMapper.selectAgentById(tbAgent.getAgentId());
if (existAgent == null) {
return ResultDomain.failure("智能体不存在");
}
// 3. 检查名称是否重复(排除自身)
if (StringUtils.hasText(tbAgent.getName())) {
int count = agentMapper.countByName(tbAgent.getName(), tbAgent.getAgentId());
if (count > 0) {
return ResultDomain.failure("智能体名称已存在");
}
}
// 4. 加锁更新(避免并发问题)
agentLock.lock();
try {
// 5. 设置更新者
String userId = LoginUtil.getCurrentUserId();
if (StringUtils.hasText(userId)) {
tbAgent.setUpdater(userId);
}
// 6. 更新数据库
int rows = agentMapper.updateAgent(tbAgent);
if (rows > 0) {
// 7. 返回最新数据
TbAgent updated = agentMapper.selectAgentById(tbAgent.getAgentId());
// 8. 更新缓存
String cacheKey = AGENT_CACHE_PREFIX + tbAgent.getAgentId();
logger.info("更新智能体成功: agentId={}", tbAgent.getAgentId());
redisService.set(cacheKey, updated);
return ResultDomain.success("更新智能体成功", updated);
}
} finally {
agentLock.unlock();
}
return ResultDomain.failure("更新智能体失败");
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbAgent> deleteAgent(TbAgent tbAgent) {
// 1. 参数校验
if (!StringUtils.hasText(tbAgent.getAgentId())) {
return ResultDomain.failure("智能体ID不能为空");
}
// 2. 检查是否存在
TbAgent existAgent = agentMapper.selectAgentById(tbAgent.getAgentId());
if (existAgent == null) {
return ResultDomain.failure("智能体不存在");
}
// 3. 设置更新者
String userId = LoginUtil.getCurrentUserId();
if (StringUtils.hasText(userId)) {
tbAgent.setUpdater(userId);
}
// 4. 逻辑删除
int rows = agentMapper.deleteAgent(tbAgent);
if (rows > 0) {
// 5. 删除缓存
String cacheKey = AGENT_CACHE_PREFIX + tbAgent.getAgentId();
redisService.delete(cacheKey);
logger.info("删除智能体成功: agentId={}", tbAgent.getAgentId());
return ResultDomain.success("删除智能体成功", existAgent);
}
return ResultDomain.failure("删除智能体失败");
}
/**
* 根据ID获取智能体优先从缓存获取双检加锁
*/
public ResultDomain<TbAgent> selectAgentById(String agentId) {
if (!StringUtils.hasText(agentId)) {
return ResultDomain.failure("智能体ID不能为空");
}
String cacheKey = AGENT_CACHE_PREFIX + agentId;
// 1. 先从缓存获取
TbAgent agent = (TbAgent) redisService.get(cacheKey);
if (agent != null) {
return ResultDomain.success("查询成功", agent);
}
// 2. 双检加锁
agentLock.lock();
try {
// 再次检查缓存
agent = (TbAgent) redisService.get(cacheKey);
if (agent != null) {
return ResultDomain.success("查询成功", agent);
}
// 3. 从数据库获取
agent = agentMapper.selectAgentById(agentId);
if (agent != null) {
// 4. 写入缓存
redisService.set(cacheKey, agent);
return ResultDomain.success("查询成功", agent);
}
return ResultDomain.failure("智能体不存在");
} finally {
agentLock.unlock();
}
}
/**
* 根据ApiKey获取智能体
*/
public TbAgent getAgentByApiKey(String apiKey) {
if (!StringUtils.hasText(apiKey)) {
return null;
}
return agentMapper.selectAgentByApiKey(apiKey);
}
@Override
public ResultDomain<TbAgent> getAgentPage(PageRequest<TbAgent> pageRequest) {
TbAgent filter = pageRequest.getFilter();
PageParam pageParam = pageRequest.getPageParam();
// 查询总数
long total = agentMapper.countAgents(filter);
pageParam.setTotal((int) total);
// 查询分页数据
List<TbAgent> list = agentMapper.selectAgentPage(filter, pageParam);
PageDomain<TbAgent> pageDomain = new PageDomain<>(pageParam, list);
return ResultDomain.success("查询成功", pageDomain);
}
@Override
public ResultDomain<TbAgent> getAgentList(TbAgent filter) {
// 查询列表
List<TbAgent> list = agentMapper.selectAgentList(filter);
ResultDomain<TbAgent> result = ResultDomain.success("查询成功");
result.setDataList(list);
return result;
}
}

View File

@@ -0,0 +1,165 @@
package org.xyzh.ai.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.xyzh.ai.client.DifyApiClient;
import org.xyzh.api.ai.service.DifyProxyService;
import org.xyzh.common.core.domain.ResultDomain;
import java.util.Map;
/**
* @description Dify代理服务实现直接转发请求到Dify API
* @filename DifyProxyServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@DubboService(version="1.0.0", group="ai", timeout=3000, retries=0)
public class DifyProxyServiceImpl implements DifyProxyService {
private static final Logger logger = LoggerFactory.getLogger(DifyProxyServiceImpl.class);
@Autowired
private DifyApiClient difyApiClient;
@Override
public ResultDomain<JSONObject> getDocumentSegments(String datasetId, String documentId) {
// 1. 参数校验
if (!StringUtils.hasText(datasetId)) {
return ResultDomain.failure("数据集ID不能为空");
}
if (!StringUtils.hasText(documentId)) {
return ResultDomain.failure("文档ID不能为空");
}
try {
// 2. 调用Dify API获取分段
String url = String.format("/datasets/%s/documents/%s/segments", datasetId, documentId);
String response = difyApiClient.get(url, null);
if (StringUtils.hasText(response)) {
JSONObject result = JSON.parseObject(response);
logger.info("获取文档分段成功: datasetId={}, documentId={}", datasetId, documentId);
return ResultDomain.success("查询成功", result);
}
return ResultDomain.failure("获取文档分段失败");
} catch (Exception e) {
logger.error("获取文档分段异常: datasetId={}, documentId={}, error={}", datasetId, documentId, e.getMessage(), e);
return ResultDomain.failure("获取文档分段异常: " + e.getMessage());
}
}
@Override
public ResultDomain<String> updateSegment(String datasetId, String documentId, String segmentId, Map<String, Object> requestBody) {
// 1. 参数校验
if (!StringUtils.hasText(datasetId)) {
return ResultDomain.failure("数据集ID不能为空");
}
if (!StringUtils.hasText(documentId)) {
return ResultDomain.failure("文档ID不能为空");
}
if (!StringUtils.hasText(segmentId)) {
return ResultDomain.failure("分段ID不能为空");
}
try {
// 2. 调用Dify API更新分段
String url = String.format("/datasets/%s/documents/%s/segments/%s", datasetId, documentId, segmentId);
String response = difyApiClient.patch(url, requestBody, null);
logger.info("更新分段成功: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
return ResultDomain.success("更新成功", response);
} catch (Exception e) {
logger.error("更新分段异常: datasetId={}, documentId={}, segmentId={}, error={}",
datasetId, documentId, segmentId, e.getMessage(), e);
return ResultDomain.failure("更新分段异常: " + e.getMessage());
}
}
@Override
public ResultDomain<String> createSegment(String datasetId, String documentId, Map<String, Object> requestBody) {
// 1. 参数校验
if (!StringUtils.hasText(datasetId)) {
return ResultDomain.failure("数据集ID不能为空");
}
if (!StringUtils.hasText(documentId)) {
return ResultDomain.failure("文档ID不能为空");
}
try {
// 2. 调用Dify API创建分段
String url = String.format("/datasets/%s/documents/%s/segments", datasetId, documentId);
String response = difyApiClient.post(url, requestBody, null);
logger.info("创建分段成功: datasetId={}, documentId={}", datasetId, documentId);
return ResultDomain.success("创建成功", response);
} catch (Exception e) {
logger.error("创建分段异常: datasetId={}, documentId={}, error={}", datasetId, documentId, e.getMessage(), e);
return ResultDomain.failure("创建分段异常: " + e.getMessage());
}
}
@Override
public ResultDomain<String> deleteSegment(String datasetId, String documentId, String segmentId) {
// 1. 参数校验
if (!StringUtils.hasText(datasetId)) {
return ResultDomain.failure("数据集ID不能为空");
}
if (!StringUtils.hasText(documentId)) {
return ResultDomain.failure("文档ID不能为空");
}
if (!StringUtils.hasText(segmentId)) {
return ResultDomain.failure("分段ID不能为空");
}
try {
// 2. 调用Dify API删除分段
String url = String.format("/datasets/%s/documents/%s/segments/%s", datasetId, documentId, segmentId);
String response = difyApiClient.delete(url, null);
logger.info("删除分段成功: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
return ResultDomain.success("删除成功", response);
} catch (Exception e) {
logger.error("删除分段异常: datasetId={}, documentId={}, segmentId={}, error={}",
datasetId, documentId, segmentId, e.getMessage(), e);
return ResultDomain.failure("删除分段异常: " + e.getMessage());
}
}
@Override
public ResultDomain<String> updateDocumentStatus(String datasetId, String action, Map<String, Object> requestBody) {
// 1. 参数校验
if (!StringUtils.hasText(datasetId)) {
return ResultDomain.failure("数据集ID不能为空");
}
if (!StringUtils.hasText(action)) {
return ResultDomain.failure("操作类型不能为空");
}
// 校验action合法性
if (!action.equals("enable") && !action.equals("disable") &&
!action.equals("archive") && !action.equals("un_archive")) {
return ResultDomain.failure("操作类型不合法支持enable/disable/archive/un_archive");
}
try {
// 2. 调用Dify API更新文档状态
String url = String.format("/datasets/%s/documents/%s/status", datasetId, action);
String response = difyApiClient.patch(url, requestBody, null);
logger.info("更新文档状态成功: datasetId={}, action={}", datasetId, action);
return ResultDomain.success("更新成功", response);
} catch (Exception e) {
logger.error("更新文档状态异常: datasetId={}, action={}, error={}", datasetId, action, e.getMessage(), e);
return ResultDomain.failure("更新文档状态异常: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,661 @@
package org.xyzh.ai.service.impl;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.xyzh.ai.client.DifyApiClient;
import org.xyzh.ai.client.dto.DatasetCreateRequest;
import org.xyzh.ai.client.dto.DatasetCreateResponse;
import org.xyzh.ai.client.dto.DatasetDetailResponse;
import org.xyzh.ai.client.dto.DocumentListResponse;
import org.xyzh.ai.mapper.TbKnowledgeFileMapper;
import org.xyzh.ai.mapper.TbKnowledgeMapper;
import org.xyzh.api.ai.dto.TbKnowledge;
import org.xyzh.api.ai.dto.TbKnowledgeFile;
import org.xyzh.api.ai.service.AIFileUploadService;
import org.xyzh.api.ai.service.KnowledgeService;
import org.xyzh.api.file.dto.TbSysFileDTO;
import org.xyzh.api.file.service.FileService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.auth.utils.LoginUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @description 知识库服务实现
* @filename KnowledgeServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@DubboService(version="1.0.0", group="ai", timeout=3000, retries=0)
public class KnowledgeServiceImpl implements KnowledgeService {
private static final Logger logger = LoggerFactory.getLogger(KnowledgeServiceImpl.class);
@Autowired
private TbKnowledgeMapper knowledgeMapper;
@Autowired
private TbKnowledgeFileMapper knowledgeFileMapper;
@Autowired
private DifyApiClient difyApiClient;
@DubboReference(version = "1.0.0", group = "file", timeout = 30000)
private FileService fileService;
@DubboReference(version = "1.0.0", group = "ai", timeout = 30000)
private AIFileUploadService aiFileUploadService;
// ================================= 知识库管理 =================================
/**
* @description 创建知识库基础信息包含dify知识库各种参数的配置
* 注意涉及外部API调用不使用@Transactional采用手动补偿机制
* @param knowledge 知识库信息
* @param permissionType 权限类型PUBLIC-公开DEPARTMENT-部门ROLE-角色PRIVATE-私有
* @param deptIds 部门ID列表DEPARTMENT类型需要
* @param roleIds 角色ID列表ROLE/PRIVATE类型需要
* @return ResultDomain<TbKnowledge> 创建结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledge> createKnowledge(TbKnowledge knowledge, String permissionType, List<String> deptIds, List<String> roleIds) {
// 1. 参数校验
if (!StringUtils.hasText(knowledge.getTitle())) {
return ResultDomain.failure("知识库标题不能为空");
}
// 2. 获取当前用户
String userId = LoginUtil.getCurrentUserId();
if (!StringUtils.hasText(userId)) {
return ResultDomain.failure("用户未登录");
}
// 3. 生成ID
knowledge.setOptsn(IdUtil.getOptsn());
knowledge.setKnowledgeId(IdUtil.getSnowflakeId());
knowledge.setCreator(userId);
// 4. 创建Dify知识库
String difyDatasetId = null;
try {
DatasetCreateRequest createRequest = new DatasetCreateRequest();
createRequest.setName(knowledge.getTitle());
createRequest.setDescription(knowledge.getDescription());
if (StringUtils.hasText(knowledge.getDifyIndexingTechnique())) {
createRequest.setIndexingTechnique(knowledge.getDifyIndexingTechnique());
}
DatasetCreateResponse difyResponse = difyApiClient.createDataset(createRequest);
if (difyResponse != null && StringUtils.hasText(difyResponse.getId())) {
difyDatasetId = difyResponse.getId();
knowledge.setDifyDatasetId(difyDatasetId);
} else {
return ResultDomain.failure("创建Dify知识库失败");
}
} catch (Exception e) {
logger.error("创建Dify知识库异常: {}", e.getMessage(), e);
return ResultDomain.failure("创建Dify知识库异常: " + e.getMessage());
}
// 5. 插入数据库
int rows = knowledgeMapper.insertKnowledge(knowledge);
if (rows > 0) {
logger.info("创建知识库成功: knowledgeId={}, title={}", knowledge.getKnowledgeId(), knowledge.getTitle());
return ResultDomain.success("创建知识库成功", knowledge);
}
// 数据库保存失败补偿删除Dify知识库
if (StringUtils.hasText(difyDatasetId)) {
try {
difyApiClient.deleteDataset(difyDatasetId);
logger.info("补偿删除Dify知识库成功: difyDatasetId={}", difyDatasetId);
} catch (Exception e) {
logger.warn("补偿删除Dify知识库失败: {}", e.getMessage());
}
}
return ResultDomain.failure("创建知识库失败");
}
/**
* @description 更新知识库包含dify知识库各种参数的配置
* @param knowledge 知识库信息
* @return ResultDomain<TbKnowledge> 更新结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledge> updateKnowledge(TbKnowledge knowledge) {
// 1. 参数校验
if (!StringUtils.hasText(knowledge.getKnowledgeId())) {
return ResultDomain.failure("知识库ID不能为空");
}
// 2. 检查是否存在
TbKnowledge existing = knowledgeMapper.selectKnowledgeById(knowledge.getKnowledgeId());
if (existing == null) {
return ResultDomain.failure("知识库不存在");
}
// 3. 设置更新者
String userId = LoginUtil.getCurrentUserId();
if (StringUtils.hasText(userId)) {
knowledge.setUpdater(userId);
}
// 4. 更新数据库
int rows = knowledgeMapper.updateKnowledge(knowledge);
if (rows > 0) {
logger.info("更新知识库成功: knowledgeId={}", knowledge.getKnowledgeId());
TbKnowledge updated = knowledgeMapper.selectKnowledgeById(knowledge.getKnowledgeId());
return ResultDomain.success("更新知识库成功", updated);
}
return ResultDomain.failure("更新知识库失败");
}
/**
* @description 删除知识库同时删除dify知识库
* 注意Dify删除失败不影响本地删除
* @param knowledgeId 知识库ID
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Boolean> deleteKnowledge(String knowledgeId) {
// 1. 参数校验
if (!StringUtils.hasText(knowledgeId)) {
return ResultDomain.failure("知识库ID不能为空");
}
// 2. 检查是否存在
TbKnowledge existing = knowledgeMapper.selectKnowledgeById(knowledgeId);
if (existing == null) {
return ResultDomain.failure("知识库不存在");
}
// 3. 删除Dify知识库
if (StringUtils.hasText(existing.getDifyDatasetId())) {
try {
difyApiClient.deleteDataset(existing.getDifyDatasetId());
} catch (Exception e) {
logger.warn("删除Dify知识库失败: {}", e.getMessage());
}
}
// 4. 设置更新者并软删除
TbKnowledge delete = new TbKnowledge();
delete.setKnowledgeId(knowledgeId);
String userId = LoginUtil.getCurrentUserId();
if (StringUtils.hasText(userId)) {
delete.setUpdater(userId);
}
int rows = knowledgeMapper.deleteKnowledge(delete);
if (rows > 0) {
logger.info("删除知识库成功: knowledgeId={}", knowledgeId);
return ResultDomain.success("删除知识库成功", true);
}
return ResultDomain.failure("删除知识库失败");
}
/**
* @description 获取知识库详情包含dify知识库各种参数的配置
* @param knowledgeId 知识库ID
* @return ResultDomain<TbKnowledge> 知识库信息
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledge> getKnowledgeById(String knowledgeId) {
if (!StringUtils.hasText(knowledgeId)) {
return ResultDomain.failure("知识库ID不能为空");
}
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
if (knowledge == null) {
return ResultDomain.failure("知识库不存在");
}
return ResultDomain.success("查询成功", knowledge);
}
/**
* @description 查询知识库列表
* @param filter 过滤条件
* @return ResultDomain<TbKnowledge> 知识库列表
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledge> listKnowledges(TbKnowledge filter) {
List<TbKnowledge> list = knowledgeMapper.selectKnowledgeList(filter);
return ResultDomain.success("查询成功", list);
}
/**
* @description 分页查询知识库
* @param filter 过滤条件
* @param pageParam 分页参数
* @return ResultDomain<TbKnowledge> 分页结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledge> pageKnowledges(TbKnowledge filter, PageParam pageParam) {
List<TbKnowledge> list = knowledgeMapper.selectKnowledgePage(filter, pageParam);
long total = knowledgeMapper.countKnowledges(filter);
pageParam.setTotal((int) total);
return ResultDomain.success("查询成功", list);
}
/**
* @description 获取知识库统计信息从Dify同步文档数量和分段数量
* @param knowledgeId 知识库ID
* @return ResultDomain<TbKnowledge> 统计信息
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledge> getKnowledgeStats(String knowledgeId) {
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
if (knowledge == null) {
return ResultDomain.failure("知识库不存在");
}
// 从Dify同步统计信息
if (StringUtils.hasText(knowledge.getDifyDatasetId())) {
try {
DatasetDetailResponse detail = difyApiClient.getDatasetDetail(knowledge.getDifyDatasetId());
if (detail != null) {
TbKnowledge update = new TbKnowledge();
update.setKnowledgeId(knowledgeId);
update.setDocumentCount(detail.getDocumentCount());
update.setTotalChunks(detail.getWordCount());
knowledgeMapper.updateKnowledge(update);
knowledge.setDocumentCount(detail.getDocumentCount());
knowledge.setTotalChunks(detail.getWordCount());
}
} catch (Exception e) {
logger.warn("同步Dify统计信息失败: {}", e.getMessage());
}
}
return ResultDomain.success("查询成功", knowledge);
}
/**
* @description 获取可用的嵌入模型列表
* @return ResultDomain<Map<String, Object>> 嵌入模型列表
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Map<String, Object>> getAvailableEmbeddingModels() {
try {
var response = difyApiClient.getAvailableEmbeddingModels();
Map<String, Object> result = new HashMap<>();
result.put("data", response.getData());
return ResultDomain.success("查询成功", result);
} catch (Exception e) {
logger.error("获取嵌入模型列表失败: {}", e.getMessage(), e);
return ResultDomain.failure("获取嵌入模型列表失败: " + e.getMessage());
}
}
/**
* @description 获取可用的Rerank模型列表
* @return ResultDomain<Map<String, Object>> Rerank模型列表
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Map<String, Object>> getAvailableRerankModels() {
try {
var response = difyApiClient.getAvailableRerankModels();
Map<String, Object> result = new HashMap<>();
result.put("data", response.getData());
return ResultDomain.success("查询成功", result);
} catch (Exception e) {
logger.error("获取Rerank模型列表失败: {}", e.getMessage(), e);
return ResultDomain.failure("获取Rerank模型列表失败: " + e.getMessage());
}
}
/**
* @description 获取知识库文档列表从Dify获取
* @param knowledgeId 知识库ID
* @param page 页码从1开始
* @param limit 每页数量
* @return ResultDomain<Map<String, Object>> 文档列表
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Map<String, Object>> getDocumentList(String knowledgeId, Integer page, Integer limit) {
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
if (knowledge == null || !StringUtils.hasText(knowledge.getDifyDatasetId())) {
return ResultDomain.failure("知识库不存在或未关联Dify");
}
try {
DocumentListResponse response = difyApiClient.listDocuments(knowledge.getDifyDatasetId(), page, limit);
Map<String, Object> result = new HashMap<>();
result.put("data", response.getData());
result.put("total", response.getTotal());
result.put("page", response.getPage());
result.put("limit", response.getLimit());
return ResultDomain.success("查询成功", result);
} catch (Exception e) {
logger.error("获取Dify文档列表失败: {}", e.getMessage(), e);
return ResultDomain.failure("获取文档列表失败: " + e.getMessage());
}
}
// ================================= 文件管理 =================================
/**
* @description 上传文件到知识库完整流程minio + Dify + 数据库)
* @param knowledgeId 知识库ID
* @param file 上传的文件
* @param indexingTechnique 索引方式
* @return ResultDomain<TbKnowledgeFile> 上传结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledgeFile> uploadKnowledgeFile(String knowledgeId, MultipartFile file, String indexingTechnique) {
// 1. 参数校验
if (!StringUtils.hasText(knowledgeId)) {
return ResultDomain.failure("知识库ID不能为空");
}
if (file == null || file.isEmpty()) {
return ResultDomain.failure("文件不能为空");
}
// 2. 获取知识库
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
if (knowledge == null) {
return ResultDomain.failure("知识库不存在");
}
if (!StringUtils.hasText(knowledge.getDifyDatasetId())) {
return ResultDomain.failure("知识库未关联Dify");
}
// 3. 调用FileService上传到minio
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFile(file, "knowledge", knowledgeId);
if (!fileResult.getSuccess() || fileResult.getData() == null) {
return ResultDomain.failure("上传文件到存储服务失败: " + fileResult.getMessage());
}
TbSysFileDTO sysFile = fileResult.getData();
String fileId = sysFile.getFileId();
logger.info("上传文件到minio成功: fileId={}, path={}", fileId, sysFile.getPath());
// 4. 上传到Dify知识库
String difyFileId = null;
File tempFile = null;
try {
tempFile = File.createTempFile("knowledge_", "_" + file.getOriginalFilename());
file.transferTo(tempFile);
String technique = StringUtils.hasText(indexingTechnique) ? indexingTechnique : knowledge.getDifyIndexingTechnique();
ResultDomain<String> difyResult = aiFileUploadService.uploadFileToDify(
knowledge.getDifyDatasetId(), tempFile, file.getOriginalFilename(), technique);
if (difyResult.getSuccess() && StringUtils.hasText(difyResult.getData())) {
difyFileId = difyResult.getData();
logger.info("上传文件到Dify成功: difyFileId={}", difyFileId);
} else {
logger.error("上传文件到Dify失败: {}", difyResult.getMessage());
fileService.deleteFile(fileId);
return ResultDomain.failure("上传文件到Dify失败: " + difyResult.getMessage());
}
} catch (Exception e) {
logger.error("上传文件到Dify异常: {}", e.getMessage(), e);
fileService.deleteFile(fileId);
return ResultDomain.failure("上传文件到Dify异常: " + e.getMessage());
} finally {
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}
}
// 5. 保存tb_knowledge_file
TbKnowledgeFile knowledgeFile = new TbKnowledgeFile();
knowledgeFile.setOptsn(IdUtil.getOptsn());
knowledgeFile.setKnowledgeId(knowledgeId);
knowledgeFile.setFileRootId(fileId);
knowledgeFile.setFileId(fileId);
knowledgeFile.setDifyFileId(difyFileId);
knowledgeFile.setVersion(1);
int rows = knowledgeFileMapper.insertKnowledgeFile(knowledgeFile);
if (rows > 0) {
logger.info("保存知识库文件记录成功: knowledgeId={}, fileId={}, difyFileId={}", knowledgeId, fileId, difyFileId);
return ResultDomain.success("上传成功", knowledgeFile);
}
// 补偿删除
fileService.deleteFile(fileId);
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), difyFileId);
return ResultDomain.failure("保存知识库文件记录失败");
}
/**
* @description 批量上传文件到知识库
* @param knowledgeId 知识库ID
* @param files 文件列表
* @param indexingTechnique 索引方式
* @return ResultDomain<TbKnowledgeFile> 上传结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledgeFile> batchUploadKnowledgeFile(String knowledgeId, List<MultipartFile> files, String indexingTechnique) {
if (!StringUtils.hasText(knowledgeId)) {
return ResultDomain.failure("知识库ID不能为空");
}
if (files == null || files.isEmpty()) {
return ResultDomain.failure("文件列表不能为空");
}
List<TbKnowledgeFile> successList = new ArrayList<>();
List<String> failedFiles = new ArrayList<>();
for (MultipartFile file : files) {
ResultDomain<TbKnowledgeFile> result = uploadKnowledgeFile(knowledgeId, file, indexingTechnique);
if (result.getSuccess() && result.getData() != null) {
successList.add(result.getData());
} else {
failedFiles.add(file.getOriginalFilename() + "(" + result.getMessage() + ")");
}
}
if (failedFiles.isEmpty()) {
return ResultDomain.success("批量上传成功", successList);
} else if (successList.isEmpty()) {
return ResultDomain.failure("批量上传全部失败: " + String.join(", ", failedFiles));
} else {
return ResultDomain.success("部分上传成功,失败: " + String.join(", ", failedFiles), successList);
}
}
/**
* @description 上传新版本文件fileRootId一致version递增
* @param knowledgeId 知识库ID
* @param file 新文件
* @param fileRootId 文件根ID
* @return ResultDomain<TbKnowledgeFile> 新版本文件信息
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledgeFile> updateKnowledgeFileVersion(String knowledgeId, MultipartFile file, String fileRootId) {
// 1. 参数校验
if (!StringUtils.hasText(knowledgeId)) {
return ResultDomain.failure("知识库ID不能为空");
}
if (file == null || file.isEmpty()) {
return ResultDomain.failure("文件不能为空");
}
if (!StringUtils.hasText(fileRootId)) {
return ResultDomain.failure("文件根ID不能为空");
}
// 2. 获取知识库
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
if (knowledge == null) {
return ResultDomain.failure("知识库不存在");
}
if (!StringUtils.hasText(knowledge.getDifyDatasetId())) {
return ResultDomain.failure("知识库未关联Dify");
}
// 3. 获取旧版本
List<TbKnowledgeFile> oldVersions = knowledgeFileMapper.selectFileVersions(fileRootId);
if (oldVersions == null || oldVersions.isEmpty()) {
return ResultDomain.failure("原文件不存在");
}
// 4. 上传新版本到minio
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileVersion(file, "knowledge", knowledgeId, fileRootId);
if (!fileResult.getSuccess() || fileResult.getData() == null) {
return ResultDomain.failure("上传新版本文件失败: " + fileResult.getMessage());
}
TbSysFileDTO sysFile = fileResult.getData();
String newFileId = sysFile.getFileId();
int newVersion = sysFile.getVersion();
logger.info("上传新版本到minio成功: fileId={}, version={}", newFileId, newVersion);
// 5. 删除Dify旧文档
for (TbKnowledgeFile oldFile : oldVersions) {
if (StringUtils.hasText(oldFile.getDifyFileId())) {
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), oldFile.getDifyFileId());
}
}
// 6. 上传新文件到Dify
String newDifyFileId = null;
File tempFile = null;
try {
tempFile = File.createTempFile("knowledge_update_", "_" + file.getOriginalFilename());
file.transferTo(tempFile);
ResultDomain<String> difyResult = aiFileUploadService.uploadFileToDify(
knowledge.getDifyDatasetId(), tempFile, file.getOriginalFilename(), knowledge.getDifyIndexingTechnique());
if (difyResult.getSuccess() && StringUtils.hasText(difyResult.getData())) {
newDifyFileId = difyResult.getData();
} else {
fileService.deleteFile(newFileId);
return ResultDomain.failure("上传新文件到Dify失败: " + difyResult.getMessage());
}
} catch (Exception e) {
logger.error("上传新文件到Dify异常: {}", e.getMessage(), e);
fileService.deleteFile(newFileId);
return ResultDomain.failure("上传新文件到Dify异常: " + e.getMessage());
} finally {
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}
}
// 7. 保存tb_knowledge_file
TbKnowledgeFile newKnowledgeFile = new TbKnowledgeFile();
newKnowledgeFile.setOptsn(IdUtil.getOptsn());
newKnowledgeFile.setKnowledgeId(knowledgeId);
newKnowledgeFile.setFileRootId(fileRootId);
newKnowledgeFile.setFileId(newFileId);
newKnowledgeFile.setDifyFileId(newDifyFileId);
newKnowledgeFile.setVersion(newVersion);
int rows = knowledgeFileMapper.insertKnowledgeFile(newKnowledgeFile);
if (rows > 0) {
logger.info("保存新版本记录成功: knowledgeId={}, fileRootId={}, newVersion={}", knowledgeId, fileRootId, newVersion);
return ResultDomain.success("更新成功", newKnowledgeFile);
}
// 补偿
fileService.deleteFile(newFileId);
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), newDifyFileId);
return ResultDomain.failure("保存新版本记录失败");
}
/**
* @description 删除知识库文件根据fileRootId删除所有版本
* @param fileRootId 文件根ID
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<Boolean> deleteKnowledgeFileById(String fileRootId) {
if (!StringUtils.hasText(fileRootId)) {
return ResultDomain.failure("文件根ID不能为空");
}
// 1. 获取所有版本
List<TbKnowledgeFile> versions = knowledgeFileMapper.selectFileVersions(fileRootId);
if (versions == null || versions.isEmpty()) {
return ResultDomain.failure("文件不存在");
}
// 2. 删除Dify中的文档
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(versions.get(0).getKnowledgeId());
if (knowledge != null && StringUtils.hasText(knowledge.getDifyDatasetId())) {
List<String> difyDocIds = new ArrayList<>();
for (TbKnowledgeFile file : versions) {
if (StringUtils.hasText(file.getDifyFileId())) {
difyDocIds.add(file.getDifyFileId());
}
}
if (!difyDocIds.isEmpty()) {
aiFileUploadService.batchDeleteFilesFromDify(knowledge.getDifyDatasetId(), difyDocIds);
}
}
// 3. 软删除本地记录和minio文件
int rows = knowledgeFileMapper.deleteFilesByRootId(fileRootId);
if (rows > 0) {
logger.info("删除知识库文件成功: fileRootId={}", fileRootId);
for (TbKnowledgeFile file : versions) {
fileService.deleteFile(file.getFileId());
}
return ResultDomain.success("删除成功", true);
}
return ResultDomain.failure("删除文件失败");
}
/**
* @description 获取文件历史版本
* @param fileRootId 文件根ID
* @return ResultDomain<TbKnowledgeFile> 文件历史版本列表
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbKnowledgeFile> getKnowledgeFileHistory(String fileRootId) {
if (!StringUtils.hasText(fileRootId)) {
return ResultDomain.failure("文件根ID不能为空");
}
List<TbKnowledgeFile> versions = knowledgeFileMapper.selectFileVersions(fileRootId);
return ResultDomain.success("查询成功", versions);
}
}

View File

@@ -5,18 +5,19 @@ server:
# context-path: /urban-lifeline/agent # 微服务架构下context-path由Gateway管理 # context-path: /urban-lifeline/agent # 微服务架构下context-path由Gateway管理
# ================== Auth ==================== # ================== Auth ====================
urban-lifeline: auth:
auth: enabled: true
enabled: true gateway-mode: true
whitelist: whitelist:
- /swagger-ui/** - /swagger-ui/**
- /swagger-ui.html - /swagger-ui.html
- /v3/api-docs/** - /v3/api-docs/**
- /webjars/** - /webjars/**
- /favicon.ico - /favicon.ico
- /error - /error
- /actuator/health - /actuator/health
- /actuator/info - /actuator/info
- /ai/chat/* # AI对话有非系统用户对话的接口无登录状态
security: security:
aes: aes:

View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.ai.mapper.TbAgentMapper">
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbAgent">
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="agent_id" property="agentId" jdbcType="VARCHAR"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
<result column="link" property="link" jdbcType="VARCHAR"/>
<result column="api_key" property="apiKey" jdbcType="VARCHAR"/>
<result column="outer" property="outer" jdbcType="BOOLEAN"/>
<result column="introduce" property="introduce" jdbcType="VARCHAR"/>
<result column="prompt_cards" property="promptCards" jdbcType="OTHER" typeHandler="org.xyzh.ai.handler.PromptCardsTypeHandler"/>
<result column="category" property="category" jdbcType="VARCHAR"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="updater" property="updater" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<sql id="Base_Column_List">
optsn, agent_id, name, description, link, api_key, outer, introduce, prompt_cards,
category, creator, updater, create_time, update_time, delete_time, deleted
</sql>
<insert id="insertAgent" parameterType="org.xyzh.api.ai.dto.TbAgent">
INSERT INTO ai.tb_agent (
optsn, agent_id, name, api_key, introduce, category
<if test="outer !=null">, outer</if>
<if test="description != null">, description</if>
<if test="link != null">, link</if>
<if test="promptCards != null">, prompt_cards</if>
<if test="creator != null">, creator</if>
) VALUES (
#{optsn}, #{agentId}, #{name}, #{apiKey}, #{introduce}, #{category}
<if test="outer !=null">, #{outer}</if>
<if test="description != null">, #{description}</if>
<if test="link != null">, #{link}</if>
<if test="promptCards != null">, #{promptCards, typeHandler=org.xyzh.ai.handler.PromptCardsTypeHandler}</if>
<if test="creator != null">, #{creator}</if>
)
</insert>
<update id="updateAgent" parameterType="org.xyzh.api.ai.dto.TbAgent">
UPDATE ai.tb_agent
<set>
<if test="name != null">name = #{name},</if>
<if test="description != null">description = #{description},</if>
<if test="link != null">link = #{link},</if>
<if test="apiKey != null">api_key = #{apiKey},</if>
<if test="outer != null">outer = #{outer},</if>
<if test="introduce != null">introduce = #{introduce},</if>
<if test="promptCards != null">prompt_cards = #{promptCards, typeHandler=org.xyzh.ai.handler.PromptCardsTypeHandler},</if>
<if test="category != null">category = #{category},</if>
<if test="updater != null">updater = #{updater},</if>
update_time = now()
</set>
WHERE agent_id = #{agentId} AND deleted = false
</update>
<update id="deleteAgent" parameterType="org.xyzh.api.ai.dto.TbAgent">
UPDATE ai.tb_agent
SET deleted = true,
delete_time = now(),
updater = #{updater}
WHERE agent_id = #{agentId} AND deleted = false
</update>
<select id="selectAgentById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_agent
WHERE agent_id = #{agentId} AND deleted = false
</select>
<select id="selectAgentByApiKey" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_agent
WHERE api_key = #{apiKey} AND deleted = false
</select>
<select id="selectAgentList" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_agent
WHERE deleted = false
<if test="filter != null">
<if test="filter.name != null and filter.name != ''">
AND name LIKE CONCAT('%', #{filter.name}, '%')
</if>
<if test="filter.category != null and filter.category != ''">
AND category = #{filter.category}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
</if>
ORDER BY create_time DESC
</select>
<select id="selectAgentPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_agent
WHERE deleted = false
<if test="filter != null">
<if test="filter.name != null and filter.name != ''">
AND name LIKE CONCAT('%', #{filter.name}, '%')
</if>
<if test="filter.category != null and filter.category != ''">
AND category = #{filter.category}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
</if>
ORDER BY create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<select id="countAgents" resultType="long">
SELECT COUNT(*)
FROM ai.tb_agent
WHERE deleted = false
<if test="filter != null">
<if test="filter.name != null and filter.name != ''">
AND name LIKE CONCAT('%', #{filter.name}, '%')
</if>
<if test="filter.category != null and filter.category != ''">
AND category = #{filter.category}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
</if>
</select>
<select id="countByName" resultType="int">
SELECT COUNT(*)
FROM ai.tb_agent
WHERE deleted = false AND name = #{name}
<if test="excludeId != null and excludeId != ''">
AND agent_id != #{excludeId}
</if>
</select>
</mapper>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.ai.mapper.TbChatMapper">
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbChat">
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="chat_id" property="chatId" jdbcType="VARCHAR"/>
<result column="agent_id" property="agentId" jdbcType="VARCHAR"/>
<result column="user_id" property="userId" jdbcType="VARCHAR"/>
<result column="user_type" property="userType" jdbcType="BOOLEAN"/>
<result column="title" property="title" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<sql id="Base_Column_List">
optsn, chat_id, agent_id, user_id, user_type, title,
create_time, update_time, delete_time, deleted
</sql>
<insert id="insertChat" parameterType="org.xyzh.api.ai.dto.TbChat">
INSERT INTO ai.tb_chat (
optsn, chat_id, agent_id, user_id, title
<if test="userType != null">, user_type</if>
) VALUES (
#{optsn}, #{chatId}, #{agentId}, #{userId}, #{title}
<if test="userType != null">, #{userType}</if>
)
</insert>
<update id="updateChat" parameterType="org.xyzh.api.ai.dto.TbChat">
UPDATE ai.tb_chat
<set>
<if test="title != null">title = #{title},</if>
update_time = now()
</set>
WHERE chat_id = #{chatId} AND deleted = false
</update>
<update id="deleteChat" parameterType="org.xyzh.api.ai.dto.TbChat">
UPDATE ai.tb_chat
SET deleted = true,
delete_time = now()
WHERE chat_id = #{chatId} AND deleted = false
</update>
<select id="selectChatById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_chat
WHERE chat_id = #{chatId} AND deleted = false
</select>
<select id="selectChatList" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_chat
WHERE deleted = false
<if test="agentId != null and agentId != ''">
AND agent_id = #{agentId}
</if>
<if test="userId != null and userId != ''">
AND user_id = #{userId}
</if>
ORDER BY update_time DESC, create_time DESC
</select>
<select id="selectChatPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_chat
WHERE deleted = false
<if test="filter != null">
<if test="filter.agentId != null and filter.agentId != ''">
AND agent_id = #{filter.agentId}
</if>
<if test="filter.userId != null and filter.userId != ''">
AND user_id = #{filter.userId}
</if>
<if test="filter.title != null and filter.title != ''">
AND title LIKE CONCAT('%', #{filter.title}, '%')
</if>
</if>
ORDER BY update_time DESC, create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<select id="countChats" resultType="long">
SELECT COUNT(*)
FROM ai.tb_chat
WHERE deleted = false
<if test="filter != null">
<if test="filter.agentId != null and filter.agentId != ''">
AND agent_id = #{filter.agentId}
</if>
<if test="filter.userId != null and filter.userId != ''">
AND user_id = #{filter.userId}
</if>
<if test="filter.title != null and filter.title != ''">
AND title LIKE CONCAT('%', #{filter.title}, '%')
</if>
</if>
</select>
</mapper>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.ai.mapper.TbChatMessageMapper">
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbChatMessage">
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="message_id" property="messageId" jdbcType="VARCHAR"/>
<result column="dify_message_id" property="difyMessageId" jdbcType="VARCHAR"/>
<result column="chat_id" property="chatId" jdbcType="VARCHAR"/>
<result column="role" property="role" jdbcType="VARCHAR"/>
<result column="content" property="content" jdbcType="VARCHAR"/>
<result column="files" property="files" jdbcType="ARRAY" typeHandler="org.xyzh.common.utils.json.StringArrayTypeHandler"/>
<result column="comment" property="comment" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<sql id="Base_Column_List">
optsn, message_id, dify_message_id, chat_id, role, content, files, comment,
create_time, update_time, delete_time, deleted
</sql>
<insert id="insertChatMessage" parameterType="org.xyzh.api.ai.dto.TbChatMessage">
INSERT INTO ai.tb_chat_message (
optsn, message_id, chat_id, role, content
<if test="difyMessageId != null">, dify_message_id</if>
<if test="files != null">, files</if>
<if test="comment != null">, comment</if>
) VALUES (
#{optsn}, #{messageId}, #{chatId}, #{role}, #{content}
<if test="difyMessageId != null">, #{difyMessageId}</if>
<if test="files != null">, #{files, typeHandler=org.xyzh.common.utils.json.StringArrayTypeHandler}</if>
<if test="comment != null">, #{comment}</if>
)
</insert>
<update id="updateChatMessage" parameterType="org.xyzh.api.ai.dto.TbChatMessage">
UPDATE ai.tb_chat_message
<set>
<if test="content != null">content = #{content},</if>
<if test="comment != null">comment = #{comment},</if>
update_time = now()
</set>
WHERE message_id = #{messageId} AND deleted = false
</update>
<update id="deleteChatMessage" parameterType="org.xyzh.api.ai.dto.TbChatMessage">
UPDATE ai.tb_chat_message
SET deleted = true,
delete_time = now()
WHERE message_id = #{messageId} AND deleted = false
</update>
<select id="selectMessageById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_chat_message
WHERE message_id = #{messageId} AND deleted = false
</select>
<select id="selectMessagesByChatId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_chat_message
WHERE chat_id = #{chatId} AND deleted = false
ORDER BY create_time ASC
</select>
<select id="selectMessagePage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_chat_message
WHERE chat_id = #{chatId} AND deleted = false
ORDER BY create_time ASC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<select id="countMessages" resultType="long">
SELECT COUNT(*)
FROM ai.tb_chat_message
WHERE chat_id = #{chatId} AND deleted = false
</select>
<update id="deleteMessagesByChatId">
UPDATE ai.tb_chat_message
SET deleted = true,
delete_time = now()
WHERE chat_id = #{chatId} AND deleted = false
</update>
</mapper>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.ai.mapper.TbKnowledgeFileMapper">
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbKnowledgeFile">
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="knowledge_id" property="knowledgeId" jdbcType="VARCHAR"/>
<result column="file_root_id" property="fileRootId" jdbcType="VARCHAR"/>
<result column="file_id" property="fileId" jdbcType="VARCHAR"/>
<result column="dify_file_id" property="difyFileId" jdbcType="VARCHAR"/>
<result column="version" property="version" jdbcType="INTEGER"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<sql id="Base_Column_List">
optsn, knowledge_id, file_root_id, file_id, dify_file_id, version,
create_time, update_time, delete_time, deleted
</sql>
<insert id="insertKnowledgeFile" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFile">
INSERT INTO ai.tb_knowledge_file (
optsn, knowledge_id, file_root_id, file_id, dify_file_id
<if test="version != null">, version</if>
) VALUES (
#{optsn}, #{knowledgeId}, #{fileRootId}, #{fileId}, #{difyFileId}
<if test="version != null">, #{version}</if>
)
</insert>
<update id="updateKnowledgeFile" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFile">
UPDATE ai.tb_knowledge_file
<set>
<if test="difyFileId != null">dify_file_id = #{difyFileId},</if>
<if test="version != null">version = #{version},</if>
update_time = now()
</set>
WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false
</update>
<update id="deleteKnowledgeFile" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFile">
UPDATE ai.tb_knowledge_file
SET deleted = true,
delete_time = now()
WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false
</update>
<select id="selectKnowledgeFile" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge_file
WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false
</select>
<select id="selectFilesByKnowledgeId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge_file
WHERE knowledge_id = #{knowledgeId} AND deleted = false
ORDER BY create_time DESC
</select>
<select id="selectFileVersions" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge_file
WHERE file_root_id = #{fileRootId} AND deleted = false
ORDER BY version DESC
</select>
<select id="selectFilePage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge_file
WHERE knowledge_id = #{knowledgeId} AND deleted = false
ORDER BY create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<select id="countFiles" resultType="long">
SELECT COUNT(*)
FROM ai.tb_knowledge_file
WHERE knowledge_id = #{knowledgeId} AND deleted = false
</select>
<update id="deleteFilesByRootId">
UPDATE ai.tb_knowledge_file
SET deleted = true,
delete_time = now()
WHERE file_root_id = #{fileRootId} AND deleted = false
</update>
<select id="selectLatestVersion" resultType="java.lang.Integer">
SELECT COALESCE(MAX(version), 0)
FROM ai.tb_knowledge_file
WHERE knowledge_id = #{knowledgeId} AND file_root_id = #{fileRootId}
</select>
</mapper>

View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.ai.mapper.TbKnowledgeMapper">
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbKnowledge">
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="knowledge_id" property="knowledgeId" jdbcType="VARCHAR"/>
<result column="title" property="title" jdbcType="VARCHAR"/>
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
<result column="dify_dataset_id" property="difyDatasetId" jdbcType="VARCHAR"/>
<result column="dify_indexing_technique" property="difyIndexingTechnique" jdbcType="VARCHAR"/>
<result column="embedding_model" property="embeddingModel" jdbcType="VARCHAR"/>
<result column="embedding_model_provider" property="embeddingModelProvider" jdbcType="VARCHAR"/>
<result column="rerank_model" property="rerankModel" jdbcType="VARCHAR"/>
<result column="rerank_model_provider" property="rerankModelProvider" jdbcType="VARCHAR"/>
<result column="reranking_enable" property="rerankingEnable" jdbcType="INTEGER"/>
<result column="retrieval_top_k" property="retrievalTopK" jdbcType="INTEGER"/>
<result column="retrieval_score_threshold" property="retrievalScoreThreshold" jdbcType="DECIMAL"/>
<result column="document_count" property="documentCount" jdbcType="INTEGER"/>
<result column="total_chunks" property="totalChunks" jdbcType="INTEGER"/>
<result column="service" property="service" jdbcType="VARCHAR"/>
<result column="project_id" property="projectId" jdbcType="VARCHAR"/>
<result column="category" property="category" jdbcType="VARCHAR"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="dept_path" property="deptPath" jdbcType="VARCHAR"/>
<result column="updater" property="updater" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<sql id="Base_Column_List">
optsn, knowledge_id, title, avatar, description, dify_dataset_id,
dify_indexing_technique, embedding_model, embedding_model_provider,
rerank_model, rerank_model_provider, reranking_enable,
retrieval_top_k, retrieval_score_threshold, document_count, total_chunks,
service, project_id, category, creator, dept_path, updater,
create_time, update_time, delete_time, deleted
</sql>
<insert id="insertKnowledge" parameterType="org.xyzh.api.ai.dto.TbKnowledge">
INSERT INTO ai.tb_knowledge (
optsn, knowledge_id, title, creator
<if test="avatar != null">, avatar</if>
<if test="description != null">, description</if>
<if test="difyDatasetId != null">, dify_dataset_id</if>
<if test="difyIndexingTechnique != null">, dify_indexing_technique</if>
<if test="embeddingModel != null">, embedding_model</if>
<if test="embeddingModelProvider != null">, embedding_model_provider</if>
<if test="rerankModel != null">, rerank_model</if>
<if test="rerankModelProvider != null">, rerank_model_provider</if>
<if test="rerankingEnable != null">, reranking_enable</if>
<if test="retrievalTopK != null">, retrieval_top_k</if>
<if test="retrievalScoreThreshold != null">, retrieval_score_threshold</if>
<if test="service != null">, service</if>
<if test="projectId != null">, project_id</if>
<if test="category != null">, category</if>
<if test="deptPath != null">, dept_path</if>
) VALUES (
#{optsn}, #{knowledgeId}, #{title}, #{creator}
<if test="avatar != null">, #{avatar}</if>
<if test="description != null">, #{description}</if>
<if test="difyDatasetId != null">, #{difyDatasetId}</if>
<if test="difyIndexingTechnique != null">, #{difyIndexingTechnique}</if>
<if test="embeddingModel != null">, #{embeddingModel}</if>
<if test="embeddingModelProvider != null">, #{embeddingModelProvider}</if>
<if test="rerankModel != null">, #{rerankModel}</if>
<if test="rerankModelProvider != null">, #{rerankModelProvider}</if>
<if test="rerankingEnable != null">, #{rerankingEnable}</if>
<if test="retrievalTopK != null">, #{retrievalTopK}</if>
<if test="retrievalScoreThreshold != null">, #{retrievalScoreThreshold}</if>
<if test="service != null">, #{service}</if>
<if test="projectId != null">, #{projectId}</if>
<if test="category != null">, #{category}</if>
<if test="deptPath != null">, #{deptPath}</if>
)
</insert>
<update id="updateKnowledge" parameterType="org.xyzh.api.ai.dto.TbKnowledge">
UPDATE ai.tb_knowledge
<set>
<if test="title != null">title = #{title},</if>
<if test="avatar != null">avatar = #{avatar},</if>
<if test="description != null">description = #{description},</if>
<if test="difyDatasetId != null">dify_dataset_id = #{difyDatasetId},</if>
<if test="difyIndexingTechnique != null">dify_indexing_technique = #{difyIndexingTechnique},</if>
<if test="embeddingModel != null">embedding_model = #{embeddingModel},</if>
<if test="embeddingModelProvider != null">embedding_model_provider = #{embeddingModelProvider},</if>
<if test="rerankModel != null">rerank_model = #{rerankModel},</if>
<if test="rerankModelProvider != null">rerank_model_provider = #{rerankModelProvider},</if>
<if test="rerankingEnable != null">reranking_enable = #{rerankingEnable},</if>
<if test="retrievalTopK != null">retrieval_top_k = #{retrievalTopK},</if>
<if test="retrievalScoreThreshold != null">retrieval_score_threshold = #{retrievalScoreThreshold},</if>
<if test="documentCount != null">document_count = #{documentCount},</if>
<if test="totalChunks != null">total_chunks = #{totalChunks},</if>
<if test="service != null">service = #{service},</if>
<if test="projectId != null">project_id = #{projectId},</if>
<if test="category != null">category = #{category},</if>
<if test="updater != null">updater = #{updater},</if>
update_time = now()
</set>
WHERE knowledge_id = #{knowledgeId} AND deleted = false
</update>
<update id="deleteKnowledge" parameterType="org.xyzh.api.ai.dto.TbKnowledge">
UPDATE ai.tb_knowledge
SET deleted = true,
delete_time = now(),
updater = #{updater}
WHERE knowledge_id = #{knowledgeId} AND deleted = false
</update>
<select id="selectKnowledgeById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge
WHERE knowledge_id = #{knowledgeId} AND deleted = false
</select>
<select id="selectKnowledgeByDifyId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge
WHERE dify_dataset_id = #{difyDatasetId} AND deleted = false
</select>
<select id="selectKnowledgeList" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge
WHERE deleted = false
<if test="filter != null">
<if test="filter.title != null and filter.title != ''">
AND title LIKE CONCAT('%', #{filter.title}, '%')
</if>
<if test="filter.service != null and filter.service != ''">
AND service = #{filter.service}
</if>
<if test="filter.projectId != null and filter.projectId != ''">
AND project_id = #{filter.projectId}
</if>
<if test="filter.category != null and filter.category != ''">
AND category = #{filter.category}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
</if>
ORDER BY create_time DESC
</select>
<select id="selectKnowledgePage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM ai.tb_knowledge
WHERE deleted = false
<if test="filter != null">
<if test="filter.title != null and filter.title != ''">
AND title LIKE CONCAT('%', #{filter.title}, '%')
</if>
<if test="filter.service != null and filter.service != ''">
AND service = #{filter.service}
</if>
<if test="filter.projectId != null and filter.projectId != ''">
AND project_id = #{filter.projectId}
</if>
<if test="filter.category != null and filter.category != ''">
AND category = #{filter.category}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
</if>
ORDER BY create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<select id="countKnowledges" resultType="long">
SELECT COUNT(*)
FROM ai.tb_knowledge
WHERE deleted = false
<if test="filter != null">
<if test="filter.title != null and filter.title != ''">
AND title LIKE CONCAT('%', #{filter.title}, '%')
</if>
<if test="filter.service != null and filter.service != ''">
AND service = #{filter.service}
</if>
<if test="filter.projectId != null and filter.projectId != ''">
AND project_id = #{filter.projectId}
</if>
<if test="filter.category != null and filter.category != ''">
AND category = #{filter.category}
</if>
<if test="filter.creator != null and filter.creator != ''">
AND creator = #{filter.creator}
</if>
</if>
</select>
</mapper>

View File

@@ -0,0 +1,32 @@
package org.xyzh.api.ai.dto;
import java.io.Serializable;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "对话消息数据预处理对象")
public class ChatPrepareData implements Serializable {
private final static long serialVersionUID = 1L;
@Schema(description = "智能体Id")
private String agentId;
@Schema(description = "对话Id")
private String chatId;
@Schema(description = "用户问题")
private String query;
@Schema(description = "本次对话携带的dify文件对象")
private List<DifyFileInfo> files;
@Schema(description = "用户ID来客传wechatId员工传userId")
private String userId;
@Schema(description = "用户类型false=来客true=员工)")
private Boolean userType;
}

View File

@@ -29,6 +29,9 @@ public class TbAgent extends BaseDTO{
@Schema(description = "智能体APIKEY") @Schema(description = "智能体APIKEY")
private String apiKey; private String apiKey;
@Schema(description = "是否是对外智能体,未登录可用")
private Boolean outer;
@Schema(description = "引导词") @Schema(description = "引导词")
private String introduce; private String introduce;

View File

@@ -15,6 +15,9 @@ public class TbChatMessage extends BaseDTO{
@Schema(description = "消息ID") @Schema(description = "消息ID")
private String messageId; private String messageId;
@Schema(description = "Dify消息ID")
private String difyMessageId;
@Schema(description = "对话ID") @Schema(description = "对话ID")
private String chatId; private String chatId;

View File

@@ -61,7 +61,7 @@ public class TbKnowledge extends BaseDTO{
@Schema(description = "bidding所属项目ID") @Schema(description = "bidding所属项目ID")
private String projectId; private String projectId;
@Schema(description = "所属分类 workcase 内部知识库、外部知识库") @Schema(description = "所属分类 workcase内部知识库、外部知识库其他服务default")
private String category; private String category;

View File

@@ -23,6 +23,6 @@ public class TbKnowledgeFile extends BaseDTO{
private String difyFileId; private String difyFileId;
@Schema(description = "文件版本") @Schema(description = "文件版本")
private String version; private Integer version;
} }

View File

@@ -1,64 +1,62 @@
package org.xyzh.api.ai.service; package org.xyzh.api.ai.service;
import java.io.File;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.xyzh.api.ai.dto.TbKnowledgeFile;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageParam;
/**
* @description AI文件上传服务只负责与Dify交互不处理minio和数据库
* @author yslg
* @since 2025-12-18
*/
public interface AIFileUploadService { public interface AIFileUploadService {
// ============================ 对话文件管理 ============================ // ============================ 对话文件管理 ============================
/**
* 上传文件用于对话(图文多模态)
* @param file 上传的文件
* @param agentId 智能体ID
* @return Dify文件信息包含id、name、size等
*/
ResultDomain<Map<String, Object>> uploadFileForChat(
MultipartFile file,
String agentId
);
/**
* @description 上传文件用于对话(图文多模态)
* @param file 上传的文件
* @param agentId 智能体ID
* @return ResultDomain<Map<String, Object>> Dify文件信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<Map<String, Object>> uploadFileForChat(MultipartFile file, String agentId);
// ============================ 知识库文件管理 ============================ // ============================ 知识库Dify文档管理 ============================
/** /**
* 上传文件到知识库(同步到Dify * @description 上传文件到Dify知识库(只负责Dify上传不处理minio和数据库
* @param knowledgeId 知识库ID * @param difyDatasetId Dify知识库ID
* @param file 上传的文件 * @param file 文件
* @param indexingTechnique 索引方式(可选) * @param fileName 文件名
* @return 上传结果 * @param indexingTechnique 索引方式
* @return ResultDomain<String> Dify文档ID
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledgeFile> uploadToKnowledge( ResultDomain<String> uploadFileToDify(String difyDatasetId, File file, String fileName, String indexingTechnique);
String knowledgeId,
MultipartFile file,
String indexingTechnique
);
/** /**
* 批量上传文件到知识库 * @description 从Dify知识库删除文档
* @param knowledgeId 知识库ID * @param difyDatasetId Dify知识库ID
* @param files 上传的文件列表 * @param difyDocumentId Dify文档ID
* @param indexingTechnique 索引方式(可选) * @return ResultDomain<Boolean> 删除结果
* @return 上传结果列表 * @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledgeFile> batchUploadToKnowledge( ResultDomain<Boolean> deleteFileFromDify(String difyDatasetId, String difyDocumentId);
String knowledgeId,
List<MultipartFile> files,
String indexingTechnique
);
/** /**
* 同时删除Dify中的文档 * @description 批量从Dify知识库删除文档
* @param fileId dify的文件ID * @param difyDatasetId Dify知识库ID
* @return 删除结果 * @param difyDocumentIds Dify文档ID列表
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<Boolean> deleteKnowledgeFile(String fileId); ResultDomain<Boolean> batchDeleteFilesFromDify(String difyDatasetId, List<String> difyDocumentIds);
} }

View File

@@ -1,9 +1,7 @@
package org.xyzh.api.ai.service; package org.xyzh.api.ai.service;
import java.util.List;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.xyzh.api.ai.dto.DifyFileInfo; import org.xyzh.api.ai.dto.ChatPrepareData;
import org.xyzh.api.ai.dto.TbChat; import org.xyzh.api.ai.dto.TbChat;
import org.xyzh.api.ai.dto.TbChatMessage; import org.xyzh.api.ai.dto.TbChatMessage;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
@@ -17,7 +15,7 @@ public interface AgentChatService {
* @param title 会话标题(可选) * @param title 会话标题(可选)
* @return 会话信息 * @return 会话信息
*/ */
ResultDomain<TbChat> createChat(String agentId, String title); ResultDomain<TbChat> createChat(TbChat chat);
/** /**
* 更新会话名称 * 更新会话名称
@@ -26,7 +24,7 @@ public interface AgentChatService {
* @param title 会话标题(可选) * @param title 会话标题(可选)
* @return 会话信息 * @return 会话信息
*/ */
ResultDomain<TbChat> updateChat(String agentId, String chatId, String title); ResultDomain<TbChat> updateChat(TbChat chat);
/** /**
* 删除会话 * 删除会话
@@ -34,40 +32,31 @@ public interface AgentChatService {
* @param chatId 会话ID * @param chatId 会话ID
* @return 会话信息 * @return 会话信息
*/ */
ResultDomain<TbChat> deleteChat(String agentId, String chatId); ResultDomain<TbChat> deleteChat(TbChat chat);
/** /**
* 获取会话列表 * 获取会话列表
* @param agentId 智能体ID * @param agentId 智能体ID
* @return 会话列表 * @return 会话列表
*/ */
ResultDomain<TbChat> getChatList(String agentId); ResultDomain<TbChat> getChatList(TbChat filter);
// ====================== 智能体聊天管理 ====================== // ====================== 智能体聊天管理 ======================
/** /**
* 获取会话消息列表 * 获取会话消息列表
* @param agentId 智能体ID * @param filter 会话过滤条件包含agentId, chatId, userId, userType
* @param chatId 会话ID
* @return 会话消息列表 * @return 会话消息列表
*/ */
ResultDomain<TbChatMessage> getChatMessageList(String agentId, String chatId); ResultDomain<TbChatMessage> getChatMessageList(TbChat filter);
/** /**
* 准备聊天数据POST传递复杂参数 * 准备聊天数据POST传递复杂参数
* @param agentId 智能体ID * @param prepareData 对话准备数据包含agentId, chatId, query, files, userId, userType
* @param chatId 会话ID可选为空则创建新会话
* @param query 用户问题
* @param filesData 上传的文件列表Dify文件信息
* @return ResultDomain<String> 返回sessionId * @return ResultDomain<String> 返回sessionId
*/ */
ResultDomain<String> prepareChatMessageSession( ResultDomain<String> prepareChatMessageSession(ChatPrepareData prepareData);
String agentId,
String chatId,
String query,
List<DifyFileInfo> filesData
);
/** /**
* 流式对话SSE- 使用sessionId建立SSE连接 产生chatMessage * 流式对话SSE- 使用sessionId建立SSE连接 产生chatMessage
@@ -78,21 +67,20 @@ public interface AgentChatService {
/** /**
* 停止对话生成通过Dify TaskID * 停止对话生成通过Dify TaskID
* @param filter 会话过滤条件包含agentId, userId, userType
* @param taskId Dify任务ID * @param taskId Dify任务ID
* @param agentId 智能体ID
* @return 停止结果 * @return 停止结果
*/ */
ResultDomain<Boolean> stopChatMessageByTaskId(String taskId, String agentId); ResultDomain<Boolean> stopChatMessageByTaskId(TbChat filter, String taskId);
/** /**
* 评价 * 评价
* @param agentId 智能体ID * @param filter 会话过滤条件包含agentId, chatId, userId, userType
* @param chatId 会话ID
* @param messageId 消息ID * @param messageId 消息ID
* @param comment 评价 * @param comment 评价
* @return 评价结果 * @return 评价结果
*/ */
ResultDomain<Boolean> commentChatMessage(String agentId, String chatId, String messageId, String comment); ResultDomain<Boolean> commentChatMessage(TbChat filter, String messageId, String comment);
} }

View File

@@ -2,6 +2,7 @@ package org.xyzh.api.ai.service;
import org.xyzh.api.ai.dto.TbAgent; import org.xyzh.api.ai.dto.TbAgent;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
/** /**
* Agent服务接口 * Agent服务接口
@@ -12,6 +13,8 @@ public interface AgentService {
/** /**
* 添加Agent, 用户自己在dify搭建发布后在本服务进行接入 * 添加Agent, 用户自己在dify搭建发布后在本服务进行接入
* @param tbAgent * @param tbAgent
* @author yslg
* @since 2025-12-17
* @return * @return
*/ */
ResultDomain<TbAgent> addAgent(TbAgent tbAgent); ResultDomain<TbAgent> addAgent(TbAgent tbAgent);
@@ -19,6 +22,8 @@ public interface AgentService {
/** /**
* 更新Agent * 更新Agent
* @param tbAgent * @param tbAgent
* @author yslg
* @since 2025-12-17
* @return * @return
*/ */
ResultDomain<TbAgent> updateAgent(TbAgent tbAgent); ResultDomain<TbAgent> updateAgent(TbAgent tbAgent);
@@ -26,7 +31,27 @@ public interface AgentService {
/** /**
* 删除Agent * 删除Agent
* @param tbAgent * @param tbAgent
* @author yslg
* @since 2025-12-17
* @return * @return
*/ */
ResultDomain<TbAgent> deleteAgent(TbAgent tbAgent); ResultDomain<TbAgent> deleteAgent(TbAgent tbAgent);
ResultDomain<TbAgent> selectAgentById(String agentId);
/**
* @description 分页获取当前用户可用的agent智能体
* @param pageRequest
* @author yslg
* @since 2025-12-17
*/
ResultDomain<TbAgent> getAgentPage(PageRequest<TbAgent> pageRequest);
/**
* @description 获取当前用户所有可用智能体列表
* @param tbAgent
* @author yslg
* @since 2025-12-17
*/
ResultDomain<TbAgent> getAgentList(TbAgent tbAgent);
} }

View File

@@ -3,6 +3,7 @@ package org.xyzh.api.ai.service;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.web.multipart.MultipartFile;
import org.xyzh.api.ai.dto.TbKnowledge; import org.xyzh.api.ai.dto.TbKnowledge;
import org.xyzh.api.ai.dto.TbKnowledgeFile; import org.xyzh.api.ai.dto.TbKnowledgeFile;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
@@ -11,13 +12,16 @@ import org.xyzh.common.core.page.PageParam;
public interface KnowledgeService { public interface KnowledgeService {
// ================================= 知识库管理 ================================= // ================================= 知识库管理 =================================
/** /**
* 创建知识库同步到Dify * @description 创建知识库基础信息包含dify知识库各种参数的配置
* @param knowledge 知识库信息 * @param knowledge 知识库信息
* @param permissionType 权限类型PUBLIC-公开DEPARTMENT-部门ROLE-角色PRIVATE-私有 * @param permissionType 权限类型PUBLIC-公开DEPARTMENT-部门ROLE-角色PRIVATE-私有
* @param deptIds 部门ID列表DEPARTMENT类型需要 * @param deptIds 部门ID列表DEPARTMENT类型需要
* @param roleIds 角色ID列表ROLE/PRIVATE类型需要 * @param roleIds 角色ID列表ROLE/PRIVATE类型需要
* @return 创建结果 * @return ResultDomain<TbKnowledge> 创建结果
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledge> createKnowledge( ResultDomain<TbKnowledge> createKnowledge(
TbKnowledge knowledge, TbKnowledge knowledge,
@@ -27,98 +31,138 @@ public interface KnowledgeService {
); );
/** /**
* 更新知识库 * @description 更新知识库包含dify知识库各种参数的配置
* @param knowledge 知识库信息 * @param knowledge 知识库信息
* @return 更新结果 * @return ResultDomain<TbKnowledge> 更新结果
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledge> updateKnowledge(TbKnowledge knowledge); ResultDomain<TbKnowledge> updateKnowledge(TbKnowledge knowledge);
/** /**
* 删除知识库同时删除Dify中的知识库 * @description 删除知识库同时删除dify知识库
* @param knowledgeId 知识库ID * @param knowledgeId 知识库ID
* @return 删除结果 * @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<Boolean> deleteKnowledge(String knowledgeId); ResultDomain<Boolean> deleteKnowledge(String knowledgeId);
/** /**
* 根据ID查询知识库带权限校验 * @description 获取知识库详情包含dify知识库各种参数的配置
* @param knowledgeId 知识库ID * @param knowledgeId 知识库ID
* @return 知识库信息 * @return ResultDomain<TbKnowledge> 知识库信息
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledge> getKnowledgeById(String knowledgeId); ResultDomain<TbKnowledge> getKnowledgeById(String knowledgeId);
/** /**
* 查询用户有权限的知识库列表 * @description 查询知识库列表
* @param filter 过滤条件 * @param filter 过滤条件
* @return 知识库列表 * @return ResultDomain<TbKnowledge> 知识库列表
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledge> listKnowledges(TbKnowledge filter); ResultDomain<TbKnowledge> listKnowledges(TbKnowledge filter);
/** /**
* 分页查询知识库 * @description 分页查询知识库
* @param filter 过滤条件 * @param filter 过滤条件
* @param pageParam 分页参数 * @param pageParam 分页参数
* @return 分页结果 * @return ResultDomain<TbKnowledge> 分页结果
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledge> pageKnowledges(TbKnowledge filter, PageParam pageParam); ResultDomain<TbKnowledge> pageKnowledges(TbKnowledge filter, PageParam pageParam);
/** /**
* 查询知识库的文档数量和分段数量从Dify同步 * @description 获取知识库统计信息从Dify同步文档数量和分段数量)
* @param knowledgeId 知识库ID * @param knowledgeId 知识库ID
* @return 统计信息 * @return ResultDomain<TbKnowledge> 统计信息
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<TbKnowledge> getKnowledgeStats(String knowledgeId); ResultDomain<TbKnowledge> getKnowledgeStats(String knowledgeId);
/** /**
* 获取可用的嵌入模型列表 * @description 获取可用的嵌入模型列表
* @return 嵌入模型列表 * @return ResultDomain<Map<String, Object>> 嵌入模型列表
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<Map<String, Object>> getAvailableEmbeddingModels(); ResultDomain<Map<String, Object>> getAvailableEmbeddingModels();
/** /**
* 获取可用的Rerank模型列表 * @description 获取可用的Rerank模型列表
* @return Rerank模型列表 * @return ResultDomain<Map<String, Object>> Rerank模型列表
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<Map<String, Object>> getAvailableRerankModels(); ResultDomain<Map<String, Object>> getAvailableRerankModels();
// ================================= 文件管理 =================================
/** /**
* 获取Dify知识库文档列表 * @description 获取知识库文档列表从Dify获取
* @param knowledgeId 知识库ID * @param knowledgeId 知识库ID
* @param page 页码从1开始 * @param page 页码从1开始
* @param limit 每页数量 * @param limit 每页数量
* @return 文档列表 * @return ResultDomain<Map<String, Object>> 文档列表
* @author yslg
* @since 2025-12-18
*/ */
ResultDomain<Map<String, Object>> getDocumentList(String knowledgeId, Integer page, Integer limit); ResultDomain<Map<String, Object>> getDocumentList(String knowledgeId, Integer page, Integer limit);
// ================================= 文件管理 =================================
/** /**
* 添加知识库文件 * @description 上传文件到知识库完整流程minio + Dify + 数据库)
* @param knowledgeFile 文件信息 * @param knowledgeId 知识库ID
* @return 添加结果 * @param file 上传的文件
*/ * @param indexingTechnique 索引方式
ResultDomain<TbKnowledgeFile> addKnowledgeFile(TbKnowledgeFile knowledgeFile); * @return ResultDomain<TbKnowledgeFile> 上传结果
/**
* 更新知识库文件, 产生一个新version 删除dify中旧版本的文件的document并上传新文件重新索引
* @param knowledgeFile 文件信息
* @return 更新结果
*/
ResultDomain<TbKnowledgeFile> updateKnowledgeFile(TbKnowledgeFile knowledgeFile);
/**
* 删除知识库文件
* @param knowledgeFile 文件信息
* @return 删除结果
*/
ResultDomain<TbKnowledgeFile> deleteKnowledgeFile(TbKnowledgeFile knowledgeFile);
/**
* @description 获取一个文件的所有历史版本
* @param fileId 文件ID
* @author yslg * @author yslg
* @since 2025-12-15 * @since 2025-12-18
*/ */
ResultDomain<TbKnowledgeFile> getKnowledgeFileHistory(String fileId); ResultDomain<TbKnowledgeFile> uploadKnowledgeFile(String knowledgeId, MultipartFile file, String indexingTechnique);
/**
* @description 批量上传文件到知识库
* @param knowledgeId 知识库ID
* @param files 文件列表
* @param indexingTechnique 索引方式
* @return ResultDomain<TbKnowledgeFile> 上传结果
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbKnowledgeFile> batchUploadKnowledgeFile(String knowledgeId, List<MultipartFile> files, String indexingTechnique);
/**
* @description 上传新版本文件fileRootId一致version递增
* @param knowledgeId 知识库ID
* @param file 新文件
* @param fileRootId 文件根ID
* @return ResultDomain<TbKnowledgeFile> 新版本文件信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbKnowledgeFile> updateKnowledgeFileVersion(String knowledgeId, MultipartFile file, String fileRootId);
/**
* @description 删除知识库文件根据fileRootId删除所有版本
* @param fileRootId 文件根ID
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/
ResultDomain<Boolean> deleteKnowledgeFileById(String fileRootId);
/**
* @description 获取文件历史版本
* @param fileRootId 文件根ID
* @return ResultDomain<TbKnowledgeFile> 文件历史版本列表
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbKnowledgeFile> getKnowledgeFileHistory(String fileRootId);
} }

View File

@@ -0,0 +1,50 @@
package org.xyzh.api.ai.vo;
import java.util.List;
import org.xyzh.api.ai.dto.PromptCard;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @description Agent视图
* @filename AgentVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@Schema(description = "Agent视图")
public class AgentVO extends BaseVO{
private static final long serialVersionUID = 1L;
@Schema(description = "智能体ID")
private String agentId;
@Schema(description = "智能体名称")
private String name;
@Schema(description = "智能体描述")
private String description;
@Schema(description = "智能体url")
private String link;
@Schema(description = "智能体APIKEY")
private String apiKey;
@Schema(description = "是否是对外智能体,未登录可用")
private Boolean outer;
@Schema(description = "引导词")
private String introduce;
@Schema(description = "提示卡片数组")
private List<PromptCard> promptCards;
@Schema(description = "分类")
private String category;
}

View File

@@ -46,7 +46,7 @@ public class BidResponseDTO extends BaseDTO {
private String generationStatus; private String generationStatus;
@Schema(description = "版本号") @Schema(description = "版本号")
private String version; private Integer version;
@Schema(description = "父版本ID") @Schema(description = "父版本ID")
private String parentVersionId; private String parentVersionId;

View File

@@ -40,7 +40,7 @@ public class BiddingDocumentDTO extends BaseDTO {
private String mimeType; private String mimeType;
@Schema(description = "版本号") @Schema(description = "版本号")
private String version; private Integer version;
@Schema(description = "语言") @Schema(description = "语言")
private String language; private String language;

View File

@@ -78,7 +78,7 @@ public class BidResponseVO extends BaseVO {
private String fileUrl; private String fileUrl;
@Schema(description = "版本号") @Schema(description = "版本号")
private String version; private Integer version;
@Schema(description = "父版本ID") @Schema(description = "父版本ID")
private String parentVersionId; private String parentVersionId;

View File

@@ -58,7 +58,7 @@ public class BiddingDocumentVO extends BaseVO {
private String fileIcon; private String fileIcon;
@Schema(description = "版本号") @Schema(description = "版本号")
private String version; private Integer version;
@Schema(description = "语言") @Schema(description = "语言")
private String language; private String language;

View File

@@ -26,7 +26,7 @@ public class TbSysFileDTO extends BaseDTO {
private String fileRootId; private String fileRootId;
@Schema(description = "文件版本") @Schema(description = "文件版本")
private String version; private Integer version;
@Schema(description = "文件名") @Schema(description = "文件名")
private String name; private String name;

View File

@@ -83,4 +83,16 @@ public interface FileService {
*/ */
ResultDomain<TbSysFileDTO> saveTempFile(MultipartFile file, String module, String businessId); ResultDomain<TbSysFileDTO> saveTempFile(MultipartFile file, String module, String businessId);
/**
* @description 上传新版本文件用于文件更新fileRootId保持一致version递增
* @param file 文件对象
* @param module 所属模块
* @param businessId 业务ID
* @param fileRootId 文件根ID多版本一致
* @return ResultDomain<TbSysFileDTO> 上传结果,包含新版本文件信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbSysFileDTO> uploadFileVersion(MultipartFile file, String module, String businessId, String fileRootId);
} }

View File

@@ -0,0 +1,63 @@
package org.xyzh.api.system.service;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbGuestDTO;
/**
* @description 来客服务接口
* @filename GuestService.java
* @author yslg
* @copyright xyzh
* @since 2025-12-18
*/
public interface GuestService {
/**
* @description 创建来客
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbGuestDTO> createGuest(TbGuestDTO guest);
/**
* @description 更新来客
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbGuestDTO> updateGuest(TbGuestDTO guest);
/**
* @description 删除来客
* @param userId 来客ID
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbGuestDTO> deleteGuest(String userId);
/**
* @description 查询单个来客
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbGuestDTO> selectGuestOne(TbGuestDTO guest);
/**
* @description 查询来客列表
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbGuestDTO> selectGuestList(TbGuestDTO guest);
/**
* @description 查询来客分页列表
* @param pageRequest 分页请求
* @author yslg
* @since 2025-12-18
*/
ResultDomain<TbGuestDTO> selectGuestPage(PageRequest<TbGuestDTO> pageRequest);
}

View File

@@ -23,7 +23,7 @@ import org.xyzh.common.dto.sys.TbSysDeptDTO;
import org.xyzh.common.dto.sys.TbSysPermissionDTO; import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysViewDTO; import org.xyzh.common.dto.sys.TbSysViewDTO;
import org.xyzh.common.redis.service.RedisService; import org.xyzh.common.redis.service.RedisService;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.crypto.AesEncryptUtil; import org.xyzh.common.utils.crypto.AesEncryptUtil;
import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.DubboService;
@@ -79,7 +79,7 @@ public class AuthServiceImpl implements AuthService{
switch (captchaType) { switch (captchaType) {
case "image": case "image":
// 生成图片验证码 // 生成图片验证码
String captchaId = IDUtils.generateID(); String captchaId = IdUtil.generateID();
String captchaCode = CapcatUtils.generateVerificationCode(); String captchaCode = CapcatUtils.generateVerificationCode();
// 存储验证码到Redis有效期5分钟 // 存储验证码到Redis有效期5分钟
@@ -97,7 +97,7 @@ public class AuthServiceImpl implements AuthService{
return ResultDomain.failure("手机号不能为空"); return ResultDomain.failure("手机号不能为空");
} }
String smsSessionId = IDUtils.generateID(); String smsSessionId = IdUtil.generateID();
String smsCode = CapcatUtils.generateVerificationCode(); String smsCode = CapcatUtils.generateVerificationCode();
// 存储短信验证码到Redis有效期5分钟 // 存储短信验证码到Redis有效期5分钟
@@ -119,7 +119,7 @@ public class AuthServiceImpl implements AuthService{
return ResultDomain.failure("邮箱不能为空"); return ResultDomain.failure("邮箱不能为空");
} }
String emailSessionId = IDUtils.generateID(); String emailSessionId = IdUtil.generateID();
String emailCode = CapcatUtils.generateVerificationCode(); String emailCode = CapcatUtils.generateVerificationCode();
// 存储邮箱验证码到Redis有效期5分钟 // 存储邮箱验证码到Redis有效期5分钟
@@ -210,7 +210,7 @@ public class AuthServiceImpl implements AuthService{
@Override @Override
public ResultDomain<LoginDomain> login(LoginParam loginParam, HttpServletRequest request) { public ResultDomain<LoginDomain> login(LoginParam loginParam, HttpServletRequest request) {
String loginType = loginParam.getLoginType(); String loginType = loginParam.getLoginType();
String loginAttempt = IDUtils.generateID(); // 生成登录尝试ID String loginAttempt = IdUtil.generateID(); // 生成登录尝试ID
try { try {
logger.info("用户登录请求loginType={}, username={}, attempt={}", loginType, loginParam.getUsername(), loginAttempt); logger.info("用户登录请求loginType={}, username={}, attempt={}", loginType, loginParam.getUsername(), loginAttempt);

View File

@@ -24,6 +24,10 @@
<groupId>org.xyzh.common</groupId> <groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId> <artifactId>common-auth</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.xyzh.common</groupId> <groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId> <artifactId>common-core</artifactId>

View File

@@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
import org.xyzh.common.core.domain.LoginDomain; import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.dto.sys.TbSysUserDTO; import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.dto.sys.TbSysUserInfoDTO; import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.util.Date; import java.util.Date;
@@ -55,7 +55,7 @@ public class JwtTokenUtil {
return Jwts.builder() return Jwts.builder()
.setClaims(claims) .setClaims(claims)
.setSubject(user.getUserId()) .setSubject(user.getUserId())
.setId(IDUtils.generateID()) // 使用IDUtils生成JWT ID .setId(IdUtil.generateID()) // 使用IdUtil生成JWT ID
.setIssuedAt(new Date()) .setIssuedAt(new Date())
.setExpiration(generateExpirationDate()) .setExpiration(generateExpirationDate())
.signWith(getSigningKey()) .signWith(getSigningKey())

View File

@@ -0,0 +1,116 @@
package org.xyzh.common.auth.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.redis.service.RedisService;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
/**
* @description 登录用户工具类从Redis获取当前登录用户信息
* @filename LoginUtil.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
@Component
public class LoginUtil {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private static final String LOGIN_TOKEN_PREFIX = "login:token:";
@Autowired
private RedisService redisService;
private static LoginUtil instance;
@PostConstruct
public void init() {
instance = this;
}
/**
* 获取当前登录用户信息
*/
public static LoginDomain getCurrentLogin() {
String token = getToken();
if (!StringUtils.hasText(token)) {
return null;
}
try {
String cacheKey = LOGIN_TOKEN_PREFIX + token;
Object obj = instance.redisService.get(cacheKey);
if (obj instanceof LoginDomain) {
return (LoginDomain) obj;
}
} catch (Exception e) {
// 忽略异常
}
return null;
}
/**
* 获取当前用户ID
*/
public static String getCurrentUserId() {
LoginDomain login = getCurrentLogin();
if (login != null && login.getUser() != null) {
return login.getUser().getUserId();
}
return null;
}
/**
* 获取当前用户名
*/
public static String getCurrentUserName() {
LoginDomain login = getCurrentLogin();
if (login != null && login.getUserInfo() != null) {
return login.getUserInfo().getUsername();
}
return null;
}
/**
* 判断用户是否已登录
*/
public static boolean isLoggedIn() {
return getCurrentLogin() != null;
}
/**
* 从请求头获取Token
*/
public static String getToken() {
HttpServletRequest request = getRequest();
if (request == null) {
return null;
}
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
return authHeader.substring(BEARER_PREFIX.length());
}
return null;
}
/**
* 获取当前请求
*/
private static HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
return attributes.getRequest();
}
return null;
}
}

View File

@@ -22,6 +22,9 @@ public class PageDomain<T> implements Serializable {
* @since 2025-11-02 * @since 2025-11-02
*/ */
private List<T> dataList; private List<T> dataList;
public PageDomain() {
}
public PageDomain(PageParam pageParam, List<T> dataList) { public PageDomain(PageParam pageParam, List<T> dataList) {
if (pageParam == null) { if (pageParam == null) {

View File

@@ -73,4 +73,11 @@ public class PageParam implements Serializable {
} }
this.offset = (this.page - 1) * this.pageSize; this.offset = (this.page - 1) * this.pageSize;
} }
public void setTotal(int total){
this.total = total;
if (this.pageSize > 0) {
this.totalPages = (int) Math.ceil((double) total / pageSize);
}
}
} }

View File

@@ -0,0 +1,27 @@
package org.xyzh.common.dto.sys;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "来客信息")
public class TbGuestDTO extends BaseDTO{
private static final long serialVersionUID = 1L;
@Schema(description = "来客ID")
private String userId;
@Schema(description = "姓名")
private String name;
@Schema(description = "电话")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "微信ID")
private String wechatId;
}

View File

@@ -24,9 +24,15 @@ public class BaseVO implements Serializable {
@Schema(description = "创建人") @Schema(description = "创建人")
private String creator; private String creator;
@Schema(description = "创建人名称")
private String creatorName;
@Schema(description = "更新人") @Schema(description = "更新人")
private String updater; private String updater;
@Schema(description = "更新人名称")
private String updaterName;
@Schema(description = "部门路径") @Schema(description = "部门路径")
private String deptPath; private String deptPath;

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,36 @@
package org.xyzh.common.exception;
import lombok.Getter;
/**
* @description 业务异常
* @filename BusinessException.java
* @author yslg
* @copyright yslg
* @since 2025-12-17
*/
@Getter
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Integer code;
private final String message;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = 500;
this.message = message;
}
}

View File

@@ -0,0 +1,124 @@
package org.xyzh.common.exception.handler;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.exception.BusinessException;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @description 全局异常处理器
* @filename GlobalExceptionHandler.java
* @author yslg
* @copyright yslg
* @since 2025-12-17
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return ResultDomain.failure(e.getCode(), e.getMessage());
}
/**
* 参数校验异常 - @RequestBody 参数校验失败
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
log.warn("参数校验失败: {}", message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 参数校验异常 - @Validated 校验失败(方法参数直接校验)
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleConstraintViolationException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
String message = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
log.warn("参数校验失败: {}", message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 参数绑定异常
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleBindException(BindException e) {
String message = e.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
log.warn("参数绑定失败: {}", message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 缺少请求参数
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
String message = "缺少必要参数: " + e.getParameterName();
log.warn(message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 缺少请求Partmultipart/form-data
*/
@ExceptionHandler(MissingServletRequestPartException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMissingServletRequestPartException(MissingServletRequestPartException e) {
String message = "缺少必要参数: " + e.getRequestPartName();
log.warn(message);
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 文件上传大小超限
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.warn("文件上传大小超限: {}", e.getMessage());
return ResultDomain.failure(HttpStatus.BAD_REQUEST.value(), "上传文件大小超过限制");
}
/**
* 其他未捕获异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public ResultDomain<?> handleException(Exception e) {
log.error("系统异常: ", e);
return ResultDomain.failure(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统异常,请联系管理员");
}
}

View File

@@ -1,23 +0,0 @@
package org.xyzh.common.utils;
import java.util.UUID;
/**
* @description IDUtils.java文件描述
* @filename IDUtils.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
public class IDUtils {
/**
* @description 生成UUID
* @return UUID
* @author yslg
* @since 2025-11-02
*/
public static String generateID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}

View File

@@ -0,0 +1,46 @@
package org.xyzh.common.utils.id;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
/**
* @description ID生成工具类
* @filename IdUtil.java
* @author yslg
* @copyright xyzh
* @since 2025-12-17
*/
public class IdUtil {
private static final AtomicLong SEQUENCE = new AtomicLong(0);
/**
* 生成流水号UUID格式无横线
*/
public static String getOptsn() {
return "optsn"+UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成雪花ID简化实现使用时间戳+序列号)
*/
public static String getSnowflakeId() {
long timestamp = System.currentTimeMillis();
long seq = SEQUENCE.incrementAndGet() % 10000;
return String.valueOf(timestamp) + String.format("%04d", seq);
}
/**
* 生成UUID
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 生成ID
*/
public static String generateID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}

View File

@@ -1,8 +1,13 @@
package org.xyzh.common.utils.validation; package org.xyzh.common.utils.validation;
import org.xyzh.common.utils.validation.method.InSetValidateMethod;
import org.xyzh.common.utils.validation.method.MinFieldsValidateMethod;
import org.xyzh.common.utils.validation.method.ObjectValidateMethod;
import org.xyzh.common.utils.validation.method.ValidateMethod; import org.xyzh.common.utils.validation.method.ValidateMethod;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -37,10 +42,10 @@ public class ValidationUtils {
for (ValidationParam param : validationParams) { for (ValidationParam param : validationParams) {
try { try {
Object fieldValue = getFieldValue(obj, param.getFieldName()); Object fieldValue = getFieldValue(obj, param.getFieldName());
validateField(param, fieldValue, result); validateField(param, fieldValue, obj, result);
} catch (Exception e) { } catch (Exception e) {
result.addError(param.getFieldLabel() + "字段获取失败: " + e.getMessage()); result.addError(param.getFieldLabel() + "字段获取失败: " + e.getMessage());
} }
} }
return result; return result;
@@ -52,7 +57,7 @@ public class ValidationUtils {
* @param validationParams 校验参数列表 * @param validationParams 校验参数列表
* @return ValidationResult 校验结果 * @return ValidationResult 校验结果
*/ */
public static ValidationResult validateMap(Map<String, Object> map, List<ValidationParam> validationParams) { public static <V> ValidationResult validateMap(Map<String, V> map, List<ValidationParam> validationParams) {
ValidationResult result = new ValidationResult(); ValidationResult result = new ValidationResult();
if (map == null) { if (map == null) {
@@ -66,7 +71,7 @@ public class ValidationUtils {
for (ValidationParam param : validationParams) { for (ValidationParam param : validationParams) {
Object fieldValue = map.get(param.getFieldName()); Object fieldValue = map.get(param.getFieldName());
validateField(param, fieldValue, result); validateField(param, fieldValue, map, result);
} }
return result; return result;
@@ -76,9 +81,10 @@ public class ValidationUtils {
* @description 校验单个字段 * @description 校验单个字段
* @param param 校验参数 * @param param 校验参数
* @param fieldValue 字段值 * @param fieldValue 字段值
* @param obj 原始对象
* @param result 校验结果 * @param result 校验结果
*/ */
private static void validateField(ValidationParam param, Object fieldValue, ValidationResult result) { private static void validateField(ValidationParam param, Object fieldValue, Object obj, ValidationResult result) {
String fieldLabel = param.getFieldLabel(); String fieldLabel = param.getFieldLabel();
// 1. 必填校验 // 1. 必填校验
@@ -176,10 +182,11 @@ public class ValidationUtils {
} }
} }
// 8. 使用ValidateMethod校验直接传入实例,保留兼容性 // 8. 使用ValidateMethod校验直接传入实例
if (param.getValidateMethod() != null) { if (param.getValidateMethod() != null) {
try { try {
if (!param.getValidateMethod().validate(fieldValue)) { Object validateTarget = (param.getValidateMethod() instanceof ObjectValidateMethod) ? obj : fieldValue;
if (!param.getValidateMethod().validate(validateTarget)) {
String errorMsg = param.getValidateMethod().getErrorMessage(); String errorMsg = param.getValidateMethod().getErrorMessage();
if (errorMsg != null && !errorMsg.isEmpty()) { if (errorMsg != null && !errorMsg.isEmpty()) {
result.addError(errorMsg); result.addError(errorMsg);
@@ -194,13 +201,27 @@ public class ValidationUtils {
} }
/** /**
* @description 获取对象字段值支持getter方法直接访问) * @description 获取对象字段值支持getter方法直接访问和嵌套路径
* @param obj 对象 * @param obj 对象
* @param fieldName 字段名 * @param fieldName 字段名,支持嵌套路径如"pageParam.page"
* @return 字段值 * @return 字段值
* @throws Exception 异常 * @throws Exception 异常
*/ */
private static Object getFieldValue(Object obj, String fieldName) throws Exception { private static Object getFieldValue(Object obj, String fieldName) throws Exception {
if (obj == null) {
return null;
}
// 支持嵌套路径,如"pageParam.page"
if (fieldName.contains(".")) {
String[] parts = fieldName.split("\\.", 2);
Object nestedObj = getFieldValue(obj, parts[0]);
if (nestedObj == null) {
return null;
}
return getFieldValue(nestedObj, parts[1]);
}
if (obj instanceof Map) { if (obj instanceof Map) {
return ((Map<?, ?>) obj).get(fieldName); return ((Map<?, ?>) obj).get(fieldName);
} }
@@ -317,5 +338,46 @@ public class ValidationUtils {
.patternDesc("请输入有效的手机号码") .patternDesc("请输入有效的手机号码")
.build(); .build();
} }
}
/**
* @description 创建"至少填一个"校验参数
* @param fieldNames 字段名列表
* @param fieldLabels 字段标签列表
* @return ValidationParam
*/
public static ValidationParam atLeastOne(List<String> fieldNames, List<String> fieldLabels) {
return minFields(fieldNames, fieldLabels, 1);
}
/**
* @description 创建"至少填几个"校验参数
* @param fieldNames 字段名列表
* @param fieldLabels 字段标签列表
* @param minCount 至少填写的数量
* @return ValidationParam
*/
public static ValidationParam minFields(List<String> fieldNames, List<String> fieldLabels, int minCount) {
return ValidationParam.builder()
.fieldName("_minFields")
.fieldLabel(String.join("", fieldLabels))
.validateMethod(new MinFieldsValidateMethod(fieldNames, fieldLabels, minCount))
.build();
}
/**
* @description 创建N选1校验参数值必须是集合中的一个
* @param fieldName 字段名
* @param fieldLabel 字段标签
* @param required 是否必填
* @param allowedValues 允许的值集合
* @return ValidationParam
*/
public static ValidationParam inSet(String fieldName, String fieldLabel, Boolean required, Collection<?> allowedValues) {
return ValidationParam.builder()
.fieldName(fieldName)
.fieldLabel(fieldLabel)
.required(required)
.validateMethod(new InSetValidateMethod(fieldLabel, allowedValues))
.build();
}
}

View File

@@ -0,0 +1,68 @@
package org.xyzh.common.utils.validation.method;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @description N选1校验方法值必须是指定集合中的一个
* @filename InSetValidateMethod.java
* @author yslg
* @copyright xyzh
* @since 2025-12-18
*/
public class InSetValidateMethod implements ValidateMethod {
private final Set<Object> allowedValues;
private final String errorMessage;
/**
* @description 构造函数
* @param fieldLabel 字段标签
* @param allowedValues 允许的值集合
*/
public InSetValidateMethod(String fieldLabel, Collection<?> allowedValues) {
this.allowedValues = allowedValues.stream().map(v -> (Object) v).collect(Collectors.toSet());
this.errorMessage = fieldLabel + "必须是以下值之一: " + formatValues(allowedValues);
}
/**
* @description 构造函数(自定义错误信息)
* @param fieldLabel 字段标签
* @param allowedValues 允许的值集合
* @param customErrorMessage 自定义错误信息
*/
public InSetValidateMethod(String fieldLabel, Collection<?> allowedValues, String customErrorMessage) {
this.allowedValues = allowedValues.stream().map(v -> (Object) v).collect(Collectors.toSet());
this.errorMessage = customErrorMessage;
}
@Override
public Boolean validate(Object value) {
if (value == null) {
return false;
}
return allowedValues.contains(value);
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@Override
public String getName() {
return "N选1校验";
}
/**
* @description 格式化允许值列表
* @param values 值集合
* @return 格式化后的字符串
*/
private String formatValues(Collection<?> values) {
return values.stream()
.map(v -> v instanceof String ? "\"" + v + "\"" : String.valueOf(v))
.collect(Collectors.joining(", ", "[", "]"));
}
}

View File

@@ -0,0 +1,98 @@
package org.xyzh.common.utils.validation.method;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* @description 至少填几个字段校验方法
* @filename MinFieldsValidateMethod.java
* @author yslg
* @copyright yslg
* @since 2025-12-18
*/
public class MinFieldsValidateMethod implements ObjectValidateMethod {
private final List<String> fieldNames;
private final int minCount;
private final String errorMessage;
public MinFieldsValidateMethod(List<String> fieldNames, List<String> fieldLabels) {
this(fieldNames, fieldLabels, 1);
}
public MinFieldsValidateMethod(List<String> fieldNames, List<String> fieldLabels, int minCount) {
this.fieldNames = fieldNames;
this.minCount = minCount;
this.errorMessage = String.join("", fieldLabels) + "至少填写" + minCount + "";
}
@Override
public Boolean validate(Object obj) {
if (obj == null || fieldNames == null || fieldNames.isEmpty()) {
return false;
}
int count = 0;
for (String fieldName : fieldNames) {
try {
Object fieldValue = getFieldValue(obj, fieldName);
if (fieldValue != null) {
if (fieldValue instanceof String) {
if (!((String) fieldValue).trim().isEmpty()) {
count++;
}
} else {
count++;
}
}
} catch (Exception e) {
// 字段不存在,忽略
}
}
return count >= minCount;
}
private Object getFieldValue(Object obj, String fieldName) throws Exception {
if (obj instanceof Map) {
return ((Map<?, ?>) obj).get(fieldName);
}
Class<?> clazz = obj.getClass();
// 尝试getter方法
try {
String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method getter = clazz.getMethod(getterName);
return getter.invoke(obj);
} catch (NoSuchMethodException e) {
// 尝试直接访问字段
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException ex) {
// 尝试父类
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
Field field = superClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}
throw ex;
}
}
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@Override
public String getName() {
return "至少填" + minCount + "个校验";
}
}

View File

@@ -0,0 +1,11 @@
package org.xyzh.common.utils.validation.method;
/**
* @description 对象级校验方法接口,表示对整个对象进行校验
* @filename ObjectValidateMethod.java
* @author yslg
* @copyright yslg
* @since 2025-12-18
*/
public interface ObjectValidateMethod extends ValidateMethod {
}

View File

@@ -20,6 +20,7 @@
<module>common-redis</module> <module>common-redis</module>
<module>common-utils</module> <module>common-utils</module>
<module>common-all</module> <module>common-all</module>
<module>common-exception</module>
</modules> </modules>
<properties> <properties>
@@ -59,6 +60,11 @@
<artifactId>common-utils</artifactId> <artifactId>common-utils</artifactId>
<version>${urban-lifeline.version}</version> <version>${urban-lifeline.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
</project> </project>

View File

@@ -84,4 +84,14 @@ public interface FileMapper extends BaseMapper<TbSysFileDTO> {
*/ */
@Select("SELECT * FROM file.tb_sys_file WHERE bucket_name = #{bucketName} AND object_name = #{objectName} AND deleted = 0") @Select("SELECT * FROM file.tb_sys_file WHERE bucket_name = #{bucketName} AND object_name = #{objectName} AND deleted = 0")
TbSysFileDTO selectByMinioObject(@Param("bucketName") String bucketName, @Param("objectName") String objectName); TbSysFileDTO selectByMinioObject(@Param("bucketName") String bucketName, @Param("objectName") String objectName);
/**
* @description 根据文件根ID查询最大版本号
* @param fileRootId 文件根ID
* @return Integer 最大版本号
* @author yslg
* @since 2025-12-18
*/
@Select("SELECT MAX(version) FROM file.tb_sys_file WHERE file_root_id = #{fileRootId} AND deleted = 0")
Integer selectMaxVersionByFileRootId(@Param("fileRootId") String fileRootId);
} }

View File

@@ -157,6 +157,13 @@ public class FileServiceImpl implements FileService {
} }
} }
/**
* @description 软删除文件只标记数据库记录为已删除不删除minio文件
* @param fileId 文件ID
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-18
*/
@Override @Override
public ResultDomain<Boolean> deleteFile(String fileId) { public ResultDomain<Boolean> deleteFile(String fileId) {
try { try {
@@ -167,13 +174,13 @@ public class FileServiceImpl implements FileService {
} }
// 删除MinIO中的文件 // 删除MinIO中的文件
minioUtil.deleteFile(sysFile.getBucketName(), sysFile.getObjectName()); // minioUtil.deleteFile(sysFile.getBucketName(), sysFile.getObjectName());
// 逻辑删除数据库记录 // 逻辑删除数据库记录
int result = fileMapper.deleteByFileId(fileId, "system"); int result = fileMapper.deleteByFileId(fileId, "system");
if (result > 0) { if (result > 0) {
logger.info("文件删除成功: {}", fileId); logger.info("文件删除成功: {}", fileId);
return ResultDomain.success("文件删除成功", true); return ResultDomain.success("文件删除成功", true);
} else { } else {
return ResultDomain.failure("文件删除失败"); return ResultDomain.failure("文件删除失败");
@@ -276,6 +283,97 @@ public class FileServiceImpl implements FileService {
return ResultDomain.failure("临时文件保存失败: " + e.getMessage()); return ResultDomain.failure("临时文件保存失败: " + e.getMessage());
} }
} }
/**
* @description 上传新版本文件用于文件更新fileRootId保持一致version递增
* @param file 文件对象
* @param module 所属模块
* @param businessId 业务ID
* @param fileRootId 文件根ID多版本一致
* @return ResultDomain<TbSysFileDTO> 上传结果,包含新版本文件信息
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbSysFileDTO> uploadFileVersion(MultipartFile file, String module, String businessId, String fileRootId) {
try {
if (file == null || file.isEmpty()) {
return ResultDomain.failure("文件不能为空");
}
if (fileRootId == null || fileRootId.isEmpty()) {
return ResultDomain.failure("文件根ID不能为空");
}
// 1. 获取当前最大版本号
Integer maxVersion = fileMapper.selectMaxVersionByFileRootId(fileRootId);
int newVersion = (maxVersion != null ? maxVersion : 0) + 1;
// 2. 生成文件信息
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String contentType = file.getContentType();
long size = file.getSize();
// 3. 生成唯一的对象名称
String objectName = generateObjectName(originalFilename, module);
// 4. 计算文件MD5
String md5Hash = calculateMD5(file.getBytes());
// 5. 上传到MinIO
String bucketName = minioConfig.getBucketName();
boolean uploadSuccess = minioUtil.uploadFile(
bucketName,
objectName,
file.getInputStream(),
size,
contentType
);
if (!uploadSuccess) {
return ResultDomain.failure("文件上传到MinIO失败");
}
// 6. 构建文件访问URL
String fileUrl = minioConfig.buildFileUrl(objectName);
// 7. 保存到数据库(新版本记录)
TbSysFileDTO fileDTO = new TbSysFileDTO();
fileDTO.setOptsn(UUID.randomUUID().toString());
fileDTO.setFileId(UUID.randomUUID().toString());
fileDTO.setFileRootId(fileRootId);
fileDTO.setVersion(newVersion);
fileDTO.setName(originalFilename);
fileDTO.setPath(objectName);
fileDTO.setSize(size);
fileDTO.setType(extension);
fileDTO.setStorageType("MINIO");
fileDTO.setMimeType(contentType);
fileDTO.setUrl(fileUrl);
fileDTO.setStatus("NORMAL");
fileDTO.setModule(module);
fileDTO.setBusinessId(businessId);
fileDTO.setObjectName(objectName);
fileDTO.setBucketName(bucketName);
fileDTO.setMd5Hash(md5Hash);
fileDTO.setExtension(extension);
fileDTO.setCreateTime(new java.util.Date());
int result = fileMapper.insertFile(fileDTO);
if (result <= 0) {
// 如果数据库保存失败删除MinIO中的文件
minioUtil.deleteFile(bucketName, objectName);
return ResultDomain.failure("文件信息保存失败");
}
logger.info("新版本文件上传成功: {}, version: {}, fileRootId: {}", originalFilename, newVersion, fileRootId);
return ResultDomain.success("文件上传成功", fileDTO);
} catch (Exception e) {
logger.error("新版本文件上传失败", e);
return ResultDomain.failure("文件上传失败: " + e.getMessage());
}
}
/** /**
* 生成唯一的对象名称 * 生成唯一的对象名称

View File

@@ -274,6 +274,11 @@
<artifactId>common-utils</artifactId> <artifactId>common-utils</artifactId>
<version>${urban-lifeline.version}</version> <version>${urban-lifeline.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-exception</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<!-- 服务模块 --> <!-- 服务模块 -->
<dependency> <dependency>
<groupId>org.xyzh</groupId> <groupId>org.xyzh</groupId>

View File

@@ -0,0 +1,78 @@
package org.xyzh.system.controller;
import java.util.Arrays;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xyzh.api.system.service.GuestService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbGuestDTO;
import org.xyzh.common.utils.validation.ValidationUtils;
import jakarta.validation.constraints.NotNull;
/**
* @description 来客控制器
* @filename GuestController.java
* @author yslg
* @copyright xyzh
* @since 2025-12-18
*/
@Validated
@RestController
@RequestMapping("/system/guest")
public class GuestController {
@DubboReference(version = "1.0.0", group = "system", timeout = 3000, retries = 0)
private GuestService guestService;
@PostMapping
public ResultDomain<TbGuestDTO> createGuest(TbGuestDTO guest) {
ValidationUtils.validate(guest, Arrays.asList(
ValidationUtils.requiredString("name", "姓名"),
ValidationUtils.atLeastOne(
Arrays.asList("phone", "email", "wechatId"),
Arrays.asList("电话", "邮箱", "微信ID")
)
));
return guestService.createGuest(guest);
}
@PutMapping
public ResultDomain<TbGuestDTO> updateGuest(TbGuestDTO guest) {
ValidationUtils.validate(guest, Arrays.asList(
ValidationUtils.requiredString("name", "姓名"),
ValidationUtils.atLeastOne(
Arrays.asList("phone", "email", "wechatId"),
Arrays.asList("电话", "邮箱", "微信ID")
)
));
return guestService.updateGuest(guest);
}
@DeleteMapping
public ResultDomain<TbGuestDTO> deleteGuest(@NotNull String userId) {
return guestService.deleteGuest(userId);
}
@GetMapping("/list")
public ResultDomain<TbGuestDTO> listGuest(TbGuestDTO filter) {
return guestService.selectGuestList(filter);
}
@PostMapping("/page")
public ResultDomain<TbGuestDTO> pageGuest(PageRequest<TbGuestDTO> pageRequest) {
return guestService.selectGuestPage(pageRequest);
}
}

View File

@@ -0,0 +1,71 @@
package org.xyzh.system.mapper.user;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.sys.TbGuestDTO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @description 来客Mapper
* @filename TbGuestMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-12-18
*/
@Mapper
public interface TbGuestMapper extends BaseMapper<TbGuestDTO>{
/**
* @description 插入来客
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
int insertGuest(TbGuestDTO guest);
/**
* @description 更新来客
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
int updateGuest(TbGuestDTO guest);
/**
* @description 删除来客
* @param userId 来客ID
* @author yslg
* @since 2025-12-18
*/
int deleteGuest(String userId);
/**
* @description 查询单个来客
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
TbGuestDTO selectGuestOne(TbGuestDTO guest);
/**
* @description 查询来客列表
* @param guest 来客信息
* @author yslg
* @since 2025-12-18
*/
List<TbGuestDTO> selectGuestList(TbGuestDTO guest);
/**
* @description 查询来客分页列表
* @param guest 来客信息
* @param pageParam 分页参数
* @author yslg
* @since 2025-12-18
*/
List<TbGuestDTO> selectGuestPage(TbGuestDTO guest, PageParam pageParam);
int countGuest(TbGuestDTO guest);
}

View File

@@ -14,7 +14,7 @@ import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbSysAclDTO; import org.xyzh.common.dto.sys.TbSysAclDTO;
import org.xyzh.common.dto.sys.TbSysAclPolicyDTO; import org.xyzh.common.dto.sys.TbSysAclPolicyDTO;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.StringUtils; import org.xyzh.common.utils.StringUtils;
import org.xyzh.system.mapper.acl.TbSysAclMapper; import org.xyzh.system.mapper.acl.TbSysAclMapper;
import org.xyzh.system.mapper.acl.TbSysAclPolicyMapper; import org.xyzh.system.mapper.acl.TbSysAclPolicyMapper;
@@ -54,7 +54,7 @@ public class AclServiceImpl implements AclService {
return ResultDomain.failure(MSG_ACL_PARAM_REQUIRED); return ResultDomain.failure(MSG_ACL_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(aclDTO.getAclId())) { if (StringUtils.isBlank(aclDTO.getAclId())) {
aclDTO.setAclId(IDUtils.generateID()); aclDTO.setAclId(IdUtil.generateID());
} }
if (aclDTO.getCreateTime() == null) { if (aclDTO.getCreateTime() == null) {
aclDTO.setCreateTime(new Date()); aclDTO.setCreateTime(new Date());
@@ -178,7 +178,7 @@ public class AclServiceImpl implements AclService {
return ResultDomain.failure(MSG_POLICY_PARAM_REQUIRED); return ResultDomain.failure(MSG_POLICY_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(aclPolicyDTO.getPolicyId())) { if (StringUtils.isBlank(aclPolicyDTO.getPolicyId())) {
aclPolicyDTO.setPolicyId(IDUtils.generateID()); aclPolicyDTO.setPolicyId(IdUtil.generateID());
} }
if (aclPolicyDTO.getCreateTime() == null) { if (aclPolicyDTO.getCreateTime() == null) {
aclPolicyDTO.setCreateTime(new Date()); aclPolicyDTO.setCreateTime(new Date());

View File

@@ -21,7 +21,7 @@ import org.xyzh.common.dto.sys.TbSysDeptRoleDTO;
import org.xyzh.common.dto.sys.TbSysRoleDTO; import org.xyzh.common.dto.sys.TbSysRoleDTO;
import org.xyzh.common.dto.sys.TbSysRolePermissionDTO; import org.xyzh.common.dto.sys.TbSysRolePermissionDTO;
import org.xyzh.common.dto.sys.TbSysUserRoleDTO; import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.StringUtils; import org.xyzh.common.utils.StringUtils;
import org.xyzh.system.mapper.dept.TbSysDeptMapper; import org.xyzh.system.mapper.dept.TbSysDeptMapper;
import org.xyzh.system.mapper.role.TbSysRoleMapper; import org.xyzh.system.mapper.role.TbSysRoleMapper;
@@ -67,7 +67,7 @@ public class DeptRoleServiceImpl implements DeptRoleService {
return ResultDomain.failure(MSG_DEPT_PARAM_REQUIRED); return ResultDomain.failure(MSG_DEPT_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(deptDTO.getDeptId())) { if (StringUtils.isBlank(deptDTO.getDeptId())) {
deptDTO.setDeptId(IDUtils.generateID()); deptDTO.setDeptId(IdUtil.generateID());
} }
if (deptDTO.getCreateTime() == null) { if (deptDTO.getCreateTime() == null) {
deptDTO.setCreateTime(new Date()); deptDTO.setCreateTime(new Date());
@@ -166,7 +166,7 @@ public class DeptRoleServiceImpl implements DeptRoleService {
return ResultDomain.failure(MSG_ROLE_PARAM_REQUIRED); return ResultDomain.failure(MSG_ROLE_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(roleDTO.getRoleId())) { if (StringUtils.isBlank(roleDTO.getRoleId())) {
roleDTO.setRoleId(IDUtils.generateID()); roleDTO.setRoleId(IdUtil.generateID());
} }
if (roleDTO.getCreateTime() == null) { if (roleDTO.getCreateTime() == null) {
roleDTO.setCreateTime(new Date()); roleDTO.setCreateTime(new Date());

View File

@@ -0,0 +1,81 @@
package org.xyzh.system.service.impl;
import java.util.List;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.xyzh.api.system.service.GuestService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbGuestDTO;
import org.xyzh.system.mapper.user.TbGuestMapper;
/**
* @description 来客服务接口实现
* @filename GuestServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-12-18
*/
@DubboService(
version = "1.0.0",
group = "system",
timeout = 3000,
retries = 0
)
public class GuestServiceImpl implements GuestService{
private static final Logger logger = LoggerFactory.getLogger(GuestServiceImpl.class);
@Autowired
private TbGuestMapper guestMapper;
@Override
@Transactional
public ResultDomain<TbGuestDTO> createGuest(TbGuestDTO guest) {
guestMapper.insertGuest(guest);
return ResultDomain.success("创建成功",guest);
}
@Override
@Transactional
public ResultDomain<TbGuestDTO> updateGuest(TbGuestDTO guest) {
guestMapper.updateGuest(guest);
return ResultDomain.success("更新成功",guest);
}
@Override
public ResultDomain<TbGuestDTO> deleteGuest(String userId) {
guestMapper.deleteGuest(userId);
return ResultDomain.success("删除成功");
}
@Override
public ResultDomain<TbGuestDTO> selectGuestList(TbGuestDTO guest) {
guestMapper.selectGuestList(guest);
return ResultDomain.success("查询成功",guest);
}
@Override
public ResultDomain<TbGuestDTO> selectGuestOne(TbGuestDTO guest) {
guestMapper.selectGuestOne(guest);
return ResultDomain.success("查询成功",guest);
}
@Override
public ResultDomain<TbGuestDTO> selectGuestPage(PageRequest<TbGuestDTO> pageRequest) {
List<TbGuestDTO> guestList = guestMapper.selectGuestPage(pageRequest.getFilter(),pageRequest.getPageParam());
int total = guestMapper.countGuest(pageRequest.getFilter());
PageDomain<TbGuestDTO> pageDomain = new PageDomain<>();
pageDomain.setPageParam(pageRequest.getPageParam());
pageDomain.getPageParam().setTotal(total);
pageDomain.setDataList(guestList);
return ResultDomain.success("查询成功",pageDomain);
}
}

View File

@@ -15,7 +15,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.xyzh.common.core.page.PageParam; import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.StringUtils; import org.xyzh.common.utils.StringUtils;
import org.xyzh.system.mapper.module.TbSysModuleMapper; import org.xyzh.system.mapper.module.TbSysModuleMapper;
import org.xyzh.system.mapper.permission.TbSysPermissionMapper; import org.xyzh.system.mapper.permission.TbSysPermissionMapper;
@@ -61,7 +61,7 @@ public class ModulePermissionServiceImpl implements ModulePermissionService {
return ResultDomain.failure(MSG_MODULE_PARAM_REQUIRED); return ResultDomain.failure(MSG_MODULE_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(moduleDTO.getModuleId())) { if (StringUtils.isBlank(moduleDTO.getModuleId())) {
moduleDTO.setModuleId(IDUtils.generateID()); moduleDTO.setModuleId(IdUtil.generateID());
} }
if (moduleDTO.getCreateTime() == null) { if (moduleDTO.getCreateTime() == null) {
moduleDTO.setCreateTime(new Date()); moduleDTO.setCreateTime(new Date());
@@ -138,7 +138,7 @@ public class ModulePermissionServiceImpl implements ModulePermissionService {
return ResultDomain.failure(MSG_PERMISSION_PARAM_REQUIRED); return ResultDomain.failure(MSG_PERMISSION_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(permissionDTO.getPermissionId())) { if (StringUtils.isBlank(permissionDTO.getPermissionId())) {
permissionDTO.setPermissionId(IDUtils.generateID()); permissionDTO.setPermissionId(IdUtil.generateID());
} }
if (permissionDTO.getCreateTime() == null) { if (permissionDTO.getCreateTime() == null) {
permissionDTO.setCreateTime(new Date()); permissionDTO.setCreateTime(new Date());

View File

@@ -15,7 +15,7 @@ import java.util.List;
import org.xyzh.common.core.page.PageParam; import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.StringUtils; import org.xyzh.common.utils.StringUtils;
import org.xyzh.system.mapper.config.TbSysConfigMapper; import org.xyzh.system.mapper.config.TbSysConfigMapper;
@@ -231,7 +231,7 @@ public class SysConfigServiceImpl implements SysConfigService {
return ResultDomain.failure(MSG_CONFIG_PARAM_REQUIRED); return ResultDomain.failure(MSG_CONFIG_PARAM_REQUIRED);
} }
if (StringUtils.isBlank(configDTO.getConfigId())) { if (StringUtils.isBlank(configDTO.getConfigId())) {
configDTO.setConfigId(IDUtils.generateID()); configDTO.setConfigId(IdUtil.generateID());
} }
if (configDTO.getCreateTime() == null) { if (configDTO.getCreateTime() == null) {
configDTO.setCreateTime(new Date()); configDTO.setCreateTime(new Date());

View File

@@ -18,7 +18,7 @@ import java.util.List;
import org.xyzh.common.core.page.PageParam; import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageDomain;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.NonUtils; import org.xyzh.common.utils.NonUtils;
import org.xyzh.common.utils.StringUtils; import org.xyzh.common.utils.StringUtils;
import org.xyzh.common.utils.crypto.AesEncryptUtil; import org.xyzh.common.utils.crypto.AesEncryptUtil;
@@ -80,7 +80,7 @@ public class SysUserServiceImpl implements SysUserService {
// 设置用户基本信息 // 设置用户基本信息
Date now = new Date(); Date now = new Date();
if (StringUtils.isBlank(dto.getUserId())) { if (StringUtils.isBlank(dto.getUserId())) {
dto.setUserId(IDUtils.generateID()); dto.setUserId(IdUtil.generateID());
} }
dto.setPhone(userVO.getPhone()); dto.setPhone(userVO.getPhone());
dto.setCreateTime(now); dto.setCreateTime(now);
@@ -299,7 +299,7 @@ public class SysUserServiceImpl implements SysUserService {
if (StringUtils.isBlank(userId)) { if (StringUtils.isBlank(userId)) {
return ResultDomain.failure(MSG_USER_ID_REQUIRED); return ResultDomain.failure(MSG_USER_ID_REQUIRED);
} }
String newPwd = IDUtils.generateID(); String newPwd = IdUtil.generateID();
if (newPwd.length() > 12) { if (newPwd.length() > 12) {
newPwd = newPwd.substring(0, 12); newPwd = newPwd.substring(0, 12);
} }

View File

@@ -16,7 +16,7 @@ import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbSysViewDTO; import org.xyzh.common.dto.sys.TbSysViewDTO;
import org.xyzh.common.dto.sys.TbSysViewPermissionDTO; import org.xyzh.common.dto.sys.TbSysViewPermissionDTO;
import org.xyzh.common.utils.IDUtils; import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.StringUtils; import org.xyzh.common.utils.StringUtils;
import org.xyzh.system.mapper.view.TbSysViewMapper; import org.xyzh.system.mapper.view.TbSysViewMapper;
import org.xyzh.system.mapper.view.TbSysViewPermissionMapper; import org.xyzh.system.mapper.view.TbSysViewPermissionMapper;
@@ -50,7 +50,7 @@ public class ViewServiceImpl implements ViewService {
return ResultDomain.failure("视图参数不能为空"); return ResultDomain.failure("视图参数不能为空");
} }
if (StringUtils.isBlank(viewDTO.getViewId())) { if (StringUtils.isBlank(viewDTO.getViewId())) {
viewDTO.setViewId(IDUtils.generateID()); viewDTO.setViewId(IdUtil.generateID());
} }
if (viewDTO.getCreateTime() == null) { if (viewDTO.getCreateTime() == null) {
viewDTO.setCreateTime(new Date()); viewDTO.setCreateTime(new Date());

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.system.mapper.user.TbGuestMapper">
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.sys.TbGuestDTO">
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="user_id" property="userId" jdbcType="VARCHAR"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR"/>
<result column="email" property="email" jdbcType="VARCHAR"/>
<result column="wechat_id" property="wechatId" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
</resultMap>
<sql id="Base_Column_List">
optsn, user_id, name, phone, email, wechat_id, create_time, update_time, delete_time, deleted
</sql>
<insert id="insertGuest" parameterType="org.xyzh.common.dto.sys.TbGuestDTO">
INSERT INTO sys.tb_guest (
optsn, user_id, name
<if test="phone != null">, phone</if>
<if test="email != null">, email</if>
<if test="wechatId != null">, wechat_id</if>
<if test="createTime != null">, create_time</if>
<if test="deleted != null">, deleted</if>
) VALUES (
#{optsn}, #{userId}, #{name}
<if test="phone != null">, #{phone}</if>
<if test="email != null">, #{email}</if>
<if test="wechatId != null">, #{wechatId}</if>
<if test="createTime != null">, #{createTime}</if>
<if test="deleted != null">, #{deleted}</if>
)
</insert>
<update id="updateGuest" parameterType="org.xyzh.common.dto.sys.TbGuestDTO">
UPDATE sys.tb_guest
<set>
<if test="name != null">name = #{name},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="email != null">email = #{email},</if>
<if test="wechatId != null">wechat_id = #{wechatId},</if>
update_time = now()
</set>
WHERE user_id = #{userId} AND deleted = false
</update>
<update id="deleteGuest">
UPDATE sys.tb_guest
SET deleted = true,
delete_time = now()
WHERE user_id = #{userId} AND deleted = false
</update>
<select id="selectGuestOne" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM sys.tb_guest
<where>
deleted = false
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="phone != null and phone != ''">
AND phone LIKE CONCAT('%', #{phone}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="wechatId != null and wechatId != ''">
AND wechat_id = #{wechatId}
</if>
</where>
</select>
<select id="selectGuestList" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM sys.tb_guest
WHERE deleted = false
<if test="filter != null">
<if test="filter.name != null and filter.name != ''">
AND name LIKE CONCAT('%', #{filter.name}, '%')
</if>
<if test="filter.phone != null and filter.phone != ''">
AND phone LIKE CONCAT('%', #{filter.phone}, '%')
</if>
<if test="filter.email != null and filter.email != ''">
AND email = #{filter.email}
</if>
<if test="filter.wechatId != null and filter.wechatId != ''">
AND wechat_id = #{filter.wechatId}
</if>
</if>
ORDER BY create_time DESC
</select>
<select id="selectGuestPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM sys.tb_guest
WHERE deleted = false
<if test="filter != null">
<if test="filter.name != null and filter.name != ''">
AND name LIKE CONCAT('%', #{filter.name}, '%')
</if>
<if test="filter.phone != null and filter.phone != ''">
AND phone LIKE CONCAT('%', #{filter.phone}, '%')
</if>
<if test="filter.email != null and filter.email != ''">
AND email = #{filter.email}
</if>
<if test="filter.wechatId != null and filter.wechatId != ''">
AND wechat_id = #{filter.wechatId}
</if>
</if>
ORDER BY create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<select id="countGuest">
SELECT COUNT(user_id)
FROM sys.tb_guest
<where>
deleted = false
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="phone != null and phone != ''">
AND phone LIKE CONCAT('%', #{phone}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="wechatId != null and wechatId != ''">
AND wechat_id = #{wechatId}
</if>
</where>
</select>
</mapper>