迁移结束
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ls:*)"
|
||||
"Bash(ls:*)",
|
||||
"Bash(tree:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(dir:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +210,6 @@ CREATE TABLE IF NOT EXISTS `tb_customer_service` (
|
||||
`status` varchar(20) NOT NULL DEFAULT 'offline' COMMENT '状态:online-在线 busy-忙碌 offline-离线',
|
||||
`skill_tags` json DEFAULT NULL COMMENT '技能标签',
|
||||
`max_concurrent` int NOT NULL DEFAULT 5 COMMENT '最大并发接待数',
|
||||
`current_workload` int NOT NULL DEFAULT 0 COMMENT '当前工作量',
|
||||
`total_served` int NOT NULL DEFAULT 0 COMMENT '累计服务次数',
|
||||
`avg_response_time` int DEFAULT NULL COMMENT '平均响应时间(秒)',
|
||||
`satisfaction_score` decimal(3,2) DEFAULT NULL COMMENT '满意度评分(0-5)',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 1 COMMENT '租户ID',
|
||||
@@ -221,7 +219,6 @@ CREATE TABLE IF NOT EXISTS `tb_customer_service` (
|
||||
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||
PRIMARY KEY (`user_id`),
|
||||
KEY `idx_status_workload` (`status`, `current_workload`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客服人员配置表';
|
||||
|
||||
|
||||
Submodule ai-management-dify updated: aa57f8551a...f1b32d51cb
Submodule ai-management-platform updated: 410de71bf2...bfea48fa67
33
docker/kkfileView/.env.example
Normal file
33
docker/kkfileView/.env.example
Normal file
@@ -0,0 +1,33 @@
|
||||
# ================================================
|
||||
# KKFileView 配置
|
||||
# 复制此文件为 .env 并修改配置
|
||||
# ================================================
|
||||
# 新增:KKFileView代理后的完整基础URL(HTTPS+域名,无端口、无末尾斜杠)
|
||||
KKFILEVIEW_BASE_URL=https://org.xyzh.yslg.kkfileview
|
||||
# ------------------------------
|
||||
# 端口配置
|
||||
# ------------------------------
|
||||
KKFILEVIEW_PORT=8012
|
||||
|
||||
# ------------------------------
|
||||
# 数据目录
|
||||
# ------------------------------
|
||||
DATA_ROOT=../volumes
|
||||
|
||||
# ------------------------------
|
||||
# 存储配置(可选)
|
||||
# ------------------------------
|
||||
# STORAGE_TYPE=local
|
||||
# STORAGE_PATH=/data
|
||||
|
||||
# ------------------------------
|
||||
# 其他配置(可选)
|
||||
# ------------------------------
|
||||
# 预览服务地址(如果需要外部访问)
|
||||
# KKFILEVIEW_EXTERNAL_URL=http://localhost:8012
|
||||
|
||||
# 最大文件大小(默认 50M)
|
||||
# MAX_FILE_SIZE=52428800
|
||||
|
||||
# 转换超时时间(默认 60s)
|
||||
# CONVERT_TIMEOUT=60
|
||||
43
docker/kkfileView/docker-compose.yml
Normal file
43
docker/kkfileView/docker-compose.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
# ================================================
|
||||
# KKFileView 文档预览服务
|
||||
# ================================================
|
||||
name: urbanlifeline
|
||||
|
||||
services:
|
||||
|
||||
# ====================== KKFileView 文档预览 ======================
|
||||
kkfileview:
|
||||
image: keking/kkfileview:latest
|
||||
container_name: urban-lifeline-kkfileview
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- urban-lifeline
|
||||
ports:
|
||||
- "${KKFILEVIEW_PORT:-8012}:8012"
|
||||
environment:
|
||||
# 基础配置
|
||||
KKFILEVIEW_PORT: 8012
|
||||
# 新增:核心预览路径(从.env读取,代理后的KKFileView完整域名)
|
||||
KK_BASE_URL: ${KKFILEVIEW_BASE_URL}
|
||||
# 新增:允许外部访问(固定值0.0.0.0,无需修改)
|
||||
SERVER_HOST: 0.0.0.0
|
||||
# 存储配置(如果需要)
|
||||
# STORAGE_TYPE: local
|
||||
# STORAGE_PATH: /data
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- ${DATA_ROOT:-../volumes}/kkfileview/data:/data
|
||||
- ${DATA_ROOT:-../volumes}/kkfileview/log:/opt/kkfileview/log
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8012/onlinePreview"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
networks:
|
||||
urban-lifeline:
|
||||
external: true
|
||||
name: urban-lifeline
|
||||
Submodule pigx-ai-app deleted from f158835910
@@ -1,290 +0,0 @@
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# 定义颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 设置脚本所在目录为工作目录
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SQL_DIR="${SCRIPT_DIR}/sql"
|
||||
|
||||
# 打印带时间戳的日志
|
||||
log() {
|
||||
local level=$1
|
||||
local message=$2
|
||||
local color=$NC
|
||||
|
||||
case $level in
|
||||
"INFO") color=$BLUE;;
|
||||
"SUCCESS") color=$GREEN;;
|
||||
"WARN") color=$YELLOW;;
|
||||
"ERROR") color=$RED;;
|
||||
esac
|
||||
|
||||
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${color}${level}${NC}: ${message}"
|
||||
}
|
||||
|
||||
# 数据库连接信息(可通过环境变量覆盖)
|
||||
DB_HOST=${POSTGRES_HOST:-"localhost"}
|
||||
DB_PORT=${POSTGRES_PORT:-"5432"}
|
||||
DB_NAME=${POSTGRES_DB:-"urban_lifeline"}
|
||||
DB_USER=${POSTGRES_USER:-"postgres"}
|
||||
DB_PASSWORD=${POSTGRES_PASSWORD:-"postgres"}
|
||||
|
||||
# 设置 PSQL 环境变量以支持中文
|
||||
export PGCLIENTENCODING=UTF8
|
||||
|
||||
# 检查psql命令是否可用
|
||||
check_psql() {
|
||||
if ! command -v psql &> /dev/null; then
|
||||
echo -e "${RED}Error: psql command not found. Please install PostgreSQL client.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查并创建数据库用户
|
||||
check_and_create_user() {
|
||||
local new_user=$1
|
||||
local new_password=$2
|
||||
|
||||
# 使用 postgres 用户执行
|
||||
if sudo -u postgres psql -c "SELECT 1 FROM pg_roles WHERE rolname = '$new_user'" | grep -q 1; then
|
||||
echo -e "${GREEN}User $new_user already exists${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Creating user $new_user...${NC}"
|
||||
sudo -u postgres psql -c "CREATE USER $new_user WITH PASSWORD '$new_password' CREATEDB;"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}User $new_user created successfully${NC}"
|
||||
else
|
||||
echo -e "${RED}Failed to create user $new_user${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查数据库连接
|
||||
check_db_connection() {
|
||||
# 首先尝试以当前用户身份连接
|
||||
if ! psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "postgres" -c '\q' &> /dev/null; then
|
||||
echo -e "${YELLOW}Could not connect with current settings, attempting to create user...${NC}"
|
||||
# 创建用户并设置权限
|
||||
check_and_create_user "$DB_USER" "$DB_PASSWORD"
|
||||
|
||||
# 再次检查连接
|
||||
if ! psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "postgres" -c '\q' &> /dev/null; then
|
||||
echo -e "${RED}Error: Could not connect to PostgreSQL server.${NC}"
|
||||
echo "Please check your connection settings:"
|
||||
echo "Host: $DB_HOST"
|
||||
echo "Port: $DB_PORT"
|
||||
echo "User: $DB_USER"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行SQL文件
|
||||
execute_sql_file() {
|
||||
local sql_file=$1
|
||||
if [ ! -f "$sql_file" ]; then
|
||||
echo -e "${RED}Error: SQL file not found: $sql_file${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Executing SQL file: $sql_file${NC}"
|
||||
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$sql_file"
|
||||
local status=$?
|
||||
if [ $status -eq 0 ]; then
|
||||
echo -e "${GREEN}Successfully executed: $sql_file${NC}"
|
||||
else
|
||||
echo -e "${RED}Failed to execute: $sql_file${NC}"
|
||||
return $status
|
||||
fi
|
||||
}
|
||||
|
||||
# 初始化数据库
|
||||
init() {
|
||||
echo -e "${YELLOW}Initializing database...${NC}"
|
||||
|
||||
# 执行完整的初始化脚本
|
||||
log "INFO" "Executing initialization script..."
|
||||
# Run from inside the SQL_DIR so relative \i includes in initAll.sql (like createDB.sql)
|
||||
# resolve relative to the SQL directory.
|
||||
(
|
||||
if [ ! -d "$SQL_DIR" ]; then
|
||||
echo -e "${RED}Error: SQL directory not found: $SQL_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
cd "$SQL_DIR" || exit 1
|
||||
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "postgres" -v ON_ERROR_STOP=1 -f "initAll.sql"
|
||||
)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "SUCCESS" "Database initialization completed successfully"
|
||||
else
|
||||
log "ERROR" "Database initialization failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to create database.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 2. 创建扩展和设置搜索路径
|
||||
echo -e "${YELLOW}Creating extensions...${NC}"
|
||||
check_extensions_availability() {
|
||||
# 检查服务器上是否存在需创建的扩展
|
||||
local missing=()
|
||||
local exts=("uuid-ossp" "pg_trgm" "btree_gist")
|
||||
for ext in "${exts[@]}"; do
|
||||
# 查询 pg_available_extensions 来判断扩展是否已安装到服务器目录
|
||||
if ! PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = '$ext';" | grep -q 1; then
|
||||
missing+=("$ext")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing[@]} -ne 0 ]; then
|
||||
echo -e "${RED}Error: The following server-side extensions are not available: ${missing[*]}${NC}"
|
||||
echo "If you compiled PostgreSQL from source, you need to build and install the contrib modules into the server's installation prefix. Example steps:"
|
||||
echo " # 在 PostgreSQL 源码目录下运行:"
|
||||
echo " cd /path/to/postgresql-source/contrib"
|
||||
echo " make"
|
||||
echo " sudo make install"
|
||||
echo "或者只安装缺失的模块(例如 uuid-ossp):"
|
||||
echo " cd /path/to/postgresql-source/contrib/uuid-ossp"
|
||||
echo " make"
|
||||
echo " sudo make install"
|
||||
echo "安装完成后,重启 PostgreSQL 服务并重新运行此脚本:"
|
||||
echo " sudo systemctl restart postgresql"
|
||||
echo "如果你使用的是容器或自定义路径,请确保将编译安装的扩展安装到 PostgreSQL 的 \$(pg_config --sharedir)/extension 目录下。"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# 检查扩展可用性,若缺失则给出建议并退出
|
||||
if ! check_extensions_availability; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
|
||||
CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";
|
||||
CREATE EXTENSION IF NOT EXISTS \"pg_trgm\";
|
||||
CREATE EXTENSION IF NOT EXISTS \"btree_gist\";"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to create extensions.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 3. 逐个执行初始化SQL文件
|
||||
echo -e "${YELLOW}Initializing tables...${NC}"
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
# 跳过注释和空行
|
||||
[[ $line =~ ^--.*$ ]] && continue
|
||||
[[ -z "${line// }" ]] && continue
|
||||
|
||||
# 从 \i 命令中提取文件名
|
||||
if [[ $line =~ \\i[[:space:]]+([^[:space:]]+) ]]; then
|
||||
sql_file="${SQL_DIR}/${BASH_REMATCH[1]}"
|
||||
if [[ $sql_file != *"createDB.sql"* ]]; then # 跳过createDB.sql
|
||||
echo -e "${YELLOW}Executing: $sql_file${NC}"
|
||||
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$sql_file"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to execute: $sql_file${NC}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < "${SQL_DIR}/initAll.sql"
|
||||
|
||||
# 4. 设置搜索路径
|
||||
echo -e "${YELLOW}Setting search path...${NC}"
|
||||
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
|
||||
ALTER DATABASE $DB_NAME SET search_path TO sys, public;"
|
||||
|
||||
echo -e "${GREEN}Database initialization completed successfully.${NC}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 重新初始化数据库
|
||||
reinit() {
|
||||
echo -e "${YELLOW}Reinitializing database...${NC}"
|
||||
delete
|
||||
init
|
||||
}
|
||||
|
||||
# 删除数据库
|
||||
delete() {
|
||||
echo -e "${YELLOW}Deleting database...${NC}"
|
||||
|
||||
# 多次尝试终止连接(因为某些连接可能会立即重连)
|
||||
for i in {1..3}; do
|
||||
log "INFO" "Terminating database connections (attempt $i/3)..."
|
||||
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "postgres" -c "
|
||||
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
||||
FROM pg_stat_activity
|
||||
WHERE pg_stat_activity.datname = '$DB_NAME'
|
||||
AND pid <> pg_backend_pid();" > /dev/null 2>&1
|
||||
|
||||
# 等待连接完全关闭
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 尝试删除数据库,最多重试3次
|
||||
for i in {1..3}; do
|
||||
log "INFO" "Attempting to drop database (attempt $i/3)..."
|
||||
if PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "postgres" -c "DROP DATABASE IF EXISTS $DB_NAME;" 2>&1; then
|
||||
log "SUCCESS" "Database deleted successfully"
|
||||
return 0
|
||||
else
|
||||
log "WARN" "Failed to drop database, retrying after 2 seconds..."
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
log "ERROR" "Failed to delete database after 3 attempts"
|
||||
log "ERROR" "Please ensure all connections to the database are closed, including:"
|
||||
log "ERROR" " - Running application servers"
|
||||
log "ERROR" " - IDE database connections"
|
||||
log "ERROR" " - pgAdmin or other database tools"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "Usage: $0 {init|reinit|delete}"
|
||||
echo "Commands:"
|
||||
echo " init Initialize the database"
|
||||
echo " reinit Reinitialize the database (delete and create)"
|
||||
echo " delete Delete the database"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
check_psql
|
||||
check_db_connection
|
||||
|
||||
case "$1" in
|
||||
"init")
|
||||
init
|
||||
;;
|
||||
"reinit")
|
||||
reinit
|
||||
;;
|
||||
"delete")
|
||||
delete
|
||||
;;
|
||||
*)
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Call main with all passed arguments so the script runs when invoked
|
||||
main "$@"
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
-- 删除已存在的数据库(如果存在)
|
||||
DROP DATABASE IF EXISTS urban_lifeline;
|
||||
|
||||
-- 创建新数据库,使用 UTF8 编码,并设置适合中文的排序规则
|
||||
-- 使用 template0 确保干净的数据库模板
|
||||
-- zh_CN.UTF-8 支持中文字符排序和比较
|
||||
CREATE DATABASE urban_lifeline
|
||||
ENCODING 'UTF8'
|
||||
TEMPLATE template0
|
||||
LC_COLLATE 'zh_CN.UTF-8'
|
||||
LC_CTYPE 'zh_CN.UTF-8';
|
||||
|
||||
-- 连接到新创建的数据库
|
||||
\c urban_lifeline;
|
||||
|
||||
-- -- 创建扩展(如果需要)
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID 支持
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 文本搜索支持
|
||||
CREATE EXTENSION IF NOT EXISTS "btree_gist"; -- GiST 索引支持
|
||||
|
||||
-- 设置搜索路径(可选,但建议设置)
|
||||
-- ALTER DATABASE urban-lifeline SET search_path TO sys, public;
|
||||
|
||||
-- sudo ./configure --prefix=/opt/postgres/postgres-17.6
|
||||
-- --with-uuid=ossp --with-openssl --with-libxml --with-pam
|
||||
-- && sudo make && sudo make install
|
||||
@@ -1,188 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS ai;
|
||||
|
||||
-- AI智能体配置
|
||||
DROP TABLE IF EXISTS ai.tb_agent CASCADE;
|
||||
CREATE TABLE ai.tb_agent(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
|
||||
name VARCHAR(50) NOT NULL, -- 智能体名称
|
||||
description VARCHAR(500) DEFAULT NULL, -- 智能体描述
|
||||
link VARCHAR(500) DEFAULT NULL, -- 智能体url
|
||||
api_key VARCHAR(500) NOT NULL, -- dify智能体APIKEY
|
||||
is_outer BOOLEAN DEFAULT false, -- 是否是对外智能体,未登录可用
|
||||
introduce VARCHAR(500) NOT NULL, -- 引导词
|
||||
prompt_cards JSONB DEFAULT '[]'::jsonb, -- 提示卡片数组 [{file_id:'', prompt:''}]
|
||||
category VARCHAR(50) NOT NULL, -- 分类
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (agent_id),
|
||||
UNIQUE (optsn),
|
||||
UNIQUE (api_key)
|
||||
);
|
||||
|
||||
-- AI智能体对话
|
||||
DROP TABLE IF EXISTS ai.tb_chat CASCADE;
|
||||
CREATE TABLE ai.tb_chat(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
chat_id VARCHAR(50) NOT NULL, -- 对话ID
|
||||
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
user_type BOOLEAN NOT NULL DEFAULT true, -- 用户类型 true-系统内部人员 false-系统外部人员
|
||||
title VARCHAR(500) NOT NULL, -- 对话标题
|
||||
channel VARCHAR(50) DEFAULT 'agent', -- 对话渠道 agent、wechat
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (chat_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
-- AI智能体对话消息
|
||||
DROP TABLE IF EXISTS ai.tb_chat_message CASCADE;
|
||||
CREATE TABLE ai.tb_chat_message(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
message_id VARCHAR(50) NOT NULL, -- 消息ID
|
||||
dify_message_id VARCHAR(100) DEFAULT NULL, -- Dify消息ID
|
||||
chat_id VARCHAR(50) NOT NULL, -- 对话ID
|
||||
role VARCHAR(50) NOT NULL, -- 角色:user-用户/ai-智能体/recipient-来客
|
||||
content TEXT NOT NULL, -- 消息内容
|
||||
files VARCHAR(50)[] DEFAULT NULL, -- 文件id数组
|
||||
comment VARCHAR(50) DEFAULT NULL, -- 评价
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (message_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
|
||||
-- 知识库配置 bidding和workcase2个服务使用
|
||||
DROP TABLE IF EXISTS ai.tb_knowledge CASCADE;
|
||||
CREATE TABLE ai.tb_knowledge(
|
||||
-- 知识库dify相关配置
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
|
||||
title VARCHAR(255) NOT NULL, -- 知识库标题
|
||||
avatar VARCHAR(255) DEFAULT NULL, -- 知识库头像
|
||||
description VARCHAR(500) DEFAULT NULL, -- 知识库描述
|
||||
dify_dataset_id VARCHAR(100) DEFAULT NULL, -- Dify知识库ID(Dataset ID)
|
||||
dify_indexing_technique VARCHAR(50) DEFAULT 'high_quality', -- Dify索引方式(high_quality/economy)
|
||||
embedding_model VARCHAR(100) DEFAULT NULL, -- 向量模型名称
|
||||
embedding_model_provider VARCHAR(100) DEFAULT NULL, -- 向量模型提供商
|
||||
rerank_model VARCHAR(100) DEFAULT NULL, -- Rerank模型名称
|
||||
rerank_model_provider VARCHAR(100) DEFAULT NULL, -- Rerank模型提供商
|
||||
reranking_enable BOOLEAN DEFAULT false, -- 是否启用Rerank
|
||||
retrieval_top_k INTEGER DEFAULT 2, -- 检索Top K(返回前K个结果)
|
||||
retrieval_score_threshold DECIMAL(3,2) DEFAULT 0.00, -- 检索分数阈值(0.00-1.00)
|
||||
document_count INTEGER DEFAULT 0, -- 文档数量
|
||||
total_chunks INTEGER DEFAULT 0, -- 总分段数
|
||||
-- 下面是服务使用
|
||||
service VARCHAR(50) DEFAULT NULL, -- 所属服务 workcase、bidding
|
||||
project_id VARCHAR(50) DEFAULT NULL, -- bidding所属项目ID
|
||||
category VARCHAR(50) DEFAULT NULL, -- 所属分类 workcase 内部知识库、外部知识库
|
||||
creator VARCHAR(50) NOT NULL, -- 创建者(用户ID)
|
||||
dept_path VARCHAR(50) DEFAULT NULL, -- 创建者部门路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (optsn),
|
||||
UNIQUE (knowledge_id),
|
||||
UNIQUE (dify_dataset_id)
|
||||
);
|
||||
|
||||
-- 知识库配置表字段注释
|
||||
COMMENT ON TABLE ai.tb_knowledge IS '知识库配置表';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.optsn IS '流水号';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.knowledge_id IS '知识库ID';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.title IS '知识库标题';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.avatar IS '知识库头像';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.description IS '知识库描述';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.dify_dataset_id IS 'Dify知识库ID(Dataset ID)';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.dify_indexing_technique IS 'Dify索引方式(high_quality/economy)';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.embedding_model IS '向量模型名称';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.embedding_model_provider IS '向量模型提供商';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.rerank_model IS 'Rerank模型名称';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.rerank_model_provider IS 'Rerank模型提供商';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.reranking_enable IS '是否启用Rerank';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.retrieval_top_k IS '检索Top K(返回前K个结果)';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.retrieval_score_threshold IS '检索分数阈值(0.00-1.00)';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.document_count IS '文档数量';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.total_chunks IS '总分段数';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.service IS '所属服务 workcase、bidding';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.project_id IS 'bidding所属项目ID';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.category IS '所属分类 workcase 内部知识库、外部知识库';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.creator IS '创建者(用户ID)';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.dept_path IS '创建者部门路径';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.updater IS '更新者';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge.deleted IS '是否删除';
|
||||
-- bidding知识库根据project等变化
|
||||
-- workcase知识库固定8个
|
||||
-- workcase外部知识库:4个知识库:
|
||||
-- 1. 设备操作指南
|
||||
-- 2. 常见故障解决方案
|
||||
-- 3. 三包外服务政策
|
||||
-- 4. 配件咨询话术
|
||||
-- workcase内部知识库:4个知识库:
|
||||
-- 1. 技术维修手册
|
||||
-- 2. 产品参数明细
|
||||
-- 3. 内部服务流程规范
|
||||
-- 4. 客户服务话术模板
|
||||
|
||||
-- 知识库文件 文件上传dify知识库,对dify内的文件修改不生成新版本, 只有重新上传才生成新版本
|
||||
DROP TABLE IF EXISTS ai.tb_knowledge_file CASCADE;
|
||||
CREATE TABLE ai.tb_knowledge_file(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
|
||||
file_root_id VARCHAR(50) NOT NULL, -- 文件根ID
|
||||
file_id VARCHAR(50) NOT NULL, -- 文件ID
|
||||
dify_file_id VARCHAR(50) NOT NULL, -- dify文件ID
|
||||
version INTEGER NOT NULL DEFAULT 1, -- 文件版本
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (optsn),
|
||||
UNIQUE (knowledge_id, file_id)
|
||||
);
|
||||
|
||||
-- 知识库文件表字段注释
|
||||
COMMENT ON TABLE ai.tb_knowledge_file IS '知识库文件表';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.optsn IS '流水号';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.knowledge_id IS '知识库ID';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.file_root_id IS '文件根ID';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.file_id IS '文件ID';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.dify_file_id IS 'dify文件ID';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.version IS '文件版本';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN ai.tb_knowledge_file.deleted IS '是否删除';
|
||||
|
||||
DROP TABLE IF EXISTS ai.tb_knowledge_file_log CASCADE;
|
||||
CREATE TABLE ai.tb_knowledge_file_log(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
log_id VARCHAR(50) NOT NULL, -- 日志ID
|
||||
knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID
|
||||
file_root_id VARCHAR(50) NOT NULL, -- 文件根ID
|
||||
file_id VARCHAR(50) NOT NULL, -- 文件ID
|
||||
file_name VARCHAR(100) NOT NULL, -- 文件名
|
||||
service VARCHAR(50) NOT NULL, -- 所属服务 workcase、bidding
|
||||
version INTEGER NOT NULL DEFAULT 1, -- 文件版本
|
||||
action VARCHAR(50) NOT NULL, -- 操作类型 upload、update、delete
|
||||
creator VARCHAR(50) NOT NULL, -- 创建者(用户ID)
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建者姓名
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
PRIMARY KEY (optsn),
|
||||
UNIQUE (knowledge_id, file_id)
|
||||
);
|
||||
@@ -1,264 +0,0 @@
|
||||
-- =============================
|
||||
-- 招投标智能体业务模块
|
||||
-- 支持:招标文件管理、投标文件生成、评分分析、流程跟踪
|
||||
-- =============================
|
||||
CREATE SCHEMA IF NOT EXISTS bidding;
|
||||
|
||||
-- 招标项目表
|
||||
DROP TABLE IF EXISTS bidding.tb_bidding_project CASCADE;
|
||||
CREATE TABLE bidding.tb_bidding_project (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
project_id VARCHAR(50) NOT NULL, -- 项目ID
|
||||
project_no VARCHAR(100) NOT NULL, -- 项目编号
|
||||
project_name VARCHAR(500) NOT NULL, -- 项目名称
|
||||
project_type VARCHAR(50) NOT NULL, -- 项目类型:public-公开招标/invitation-邀请招标/competitive_negotiation-竞争性谈判
|
||||
industry VARCHAR(100), -- 所属行业
|
||||
source_platform VARCHAR(100), -- 来源平台(如:政府采购网、企业官网等)
|
||||
source_url VARCHAR(500), -- 来源URL
|
||||
publish_date TIMESTAMPTZ, -- 发布日期
|
||||
deadline TIMESTAMPTZ, -- 投标截止日期
|
||||
opening_date TIMESTAMPTZ, -- 开标日期
|
||||
budget_amount DECIMAL(18,2), -- 预算金额
|
||||
currency VARCHAR(10) DEFAULT 'CNY', -- 货币单位
|
||||
project_status VARCHAR(30) NOT NULL DEFAULT 'collecting', -- 项目状态:collecting-收集中/analyzing-分析中/preparing-准备投标/submitted-已提交/opened-已开标/won-中标/lost-未中标/abandoned-放弃
|
||||
winning_status VARCHAR(30), -- 中标状态:pending-待定/won-中标/lost-未中标
|
||||
winning_amount DECIMAL(18,2), -- 中标金额
|
||||
client_name VARCHAR(255), -- 客户名称
|
||||
client_contact VARCHAR(100), -- 客户联系方式
|
||||
contact_person VARCHAR(100), -- 联系人
|
||||
project_location VARCHAR(500), -- 项目地点
|
||||
description TEXT, -- 项目描述
|
||||
keywords TEXT[], -- 关键词数组
|
||||
metadata JSONB DEFAULT NULL, -- 项目元数据
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
responsible_user VARCHAR(50), -- 负责人
|
||||
team_members VARCHAR(50)[], -- 团队成员数组
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (project_id),
|
||||
UNIQUE (optsn),
|
||||
UNIQUE (project_no)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_project_status ON bidding.tb_bidding_project(project_status) WHERE deleted = false;
|
||||
CREATE INDEX idx_project_deadline ON bidding.tb_bidding_project(deadline) WHERE deleted = false;
|
||||
CREATE INDEX idx_project_dept ON bidding.tb_bidding_project(dept_path) WHERE deleted = false;
|
||||
CREATE INDEX idx_project_responsible ON bidding.tb_bidding_project(responsible_user) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bidding_project IS '招标项目表';
|
||||
COMMENT ON COLUMN bidding.tb_bidding_project.project_status IS '项目状态:collecting/analyzing/preparing/submitted/opened/won/lost/abandoned';
|
||||
|
||||
-- 招标文件表
|
||||
DROP TABLE IF EXISTS bidding.tb_bidding_document CASCADE;
|
||||
CREATE TABLE bidding.tb_bidding_document (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
doc_id VARCHAR(50) NOT NULL, -- 文档ID
|
||||
project_id VARCHAR(50) NOT NULL, -- 所属项目ID
|
||||
doc_type VARCHAR(50) NOT NULL, -- 文档类型:tender-招标文件/technical-技术标/commercial-商务标/clarification-澄清文件/other-其他
|
||||
doc_name VARCHAR(500) NOT NULL, -- 文档名称
|
||||
file_id VARCHAR(50), -- 关联文件表ID
|
||||
file_path VARCHAR(500), -- 文件路径
|
||||
file_size BIGINT, -- 文件大小
|
||||
mime_type VARCHAR(100), -- MIME类型
|
||||
version INTEGER DEFAULT 1, -- 版本号
|
||||
language VARCHAR(20) DEFAULT 'zh-CN', -- 语言
|
||||
page_count INTEGER, -- 页数
|
||||
parse_status VARCHAR(30) DEFAULT 'pending', -- 解析状态:pending-待解析/parsing-解析中/completed-已完成/failed-失败
|
||||
parse_result JSONB, -- 解析结果(JSON格式:提取的要素、表格、图纸等)
|
||||
extraction_data JSONB, -- 提取的结构化数据
|
||||
ai_analysis TEXT, -- AI分析结果
|
||||
upload_date TIMESTAMPTZ DEFAULT now(), -- 上传日期
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (doc_id),
|
||||
UNIQUE (optsn),
|
||||
FOREIGN KEY (project_id) REFERENCES bidding.tb_bidding_project(project_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_doc_project ON bidding.tb_bidding_document(project_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_doc_type ON bidding.tb_bidding_document(doc_type) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bidding_document IS '招标文件表';
|
||||
COMMENT ON COLUMN bidding.tb_bidding_document.parse_status IS '解析状态:pending/parsing/completed/failed';
|
||||
|
||||
-- 招标要素提取表
|
||||
DROP TABLE IF EXISTS bidding.tb_bidding_requirement CASCADE;
|
||||
CREATE TABLE bidding.tb_bidding_requirement (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
req_id VARCHAR(50) NOT NULL, -- 要素ID
|
||||
project_id VARCHAR(50) NOT NULL, -- 所属项目ID
|
||||
doc_id VARCHAR(50), -- 来源文档ID
|
||||
req_category VARCHAR(50) NOT NULL, -- 要素类别:commercial-商务要素/technical-技术参数/veto-否决项/qualification-资质要求/delivery-交付要求/payment-付款条件/scoring-评分标准
|
||||
req_name VARCHAR(255) NOT NULL, -- 要素名称
|
||||
req_content TEXT NOT NULL, -- 要素内容
|
||||
req_value VARCHAR(500), -- 要素值
|
||||
is_mandatory BOOLEAN DEFAULT false, -- 是否必填
|
||||
is_veto BOOLEAN DEFAULT false, -- 是否为否决项
|
||||
priority INTEGER DEFAULT 0, -- 优先级
|
||||
extraction_method VARCHAR(30) DEFAULT 'ai', -- 提取方式:ai-AI提取/manual-人工录入
|
||||
confidence_score DECIMAL(5,4), -- 置信度分数(0-1)
|
||||
source_location JSONB, -- 来源位置(页码、段落等)
|
||||
compliance_status VARCHAR(30), -- 合规状态:compliant-符合/non_compliant-不符合/pending-待确认
|
||||
response_content TEXT, -- 响应内容(我方的应答)
|
||||
notes TEXT, -- 备注
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (req_id),
|
||||
UNIQUE (optsn),
|
||||
FOREIGN KEY (project_id) REFERENCES bidding.tb_bidding_project(project_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_req_project ON bidding.tb_bidding_requirement(project_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_req_category ON bidding.tb_bidding_requirement(req_category) WHERE deleted = false;
|
||||
CREATE INDEX idx_req_veto ON bidding.tb_bidding_requirement(is_veto) WHERE deleted = false AND is_veto = true;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bidding_requirement IS '招标要素提取表';
|
||||
COMMENT ON COLUMN bidding.tb_bidding_requirement.req_category IS '要素类别:commercial/technical/veto/qualification/delivery/payment/scoring';
|
||||
|
||||
-- 投标文件生成表
|
||||
DROP TABLE IF EXISTS bidding.tb_bid_response CASCADE;
|
||||
CREATE TABLE bidding.tb_bid_response (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
response_id VARCHAR(50) NOT NULL, -- 响应文件ID
|
||||
project_id VARCHAR(50) NOT NULL, -- 所属项目ID
|
||||
response_type VARCHAR(50) NOT NULL, -- 响应类型:technical-技术标/commercial-商务标/comprehensive-综合标
|
||||
doc_name VARCHAR(500) NOT NULL, -- 文档名称
|
||||
outline TEXT, -- 文档大纲(JSON格式)
|
||||
content TEXT, -- 文档内容
|
||||
generation_method VARCHAR(30) DEFAULT 'ai', -- 生成方式:ai-AI生成/template-模板生成/manual-人工编写
|
||||
template_id VARCHAR(50), -- 使用的模板ID
|
||||
ai_model VARCHAR(100), -- 使用的AI模型
|
||||
generation_status VARCHAR(30) DEFAULT 'draft', -- 生成状态:draft-草稿/reviewing-审核中/approved-已批准/rejected-已拒绝/submitted-已提交
|
||||
file_id VARCHAR(50), -- 生成的文件ID
|
||||
file_path VARCHAR(500), -- 文件路径
|
||||
version INTEGER DEFAULT 1, -- 版本号
|
||||
parent_version_id VARCHAR(50), -- 父版本ID
|
||||
review_comments TEXT, -- 审核意见
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (response_id),
|
||||
UNIQUE (optsn),
|
||||
FOREIGN KEY (project_id) REFERENCES bidding.tb_bidding_project(project_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_response_project ON bidding.tb_bid_response(project_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_response_status ON bidding.tb_bid_response(generation_status) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bid_response IS '投标文件生成表';
|
||||
COMMENT ON COLUMN bidding.tb_bid_response.generation_status IS '生成状态:draft/reviewing/approved/rejected/submitted';
|
||||
|
||||
-- 评分规则表
|
||||
DROP TABLE IF EXISTS bidding.tb_bidding_scoring_rule CASCADE;
|
||||
CREATE TABLE bidding.tb_bidding_scoring_rule (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
rule_id VARCHAR(50) NOT NULL, -- 规则ID
|
||||
project_id VARCHAR(50) NOT NULL, -- 所属项目ID
|
||||
rule_category VARCHAR(50) NOT NULL, -- 规则类别:technical-技术分/commercial-商务分/price-价格分/credit-信誉分
|
||||
rule_name VARCHAR(255) NOT NULL, -- 规则名称
|
||||
rule_description TEXT, -- 规则描述
|
||||
max_score DECIMAL(10,2) NOT NULL, -- 最高分值
|
||||
weight DECIMAL(5,4), -- 权重(0-1)
|
||||
scoring_method VARCHAR(50), -- 评分方法:fixed-固定分值/range-区间评分/formula-公式计算
|
||||
calculation_formula TEXT, -- 计算公式
|
||||
evaluation_criteria TEXT, -- 评分标准
|
||||
our_score DECIMAL(10,2), -- 我方得分(预估)
|
||||
score_analysis TEXT, -- 得分分析
|
||||
optimization_advice TEXT, -- 优化建议
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (rule_id),
|
||||
UNIQUE (optsn),
|
||||
FOREIGN KEY (project_id) REFERENCES bidding.tb_bidding_project(project_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_rule_project ON bidding.tb_bidding_scoring_rule(project_id) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bidding_scoring_rule IS '评分规则表';
|
||||
|
||||
-- 项目流程节点表
|
||||
DROP TABLE IF EXISTS bidding.tb_bidding_process CASCADE;
|
||||
CREATE TABLE bidding.tb_bidding_process (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
process_id VARCHAR(50) NOT NULL, -- 流程节点ID
|
||||
project_id VARCHAR(50) NOT NULL, -- 所属项目ID
|
||||
node_name VARCHAR(255) NOT NULL, -- 节点名称
|
||||
node_type VARCHAR(50) NOT NULL, -- 节点类型:collection-文件收集/analysis-需求分析/preparation-文件准备/review-内部审核/submission-投标提交/opening-开标/result-结果通知
|
||||
node_order INTEGER NOT NULL, -- 节点顺序
|
||||
node_status VARCHAR(30) DEFAULT 'pending', -- 节点状态:pending-待处理/in_progress-进行中/completed-已完成/skipped-已跳过
|
||||
planned_start_time TIMESTAMPTZ, -- 计划开始时间
|
||||
planned_end_time TIMESTAMPTZ, -- 计划结束时间
|
||||
actual_start_time TIMESTAMPTZ, -- 实际开始时间
|
||||
actual_end_time TIMESTAMPTZ, -- 实际结束时间
|
||||
responsible_user VARCHAR(50), -- 负责人
|
||||
participants VARCHAR(50)[], -- 参与人员数组
|
||||
notes TEXT, -- 节点备注
|
||||
attachments VARCHAR(50)[], -- 附件ID数组
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (process_id),
|
||||
UNIQUE (optsn),
|
||||
FOREIGN KEY (project_id) REFERENCES bidding.tb_bidding_project(project_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_process_project ON bidding.tb_bidding_process(project_id, node_order) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bidding_process IS '项目流程节点表';
|
||||
COMMENT ON COLUMN bidding.tb_bidding_process.node_type IS '节点类型:collection/analysis/preparation/review/submission/opening/result';
|
||||
|
||||
-- 投标模板表
|
||||
DROP TABLE IF EXISTS bidding.tb_bid_template CASCADE;
|
||||
CREATE TABLE bidding.tb_bid_template (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
template_id VARCHAR(50) NOT NULL, -- 模板ID
|
||||
template_name VARCHAR(255) NOT NULL, -- 模板名称
|
||||
template_type VARCHAR(50) NOT NULL, -- 模板类型:technical-技术标/commercial-商务标/comprehensive-综合标
|
||||
industry VARCHAR(100), -- 适用行业
|
||||
template_content TEXT, -- 模板内容
|
||||
outline_structure JSONB, -- 大纲结构(JSON格式)
|
||||
file_id VARCHAR(50), -- 模板文件ID
|
||||
usage_count INTEGER DEFAULT 0, -- 使用次数
|
||||
is_default BOOLEAN DEFAULT false, -- 是否默认模板
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
status VARCHAR(20) DEFAULT 'active', -- 状态:active-激活/inactive-停用
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (template_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_template_type ON bidding.tb_bid_template(template_type) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE bidding.tb_bid_template IS '投标模板表';
|
||||
@@ -1,51 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS config;
|
||||
DROP TABLE IF EXISTS config.tb_sys_config CASCADE;
|
||||
CREATE TABLE config.tb_sys_config (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
config_id VARCHAR(50) NOT NULL, -- 配置ID
|
||||
key VARCHAR(255) NOT NULL, -- 配置键
|
||||
name VARCHAR(255) NOT NULL, -- 配置名称
|
||||
value VARCHAR(255) NOT NULL, -- 配置值
|
||||
config_type VARCHAR(50) NOT NULL, -- 数据类型(String, INTEGER, BOOLEAN, Float, Double)
|
||||
render_type VARCHAR(50) NOT NULL, -- 配置渲染类型(select, input, textarea, checkbox, radio, switch)
|
||||
description VARCHAR(255) NOT NULL, -- 配置描述
|
||||
re JSON DEFAULT NULL, -- 正则表达式校验规则
|
||||
options JSON DEFAULT NULL, -- 可选项,render_type为select、checkbox、radio时使用
|
||||
"group" VARCHAR(255) NOT NULL, -- 配置组
|
||||
module_id VARCHAR(255) NOT NULL, -- 模块id
|
||||
order_num INT NOT NULL, -- 配置顺序
|
||||
status INT NOT NULL DEFAULT 0, -- 配置状态 0:启用 1:禁用
|
||||
remark VARCHAR(255) NOT NULL, -- 配置备注
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径,支持like递归(如/1/2/3/)
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 配置创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 配置更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 配置删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (config_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE config.tb_sys_config IS '系统配置表';
|
||||
COMMENT ON COLUMN config.tb_sys_config.optsn IS '流水号';
|
||||
COMMENT ON COLUMN config.tb_sys_config.config_id IS '配置ID';
|
||||
COMMENT ON COLUMN config.tb_sys_config.key IS '配置键';
|
||||
COMMENT ON COLUMN config.tb_sys_config.name IS '配置名称';
|
||||
COMMENT ON COLUMN config.tb_sys_config.value IS '配置值';
|
||||
COMMENT ON COLUMN config.tb_sys_config.config_type IS '数据类型';
|
||||
COMMENT ON COLUMN config.tb_sys_config.render_type IS '数据渲染类型';
|
||||
COMMENT ON COLUMN config.tb_sys_config.description IS '配置描述';
|
||||
COMMENT ON COLUMN config.tb_sys_config.re IS '正则表达式校验规则';
|
||||
COMMENT ON COLUMN config.tb_sys_config.options IS '可选项';
|
||||
COMMENT ON COLUMN config.tb_sys_config.group IS'配置组名称';
|
||||
COMMENT ON COLUMN config.tb_sys_config.module_id IS '模块id';
|
||||
COMMENT ON COLUMN config.tb_sys_config.order_num IS '配置顺序';
|
||||
COMMENT ON COLUMN config.tb_sys_config.status IS '配置状态';
|
||||
COMMENT ON COLUMN config.tb_sys_config.remark IS '配置备注';
|
||||
COMMENT ON COLUMN config.tb_sys_config.creator IS '创建者';
|
||||
COMMENT ON COLUMN config.tb_sys_config.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN config.tb_sys_config.updater IS '更新者';
|
||||
COMMENT ON COLUMN config.tb_sys_config.create_time IS '配置创建时间';
|
||||
COMMENT ON COLUMN config.tb_sys_config.update_time IS '配置更新时间';
|
||||
COMMENT ON COLUMN config.tb_sys_config.delete_time IS '配置删除时间';
|
||||
COMMENT ON COLUMN config.tb_sys_config.deleted IS '是否删除';
|
||||
@@ -1,153 +0,0 @@
|
||||
-- ====================================================
|
||||
-- 定时任务表
|
||||
-- ====================================================
|
||||
CREATE SCHEMA IF NOT EXISTS crontab;
|
||||
DROP TABLE IF EXISTS crontab.tb_crontab_task CASCADE;
|
||||
CREATE TABLE crontab.tb_crontab_task (
|
||||
id VARCHAR(64) NOT NULL,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
task_name VARCHAR(100) NOT NULL,
|
||||
task_group VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',
|
||||
meta_id VARCHAR(64) NOT NULL,
|
||||
default_recipient SMALLINT NOT NULL DEFAULT 0, -- 是否使用默认接收人(0:否 1:是)
|
||||
bean_name VARCHAR(100) NOT NULL,
|
||||
method_name VARCHAR(100) NOT NULL,
|
||||
method_params VARCHAR(500) DEFAULT NULL,
|
||||
cron_expression VARCHAR(100) NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 0, -- 任务状态(0:暂停 1:运行中)
|
||||
description VARCHAR(500) DEFAULT NULL,
|
||||
concurrent SMALLINT NOT NULL DEFAULT 0, -- 是否允许并发执行(0:否 1:是)
|
||||
misfire_policy SMALLINT NOT NULL DEFAULT 1, -- 错过执行策略(1:立即执行 2:执行一次 3:放弃执行)
|
||||
creator VARCHAR(64) DEFAULT NULL,
|
||||
updater VARCHAR(64) DEFAULT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除(0:否 1:是)
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_task_name ON crontab.tb_crontab_task(task_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_bean_name ON crontab.tb_crontab_task(bean_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_status ON crontab.tb_crontab_task(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_deleted ON crontab.tb_crontab_task(deleted);
|
||||
|
||||
COMMENT ON TABLE crontab.tb_crontab_task IS '定时任务配置表';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.id IS '主键ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.task_id IS '任务ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.task_name IS '任务名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.task_group IS '任务分组';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.meta_id IS '任务元数据ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.default_recipient IS '是否使用默认接收人(0:否 1:是)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.bean_name IS 'Bean名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.method_name IS '方法名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.method_params IS '方法参数';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.cron_expression IS 'Cron表达式';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.status IS '任务状态(0:暂停 1:运行中)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.description IS '任务描述';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.concurrent IS '是否允许并发执行(0:否 1:是)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.misfire_policy IS '错过执行策略(1:立即执行 2:执行一次 3:放弃执行)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.creator IS '创建者';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.updater IS '更新者';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task.deleted IS '是否删除(0:否 1:是)';
|
||||
|
||||
-- ====================================================
|
||||
-- 定时任务执行日志表
|
||||
-- ====================================================
|
||||
DROP TABLE IF EXISTS crontab.tb_crontab_log CASCADE;
|
||||
CREATE TABLE crontab.tb_crontab_log (
|
||||
id VARCHAR(64) NOT NULL,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
task_name VARCHAR(100) NOT NULL,
|
||||
task_group VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',
|
||||
bean_name VARCHAR(100) NOT NULL,
|
||||
method_name VARCHAR(100) NOT NULL,
|
||||
method_params VARCHAR(500) DEFAULT NULL,
|
||||
execute_status SMALLINT NOT NULL, -- 执行状态(0:失败 1:成功)
|
||||
execute_message TEXT DEFAULT NULL,
|
||||
exception_info TEXT DEFAULT NULL,
|
||||
start_time TIMESTAMPTZ NOT NULL,
|
||||
end_time TIMESTAMPTZ DEFAULT NULL,
|
||||
execute_duration INT DEFAULT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除(0:否 1:是)
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_task_id ON crontab.tb_crontab_log(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_log_task_name ON crontab.tb_crontab_log(task_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_execute_status ON crontab.tb_crontab_log(execute_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_start_time ON crontab.tb_crontab_log(start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_log_deleted ON crontab.tb_crontab_log(deleted);
|
||||
|
||||
COMMENT ON TABLE crontab.tb_crontab_log IS '定时任务执行日志表';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.id IS '主键ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.task_id IS '任务ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.task_name IS '任务名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.task_group IS '任务分组';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.bean_name IS 'Bean名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.method_name IS '方法名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.method_params IS '方法参数';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.execute_status IS '执行状态(0:失败 1:成功)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.execute_message IS '执行结果信息';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.exception_info IS '异常信息';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.start_time IS '开始时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.end_time IS '结束时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.execute_duration IS '执行时长(毫秒)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_log.deleted IS '是否删除(0:否 1:是)';
|
||||
|
||||
-- ====================================================
|
||||
-- 定时任务元数据表(存储爬虫任务的元数据配置)
|
||||
-- ====================================================
|
||||
DROP TABLE IF EXISTS crontab.tb_crontab_task_meta CASCADE;
|
||||
CREATE TABLE crontab.tb_crontab_task_meta (
|
||||
id VARCHAR(64) NOT NULL,
|
||||
meta_id VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500) DEFAULT NULL,
|
||||
category VARCHAR(50) NOT NULL,
|
||||
bean_name VARCHAR(100) NOT NULL,
|
||||
method_name VARCHAR(100) NOT NULL,
|
||||
script_path VARCHAR(255) DEFAULT NULL,
|
||||
param_schema TEXT DEFAULT NULL,
|
||||
auto_publish SMALLINT NOT NULL DEFAULT 0, -- 是否自动发布
|
||||
sort_order INT DEFAULT 0,
|
||||
creator VARCHAR(64) DEFAULT NULL,
|
||||
updater VARCHAR(64) DEFAULT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除(0:否 1:是)
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (meta_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_category ON crontab.tb_crontab_task_meta(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_meta_deleted ON crontab.tb_crontab_task_meta(deleted);
|
||||
|
||||
COMMENT ON TABLE crontab.tb_crontab_task_meta IS '定时任务元数据表';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.id IS '主键ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.meta_id IS '元数据ID';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.name IS '任务名称';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.description IS '任务描述';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.category IS '任务分类(如:人民日报新闻爬取)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.bean_name IS 'Bean名称(执行器类名)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.method_name IS '执行方法名';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.script_path IS 'Python脚本路径(相对于basePath)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.param_schema IS '参数模板(JSON格式,定义参数名、类型、描述、默认值等)';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.auto_publish IS '是否自动发布';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.sort_order IS '排序号';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.creator IS '创建者';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.updater IS '更新者';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN crontab.tb_crontab_task_meta.deleted IS '是否删除(0:否 1:是)';
|
||||
@@ -1,79 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS file;
|
||||
|
||||
DROP TABLE IF EXISTS file.tb_sys_file CASCADE;
|
||||
CREATE TABLE file.tb_sys_file (
|
||||
-- BaseDTO 继承字段
|
||||
optsn VARCHAR(50) NOT NULL, -- 操作流水号
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建人
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新人
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门路径
|
||||
remark TEXT DEFAULT NULL, -- 备注
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否已删除(false-未删除,true-已删除)
|
||||
|
||||
-- TbSysFileDTO 特有字段
|
||||
file_id VARCHAR(50) NOT NULL, -- 文件ID (主键)
|
||||
file_root_id VARCHAR(50) DEFAULT NULL, -- 文件根ID
|
||||
version INTEGER DEFAULT 1, -- 文件版本
|
||||
name VARCHAR(255) NOT NULL, -- 文件名
|
||||
path VARCHAR(500) NOT NULL, -- 文件路径
|
||||
size BIGINT NOT NULL, -- 文件大小(字节)
|
||||
type VARCHAR(50) DEFAULT NULL, -- 文件类型
|
||||
storage_type VARCHAR(50) DEFAULT NULL, -- 存储类型
|
||||
mime_type VARCHAR(255) DEFAULT NULL, -- MIME 类型
|
||||
url VARCHAR(500) DEFAULT NULL, -- 后端下载接口路径(保留用于扩展,建议使用 /api/file/download/{fileId})
|
||||
status VARCHAR(50) DEFAULT NULL, -- 文件状态
|
||||
module VARCHAR(100) DEFAULT NULL, -- 所属模块
|
||||
business_id VARCHAR(50) DEFAULT NULL, -- 业务ID
|
||||
uploader VARCHAR(50) DEFAULT NULL, -- 上传者用户ID
|
||||
object_name VARCHAR(500) DEFAULT NULL, -- MinIO对象名称
|
||||
bucket_name VARCHAR(100) DEFAULT NULL, -- MinIO存储桶名称
|
||||
md5_hash VARCHAR(32) DEFAULT NULL, -- 文件MD5值
|
||||
extension VARCHAR(20) DEFAULT NULL, -- 文件扩展名
|
||||
|
||||
PRIMARY KEY (file_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE file.tb_sys_file IS '系统文件表';
|
||||
|
||||
-- BaseDTO 继承字段注释
|
||||
COMMENT ON COLUMN file.tb_sys_file.optsn IS '操作流水号';
|
||||
COMMENT ON COLUMN file.tb_sys_file.creator IS '创建人';
|
||||
COMMENT ON COLUMN file.tb_sys_file.updater IS '更新人';
|
||||
COMMENT ON COLUMN file.tb_sys_file.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN file.tb_sys_file.remark IS '备注';
|
||||
COMMENT ON COLUMN file.tb_sys_file.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN file.tb_sys_file.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN file.tb_sys_file.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN file.tb_sys_file.deleted IS '是否已删除(false-未删除,true-已删除)';
|
||||
|
||||
-- TbSysFileDTO 特有字段注释
|
||||
COMMENT ON COLUMN file.tb_sys_file.file_id IS '文件ID (主键)';
|
||||
COMMENT ON COLUMN file.tb_sys_file.file_root_id IS '文件根ID';
|
||||
COMMENT ON COLUMN file.tb_sys_file.version IS '文件版本';
|
||||
COMMENT ON COLUMN file.tb_sys_file.name IS '文件名';
|
||||
COMMENT ON COLUMN file.tb_sys_file.path IS '文件路径';
|
||||
COMMENT ON COLUMN file.tb_sys_file.size IS '文件大小(字节)';
|
||||
COMMENT ON COLUMN file.tb_sys_file.type IS '文件类型';
|
||||
COMMENT ON COLUMN file.tb_sys_file.storage_type IS '存储类型';
|
||||
COMMENT ON COLUMN file.tb_sys_file.mime_type IS 'MIME 类型';
|
||||
COMMENT ON COLUMN file.tb_sys_file.url IS '后端下载接口路径(保留用于扩展,建议使用 /api/file/download/{fileId})';
|
||||
COMMENT ON COLUMN file.tb_sys_file.status IS '文件状态';
|
||||
COMMENT ON COLUMN file.tb_sys_file.module IS '所属模块';
|
||||
COMMENT ON COLUMN file.tb_sys_file.business_id IS '业务ID';
|
||||
COMMENT ON COLUMN file.tb_sys_file.uploader IS '上传者用户ID';
|
||||
COMMENT ON COLUMN file.tb_sys_file.object_name IS 'MinIO对象名称';
|
||||
COMMENT ON COLUMN file.tb_sys_file.bucket_name IS 'MinIO存储桶名称';
|
||||
COMMENT ON COLUMN file.tb_sys_file.md5_hash IS '文件MD5值';
|
||||
COMMENT ON COLUMN file.tb_sys_file.extension IS '文件扩展名';
|
||||
|
||||
-- 文件表索引
|
||||
-- CREATE INDEX idx_file_module_business ON file.tb_sys_file(module, business_id) WHERE deleted = false;
|
||||
-- CREATE INDEX idx_file_uploader ON file.tb_sys_file(uploader) WHERE deleted = false;
|
||||
-- CREATE INDEX idx_file_bucket ON file.tb_sys_file(bucket_name) WHERE deleted = false;
|
||||
-- CREATE INDEX idx_file_status ON file.tb_sys_file(status) WHERE deleted = false;
|
||||
-- CREATE INDEX idx_file_create_time ON file.tb_sys_file(create_time) WHERE deleted = false;
|
||||
-- CREATE INDEX idx_file_md5 ON file.tb_sys_file(md5_hash) WHERE deleted = false;
|
||||
@@ -1,42 +0,0 @@
|
||||
DROP TABLE IF EXISTS sys.tb_sys_log CASCADE;
|
||||
CREATE TABLE sys.tb_sys_log (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
log_id VARCHAR(50) NOT NULL, -- 日志ID
|
||||
type VARCHAR(50) NOT NULL, -- 日志类型
|
||||
level VARCHAR(50) NOT NULL, -- 日志级别
|
||||
module VARCHAR(50) NOT NULL, -- 日志模块
|
||||
ip_address VARCHAR(45), -- IP地址
|
||||
ip_source VARCHAR(100), -- IP来源
|
||||
browser VARCHAR(100), -- 浏览器
|
||||
os VARCHAR(100), -- 操作系统
|
||||
message VARCHAR(255) NOT NULL, -- 日志消息
|
||||
data JSONB DEFAULT NULL, -- 日志数据
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
creator_name VARCHAR(200) DEFAULT NULL, -- 创建者姓名
|
||||
service VARCHAR(50) NOT NULL, -- 服务类型
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 日志创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 日志更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 日志删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (log_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_log IS '系统日志表';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.log_id IS '日志ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.type IS '日志类型';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.level IS '日志级别';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.module IS '日志模块';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.message IS '日志消息';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.data IS '日志数据';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.creator_name IS '创建者姓名';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.service IS '服务类型';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.create_time IS '日志创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.update_time IS '日志更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.delete_time IS '日志删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_log.deleted IS '是否删除';
|
||||
@@ -1,200 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS message;
|
||||
|
||||
DROP TABLE IF EXISTS message.tb_message CASCADE;
|
||||
CREATE TABLE message.tb_message (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
message_id VARCHAR(50) NOT NULL, -- 消息ID
|
||||
title VARCHAR(255) NOT NULL, -- 消息标题
|
||||
content VARCHAR(255) NOT NULL, -- 消息内容
|
||||
type VARCHAR(50) NOT NULL, -- 消息类型
|
||||
status VARCHAR(50) NOT NULL, -- 消息状态
|
||||
service VARCHAR(50) NOT NULL, -- 服务类型
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径(隔离)
|
||||
creator VARCHAR(50) NOT NULL DEFAULT 'system',-- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (message_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE message.tb_message IS '消息表';
|
||||
COMMENT ON COLUMN message.tb_message.optsn IS '流水号';
|
||||
COMMENT ON COLUMN message.tb_message.message_id IS '消息ID';
|
||||
COMMENT ON COLUMN message.tb_message.title IS '消息标题';
|
||||
COMMENT ON COLUMN message.tb_message.content IS '消息内容';
|
||||
COMMENT ON COLUMN message.tb_message.type IS '消息类型';
|
||||
COMMENT ON COLUMN message.tb_message.status IS '消息状态';
|
||||
COMMENT ON COLUMN message.tb_message.service IS '服务类型';
|
||||
COMMENT ON COLUMN message.tb_message.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN message.tb_message.creator IS '创建者';
|
||||
COMMENT ON COLUMN message.tb_message.updater IS '更新者';
|
||||
COMMENT ON COLUMN message.tb_message.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN message.tb_message.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN message.tb_message.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN message.tb_message.deleted IS '是否删除';
|
||||
|
||||
|
||||
-- 消息发送范围定义表(定义消息要发送给哪些对象,通过什么渠道)
|
||||
DROP TABLE IF EXISTS message.tb_message_range CASCADE;
|
||||
CREATE TABLE message.tb_message_range (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
message_id VARCHAR(50) NOT NULL, -- 消息ID
|
||||
target_type VARCHAR(20) NOT NULL, -- 目标类型:user/dept/role/all
|
||||
target_id VARCHAR(50) DEFAULT NULL, -- 目标ID(用户、部门、角色ID等,all类型时为空)
|
||||
channel VARCHAR(20) NOT NULL DEFAULT 'app', -- 发送渠道:app/sms/email/wechat_official_account/wechat_applet等
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径,支持like递归(如/1/2/3/)
|
||||
creator VARCHAR(50) NOT NULL DEFAULT 'system',-- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (optsn),
|
||||
UNIQUE (message_id, target_type, target_id, channel)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE message.tb_message_range IS '消息发送范围定义表';
|
||||
COMMENT ON COLUMN message.tb_message_range.optsn IS '流水号';
|
||||
COMMENT ON COLUMN message.tb_message_range.message_id IS '消息ID';
|
||||
COMMENT ON COLUMN message.tb_message_range.target_type IS '目标类型:user-指定用户/dept-部门/role-角色/all-全员';
|
||||
COMMENT ON COLUMN message.tb_message_range.target_id IS '目标ID(用户、部门、角色ID等,all类型时为空)';
|
||||
COMMENT ON COLUMN message.tb_message_range.channel IS '发送渠道:app/sms/email/wechat_official_account/wechat_applet等';
|
||||
COMMENT ON COLUMN message.tb_message_range.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN message.tb_message_range.creator IS '创建者';
|
||||
COMMENT ON COLUMN message.tb_message_range.updater IS '更新者';
|
||||
COMMENT ON COLUMN message.tb_message_range.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN message.tb_message_range.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN message.tb_message_range.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN message.tb_message_range.deleted IS '是否删除';
|
||||
|
||||
|
||||
-- 用户消息接收记录表(记录每个用户实际收到的消息及处理状态)
|
||||
DROP TABLE IF EXISTS message.tb_message_receiver CASCADE;
|
||||
CREATE TABLE message.tb_message_receiver (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
message_id VARCHAR(50) NOT NULL, -- 消息ID
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
channel VARCHAR(20) DEFAULT 'app', -- 接收渠道:app/sms/email/wechat等
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'unread', -- 消息状态:unread-未读/read-已读/handled-已处理/deleted-已删除
|
||||
read_time TIMESTAMPTZ DEFAULT NULL, -- 阅读时间
|
||||
handle_time TIMESTAMPTZ DEFAULT NULL, -- 处理时间
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径(数据隔离)
|
||||
creator VARCHAR(50) NOT NULL DEFAULT 'system',-- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间(接收时间)
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (optsn),
|
||||
UNIQUE (message_id, user_id, channel)
|
||||
);
|
||||
|
||||
-- 创建索引以提高查询效率
|
||||
CREATE INDEX idx_message_user_user_status ON message.tb_message_receiver(user_id, status, create_time DESC) WHERE deleted = false;
|
||||
CREATE INDEX idx_message_user_message ON message.tb_message_receiver(message_id) WHERE deleted = false;
|
||||
|
||||
COMMENT ON TABLE message.tb_message_receiver IS '用户消息接收记录表';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.optsn IS '流水号';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.message_id IS '消息ID';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.channel IS '接收渠道:app/sms/email/wechat等';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.status IS '消息状态:unread-未读/read-已读/handled-已处理/deleted-已删除';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.read_time IS '阅读时间';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.handle_time IS '处理时间';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.creator IS '创建者';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.updater IS '更新者';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.create_time IS '创建时间(接收时间)';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN message.tb_message_receiver.deleted IS '是否删除';
|
||||
|
||||
|
||||
-- 消息渠道配置表(管理各种消息发送渠道的配置)
|
||||
DROP TABLE IF EXISTS message.tb_message_channel CASCADE;
|
||||
CREATE TABLE message.tb_message_channel (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
channel_id VARCHAR(50) NOT NULL, -- 渠道ID
|
||||
channel_code VARCHAR(50) NOT NULL, -- 渠道编码:app/sms/email/wechat/dingtalk等
|
||||
channel_name VARCHAR(100) NOT NULL, -- 渠道名称
|
||||
channel_desc VARCHAR(255) DEFAULT NULL, -- 渠道描述
|
||||
config JSON DEFAULT NULL, -- 渠道配置(如API密钥、服务器地址等)
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'enabled', -- 渠道状态:enabled-启用/disabled-禁用/maintenance-维护中
|
||||
priority INTEGER DEFAULT 0, -- 优先级(数字越大优先级越高)
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径(数据隔离)
|
||||
creator VARCHAR(50) NOT NULL DEFAULT 'system',-- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (channel_id),
|
||||
UNIQUE (optsn),
|
||||
UNIQUE (channel_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE message.tb_message_channel IS '消息渠道配置表';
|
||||
COMMENT ON COLUMN message.tb_message_channel.optsn IS '流水号';
|
||||
COMMENT ON COLUMN message.tb_message_channel.channel_id IS '渠道ID';
|
||||
COMMENT ON COLUMN message.tb_message_channel.channel_code IS '渠道编码:app/sms/email/wechat/dingtalk等';
|
||||
COMMENT ON COLUMN message.tb_message_channel.channel_name IS '渠道名称';
|
||||
COMMENT ON COLUMN message.tb_message_channel.channel_desc IS '渠道描述';
|
||||
COMMENT ON COLUMN message.tb_message_channel.config IS '渠道配置(JSON格式)';
|
||||
COMMENT ON COLUMN message.tb_message_channel.status IS '渠道状态:enabled-启用/disabled-禁用/maintenance-维护中';
|
||||
COMMENT ON COLUMN message.tb_message_channel.priority IS '优先级(数字越大优先级越高)';
|
||||
COMMENT ON COLUMN message.tb_message_channel.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN message.tb_message_channel.creator IS '创建者';
|
||||
COMMENT ON COLUMN message.tb_message_channel.updater IS '更新者';
|
||||
COMMENT ON COLUMN message.tb_message_channel.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN message.tb_message_channel.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN message.tb_message_channel.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN message.tb_message_channel.deleted IS '是否删除';
|
||||
|
||||
-- =============================
|
||||
-- 消息模板表
|
||||
-- =============================
|
||||
DROP TABLE IF EXISTS message.tb_message_template CASCADE;
|
||||
CREATE TABLE message.tb_message_template (
|
||||
optsn VARCHAR(50) NOT NULL,
|
||||
template_id VARCHAR(50) NOT NULL,
|
||||
template_code VARCHAR(100) NOT NULL, -- 模板编码
|
||||
template_name VARCHAR(255) NOT NULL, -- 模板名称
|
||||
template_type VARCHAR(30) NOT NULL, -- 模板类型:system-系统/business-业务
|
||||
title_template TEXT, -- 标题模板(支持变量)
|
||||
content_template TEXT NOT NULL, -- 内容模板(支持变量)
|
||||
variables JSONB, -- 模板变量定义
|
||||
service VARCHAR(50) NOT NULL, -- 服务类型
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (template_id),
|
||||
UNIQUE (optsn),
|
||||
UNIQUE (template_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE message.tb_message_template IS '消息模板表';
|
||||
COMMENT ON COLUMN message.tb_message_template.optsn IS '流水号';
|
||||
COMMENT ON COLUMN message.tb_message_template.template_id IS '模板ID';
|
||||
COMMENT ON COLUMN message.tb_message_template.template_code IS '模板编码';
|
||||
COMMENT ON COLUMN message.tb_message_template.template_name IS '模板名称';
|
||||
COMMENT ON COLUMN message.tb_message_template.template_type IS '模板类型:system-系统/business-业务';
|
||||
COMMENT ON COLUMN message.tb_message_template.title_template IS '标题模板(支持变量)';
|
||||
COMMENT ON COLUMN message.tb_message_template.content_template IS '内容模板(支持变量)';
|
||||
COMMENT ON COLUMN message.tb_message_template.variables IS '模板变量定义';
|
||||
COMMENT ON COLUMN message.tb_message_template.service IS '服务类型';
|
||||
COMMENT ON COLUMN message.tb_message_template.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN message.tb_message_template.creator IS '创建者';
|
||||
COMMENT ON COLUMN message.tb_message_template.updater IS '更新者';
|
||||
COMMENT ON COLUMN message.tb_message_template.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN message.tb_message_template.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN message.tb_message_template.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN message.tb_message_template.deleted IS '是否删除';
|
||||
|
||||
CREATE INDEX idx_template_type ON message.tb_message_template(template_type) WHERE deleted = false;
|
||||
@@ -1,473 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS sys;
|
||||
|
||||
-- 通用更新时间触发函数(用于模拟 MySQL 的 ON UPDATE CURRENT_TIMESTAMP)
|
||||
-- CREATE OR REPLACE FUNCTION sys.set_update_time()
|
||||
-- RETURNS trigger AS $$
|
||||
-- BEGIN
|
||||
-- NEW.update_time := CURRENT_TIMESTAMP;
|
||||
-- RETURN NEW;
|
||||
-- END;
|
||||
-- $$ LANGUAGE plpgsql;
|
||||
|
||||
-- 部门表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_dept CASCADE;
|
||||
CREATE TABLE sys.tb_sys_dept (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
dept_id VARCHAR(50) NOT NULL, -- 部门ID
|
||||
name VARCHAR(100) NOT NULL, -- 部门名称
|
||||
parent_id VARCHAR(50) DEFAULT NULL, -- 父部门ID
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
description VARCHAR(255) DEFAULT NULL, -- 部门描述
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (dept_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_sys_dept_parent ON sys.tb_sys_dept USING btree (parent_id);
|
||||
COMMENT ON TABLE sys.tb_sys_dept IS '部门表';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.name IS '部门名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.parent_id IS '父部门ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.description IS '部门描述';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept.deleted IS '是否删除';
|
||||
|
||||
-- 角色表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_role CASCADE;
|
||||
CREATE TABLE sys.tb_sys_role (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
role_id VARCHAR(50) NOT NULL, -- 角色ID
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(200) DEFAULT NULL, -- 角色名称
|
||||
scope VARCHAR(20) NOT NULL DEFAULT 'dept', -- 角色作用域:global/dept
|
||||
owner_dept_id VARCHAR(50) DEFAULT NULL, -- 当scope=dept时,所属部门ID
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
dept_path VARCHAR(255) DEFAULT NULL,
|
||||
status BOOLEAN NOT NULL DEFAULT false, -- 部门全路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (role_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_role IS '角色表';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.name IS '角色名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.description IS '角色名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.scope IS '角色作用域:global=通用,dept=部门私有';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.owner_dept_id IS '部门私有角色的所属部门ID(scope=dept 时必填)';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.status IS '角色状态';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_role.deleted IS '是否删除';
|
||||
|
||||
-- 唯一性:
|
||||
-- 全局角色:name 在未删除且 scope=global 下唯一
|
||||
-- 部门私有角色:同一部门下 name 唯一(scope=dept)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_sys_role_global_name
|
||||
ON sys.tb_sys_role USING btree (name)
|
||||
WHERE deleted = false AND scope = 'global';
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_sys_role_dept_name
|
||||
ON sys.tb_sys_role USING btree (owner_dept_id, name)
|
||||
WHERE deleted = false AND scope = 'dept';
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_role_owner_dept ON sys.tb_sys_role USING btree (owner_dept_id) WHERE deleted = false;
|
||||
|
||||
-- 部门角色关联表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_dept_role CASCADE;
|
||||
CREATE TABLE sys.tb_sys_dept_role (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
dept_id VARCHAR(50) NOT NULL, -- 部门ID
|
||||
role_id VARCHAR(50) NOT NULL, -- 角色ID
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (dept_id, role_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_dept_role IS '部门角色关联表';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_dept_role.deleted IS '是否删除';
|
||||
|
||||
-- 用户角色关联表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_user_role CASCADE;
|
||||
CREATE TABLE sys.tb_sys_user_role (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
role_id VARCHAR(50) NOT NULL, -- 角色ID
|
||||
dept_id VARCHAR(50) NOT NULL, -- 部门ID
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (user_id, role_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_user_role IS '用户角色关联表';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_role.deleted IS '是否删除';
|
||||
|
||||
-- 视图表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_view CASCADE;
|
||||
CREATE TABLE sys.tb_sys_view (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
view_id VARCHAR(50) NOT NULL, -- 视图ID
|
||||
name VARCHAR(100) NOT NULL, -- 视图名称
|
||||
parent_id VARCHAR(50) DEFAULT NULL, -- 父视图ID
|
||||
url VARCHAR(255) DEFAULT NULL, -- 视图URL
|
||||
component VARCHAR(255) DEFAULT NULL, -- 视图组件
|
||||
icon VARCHAR(100) DEFAULT NULL, -- 视图图标
|
||||
type INTEGER DEFAULT 0, -- 视图类型:0=导航栏 1=侧边栏 2=按钮,3空白页
|
||||
view_type VARCHAR(20) DEFAULT 'route', -- 页面类型:route=路由页面 iframe=嵌入页面
|
||||
iframe_url VARCHAR(500) DEFAULT NULL, -- iframe URL(仅当view_type=iframe时有效)
|
||||
service VARCHAR(20) DEFAULT 'platform', -- 所属服务:platform=平台应用 bidding=招标应用 workcase=客服应用
|
||||
layout VARCHAR(100) DEFAULT NULL, -- 布局组件路径名称
|
||||
order_num INTEGER DEFAULT 0, -- 视图排序号
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
description VARCHAR(255) DEFAULT NULL, -- 视图描述
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (view_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_sys_view_parent ON sys.tb_sys_view USING btree (parent_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_sys_view_service ON sys.tb_sys_view USING btree (service) WHERE deleted = false;
|
||||
CREATE INDEX idx_sys_view_type ON sys.tb_sys_view USING btree (view_type) WHERE deleted = false;
|
||||
COMMENT ON TABLE sys.tb_sys_view IS '视图表(菜单表)';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.view_id IS '视图ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.name IS '视图名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.parent_id IS '父视图ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.url IS '视图URL';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.component IS '视图组件';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.icon IS '视图图标';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.type IS '视图类型:0=目录 1=菜单页面 2=按钮';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.view_type IS '页面类型:route=路由页面 iframe=嵌入页面';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.iframe_url IS 'iframe URL(仅当view_type=iframe时有效)';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.service IS '所属服务:platform=平台应用 bidding=招标应用 workcase=客服应用';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.layout IS '布局组件路径名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.order_num IS '视图排序号';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.description IS '视图描述';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.deleted IS '是否删除';
|
||||
|
||||
|
||||
-- 模块表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_module CASCADE;
|
||||
CREATE TABLE sys.tb_sys_module (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
module_id VARCHAR(50) NOT NULL, -- 模块ID
|
||||
name VARCHAR(100) NOT NULL, -- 模块名称
|
||||
description VARCHAR(255) DEFAULT NULL, -- 模块描述
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (module_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_module IS '模块表';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.module_id IS '模块ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.name IS '模块名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.description IS '模块描述';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_module.deleted IS '是否删除';
|
||||
|
||||
-- 权限表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_permission CASCADE;
|
||||
CREATE TABLE sys.tb_sys_permission (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
permission_id VARCHAR(50) NOT NULL, -- 权限ID
|
||||
name VARCHAR(100) NOT NULL, -- 权限名称
|
||||
code VARCHAR(100) NOT NULL, -- 权限代码
|
||||
description VARCHAR(255) DEFAULT NULL, -- 权限描述
|
||||
module_id VARCHAR(50) DEFAULT NULL, -- 所属模块ID
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
dept_path VARCHAR(255) DEFAULT NULL,
|
||||
status BOOLEAN NOT NULL DEFAULT false, -- 部门全路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (permission_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_permission IS '权限表';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.permission_id IS '权限ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.name IS '权限名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.code IS '权限代码';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.description IS '权限描述' ;
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.module_id IS '所属模块ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.status IS '角色状态';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_permission.deleted IS '是否删除';
|
||||
|
||||
-- 角色权限
|
||||
DROP TABLE IF EXISTS sys.tb_sys_role_permission CASCADE;
|
||||
CREATE TABLE sys.tb_sys_role_permission (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
role_id VARCHAR(50) NOT NULL, -- 角色ID
|
||||
permission_id VARCHAR(50) NOT NULL, -- 权限ID
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (role_id, permission_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_role_permission IS '角色权限表';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.permission_id IS '权限ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_role_permission.deleted IS '是否删除';
|
||||
|
||||
-- 视图权限
|
||||
DROP TABLE IF EXISTS sys.tb_sys_view_permission CASCADE;
|
||||
CREATE TABLE sys.tb_sys_view_permission (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
view_id VARCHAR(50) NOT NULL, -- 视图ID
|
||||
permission_id VARCHAR(50) NOT NULL, -- 权限ID
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (view_id, permission_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_view_permission IS '视图权限表';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.view_id IS '视图ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.permission_id IS '权限ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.creator IS '创建者';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.dept_path IS '部门全路径';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.updater IS '更新者';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_view_permission.deleted IS '是否删除';
|
||||
|
||||
-- 为所有表创建更新时间触发器
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_dept_update_time ON sys.tb_sys_dept;
|
||||
-- CREATE TRIGGER trg_tb_sys_dept_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_dept
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_role_update_time ON sys.tb_sys_role;
|
||||
-- CREATE TRIGGER trg_tb_sys_role_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_role
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_dept_role_update_time ON sys.tb_sys_dept_role;
|
||||
-- CREATE TRIGGER trg_tb_sys_dept_role_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_dept_role
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_user_role_update_time ON sys.tb_sys_user_role;
|
||||
-- CREATE TRIGGER trg_tb_sys_user_role_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_user_role
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_view_update_time ON sys.tb_sys_view;
|
||||
-- CREATE TRIGGER trg_tb_sys_view_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_view
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_module_update_time ON sys.tb_sys_module;
|
||||
-- CREATE TRIGGER trg_tb_sys_module_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_module
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_permission_update_time ON sys.tb_sys_permission;
|
||||
-- CREATE TRIGGER trg_tb_sys_permission_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_permission
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_role_permission_update_time ON sys.tb_sys_role_permission;
|
||||
-- CREATE TRIGGER trg_tb_sys_role_permission_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_role_permission
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- DROP TRIGGER IF EXISTS trg_tb_sys_view_permission_update_time ON sys.tb_sys_view_permission;
|
||||
-- CREATE TRIGGER trg_tb_sys_view_permission_update_time
|
||||
-- BEFORE UPDATE ON sys.tb_sys_view_permission
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION sys.set_update_time();
|
||||
|
||||
-- =============================
|
||||
-- 通用对象级权限(ACL)
|
||||
-- 支持对象类型:任意(如 article/file/course/...)
|
||||
-- 支持主体:user/dept/role
|
||||
-- 权限位:1=读(read),2=写(write),4=执行(exec),可相加
|
||||
-- include_descendants:当主体为 dept/role 时,是否包含其子级(便于“所有子级可查看”场景)
|
||||
-- allow:true=允许;false=显式拒绝(拒绝优先生效)
|
||||
-- dept_path:用于数据范围隔离(与现有规则一致)
|
||||
-- =============================
|
||||
|
||||
DROP TABLE IF EXISTS sys.tb_sys_acl CASCADE;
|
||||
CREATE TABLE sys.tb_sys_acl (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
acl_id VARCHAR(50) NOT NULL, -- ACL主键
|
||||
object_type VARCHAR(50) NOT NULL, -- 对象类型:article/file/course/...
|
||||
object_id VARCHAR(50) NOT NULL, -- 对象ID
|
||||
principal_type VARCHAR(20) NOT NULL, -- 主体类型:user/dept/role
|
||||
principal_id VARCHAR(50) NOT NULL, -- 主体ID
|
||||
principal_dept_id VARCHAR(50) DEFAULT NULL, -- 当主体为role且限定到某部门时的部门ID(支持“某部门的某角色”)
|
||||
permission SMALLINT NOT NULL, -- 权限位:1读 2写 4执行
|
||||
allow BOOLEAN NOT NULL DEFAULT true, -- 允许或显式拒绝
|
||||
include_descendants BOOLEAN NOT NULL DEFAULT false, -- 是否包含子级(对dept/role生效)
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径(数据隔离)
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
updater VARCHAR(50) DEFAULT NULL, -- 更新者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (acl_id),
|
||||
UNIQUE (object_type, object_id, principal_type, principal_id, principal_dept_id, deleted),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE sys.tb_sys_acl IS '通用对象级权限表(ACL)';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.object_type IS '对象类型:article/file/course/...';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.object_id IS '对象ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.principal_type IS '主体类型:user/dept/role';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.principal_id IS '主体ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.principal_dept_id IS '当主体为角色时,可选的限定部门ID(某部门的某角色)';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.permission IS '权限位:1读 2写 4执行,可相加';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.allow IS '是否允许(false=显式拒绝)';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.include_descendants IS '是否包含子级(dept/role时启用)';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl.dept_path IS '部门全路径(用于数据范围隔离)';
|
||||
|
||||
CREATE INDEX idx_sys_acl_object ON sys.tb_sys_acl USING btree (object_type, object_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_sys_acl_principal ON sys.tb_sys_acl USING btree (principal_type, principal_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_sys_acl_dept_path ON sys.tb_sys_acl USING btree (dept_path) WHERE deleted = false;
|
||||
-- 针对“某部门的某角色”的检索优化
|
||||
CREATE INDEX idx_sys_acl_role_scoped ON sys.tb_sys_acl USING btree (principal_id, principal_dept_id)
|
||||
WHERE deleted = false AND principal_type = 'role';
|
||||
|
||||
-- =============================
|
||||
-- ACL 策略表:定义对象类型的层级可见/可编辑规则
|
||||
-- 例如:
|
||||
-- - 同级与父级管理员可修改(edit_hierarchy_rule = 'parent_or_same_admin')
|
||||
-- - 子级全部可查看(view_hierarchy_rule = 'children_all')
|
||||
-- - 课程:下级只有指定部门/角色可查看(view_hierarchy_rule = 'children_specified')
|
||||
-- 说明:业务侧需结合“管理员”的判定(通常由角色判定)在应用层实现;本表做规则声明
|
||||
-- =============================
|
||||
|
||||
DROP TABLE IF EXISTS sys.tb_sys_acl_policy CASCADE;
|
||||
CREATE TABLE sys.tb_sys_acl_policy (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
policy_id VARCHAR(50) NOT NULL, -- 策略ID
|
||||
name VARCHAR(255) NOT NULL, -- 策略名称
|
||||
object_type VARCHAR(50) NOT NULL, -- 目标对象类型
|
||||
edit_hierarchy_rule VARCHAR(50) DEFAULT NULL, -- 编辑层级规则:parent_only/parent_or_same_admin/owner_only/none
|
||||
view_hierarchy_rule VARCHAR(50) DEFAULT NULL, -- 查看层级规则:children_all/children_specified/none
|
||||
default_permission SMALLINT DEFAULT 0, -- 默认权限位(无显式ACL时应用)
|
||||
default_allow BOOLEAN DEFAULT true, -- 默认是否允许
|
||||
apply_to_children BOOLEAN DEFAULT true, -- 是否默认应用到子级
|
||||
creator VARCHAR(50) DEFAULT NULL,
|
||||
updater VARCHAR(50) DEFAULT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (policy_id),
|
||||
UNIQUE (object_type, deleted),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE sys.tb_sys_acl_policy IS 'ACL对象类型策略表(层级可见/可编辑规则)';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl_policy.object_type IS '对象类型:article/file/course/...';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl_policy.edit_hierarchy_rule IS '编辑层级规则:parent_only/parent_or_same_admin/owner_only/none';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl_policy.view_hierarchy_rule IS '查看层级规则:children_all/children_specified/none';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl_policy.default_permission IS '默认权限位(无显式ACL时兜底)';
|
||||
COMMENT ON COLUMN sys.tb_sys_acl_policy.apply_to_children IS '默认是否应用到子级';
|
||||
|
||||
CREATE INDEX idx_sys_acl_policy_object ON sys.tb_sys_acl_policy USING btree (object_type) WHERE deleted = false;
|
||||
@@ -1,124 +0,0 @@
|
||||
|
||||
-- 创建 sys schema(如果不存在)
|
||||
CREATE SCHEMA IF NOT EXISTS sys;
|
||||
|
||||
-- 用户表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_user CASCADE;
|
||||
CREATE TABLE sys.tb_sys_user (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
usercode VARCHAR(100) DEFAULT NULL, -- 用户code。sso同步数据获取
|
||||
password VARCHAR(128) NOT NULL, -- 密码(建议存储 bcrypt/argon2 哈希)
|
||||
email VARCHAR(100), -- 电子邮件
|
||||
phone VARCHAR(500), -- 电话号码
|
||||
phone_hash VARCHAR(200), -- 电话hash
|
||||
wechat_id VARCHAR(50), -- 微信ID
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间(使用带时区时间)
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除(使用 BOOLEAN)
|
||||
status INTEGER NOT NULL DEFAULT 1, -- 状态
|
||||
remark VARCHAR(255) DEFAULT NULL,
|
||||
PRIMARY KEY (user_id),
|
||||
UNIQUE (optsn),
|
||||
UNIQUE (email),
|
||||
UNIQUE (phone),
|
||||
UNIQUE (wechat_id),
|
||||
UNIQUE (usercode)
|
||||
);
|
||||
CREATE INDEX idx_tb_sys_user_phone ON sys.tb_sys_user USING btree (phone);
|
||||
|
||||
-- 按 email 域名建立表达式索引(对域名小写处理以实现不区分大小写的域名查询)
|
||||
-- 使用 split_part(email, '@', 2) 提取 @ 之后的域名部分,再做 lower() 归一化
|
||||
-- WHERE email IS NOT NULL 可以避免索引包含大量 NULL
|
||||
CREATE INDEX idx_tb_sys_user_email_domain ON sys.tb_sys_user USING btree (lower(split_part(email, '@', 2)))
|
||||
WHERE email IS NOT NULL;
|
||||
CREATE INDEX idx_tb_sys_user_wechat_id ON sys.tb_sys_user USING btree (wechat_id);
|
||||
|
||||
-- 可选:保留列注释(如果你想把 MySQL 的 COMMENT 同步到 Postgres)
|
||||
COMMENT ON TABLE sys.tb_sys_user IS '用户表';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.password IS '密码(建议存储 bcrypt/argon2 哈希)';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.email IS '电子邮件';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.phone IS '电话号码';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.wechat_id IS '微信ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.deleted IS '是否删除';
|
||||
COMMENT ON COLUMN sys.tb_sys_user.status IS '状态';
|
||||
|
||||
-- 用户信息表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_user_info CASCADE;
|
||||
CREATE TABLE sys.tb_sys_user_info (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
avatar VARCHAR(255), -- 头像
|
||||
gender INTEGER DEFAULT 0, -- 性别
|
||||
username VARCHAR(100) NOT NULL, -- 用户名
|
||||
level INTEGER DEFAULT 1, -- 等级
|
||||
id_card VARCHAR(50), -- 身份证号
|
||||
address VARCHAR(255), -- 地址
|
||||
creator VARCHAR(50) DEFAULT NULL, -- 创建者
|
||||
remark VARCHAR(500) DEFAULT NULL, -- 备注
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(触发器维护)
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (user_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_user_info IS '用户信息表';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.avatar IS '头像';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.gender IS '性别';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.username IS '用户名';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.level IS '等级';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.id_card IS '身份证号';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.address IS '地址';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.remark IS '备注';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.delete_time IS '删除时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_user_info.deleted IS '是否删除';
|
||||
|
||||
-- 登录日志表
|
||||
DROP TABLE IF EXISTS sys.tb_sys_login_log CASCADE;
|
||||
CREATE TABLE sys.tb_sys_login_log (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号(作为主键)
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
username VARCHAR(50) NOT NULL, -- 用户名
|
||||
ip_address VARCHAR(45), -- IP地址
|
||||
ip_source VARCHAR(100), -- IP来源
|
||||
browser VARCHAR(100), -- 浏览器
|
||||
os VARCHAR(100), -- 操作系统
|
||||
password VARCHAR(128), -- 密码(建议存储 bcrypt/argon2 哈希)
|
||||
login_time TIMESTAMPTZ DEFAULT now(), -- 登录时间
|
||||
status INTEGER DEFAULT 1, -- 登录状态(0失败 1成功)
|
||||
error_count INTEGER DEFAULT 0, -- 错误次数
|
||||
message VARCHAR(255), -- 登录消息
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
PRIMARY KEY (optsn)
|
||||
);
|
||||
-- B-tree 索引(显式指定 USING btree,Postgres 默认即为 btree)
|
||||
CREATE INDEX idx_tb_sys_login_log_user_id ON sys.tb_sys_login_log USING btree (user_id);
|
||||
CREATE INDEX idx_tb_sys_login_log_login_time ON sys.tb_sys_login_log USING btree (login_time);
|
||||
|
||||
-- 可选:保留列注释
|
||||
COMMENT ON TABLE sys.tb_sys_login_log IS '登录日志表';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.username IS '用户名';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.ip_address IS 'IP地址';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.ip_source IS 'IP来源';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.browser IS '浏览器';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.os IS '操作系统';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.password IS '密码(建议存储 bcrypt/argon2 哈希)';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.login_time IS '登录时间';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.status IS '登录状态(0失败 1成功)';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.error_count IS '错误次数';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.message IS '登录消息';
|
||||
COMMENT ON COLUMN sys.tb_sys_login_log.create_time IS '创建时间';
|
||||
@@ -1,331 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS workcase;
|
||||
|
||||
-- 系统外部人员(来客)管理 用于给系统外人员创建id
|
||||
DROP TABLE IF EXISTS sys.tb_guest CASCADE;
|
||||
CREATE TABLE sys.tb_guest(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
user_id VARCHAR(50) NOT NULL, -- 来客ID
|
||||
name VARCHAR(50) NOT NULL, -- 姓名
|
||||
phone VARCHAR(50) DEFAULT NULL, -- 电话
|
||||
email VARCHAR(50) DEFAULT NULL, -- 邮箱
|
||||
wechat_id VARCHAR(50) DEFAULT NULL, -- 微信号
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 创建时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (user_id),
|
||||
UNIQUE (wechat_id),
|
||||
UNIQUE (phone),
|
||||
UNIQUE (email)
|
||||
);
|
||||
|
||||
|
||||
-- ==========================================
|
||||
-- IM聊天室 + Jitsi Meet 视频会议 表设计
|
||||
-- ==========================================
|
||||
|
||||
-- 1. 聊天室表(核心表)
|
||||
-- 一个工单对应一个聊天室,来客创建,客服人员可加入
|
||||
DROP TABLE IF EXISTS workcase.tb_chat_room CASCADE;
|
||||
CREATE TABLE workcase.tb_chat_room(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
room_id VARCHAR(50) NOT NULL, -- 聊天室ID
|
||||
workcase_id VARCHAR(50) DEFAULT NULL, -- 关联工单ID
|
||||
room_name VARCHAR(200) NOT NULL, -- 聊天室名称(如:工单#12345的客服支持)
|
||||
room_type VARCHAR(20) NOT NULL DEFAULT 'workcase', -- 聊天室类型:workcase-工单客服
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-活跃 closed-已关闭 archived-已归档
|
||||
guest_id VARCHAR(50) NOT NULL, -- 来客ID(创建者)
|
||||
guest_name VARCHAR(100) NOT NULL, -- 来客姓名
|
||||
ai_session_id VARCHAR(50) DEFAULT NULL, -- AI对话会话ID(从ai.tb_chat同步)
|
||||
message_count INTEGER NOT NULL DEFAULT 0, -- 消息总数
|
||||
device_code VARCHAR(50) NOT NULL, -- 设备代码
|
||||
last_message_time TIMESTAMPTZ DEFAULT NULL, -- 最后消息时间
|
||||
last_message TEXT DEFAULT NULL, -- 最后一条消息内容(用于列表展示)
|
||||
comment_level INTEGER DEFAULT 0, -- 服务评分(1-5)
|
||||
closed_by VARCHAR(50) DEFAULT NULL, -- 关闭人
|
||||
closed_time TIMESTAMPTZ DEFAULT NULL, -- 关闭时间
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人(系统自动创建)
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (room_id),
|
||||
UNIQUE (workcase_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
CREATE INDEX idx_chat_room_guest ON workcase.tb_chat_room(guest_id, status);
|
||||
CREATE INDEX idx_chat_room_time ON workcase.tb_chat_room(last_message_time DESC);
|
||||
COMMENT ON TABLE workcase.tb_chat_room IS 'IM聊天室表,一个工单对应一个聊天室';
|
||||
|
||||
-- 2. 聊天室成员表
|
||||
-- 记录聊天室内的所有成员(来客+客服人员)
|
||||
DROP TABLE IF EXISTS workcase.tb_chat_room_member CASCADE;
|
||||
CREATE TABLE workcase.tb_chat_room_member(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
member_id VARCHAR(50) NOT NULL, -- 成员记录ID
|
||||
room_id VARCHAR(50) NOT NULL, -- 聊天室ID
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID(来客ID或员工ID)
|
||||
user_type VARCHAR(20) NOT NULL, -- 用户类型:guest-来客 staff-客服 ai-AI助手
|
||||
user_name VARCHAR(100) NOT NULL, -- 用户名称
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-活跃 left-已离开 removed-被移除
|
||||
unread_count INTEGER NOT NULL DEFAULT 0, -- 该成员的未读消息数
|
||||
last_read_time TIMESTAMPTZ DEFAULT NULL, -- 最后阅读时间
|
||||
last_read_msg_id VARCHAR(50) DEFAULT NULL, -- 最后阅读的消息ID
|
||||
join_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 加入时间
|
||||
leave_time TIMESTAMPTZ DEFAULT NULL, -- 离开时间
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
PRIMARY KEY (member_id),
|
||||
UNIQUE (room_id, user_id)
|
||||
);
|
||||
CREATE INDEX idx_chat_member_room ON workcase.tb_chat_room_member(room_id, status);
|
||||
CREATE INDEX idx_chat_member_user ON workcase.tb_chat_room_member(user_id, user_type, status);
|
||||
COMMENT ON TABLE workcase.tb_chat_room_member IS '聊天室成员表,记录来客和客服人员';
|
||||
|
||||
-- 3. 聊天室消息表
|
||||
-- 存储所有聊天消息(AI对话+人工客服对话)
|
||||
DROP TABLE IF EXISTS workcase.tb_chat_room_message CASCADE;
|
||||
CREATE TABLE workcase.tb_chat_room_message(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
message_id VARCHAR(50) NOT NULL, -- 消息ID
|
||||
room_id VARCHAR(50) NOT NULL, -- 聊天室ID
|
||||
sender_id VARCHAR(50) NOT NULL, -- 发送者ID
|
||||
sender_type VARCHAR(20) NOT NULL, -- 发送者类型:guest-来客 agent-客服 ai-AI助手 system-系统消息
|
||||
sender_name VARCHAR(100) NOT NULL, -- 发送者名称
|
||||
message_type VARCHAR(20) NOT NULL DEFAULT 'text', -- 消息类型:text-文本 image-图片 file-文件 voice-语音 video-视频 system-系统消息 meeting-会议通知
|
||||
content TEXT NOT NULL, -- 消息内容
|
||||
files VARCHAR(50)[] DEFAULT '{}', -- 附件文件ID数组(图片、文件、语音、视频等)
|
||||
content_extra JSONB DEFAULT NULL, -- 扩展内容(会议链接、引用信息等)
|
||||
reply_to_msg_id VARCHAR(50) DEFAULT NULL, -- 回复的消息ID(引用回复)
|
||||
is_ai_message BOOLEAN NOT NULL DEFAULT false, -- 是否AI消息(标记从ai.tb_chat同步的消息)
|
||||
ai_message_id VARCHAR(50) DEFAULT NULL, -- AI原始消息ID(用于追溯)
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'sent', -- 状态:sending-发送中 sent-已发送 delivered-已送达 read-已读 failed-失败 recalled-已撤回
|
||||
read_count INTEGER NOT NULL DEFAULT 0, -- 已读人数
|
||||
send_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 发送时间
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
PRIMARY KEY (message_id)
|
||||
);
|
||||
CREATE INDEX idx_chat_msg_room ON workcase.tb_chat_room_message(room_id, send_time DESC);
|
||||
CREATE INDEX idx_chat_msg_sender ON workcase.tb_chat_room_message(sender_id, sender_type);
|
||||
CREATE INDEX idx_chat_msg_ai ON workcase.tb_chat_room_message(ai_message_id) WHERE ai_message_id IS NOT NULL;
|
||||
COMMENT ON TABLE workcase.tb_chat_room_message IS 'IM聊天消息表,包含AI对话和人工客服消息';
|
||||
|
||||
DROP TABLE IF EXISTS workcase.tb_chat_room_summary CASCADE;
|
||||
CREATE TABLE workcase.tb_chat_room_summary (
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
summary_id VARCHAR(50) NOT NULL, -- 总结ID
|
||||
room_id VARCHAR(50) NOT NULL, -- 聊天室ID
|
||||
question TEXT DEFAULT NULL, -- 核心问题
|
||||
needs VARCHAR(500)[] DEFAULT '{}', -- 核心诉求数组
|
||||
answer TEXT DEFAULT NULL, -- 解决方案
|
||||
workcloud VARCHAR(500)[] DEFAULT '{}', -- 词云关键词数组
|
||||
message_count INTEGER DEFAULT 0, -- 参与总结的消息数量
|
||||
summary_time TIMESTAMPTZ DEFAULT NULL, -- 总结生成时间
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (summary_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
CREATE INDEX idx_chat_room_summary_room ON workcase.tb_chat_room_summary(room_id, summary_time DESC);
|
||||
CREATE INDEX idx_chat_room_summary_time ON workcase.tb_chat_room_summary(summary_time DESC);
|
||||
COMMENT ON TABLE workcase.tb_chat_room_summary IS '聊天室总结表,保存AI生成的聊天总结分析';
|
||||
|
||||
|
||||
-- 4. 视频会议表(Jitsi Meet)
|
||||
-- 记录聊天室内创建的视频会议
|
||||
DROP TABLE IF EXISTS workcase.tb_video_meeting CASCADE;
|
||||
CREATE TABLE workcase.tb_video_meeting(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
meeting_id VARCHAR(50) NOT NULL, -- 会议ID(也是Jitsi房间名)
|
||||
room_id VARCHAR(50) NOT NULL, -- 关联聊天室ID
|
||||
workcase_id VARCHAR(50) NOT NULL, -- 关联工单ID
|
||||
meeting_name VARCHAR(200) NOT NULL, -- 会议名称
|
||||
meeting_password VARCHAR(50) DEFAULT NULL, -- 会议密码(可选)
|
||||
description VARCHAR(500) DEFAULT NULL, -- 会议模式
|
||||
jwt_token TEXT DEFAULT NULL, -- JWT Token(用于身份验证)
|
||||
jitsi_room_name VARCHAR(200) NOT NULL, -- Jitsi房间名(格式:workcase_{workcase_id}_{timestamp})
|
||||
jitsi_server_url VARCHAR(500) NOT NULL DEFAULT 'https://meet.jit.si', -- Jitsi服务器地址
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'scheduled', -- 状态:scheduled-已安排 ongoing-进行中 ended-已结束 cancelled-已取消
|
||||
creator_type VARCHAR(20) NOT NULL, -- 创建者类型:guest-来客 agent-客服
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建者名称
|
||||
participant_count INTEGER NOT NULL DEFAULT 0, -- 参与人数
|
||||
max_participants INTEGER DEFAULT 10, -- 最大参与人数
|
||||
start_time TIMESTAMPTZ NOT NULL, -- 定义会议开始时间
|
||||
end_time TIMESTAMPTZ NOT NULL, -- 定义会议结束时间
|
||||
advance INTEGER DEFAULT 5, -- 提前入会时间(分钟)
|
||||
actual_start_time TIMESTAMPTZ DEFAULT NULL, -- 真正会议开始时间
|
||||
actual_end_time TIMESTAMPTZ DEFAULT NULL, -- 真正会议结束时间
|
||||
duration_seconds INTEGER DEFAULT 0, -- 会议时长(秒)
|
||||
iframe_url TEXT DEFAULT NULL, -- iframe嵌入URL(生成后存储)
|
||||
config JSONB DEFAULT NULL, -- Jitsi配置项(自定义配置)
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (meeting_id),
|
||||
UNIQUE (jitsi_room_name)
|
||||
);
|
||||
CREATE INDEX idx_meeting_room ON workcase.tb_video_meeting(room_id, status);
|
||||
CREATE INDEX idx_meeting_workcase ON workcase.tb_video_meeting(workcase_id, status);
|
||||
CREATE INDEX idx_meeting_time ON workcase.tb_video_meeting(create_time DESC);
|
||||
COMMENT ON TABLE workcase.tb_video_meeting IS 'Jitsi Meet视频会议表';
|
||||
|
||||
-- 5. 会议参与记录表(可选,用于审计和统计)
|
||||
DROP TABLE IF EXISTS workcase.tb_meeting_participant CASCADE;
|
||||
CREATE TABLE workcase.tb_meeting_participant(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
participant_id VARCHAR(50) NOT NULL, -- 参与记录ID
|
||||
meeting_id VARCHAR(50) NOT NULL, -- 会议ID
|
||||
user_id VARCHAR(50) NOT NULL, -- 用户ID
|
||||
user_type VARCHAR(20) NOT NULL, -- 用户类型:guest-来客 agent-客服
|
||||
user_name VARCHAR(100) NOT NULL, -- 用户名称
|
||||
join_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 加入时间
|
||||
leave_time TIMESTAMPTZ DEFAULT NULL, -- 离开时间
|
||||
duration_seconds INTEGER DEFAULT 0, -- 参与时长(秒)
|
||||
is_moderator BOOLEAN NOT NULL DEFAULT false, -- 是否主持人
|
||||
join_method VARCHAR(20) DEFAULT 'web', -- 加入方式:web-网页 mobile-移动端 desktop-桌面端
|
||||
device_info VARCHAR(200) DEFAULT NULL, -- 设备信息
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
PRIMARY KEY (participant_id)
|
||||
);
|
||||
CREATE INDEX idx_meeting_participant ON workcase.tb_meeting_participant(meeting_id, join_time);
|
||||
CREATE INDEX idx_participant_user ON workcase.tb_meeting_participant(user_id, user_type);
|
||||
COMMENT ON TABLE workcase.tb_meeting_participant IS '视频会议参与记录表';
|
||||
|
||||
-- 7. 会议转录记录表(音频转文字)
|
||||
DROP TABLE IF EXISTS workcase.tb_meeting_transcription CASCADE;
|
||||
CREATE TABLE workcase.tb_meeting_transcription(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
transcription_id VARCHAR(50) NOT NULL, -- 转录记录ID
|
||||
meeting_id VARCHAR(50) NOT NULL, -- 会议ID
|
||||
speaker_id VARCHAR(50) NOT NULL, -- 说话人ID
|
||||
speaker_name VARCHAR(100) NOT NULL, -- 说话人名称
|
||||
speaker_type VARCHAR(20) NOT NULL, -- 说话人类型:guest-来客 agent-客服
|
||||
content TEXT NOT NULL, -- 转录文本内容
|
||||
content_raw TEXT DEFAULT NULL, -- 原始转录结果(含标点前)
|
||||
language VARCHAR(10) DEFAULT 'zh-CN', -- 语言:zh-CN en-US等
|
||||
confidence NUMERIC(3,2) DEFAULT NULL, -- 识别置信度(0-1)
|
||||
speech_start_time TIMESTAMPTZ NOT NULL, -- 语音开始时间
|
||||
speech_end_time TIMESTAMPTZ NOT NULL, -- 语音结束时间
|
||||
duration_ms INTEGER NOT NULL, -- 语音时长(毫秒)
|
||||
audio_url VARCHAR(500) DEFAULT NULL, -- 音频片段URL(可选)
|
||||
segment_index INTEGER NOT NULL DEFAULT 0, -- 片段序号(按时间排序)
|
||||
is_final BOOLEAN NOT NULL DEFAULT true, -- 是否最终结果(实时转录会有中间结果)
|
||||
service_provider VARCHAR(50) DEFAULT 'xunfei', -- 服务提供商:xunfei aliyun tencent google
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
PRIMARY KEY (transcription_id)
|
||||
);
|
||||
CREATE INDEX idx_transcription_meeting ON workcase.tb_meeting_transcription(meeting_id, segment_index);
|
||||
COMMENT ON TABLE workcase.tb_meeting_transcription IS '会议转录记录表,用于保存视频会议的语音转文字内容';
|
||||
|
||||
-- 8. 员工配置表
|
||||
-- 用于控制哪些人员可以在聊天室里接待来客
|
||||
DROP TABLE IF EXISTS workcase.tb_customer_service CASCADE;
|
||||
CREATE TABLE workcase.tb_customer_service(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
user_id VARCHAR(50) NOT NULL, -- 员工ID(关联sys用户ID)
|
||||
username VARCHAR(100) NOT NULL, -- 员工姓名
|
||||
user_code VARCHAR(50) DEFAULT NULL, -- 员工工号
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'offline', -- 状态:online-在线 busy-忙碌 offline-离线
|
||||
skill_tags VARCHAR(50)[] DEFAULT '{}', -- 技能标签(如:电力、燃气、水务)
|
||||
max_concurrent INTEGER NOT NULL DEFAULT 5, -- 最大并发接待数
|
||||
current_workload INTEGER NOT NULL DEFAULT 0, -- 当前工作量
|
||||
total_served INTEGER NOT NULL DEFAULT 0, -- 累计服务次数
|
||||
avg_response_time INTEGER DEFAULT NULL, -- 平均响应时间(秒)
|
||||
satisfaction_score NUMERIC(3,2) DEFAULT NULL, -- 满意度评分(0-5)
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
CREATE INDEX idx_customer_service_status ON workcase.tb_customer_service(status, current_workload);
|
||||
COMMENT ON TABLE workcase.tb_customer_service IS '员工配置表,用于控制哪些人员可以在聊天室接待来客';
|
||||
|
||||
-- 工单表
|
||||
DROP TABLE IF EXISTS workcase.tb_workcase CASCADE;
|
||||
CREATE TABLE workcase.tb_workcase(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
workcase_id VARCHAR(50) NOT NULL, -- 工单ID
|
||||
room_id VARCHAR(50) NOT NULL, -- 聊天室ID
|
||||
user_id VARCHAR(50) NOT NULL, -- 来客ID
|
||||
username VARCHAR(200) NOT NULL, -- 来客姓名
|
||||
phone VARCHAR(20) NOT NULL, -- 来客电话
|
||||
type VARCHAR(50) NOT NULL, -- 故障类型
|
||||
device VARCHAR(50) DEFAULT NULL, -- 设备名称
|
||||
device_code VARCHAR(50) DEFAULT NULL, -- 设备代码
|
||||
device_name_plate VARCHAR(50) NOT NULL, -- 设备名称牌
|
||||
device_name_plate_img VARCHAR(50) DEFAULT NULL, -- 设备名称牌图片
|
||||
address VARCHAR(1000) DEFAULT NULL, -- 现场地址
|
||||
description VARCHAR(1000) DEFAULT NULL, -- 故障描述
|
||||
imgs VARCHAR(50)[] DEFAULT '{}', -- 工单图片id
|
||||
emergency VARCHAR(50) NOT NULL DEFAULT 'normal', -- 紧急程度 normal-普通 emergency-紧急
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending', -- 状态 pending-待处理 processing-处理中 done-已完成
|
||||
processor VARCHAR(50) DEFAULT NULL, -- 处理人
|
||||
creator VARCHAR(50) NOT NULL, -- 创建人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||
PRIMARY KEY (workcase_id),
|
||||
UNIQUE (room_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
|
||||
-- 工单处理过程表(包含工单流转)
|
||||
DROP TABLE IF EXISTS workcase.tb_workcase_process CASCADE;
|
||||
CREATE TABLE workcase.tb_workcase_process(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
workcase_id VARCHAR(50) NOT NULL, -- 工单ID
|
||||
process_id VARCHAR(50) NOT NULL, -- 过程id
|
||||
action VARCHAR(50) NOT NULL, -- 动作 info:记录,assign:指派,redeploy:转派,repeal:撤销,finish:完成
|
||||
message VARCHAR(200) DEFAULT NULL, -- 消息
|
||||
files VARCHAR(50)[] DEFAULT '{}', -- 携带文件
|
||||
processor VARCHAR(50) DEFAULT NULL, -- 处理人(指派、转派专属)
|
||||
remark VARCHAR(500) DEFAULT NULL, -- 备注
|
||||
creator VARCHAR(50) NOT NULL, -- 过程发起人
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
PRIMARY KEY (process_id)
|
||||
);
|
||||
|
||||
-- 工单设备涉及的文件表
|
||||
DROP TABLE IF EXISTS workcase.tb_workcase_device CASCADE;
|
||||
CREATE TABLE workcase.tb_workcase_device(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
workcase_id VARCHAR(50) NOT NULL, -- 工单ID
|
||||
device VARCHAR(50) NOT NULL, -- 设备名称
|
||||
device_code VARCHAR(50) DEFAULT NULL, -- 设备代码
|
||||
file_id VARCHAR(50) NOT NULL, -- 文件id
|
||||
file_name VARCHAR(50) NOT NULL, -- 文件名
|
||||
file_root_id VARCHAR(50) DEFAULT NULL, -- 文件根id
|
||||
PRIMARY KEY(workcase_id, file_id)
|
||||
);
|
||||
|
||||
-- 来客对话、工单过程中生成的词云表
|
||||
DROP TABLE IF EXISTS workcase.tb_word_cloud CASCADE;
|
||||
CREATE TABLE workcase.tb_word_cloud(
|
||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||
word_id VARCHAR(50) NOT NULL, -- 词条ID
|
||||
word VARCHAR(100) NOT NULL, -- 词语
|
||||
frequency INTEGER NOT NULL DEFAULT 1, -- 词频
|
||||
source_type VARCHAR(20) NOT NULL, -- 来源类型 chat-聊天 workcase-工单 global-全局
|
||||
source_id VARCHAR(50) DEFAULT NULL, -- 来源ID(room_id/workcase_id,NULL表示全局统计)
|
||||
category VARCHAR(50) DEFAULT NULL, -- 分类(如:fault-故障类型 device-设备 emotion-情绪词等)
|
||||
stat_date DATE NOT NULL, -- 统计日期(按天聚合)
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
|
||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||
PRIMARY KEY (word_id),
|
||||
UNIQUE (word, source_type, source_id, stat_date, category) -- 同一天同一来源同一分类的词唯一
|
||||
);
|
||||
CREATE INDEX idx_word_cloud_source ON workcase.tb_word_cloud(source_type, source_id, stat_date);
|
||||
CREATE INDEX idx_word_cloud_category ON workcase.tb_word_cloud(category, stat_date);
|
||||
COMMENT ON TABLE workcase.tb_word_cloud IS '词云统计表,记录聊天和工单中的关键词';
|
||||
@@ -1,52 +0,0 @@
|
||||
-- =============================
|
||||
-- 城市生命线AI数智化平台 - 数据库初始化脚本
|
||||
-- 按顺序执行各模块建表SQL及数据初始化
|
||||
-- =============================
|
||||
|
||||
-- =============================
|
||||
-- 第一阶段:创建数据库和表结构
|
||||
-- =============================
|
||||
\i createDB.sql
|
||||
|
||||
-- 1. 系统基础模块
|
||||
\i createTablePermission.sql
|
||||
\i createTableUser.sql
|
||||
|
||||
-- 2. 文件管理模块
|
||||
\i createTableFile.sql
|
||||
|
||||
-- 3. 消息通知模块
|
||||
\i createTableMessage.sql
|
||||
|
||||
-- 4. 日志模块
|
||||
\i createTableLog.sql
|
||||
|
||||
-- 5. 配置管理模块
|
||||
\i createTableConfig.sql
|
||||
|
||||
-- 6. AI模块 智能体+知识库
|
||||
\i createTableAI.sql
|
||||
|
||||
-- 7. 招投标业务模块
|
||||
\i createTableBidding.sql
|
||||
|
||||
-- 8. 智能客服业务模块
|
||||
\i createTableWorkcase.sql
|
||||
|
||||
-- =============================
|
||||
-- 第二阶段:初始化基础数据
|
||||
-- =============================
|
||||
|
||||
-- 1. 初始化权限相关基础数据(部门、角色、权限、视图、模块)
|
||||
\i initDataPermission.sql
|
||||
|
||||
-- 2. 初始化用户数据(管理员账户)
|
||||
\i initDataUser.sql
|
||||
|
||||
-- 3. 初始化消息渠道配置
|
||||
\i initDataMessage.sql
|
||||
|
||||
-- 4. 初始化系统配置
|
||||
\i initDataConfig.sql
|
||||
|
||||
-- 注意:文件、日志、知识库、招投标、客服等业务表无需初始化数据
|
||||
@@ -1,112 +0,0 @@
|
||||
-- 初始化系统常用配置(与 config.tb_sys_config 对应)
|
||||
-- 仅插入常用示例,可按需调整 value/remark
|
||||
|
||||
INSERT INTO config.tb_sys_config (
|
||||
optsn, config_id, key, name, value, config_type, render_type, description,
|
||||
re, options, "group", module_id, order_num, status, remark,
|
||||
creator, dept_path, updater, create_time, update_time, delete_time, deleted
|
||||
) VALUES
|
||||
|
||||
-- 站点与品牌
|
||||
('CFG-0001', 'cfg_site_name', 'site.name', '站点名称', 'urban-lifeline 平台', 'String', 'input', '站点名称', NULL, NULL, 'site', 'mod_system', 10, 0, '展示在标题/登录/页脚', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0002', 'cfg_site_logo', 'site.logo', '站点Logo', '/static/logo.png', 'String', 'input', '站点Logo地址', NULL, NULL, 'site', 'mod_system', 20, 0, '相对或绝对URL', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0003', 'cfg_site_icp', 'site.icp', 'ICP备案号', '', 'String', 'input', 'ICP备案号', NULL, NULL, 'site', 'mod_system', 30, 0, '页脚展示', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 国际化与时区
|
||||
('CFG-0101', 'cfg_i18n_locale', 'i18n.defaultLocale', '默认语言', 'zh-CN', 'String', 'select', '默认语言', NULL, '["zh-CN", "en-US"]'::json, 'i18n', 'mod_system', 10, 0, '如 zh-CN/en-US', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0102', 'cfg_timezone', 'system.timezone', '系统时区', 'Asia/Shanghai', 'String', 'input', '系统默认时区', NULL, NULL, 'i18n', 'mod_system', 20, 0, 'IANA时区名', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 安全与认证
|
||||
('CFG-0201', 'cfg_pwd_policy', 'security.passwordPolicy','密码策略', '{"minLen":8,"upper":1,"lower":1,"digit":1,"special":0}', 'String', 'textarea', '密码策略', NULL, NULL, 'security', 'mod_system', 10, 0, 'JSON结构', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0202', 'cfg_jwt_exp', 'security.jwt.expireSeconds','JWT过期时间', '86400', 'INTEGER', 'input', 'JWT过期秒数', NULL, NULL, 'security', 'mod_system', 20, 0, '默认24小时', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0203', 'cfg_session_timeout', 'security.session.timeoutMinutes','会话超时', '30', 'INTEGER', 'input', '会话超时(分钟)', NULL, NULL, 'security', 'mod_system', 30, 0, '空闲登出', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0204', 'cfg_signup_enabled', 'security.signup.enabled','开放注册', 'false', 'BOOLEAN', 'switch', '是否开放注册', NULL, NULL, 'security', 'mod_system', 40, 0, '生产建议关闭', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 存储与上传
|
||||
('CFG-0301', 'cfg_upload_max', 'upload.maxSizeMB', '最大上传大小', '50', 'INTEGER', 'input', '单文件最大上传(MB)', NULL, NULL, 'storage', 'mod_file', 10, 0, '前后端需一致校验', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0302', 'cfg_storage_backend', 'storage.backend', '存储后端', 'minio', 'String', 'select', '存储后端类型', NULL, '["local", "minio", "s3"]'::json, 'storage', 'mod_file', 20, 0, '本地/MinIO/S3等,当前默认MinIO', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0303', 'cfg_storage_base', 'storage.basePath', '存储路径', '/data/urban-lifeline', 'String', 'input', '本地存储基路径', NULL, NULL, 'storage', 'mod_file', 30, 0, '当 backend=local', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- MinIO 对象存储配置
|
||||
('CFG-0310', 'cfg_minio_endpoint', 'minio.endpoint', 'MinIO服务端点', 'http://localhost:9000', 'String', 'input', 'MinIO服务器地址', NULL, NULL, 'storage', 'mod_file', 40, 0, 'MinIO API服务地址,如 http://localhost:9000', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0311', 'cfg_minio_accesskey', 'minio.accessKey', 'MinIO访问密钥', 'minioadmin', 'String', 'input', 'MinIO AccessKey', NULL, NULL, 'storage', 'mod_file', 50, 0, 'MinIO认证的AccessKey', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0312', 'cfg_minio_secretkey', 'minio.secretKey', 'MinIO私钥', 'minioadmin123', 'String', 'password', 'MinIO SecretKey', NULL, NULL, 'storage', 'mod_file', 60, 0, 'MinIO认证的SecretKey', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0313', 'cfg_minio_bucket', 'minio.bucketName', 'MinIO存储桶', 'urban-lifeline', 'String', 'input', 'MinIO默认存储桶名称', NULL, NULL, 'storage', 'mod_file', 70, 0, '用于存储文件的默认bucket', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0314', 'cfg_minio_publicurl', 'minio.publicUrl', 'MinIO公网地址', 'http://localhost:9000', 'String', 'input', 'MinIO公网访问地址', NULL, NULL, 'storage', 'mod_file', 80, 0, '用于生成文件访问URL,可与endpoint不同', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0315', 'cfg_minio_ssl', 'minio.ssl.enabled', '启用SSL', 'false', 'BOOLEAN', 'switch', '是否启用SSL连接', NULL, NULL, 'storage', 'mod_file', 90, 0, 'HTTPS连接MinIO服务', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0316', 'cfg_minio_region', 'minio.region', 'MinIO区域', 'us-east-1', 'String', 'input', 'MinIO存储区域', NULL, NULL, 'storage', 'mod_file', 100, 0, 'AWS S3兼容的区域配置', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 文件管理配置
|
||||
('CFG-0320', 'cfg_file_allowed_exts','file.allowedExtensions', '允许的文件扩展名', 'jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip,rar', 'String', 'textarea', '允许上传的文件扩展名', NULL, NULL, 'storage', 'mod_file', 110, 0, '逗号分隔,如 jpg,png,pdf', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0321', 'cfg_file_forbidden_exts','file.forbiddenExtensions','禁止的文件扩展名', 'exe,bat,sh,php,jsp,asp', 'String', 'textarea', '禁止上传的文件扩展名', NULL, NULL, 'storage', 'mod_file', 120, 0, '逗号分隔,安全考虑', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0322', 'cfg_file_cleanup_days', 'file.tempCleanupDays', '临时文件清理天数', '7', 'INTEGER', 'input', '临时文件自动清理天数', NULL, NULL, 'storage', 'mod_file', 130, 0, '超过天数的临时文件将被清理', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0323', 'cfg_file_duplicate_check','file.duplicateCheck.enabled','启用文件去重', 'true', 'BOOLEAN', 'switch', '是否启用MD5去重检查', NULL, NULL, 'storage', 'mod_file', 140, 0, '基于MD5值检查重复文件', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0324', 'cfg_file_virus_scan', 'file.virusScan.enabled', '启用病毒扫描', 'false', 'BOOLEAN', 'switch', '是否启用文件病毒扫描', NULL, NULL, 'storage', 'mod_file', 150, 0, '需要集成防病毒引擎', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 通知(邮件/SMS)
|
||||
-- 邮件配置
|
||||
('CFG-0401', 'cfg_mail_host', 'email.host', 'SMTP服务器地址', 'smtp.qq.com', 'String', 'input', 'SMTP服务器地址', NULL, NULL, 'notify', 'mod_message', 10, 1, '邮件发送服务器地址', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0402', 'cfg_mail_port', 'email.port', 'SMTP端口', '587', 'INTEGER', 'input', 'SMTP服务器端口', NULL, NULL, 'notify', 'mod_message', 20, 1, '常用:25/465/587', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0403', 'cfg_mail_username', 'email.username', '发件人邮箱', '3223905473@qq.com', 'String', 'input', '发件人邮箱地址', NULL, NULL, 'notify', 'mod_message', 30, 1, '用于发送邮件的邮箱账号', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0404', 'cfg_mail_password', 'email.password', '邮箱授权码', 'xmdmxvtjumxocicc', 'String', 'password', '邮箱授权码/密码', NULL, NULL, 'notify', 'mod_message', 40, 1, '邮箱的授权码或密码', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0405', 'cfg_mail_fromname', 'email.fromName', '发件人名称', 'urban-lifeline平台', 'String', 'input', '发件人显示名称', NULL, NULL, 'notify', 'mod_message', 50, 1, '邮件中显示的发件人名称', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0406', 'cfg_mail_ssl', 'email.ssl.enable', '启用SSL', 'true', 'BOOLEAN', 'switch', '是否启用SSL', NULL, NULL, 'notify', 'mod_message', 60, 1, 'SSL加密连接', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0407', 'cfg_mail_timeout', 'email.timeout', '连接超时时间', '30000', 'INTEGER', 'input', '连接超时时间(毫秒)', NULL, NULL, 'notify', 'mod_message', 70, 1, 'SMTP连接超时时间(5000-60000)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 短信配置
|
||||
('CFG-0411', 'cfg_sms_provider', 'sms.provider', '短信服务商', 'aliyun', 'String', 'select', '短信服务提供商', NULL, '["aliyun", "tencent"]'::json, 'notify', 'mod_message', 80, 1, '短信服务提供商类型', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0412', 'cfg_sms_keyid', 'sms.accessKeyId', 'AccessKey ID', 'LTAI5t68do3qVXx5Rufugt3X', 'String', 'input', '短信服务AccessKey ID', NULL, NULL, 'notify', 'mod_message', 90, 1, '云服务商的AccessKey ID', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0413', 'cfg_sms_secret', 'sms.accessKeySecret', 'AccessKey Secret', '2vD9ToIff49Vph4JQXsn0Cy8nXQfzA', 'String', 'password', '短信服务AccessKey Secret', NULL, NULL, 'notify', 'mod_message', 100, 1, '云服务商的AccessKey Secret', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0414', 'cfg_sms_sign', 'sms.signName', '短信签名', 'urban-lifeline', 'String', 'input', '短信签名', NULL, NULL, 'notify', 'mod_message', 110, 1, '发送短信使用的签名', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0415', 'cfg_sms_tpl_login', 'sms.templateCode.login', '登录验证码模板', 'SMS_491985030', 'String', 'input', '登录验证码模板编码', NULL, NULL, 'notify', 'mod_message', 120, 1, '登录验证码短信模板', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0416', 'cfg_sms_tpl_register', 'sms.templateCode.register','注册验证码模板', 'SMS_491985030', 'String', 'input', '注册验证码模板编码', NULL, NULL, 'notify', 'mod_message', 130, 1, '注册验证码短信模板', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0417', 'cfg_sms_timeout', 'sms.timeout', '请求超时时间', '30000', 'INTEGER', 'input', '请求超时时间(毫秒)', NULL, NULL, 'notify', 'mod_message', 140, 1, 'API请求超时时间(5000-60000)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- Dify AI 配置
|
||||
-- Dify 基础配置
|
||||
('CFG-0450', 'cfg_dify_api_base', 'dify.apiBaseUrl', 'Dify API地址', 'http://localhost:8000/v1', 'String', 'input', 'Dify API基础地址', NULL, NULL, 'dify', 'mod_agent', 10, 1, 'Dify服务的API基础地址,如 http://localhost/dify/api', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0452', 'cfg_dify_knowledge_key','dify.knowledgeApiKey', '知识库API密钥', 'dataset-LepcmgOE95n2S7yweNhQzNoB', 'String', 'password', '知识库API密钥', NULL, NULL, 'dify', 'mod_agent', 30, 1, '用于访问Dify知识库的API密钥', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0453', 'cfg_dify_timeout', 'dify.timeout', '请求超时时间', '60', 'INTEGER', 'input', '请求超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 40, 1, 'API请求的超时时间(10-600秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0454', 'cfg_dify_conn_timeout','dify.connectTimeout', '连接超时时间', '10', 'INTEGER', 'input', '连接超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 50, 1, 'API连接的超时时间(5-60秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0455', 'cfg_dify_read_timeout','dify.readTimeout', '读取超时时间', '60', 'INTEGER', 'input', '读取超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 60, 1, 'API读取响应的超时时间(10-600秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0456', 'cfg_dify_stream_timeout','dify.streamTimeout', '流式响应超时时间', '300', 'INTEGER', 'input', '流式响应超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 70, 1, '流式API响应的超时时间(30-1800秒)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- Dify 上传配置
|
||||
('CFG-0460', 'cfg_dify_upload_types','dify.upload.allowedTypes','允许的文件类型', 'pdf,txt,docx,doc,md,html,htm,xlsx,xls,csv', 'String', 'textarea', '上传文件允许的类型', NULL, NULL, 'dify', 'mod_agent', 80, 1, '支持上传的文件类型列表,逗号分隔', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0461', 'cfg_dify_upload_max', 'dify.upload.maxSize', '最大文件大小', '50', 'INTEGER', 'input', '最大文件大小(MB)', NULL, NULL, 'dify', 'mod_agent', 90, 1, '单个文件上传的最大大小限制(1-500MB)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- Dify 知识库配置
|
||||
('CFG-0470', 'cfg_dify_index_tech', 'dify.knowledge.indexing.tchnique','默认索引方式', 'high_quality', 'String', 'select', '默认索引方式', NULL, '["high_quality", "economy"]'::json, 'dify', 'mod_agent', 100, 1, '知识库文档的默认索引方式:high_quality(高质量)或 economy(经济)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0471', 'cfg_dify_embed_model', 'dify.knowledge.embedding.model','默认Embedding模型', 'Qwen/Qwen3-Embedding-8B', 'String', 'input', '默认Embedding模型', NULL, NULL, 'dify', 'mod_agent', 110, 1, '知识库使用的默认Embedding模型名称', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0472', 'cfg_dify_embed_provider', 'dify.knowledge.embedding.model.provider','Embedding模型供应商', 'langgenius/siliconflow/siliconflow', 'String', 'input', 'Embedding模型供应商', NULL, NULL, 'dify', 'mod_agent', 120, 1, 'Embedding模型的供应商标识', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0473', 'cfg_dify_rerank_enable', 'dify.knowledge.reranking.enable','启用Rerank', 'true', 'BOOLEAN', 'switch', '是否启用Rerank重排序', NULL, NULL, 'dify', 'mod_agent', 130, 1, '启用后会对检索结果进行重排序提升相关性', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0474', 'cfg_dify_rerank_model', 'dify.knowledge.rerank.model','Rerank模型', 'Qwen/Qwen3-Reranker-8B', 'String', 'input', 'Rerank重排序模型', NULL, NULL, 'dify', 'mod_agent', 140, 1, '知识库使用的Rerank模型名称', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0475', 'cfg_dify_rerank_provider', 'dify.knowledge.rerank.model.provider','Rerank模型供应商', 'langgenius/siliconflow/siliconflow', 'String', 'input', 'Rerank模型供应商', NULL, NULL, 'dify', 'mod_agent', 150, 1, 'Rerank模型的供应商标识', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0476', 'cfg_dify_retrieval_topk', 'dify.knowledge.retrieval.top.k','检索TopK', '5', 'INTEGER', 'input', '检索返回的最大文档数', NULL, NULL, 'dify', 'mod_agent', 160, 1, '知识库检索时返回的最相关文档数量(1-20)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0477', 'cfg_dify_retrieval_threshold', 'dify.knowledge.retrieval.score.threshold','相似度阈值', '0.5', 'DOUBLE', 'input', '检索相似度阈值', NULL, NULL, 'dify', 'mod_agent', 170, 1, '低于此阈值的文档将被过滤(0.0-1.0)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
-- Dify workcase相关智能体配置
|
||||
('CFG-0478', 'cfg_dify_workcase_chat', 'dify.workcase.agent.chat','泰豪小电AgentApiKey', 'app-CDKy0wYkPnl6dA6G7eu113Vw', 'String', 'input', '泰豪小电AgentApiKey', NULL, NULL, 'dify', 'mod_agent', 160, 1, '泰豪小电AgentApiKey', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0479', 'cfg_dify_workcase_summary', 'dify.workcase.workflow.summary','工单总结AgentApikey', 'app-YMlj2B0m21KpYZPv3YdObi7r', 'String', 'input', '工单总结AgentApikey', NULL, NULL, 'dify', 'mod_agent', 170, 1, '工单总结AgentApikey', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
|
||||
-- 日志与审计
|
||||
('CFG-0501', 'cfg_log_level', 'log.level', '日志级别', 'INFO', 'String', 'select', '系统日志级别', NULL, '["DEBUG", "INFO", "WARN", "ERROR"]'::json, 'log', 'mod_system', 10, 0, 'DEBUG/INFO/WARN/ERROR', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0502', 'cfg_audit_retention', 'audit.retentionDays', '审计日志保留', '90', 'INTEGER', 'input', '审计日志保留天数', NULL, NULL, 'log', 'mod_system', 20, 0, '合规按需调整', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 平台特性
|
||||
('CFG-0601', 'cfg_maintenance', 'platform.maintenance', '维护模式', 'false', 'BOOLEAN', 'switch', '维护模式开关', NULL, NULL, 'platform', 'mod_system', 10, 0, 'true时仅管理员可用', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0602', 'cfg_feature_acl_policy','feature.acl.policy', 'ACL策略', 'enabled', 'String', 'select', 'ACL策略开关', NULL, '["enabled", "disabled"]'::json, 'platform', 'mod_system', 20, 0, 'enabled/disabled', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 微信客服配置
|
||||
('CFG-0701', 'cfg_wechat_kefu_corpid', 'wechat.kefu.corpId', '企业ID', '', 'String', 'input', '企业微信的企业ID', NULL, NULL, 'wechat', 'mod_workcase', 10, 1, '企业微信管理后台获取', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0702', 'cfg_wechat_kefu_secret', 'wechat.kefu.secret', '客服应用Secret', '', 'String', 'password', '微信客服应用的Secret', NULL, NULL, 'wechat', 'mod_workcase', 20, 1, '微信客服应用的密钥', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0703', 'cfg_wechat_kefu_token', 'wechat.kefu.token', '回调Token', '', 'String', 'input', '消息回调的Token', NULL, NULL, 'wechat', 'mod_workcase', 30, 1, '用于验证消息回调的Token', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0704', 'cfg_wechat_kefu_aeskey', 'wechat.kefu.encodingAesKey','回调加密密钥', '', 'String', 'password', '消息回调的EncodingAESKey', NULL, NULL, 'wechat', 'mod_workcase', 40, 1, '用于解密消息回调的AES密钥', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0705', 'cfg_wechat_kefu_openkfid', 'wechat.kefu.openKfid', '客服账号ID', '', 'String', 'input', '微信客服账号的open_kfid', NULL, NULL, 'wechat', 'mod_workcase', 50, 1, '用于发送消息的客服账号ID', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0706', 'cfg_wechat_kefu_welcome', 'wechat.kefu.welcomeTemplate','欢迎语模板', '您好,您的工单已创建。\n工单编号:{workcaseId}\n问题类型:{type}\n设备:{device}\n我们将尽快为您处理。', 'String', 'textarea', '客服欢迎语消息模板', NULL, NULL, 'wechat', 'mod_workcase', 60, 1, '支持变量:{workcaseId},{type},{device},{username}', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
|
||||
-- 微信小程序配置
|
||||
('CFG-0710', 'cfg_wechat_mp_appid', 'wechat.miniprogram.appid', '小程序AppID', 'wx15e67484db6d431f', 'String', 'input', '微信小程序的AppID', NULL, NULL, 'wechat', 'mod_workcase', 70, 1, '在微信公众平台获取', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||
('CFG-0711', 'cfg_wechat_mp_appsecret', 'wechat.miniprogram.appsecret', '小程序AppSecret', '127dcc9c90dd1b66a700b52094922253', 'String', 'password', '微信小程序的AppSecret', NULL, NULL, 'wechat', 'mod_workcase', 80, 1, '在微信公众平台获取,用于获取openid和解密手机号', 'system', NULL, NULL, now(), NULL, NULL, false);
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
-- 初始化消息渠道配置(与 message schema 对应)
|
||||
-- 配置常用消息发送渠道
|
||||
|
||||
-- =============================
|
||||
-- 1. 初始化消息渠道
|
||||
-- =============================
|
||||
INSERT INTO message.tb_message_channel (
|
||||
optsn, channel_id, channel_code, channel_name, channel_desc,
|
||||
config, status, priority, creator, create_time, deleted
|
||||
) VALUES
|
||||
-- 应用内消息(默认渠道,优先级最高)
|
||||
('CH-0001', 'channel_app', 'app', '应用内消息', '系统内部消息通知',
|
||||
'{"enabled": true, "realtime": true}'::json,
|
||||
'enabled', 100, 'system', now(), false),
|
||||
|
||||
-- 短信通知
|
||||
('CH-0002', 'channel_sms', 'sms', '短信通知', '短信发送服务',
|
||||
'{
|
||||
"enabled": false,
|
||||
"provider": "",
|
||||
"apiKey": "",
|
||||
"apiSecret": "",
|
||||
"signName": "",
|
||||
"templateCode": ""
|
||||
}'::json,
|
||||
'disabled', 80, 'system', now(), false),
|
||||
|
||||
-- 邮件通知
|
||||
('CH-0003', 'channel_email', 'email', '邮件通知', '电子邮件发送服务',
|
||||
'{
|
||||
"enabled": false,
|
||||
"smtpHost": "",
|
||||
"smtpPort": 465,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"fromAddress": "",
|
||||
"useSsl": true
|
||||
}'::json,
|
||||
'disabled', 70, 'system', now(), false),
|
||||
|
||||
-- 微信公众号
|
||||
('CH-0004', 'channel_wechat_mp', 'wechat_official_account', '微信公众号', '微信公众号模板消息',
|
||||
'{
|
||||
"enabled": false,
|
||||
"appId": "",
|
||||
"appSecret": "",
|
||||
"templateId": ""
|
||||
}'::json,
|
||||
'disabled', 60, 'system', now(), false),
|
||||
|
||||
-- 微信小程序
|
||||
('CH-0005', 'channel_wechat_mini', 'wechat_applet', '微信小程序', '微信小程序订阅消息',
|
||||
'{
|
||||
"enabled": false,
|
||||
"appId": "",
|
||||
"appSecret": "",
|
||||
"templateId": ""
|
||||
}'::json,
|
||||
'disabled', 50, 'system', now(), false),
|
||||
|
||||
-- 钉钉通知
|
||||
('CH-0006', 'channel_dingtalk', 'dingtalk', '钉钉通知', '钉钉工作通知',
|
||||
'{
|
||||
"enabled": false,
|
||||
"agentId": "",
|
||||
"appKey": "",
|
||||
"appSecret": "",
|
||||
"robotToken": ""
|
||||
}'::json,
|
||||
'disabled', 40, 'system', now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 2. 初始化消息模板(系统通用模板)
|
||||
-- =============================
|
||||
INSERT INTO message.tb_message_template (
|
||||
optsn, template_id, template_code, template_name, template_type,
|
||||
title_template, content_template, variables, service,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
-- 用户注册欢迎消息
|
||||
('TPL-0001', 'tpl_user_welcome', 'USER_WELCOME', '用户注册欢迎', 'system',
|
||||
'欢迎加入 {{platformName}}',
|
||||
'您好,{{username}}!\n\n欢迎加入 {{platformName}} 平台。您的账号已成功创建。\n\n账号信息:\n- 用户名:{{usercode}}\n- 邮箱:{{email}}\n- 注册时间:{{registerTime}}\n\n祝您使用愉快!',
|
||||
'["platformName", "username", "usercode", "email", "registerTime"]'::jsonb,
|
||||
'system',
|
||||
'system', now(), false),
|
||||
|
||||
-- 密码重置通知
|
||||
('TPL-0002', 'tpl_password_reset', 'PASSWORD_RESET', '密码重置通知', 'system',
|
||||
'密码重置验证码',
|
||||
'您好,{{username}}!\n\n您正在重置密码,验证码为:{{code}}\n\n验证码有效期为 {{expireMinutes}} 分钟,请尽快完成操作。\n\n如非本人操作,请忽略此消息。',
|
||||
'["username", "code", "expireMinutes"]'::jsonb,
|
||||
'system',
|
||||
'system', now(), false),
|
||||
|
||||
-- 系统维护通知
|
||||
('TPL-0003', 'tpl_system_maintenance', 'SYSTEM_MAINTENANCE', '系统维护通知', 'system',
|
||||
'系统维护通知',
|
||||
'尊敬的用户:\n\n系统将于 {{startTime}} 至 {{endTime}} 进行维护升级。\n\n维护内容:{{content}}\n\n维护期间系统将暂停服务,请您提前做好相关安排。\n\n给您带来不便,敬请谅解!',
|
||||
'["startTime", "endTime", "content"]'::jsonb,
|
||||
'system',
|
||||
'system', now(), false),
|
||||
|
||||
-- 工单创建通知
|
||||
('TPL-0101', 'tpl_ticket_created', 'TICKET_CREATED', '工单创建通知', 'business',
|
||||
'新工单通知',
|
||||
'您好,{{username}}!\n\n您有一条新的工单需要处理:\n\n工单编号:{{ticketNo}}\n工单标题:{{title}}\n优先级:{{priority}}\n创建时间:{{createTime}}\n\n请及时登录系统查看处理。',
|
||||
'["username", "ticketNo", "title", "priority", "createTime"]'::jsonb,
|
||||
'customer_service',
|
||||
'system', now(), false),
|
||||
|
||||
-- 招标公告发布通知
|
||||
('TPL-0201', 'tpl_bidding_published', 'BIDDING_PUBLISHED', '招标公告发布', 'business',
|
||||
'招标公告发布通知',
|
||||
'您好!\n\n新的招标项目已发布:\n\n项目名称:{{projectName}}\n项目编号:{{projectNo}}\n发布时间:{{publishTime}}\n截止时间:{{deadlineTime}}\n\n详情请登录系统查看。',
|
||||
'["projectName", "projectNo", "publishTime", "deadlineTime"]'::jsonb,
|
||||
'bidding',
|
||||
'system', now(), false);
|
||||
@@ -1,471 +0,0 @@
|
||||
-- 初始化权限相关基础数据(与 sys schema 对应)
|
||||
-- 包含:部门、角色、模块、权限、视图及其关联关系
|
||||
|
||||
-- =============================
|
||||
-- 1. 初始化根部门
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_dept (
|
||||
optsn, dept_id, name, parent_id, dept_path, description,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
('DEPT-0001', 'dept_root', '根部门', NULL, '/dept_root/', '系统根部门',
|
||||
'system', now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 2. 初始化全局角色
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_role (
|
||||
optsn, role_id, name, description, scope, owner_dept_id,
|
||||
status, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
-- 超级管理员(全局)
|
||||
('ROLE-0001', 'role_super_admin', '超级管理员', '拥有系统所有权限的最高管理员',
|
||||
'global', NULL, true, 'system', NULL, now(), false),
|
||||
|
||||
-- 系统管理员(全局)
|
||||
('ROLE-0002', 'role_system_admin', '系统管理员', '负责系统配置和用户管理',
|
||||
'global', NULL, true, 'system', NULL, now(), false),
|
||||
|
||||
-- 普通用户(全局)
|
||||
('ROLE-0003', 'role_user', '普通用户', '系统普通用户角色',
|
||||
'global', NULL, true, 'system', NULL, now(), false),
|
||||
|
||||
-- 访客(全局)- 注册用户默认角色,具备客服聊天和工单的所有接口权限
|
||||
('ROLE-0004', 'role_guest', '访客', '访客角色,具备客服聊天和工单的所有接口权限',
|
||||
'global', NULL, true, 'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 3. 初始化系统模块
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_module (
|
||||
optsn, module_id, name, description, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
('MODULE-0001', 'module_system', '系统管理', '用户、角色、权限、部门管理', 'system', NULL, now(), false),
|
||||
('MODULE-0002', 'module_file', '文件管理', '文件上传、下载、关联管理', 'system', NULL, now(), false),
|
||||
('MODULE-0003', 'module_message', '消息通知', '消息发送、接收、模板管理', 'system', NULL, now(), false),
|
||||
('MODULE-0004', 'module_config', '配置管理', '系统配置参数管理', 'system', NULL, now(), false),
|
||||
('MODULE-0008', 'module_agent', '智能体', '智能体管理', '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),
|
||||
('MODULE-0009', 'module_meeting', '视频会议', 'Jitsi Meet视频会议管理', 'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 4. 初始化系统权限
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_permission (
|
||||
optsn, permission_id, name, code, description, module_id, status, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
-- 系统管理模块权限
|
||||
('PERM-0001', 'perm_user_view', '用户查看', 'system:user:view', '查看用户列表和详情', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0002', 'perm_user_create', '用户创建', 'system:user:create', '创建新用户', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0003', 'perm_user_edit', '用户编辑', 'system:user:edit', '编辑用户信息', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0004', 'perm_user_delete', '用户删除', 'system:user:delete', '删除用户', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0011', 'perm_role_view', '角色查看', 'system:role:view', '查看角色列表和详情', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0012', 'perm_role_create', '角色创建', 'system:role:create', '创建新角色', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0013', 'perm_role_edit', '角色编辑', 'system:role:edit', '编辑角色信息', 'module_system', 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', true, 'system', NULL, now(), false),
|
||||
('PERM-0042', 'perm_role_export', '角色导出', 'system:role:export', '导出角色数据', 'module_system', 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', true, 'system', NULL, now(), false),
|
||||
('PERM-0102', 'perm_file_upload', '文件上传', 'file:file:upload', '上传文件', 'module_file', true, 'system', NULL, now(), false),
|
||||
('PERM-0103', 'perm_file_download', '文件下载', 'file:file:download', '下载文件', 'module_file', true, 'system', NULL, now(), false),
|
||||
('PERM-0104', 'perm_file_delete', '文件删除', 'file:file:delete', '删除文件', 'module_file', true, 'system', NULL, now(), false),
|
||||
('PERM-0105', 'perm_file_export', '文件导出', 'file:file:export', '导出文件列表数据', 'module_file', true, 'system', NULL, now(), false),
|
||||
-- 智能体权限
|
||||
('PERM-0120', 'perm_ai_create', '智能体创建', 'ai:agent:create', '创建智能体', 'module_agent', true, 'system', NULL, now(), false),
|
||||
('PERM-0121', 'perm_ai_update', '智能体更新', 'ai:agent:update', '更新智能体', 'module_agent', true, 'system', NULL, now(), false),
|
||||
('PERM-0122', 'perm_ai_delete', '智能体删除', 'ai:agent:delete', '删除智能体', 'module_agent', 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', true, 'system', NULL, now(), false),
|
||||
('PERM-0202', 'perm_message_send', '消息发送', 'message:message:send', '发送消息通知', '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', true, 'system', NULL, now(), false),
|
||||
('PERM-0302', 'perm_config_edit', '配置编辑', 'config:config:edit', '修改系统配置', 'module_config', 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', 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', true, 'system', NULL, now(), false),
|
||||
('PERM-0502', 'perm_platform_chat', 'AI助手访问', 'platform:chat:view', '访问AI助手', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0503', 'perm_platform_bidding', '招标助手访问', 'platform:bidding:view', '访问招标助手(iframe)', 'module_bidding', 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 管理后台功能权限
|
||||
('PERM-0601', 'perm_platform_admin', '平台管理后台', 'platform:admin:view', '访问平台管理后台', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0602', 'perm_platform_admin_overview', '平台数据概览', 'platform:admin:overview', '访问平台数据概览', 'module_system', true, 'system', NULL, now(), false),
|
||||
('PERM-0603', 'perm_platform_admin_user', '平台用户管理', 'platform:admin:user', '访问平台用户管理', 'module_system', 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 管理后台功能权限
|
||||
('PERM-0611', 'perm_bidding_admin', '招标管理后台', 'bidding:admin:view', '访问招标管理后台', 'module_bidding', true, 'system', NULL, now(), false),
|
||||
-- Workcase 管理后台功能权限
|
||||
('PERM-0621', 'perm_workcase_admin', '客服管理后台', 'workcase:admin:view', '访问客服管理后台', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0622', 'perm_workcase_overview', '数据概览', 'workcase:overview:view', '访问泰豪小电数据概览', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0623', 'perm_workcase_knowledge', '知识库管理', 'workcase:knowledge:view', '访问知识库管理', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0624', 'perm_workcase_tickets', '工单管理', 'workcase:tickets:view', '访问工单管理', 'module_workcase', 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),
|
||||
('PERM-0628', 'perm_workcase_chatroom', '聊天室控制台', 'workcase:chatroom:view', '访问聊天室控制台', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
|
||||
-- Workcase 接口权限(访客用户可用)
|
||||
-- AI对话接口权限
|
||||
('PERM-0701', 'perm_workcase_chat_create', 'AI对话创建', 'workcase:chat:create', '创建对话', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0702', 'perm_workcase_chat_update', 'AI对话更新', 'workcase:chat:update', '更新对话', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0703', 'perm_workcase_chat_list', 'AI对话查询', 'workcase:chat:list', '查询对话列表', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0704', 'perm_workcase_chat_message', 'AI对话消息', 'workcase:chat:message', '获取对话消息列表', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0705', 'perm_workcase_chat_stream', 'AI流式对话', 'workcase:chat:stream', '流式对话接口', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0706', 'perm_workcase_chat_analyze', 'AI对话分析', 'workcase:chat:analyze', '分析对话生成工单信息', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
-- 聊天室接口权限
|
||||
('PERM-0711', 'perm_workcase_room_create', '聊天室创建', 'workcase:room:create', '创建聊天室', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0712', 'perm_workcase_room_update', '聊天室更新', 'workcase:room:update', '更新聊天室', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0713', 'perm_workcase_room_close', '聊天室关闭', 'workcase:room:close', '关闭聊天室', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0714', 'perm_workcase_room_view', '聊天室查看', 'workcase:room:view', '查看聊天室', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0715', 'perm_workcase_room_member', '聊天室成员', 'workcase:room:member', '管理聊天室成员', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0716', 'perm_workcase_room_message', '聊天室消息', 'workcase:room:message', '发送和查看聊天室消息', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
-- 工单接口权限
|
||||
('PERM-0721', 'perm_workcase_ticket_create', '工单创建', 'workcase:ticket:create', '创建工单', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0722', 'perm_workcase_ticket_update', '工单更新', 'workcase:ticket:update', '更新工单', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0723', 'perm_workcase_ticket_view', '工单查看', 'workcase:ticket:view', '查看工单详情和列表', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0724', 'perm_workcase_ticket_process', '工单处理', 'workcase:ticket:process', '工单处理过程管理', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
('PERM-0725', 'perm_workcase_ticket_device', '工单设备', 'workcase:ticket:device', '工单设备管理', 'module_workcase', true, 'system', NULL, now(), false),
|
||||
|
||||
-- 视频会议模块权限(Jitsi Meet)
|
||||
('PERM-0730', 'perm_meeting_create', '创建会议', 'meeting:create:own', '创建视频会议', 'module_meeting', true, 'system', NULL, now(), false),
|
||||
('PERM-0731', 'perm_meeting_join', '加入会议', 'meeting:join:any', '加入视频会议', 'module_meeting', true, 'system', NULL, now(), false),
|
||||
('PERM-0732', 'perm_meeting_url', '获取会议链接', 'meeting:url:any', '获取会议加入链接', 'module_meeting', true, 'system', NULL, now(), false),
|
||||
('PERM-0733', 'perm_meeting_token', '获取会议令牌', 'meeting:token:any', '获取会议参与令牌', 'module_meeting', true, 'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 5. 初始化视图(菜单)
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_view (
|
||||
optsn, view_id, name, parent_id, url, component, icon, type,
|
||||
view_type, iframe_url, service, layout, order_num, description,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
-- =========================
|
||||
-- 平台应用菜单 (platform)
|
||||
-- =========================
|
||||
-- 一级菜单 (图标使用 lucide-vue-next)
|
||||
('VIEW-P002', 'view_platform_chat', '泰豪AI助手', NULL, '/aichat', 'public/Chat/AIChatView.vue', 'MessageCircle', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 10, '泰豪AI助手-直接智能体对话', 'system', now(), false),
|
||||
('VIEW-P001', 'view_platform_home', '全部应用', NULL, '/agents', 'public/Agents/AgentPlatformView.vue', 'LayoutGrid', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 20, '全部智能体', 'system', now(), false),
|
||||
-- iframe 嵌入菜单
|
||||
-- url: platform中的路由路径(用于sidebar定位和路由跳转)
|
||||
-- iframe_url: iframe的src地址(实际内容的URL)
|
||||
('VIEW-P005', 'view_platform_workflow', '智能体编排', NULL, '/app/workflow', NULL, 'Workflow', 1,
|
||||
'iframe', 'http://localhost:3000', 'platform', 'SidebarLayout', 30, 'Dify智能体编排(iframe)', 'system', now(), false),
|
||||
('VIEW-P003', 'view_platform_bidding', '招标助手', NULL, '/app/bidding', NULL, 'FileText', 1,
|
||||
'iframe', '/bidding/', 'platform', 'SidebarLayout', 40, '招标应用(iframe)', 'system', now(), false),
|
||||
('VIEW-P004', 'view_platform_workcase', '泰豪小电', NULL, '/app/workcase', NULL, 'Headphones', 1,
|
||||
'iframe', '/workcase/', 'platform', 'SidebarLayout', 50, '客服应用(iframe)', 'system', now(), false),
|
||||
|
||||
|
||||
-- 平台管理后台内部视图(SubSidebarLayout布局,在platform服务内)
|
||||
('VIEW-P201', 'view_platform_admin_overview', '数据概览', NULL, '/admin/overview', 'admin/overview/OverviewView.vue', 'BarChart3', 1,
|
||||
'route', NULL, 'platform', 'SubSidebarLayout', 210, '平台数据概览', 'system', now(), false),
|
||||
('VIEW-P202', 'view_platform_admin_user', '用户管理', NULL, '/admin/userManagement', 'admin/userManagement/UserManagementView.vue', 'Users', 1,
|
||||
'route', NULL, 'platform', 'SubSidebarLayout', 220, '平台用户管理', 'system', now(), false),
|
||||
('VIEW-P203', 'view_platform_admin_knowledge', '知识库', NULL, '/admin/knowledge', 'admin/knowledge/KnowledgeView.vue', 'FileText', 1,
|
||||
'route', NULL, 'platform', 'SubSidebarLayout', 230, '平台知识库管理', 'system', now(), false),
|
||||
('VIEW-P204', 'view_platform_admin_config', '系统配置', NULL, '/admin/config', 'admin/config/ConfigView.vue', 'Settings', 1,
|
||||
'route', NULL, 'platform', 'SubSidebarLayout', 240, '平台系统配置', 'system', now(), false),
|
||||
|
||||
-- -- 系统管理目录
|
||||
-- ('VIEW-P100', 'view_system', '系统管理', NULL, '/system', NULL, 'Settings', 0,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 100, '系统管理目录', 'system', now(), false),
|
||||
--
|
||||
-- -- 系统管理子菜单
|
||||
-- ('VIEW-P101', 'view_user', '用户管理', 'view_system', '/system/user', 'system/UserList', 'Users', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 10, '用户管理页面', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-P102', 'view_role', '角色管理', 'view_system', '/system/role', 'system/RoleList', 'Shield', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 20, '角色管理页面', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-P103', 'view_dept', '部门管理', 'view_system', '/system/dept', 'system/DeptList', 'Building', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 30, '部门管理页面', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-P104', 'view_permission', '权限管理', 'view_system', '/system/permission', 'system/PermissionList', 'Lock', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 40, '权限管理页面', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-P105', 'view_config', '配置管理', 'view_system', '/system/config', 'system/ConfigList', 'Settings', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 50, '配置管理页面', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-P106', 'view_file', '文件管理', 'view_system', '/system/file', 'system/FileList', 'FileText', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 60, '文件管理页面', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-P107', 'view_message', '消息管理', 'view_system', '/system/message', 'system/MessageList', 'Mail', 1,
|
||||
-- 'route', NULL, 'platform', 'SidebarLayout', 70, '消息管理页面', 'system', now(), false),
|
||||
--
|
||||
-- -- =========================
|
||||
-- -- 招标应用菜单 (bidding)
|
||||
-- -- =========================
|
||||
-- ('VIEW-B001', 'view_bidding_home', '首页', NULL, '/home', 'Home', 'House', 1,
|
||||
-- 'route', NULL, 'bidding', 'DefaultLayout', 10, '招标应用首页', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-B002', 'view_bidding_list', '招标列表', NULL, '/bidding/list', 'bidding/List', 'List', 1,
|
||||
-- 'route', NULL, 'bidding', 'DefaultLayout', 20, '招标项目列表', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-B003', 'view_bidding_detail', '招标详情', NULL, '/bidding/detail', 'bidding/Detail', 'Document', 1,
|
||||
-- 'route', NULL, 'bidding', 'DefaultLayout', 30, '招标项目详情', 'system', now(), false),
|
||||
--
|
||||
-- ('VIEW-B004', 'view_bidding_offer', '投标管理', NULL, '/bidding/offer', 'bidding/Offer', 'Edit', 1,
|
||||
-- 'route', NULL, 'bidding', 'DefaultLayout', 40, '投标管理页面', 'system', now(), false),
|
||||
|
||||
-- =========================
|
||||
-- 客服应用菜单 (workcase) - 图标使用 lucide-vue-next
|
||||
-- =========================
|
||||
-- 用户端视图
|
||||
('VIEW-W001', 'view_workcase_home', '智能客服', NULL, '/aichat', 'public/AIChat/AIChatView.vue', 'Home', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 10, '智能客服首页', 'system', now(), false),
|
||||
|
||||
('VIEW-W002', 'view_workcase_chatroom', '聊天室控制台', NULL, '/chatroom', 'public/ChatRoom/ChatRoomView.vue', 'MessageSquare', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 20, '实时聊天室控制台', 'system', now(), false),
|
||||
|
||||
-- 管理端视图(使用 SubSidebarLayout 布局)
|
||||
('VIEW-W101', 'view_workcase_admin_overview', '数据概览', NULL, '/admin/overview', 'admin/overview/OverviewView.vue', 'BarChart3', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 110, '泰豪小电数据概览', 'system', now(), false),
|
||||
|
||||
('VIEW-W102', 'view_workcase_admin_knowledge', '知识库管理', NULL, '/admin/knowledge', 'admin/knowledge/KnowLedgeView.vue', 'FileText', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 120, '知识库文档管理', 'system', now(), false),
|
||||
|
||||
('VIEW-W103', 'view_workcase_admin_tickets', '工单管理', NULL, '/admin/workcase', 'admin/workcase/WorkcaseView.vue', 'Ticket', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 130, '客服工单管理', 'system', now(), false),
|
||||
|
||||
('VIEW-W104', 'view_workcase_admin_conversation', '对话数据', NULL, '/admin/customerChat', 'admin/customerChat/CustomerChatView.vue', 'MessageCircle', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 140, '客户对话数据管理', 'system', now(), false),
|
||||
|
||||
('VIEW-W105', 'view_workcase_admin_agent', '智能体管理', NULL, '/admin/agent', 'admin/agent/AgentView.vue', 'Bot', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 150, '智能体配置管理', 'system', now(), false),
|
||||
|
||||
-- 日志管理(带子级的目录)
|
||||
('VIEW-W106', 'view_workcase_admin_log', '日志管理', NULL, '/admin/log', NULL, 'ScrollText', 1,
|
||||
'route', NULL, 'workcase', 'SubSidebarLayout', 160, '日志管理目录', 'system', now(), false),
|
||||
|
||||
('VIEW-W107', 'view_workcase_admin_log_knowledge', '知识库日志', 'view_workcase_admin_log', '/admin/log/knowledge', 'admin/log/knowledgeLog/KnowledgeLogView.vue', 'FileText', 1,
|
||||
'route', NULL, 'workcase', NULL, 161, '知识库操作日志', 'system', now(), false),
|
||||
|
||||
('VIEW-W108', 'view_workcase_admin_log_workcase', '工单日志', 'view_workcase_admin_log', '/admin/log/workcase', 'admin/log/workcaseLog/WorkcaseLogView.vue', 'Ticket', 1,
|
||||
'route', NULL, 'workcase', NULL, 162, '工单操作日志', 'system', now(), false),
|
||||
|
||||
('VIEW-W109', 'view_workcase_admin_log_system', '系统日志', 'view_workcase_admin_log', '/admin/log/system', 'admin/log/systemLog/SystemLogView.vue', 'Settings', 1,
|
||||
'route', NULL, 'workcase', NULL, 163, '系统运行日志', 'system', now(), false);
|
||||
-- =============================
|
||||
-- 6. 角色权限关联(超级管理员拥有所有权限)
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
)
|
||||
SELECT
|
||||
'RP-' || LPAD(ROW_NUMBER() OVER (ORDER BY permission_id)::TEXT, 4, '0'),
|
||||
'role_super_admin',
|
||||
permission_id,
|
||||
'system',
|
||||
NULL,
|
||||
now(),
|
||||
false
|
||||
FROM sys.tb_sys_permission
|
||||
WHERE deleted = false;
|
||||
|
||||
-- 系统管理员权限(除了用户删除外的系统管理权限 + 所有平台基础菜单)
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
)
|
||||
SELECT
|
||||
'RP-SA-' || LPAD(ROW_NUMBER() OVER (ORDER BY permission_id)::TEXT, 4, '0'),
|
||||
'role_system_admin',
|
||||
permission_id,
|
||||
'system',
|
||||
NULL,
|
||||
now(),
|
||||
false
|
||||
FROM sys.tb_sys_permission
|
||||
WHERE deleted = false
|
||||
AND code NOT IN ('system:user:delete', 'system:role:delete', 'system:dept:delete')
|
||||
AND (
|
||||
module_id IN ('module_system', 'module_file', 'module_message', 'module_config', 'module_bidding', 'module_workcase')
|
||||
OR code LIKE 'platform:%:view' -- 包含所有平台基础菜单权限
|
||||
);
|
||||
|
||||
-- 普通用户权限(基础查看和文件操作 + 平台基础菜单访问)
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
-- 平台基础菜单访问权限
|
||||
('RP-U-0001', 'role_user', 'perm_platform_home', 'system', NULL, now(), false),
|
||||
('RP-U-0002', 'role_user', 'perm_platform_chat', 'system', NULL, now(), false),
|
||||
('RP-U-0003', 'role_user', 'perm_platform_bidding', 'system', NULL, now(), false),
|
||||
('RP-U-0004', 'role_user', 'perm_platform_workcase', 'system', NULL, now(), false),
|
||||
('RP-U-0005', 'role_user', 'perm_platform_workflow', 'system', NULL, now(), false),
|
||||
-- 系统功能权限
|
||||
('RP-U-0011', 'role_user', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('RP-U-0012', 'role_user', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('RP-U-0013', 'role_user', 'perm_file_upload', 'system', NULL, now(), false),
|
||||
('RP-U-0014', 'role_user', 'perm_file_download', 'system', NULL, now(), false),
|
||||
('RP-U-0015', 'role_user', 'perm_message_view', 'system', NULL, now(), false),
|
||||
('RP-U-0016', 'role_user', 'perm_config_view', 'system', NULL, now(), false),
|
||||
--- 视频会议权限
|
||||
('RP-U-0050', 'role_user', 'perm_meeting_create', 'system', NULL, now(), false),
|
||||
('RP-U-0051', 'role_user', 'perm_meeting_join', 'system', NULL, now(), false),
|
||||
('RP-U-0052', 'role_user', 'perm_meeting_url', 'system', NULL, now(), false),
|
||||
('RP-U-0053', 'role_user', 'perm_meeting_token', 'system', NULL, now(), false);
|
||||
|
||||
-- 访客权限(基础菜单 + workcase聊天和工单全部接口权限)
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
-- 平台基础菜单访问权限
|
||||
('RP-G-0001', 'role_guest', 'perm_platform_home', 'system', NULL, now(), false),
|
||||
('RP-G-0002', 'role_guest', 'perm_platform_chat', 'system', NULL, now(), false),
|
||||
('RP-G-0003', 'role_guest', 'perm_platform_workcase', 'system', NULL, now(), false),
|
||||
-- 系统功能权限(仅查看)
|
||||
('RP-G-0011', 'role_guest', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('RP-G-0012', 'role_guest', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('RP-G-0013', 'role_guest', 'perm_message_view', 'system', NULL, now(), false),
|
||||
-- Workcase AI对话接口权限
|
||||
('RP-G-0021', 'role_guest', 'perm_workcase_chat_create', 'system', NULL, now(), false),
|
||||
('RP-G-0022', 'role_guest', 'perm_workcase_chat_update', 'system', NULL, now(), false),
|
||||
('RP-G-0023', 'role_guest', 'perm_workcase_chat_list', 'system', NULL, now(), false),
|
||||
('RP-G-0024', 'role_guest', 'perm_workcase_chat_message', 'system', NULL, now(), false),
|
||||
('RP-G-0025', 'role_guest', 'perm_workcase_chat_stream', 'system', NULL, now(), false),
|
||||
('RP-G-0026', 'role_guest', 'perm_workcase_chat_analyze', 'system', NULL, now(), false),
|
||||
-- Workcase 聊天室接口权限
|
||||
('RP-G-0031', 'role_guest', 'perm_workcase_room_create', 'system', NULL, now(), false),
|
||||
('RP-G-0032', 'role_guest', 'perm_workcase_room_update', 'system', NULL, now(), false),
|
||||
('RP-G-0033', 'role_guest', 'perm_workcase_room_close', 'system', NULL, now(), false),
|
||||
('RP-G-0034', 'role_guest', 'perm_workcase_room_view', 'system', NULL, now(), false),
|
||||
('RP-G-0035', 'role_guest', 'perm_workcase_room_member', 'system', NULL, now(), false),
|
||||
('RP-G-0036', 'role_guest', 'perm_workcase_room_message', 'system', NULL, now(), false),
|
||||
('RP-G-0037', 'role_guest', 'perm_workcase_chatroom', 'system', NULL, now(), false),
|
||||
-- Workcase 工单接口权限
|
||||
('RP-G-0041', 'role_guest', 'perm_workcase_ticket_create', 'system', NULL, now(), false),
|
||||
('RP-G-0042', 'role_guest', 'perm_workcase_ticket_update', 'system', NULL, now(), false),
|
||||
('RP-G-0043', 'role_guest', 'perm_workcase_ticket_view', 'system', NULL, now(), false),
|
||||
('RP-G-0044', 'role_guest', 'perm_workcase_ticket_process', 'system', NULL, now(), false),
|
||||
('RP-G-0045', 'role_guest', 'perm_workcase_ticket_device', 'system', NULL, now(), false),
|
||||
--- 视频会议权限
|
||||
('RP-G-0050', 'role_guest', 'perm_meeting_create', 'system', NULL, now(), false),
|
||||
('RP-G-0051', 'role_guest', 'perm_meeting_join', 'system', NULL, now(), false),
|
||||
('RP-G-0052', 'role_guest', 'perm_meeting_url', 'system', NULL, now(), false),
|
||||
('RP-G-0053', 'role_guest', 'perm_meeting_token', 'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 7. 视图权限关联
|
||||
-- =============================
|
||||
-- 将视图与对应模块的权限关联(使用真正的 view_id,不是 optsn)
|
||||
INSERT INTO sys.tb_sys_view_permission (
|
||||
optsn, view_id, permission_id, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
-- 平台基础菜单权限关联(所有登录用户都可访问)
|
||||
('VP-P001', 'view_platform_home', 'perm_platform_home', 'system', NULL, now(), false),
|
||||
('VP-P002', 'view_platform_chat', 'perm_platform_chat', 'system', NULL, now(), false),
|
||||
('VP-P003', 'view_platform_bidding', 'perm_platform_bidding', 'system', NULL, now(), false),
|
||||
('VP-P004', 'view_platform_workcase', 'perm_platform_workcase', 'system', NULL, now(), false),
|
||||
('VP-P005', 'view_platform_workflow', 'perm_platform_workflow', 'system', NULL, now(), false),
|
||||
|
||||
-- 管理后台入口权限关联(iframe入口)
|
||||
('VP-P101', 'view_platform_admin_entry', 'perm_platform_admin', 'system', NULL, now(), false),
|
||||
('VP-P102', 'view_bidding_admin_entry', 'perm_bidding_admin', 'system', NULL, now(), false),
|
||||
('VP-P103', 'view_workcase_admin_entry', 'perm_workcase_admin', 'system', NULL, now(), false),
|
||||
|
||||
-- 平台管理后台内部视图权限关联(SubSidebarLayout)
|
||||
('VP-P201', 'view_platform_admin_overview', 'perm_platform_admin_overview', 'system', NULL, now(), false),
|
||||
('VP-P202', 'view_platform_admin_user', 'perm_platform_admin_user', 'system', NULL, now(), false),
|
||||
('VP-P203', 'view_platform_admin_knowledge', 'perm_platform_admin_knowledge', 'system', NULL, now(), false),
|
||||
('VP-P204', 'view_platform_admin_config', 'perm_platform_admin_config', 'system', NULL, now(), false),
|
||||
|
||||
-- Workcase服务用户端视图关联
|
||||
('VP-W001', 'view_workcase_home', 'perm_platform_workcase', 'system', NULL, now(), false),
|
||||
('VP-W002', 'view_workcase_chatroom', 'perm_workcase_chatroom', 'system', NULL, now(), false),
|
||||
|
||||
-- Workcase服务管理端视图关联
|
||||
('VP-W101', 'view_workcase_admin_overview', 'perm_workcase_overview', 'system', NULL, now(), false),
|
||||
('VP-W102', 'view_workcase_admin_knowledge', 'perm_workcase_knowledge', 'system', NULL, now(), false),
|
||||
('VP-W103', 'view_workcase_admin_tickets', 'perm_workcase_tickets', 'system', NULL, now(), false),
|
||||
('VP-W104', 'view_workcase_admin_conversation', 'perm_workcase_conversation', 'system', NULL, now(), false),
|
||||
('VP-W105', 'view_workcase_admin_agent', 'perm_workcase_agent', 'system', NULL, now(), false),
|
||||
|
||||
-- 日志管理视图关联(包括父级和子级)
|
||||
('VP-W106', 'view_workcase_admin_log', 'perm_workcase_log', 'system', NULL, now(), false),
|
||||
('VP-W107', 'view_workcase_admin_log_knowledge', 'perm_workcase_log', 'system', NULL, now(), false),
|
||||
('VP-W108', 'view_workcase_admin_log_workcase', 'perm_workcase_log', 'system', NULL, now(), false),
|
||||
('VP-W109', 'view_workcase_admin_log_system', 'perm_workcase_log', 'system', NULL, now(), false);
|
||||
|
||||
-- -- 用户管理视图关联用户权限(已注释,因为view_user被注释掉了)
|
||||
-- -- ('VP-0001', 'view_user', 'perm_user_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0002', 'view_user', 'perm_user_create', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0003', 'view_user', 'perm_user_edit', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0004', 'view_user', 'perm_user_delete', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0005', 'view_user', 'perm_user_export', 'system', NULL, now(), false),
|
||||
-- --
|
||||
-- -- -- 角色管理视图关联角色权限
|
||||
-- -- ('VP-0011', 'view_role', 'perm_role_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0012', 'view_role', 'perm_role_create', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0013', 'view_role', 'perm_role_edit', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0014', 'view_role', 'perm_role_delete', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0015', 'view_role', 'perm_role_export', 'system', NULL, now(), false),
|
||||
-- --
|
||||
-- -- -- 部门管理视图关联部门权限
|
||||
-- -- ('VP-0021', 'view_dept', 'perm_dept_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0022', 'view_dept', 'perm_dept_create', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0023', 'view_dept', 'perm_dept_edit', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0024', 'view_dept', 'perm_dept_delete', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0025', 'view_dept', 'perm_dept_export', 'system', NULL, now(), false),
|
||||
-- --
|
||||
-- -- -- 权限管理视图关联权限管理权限
|
||||
-- -- ('VP-0031', 'view_permission', 'perm_permission_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0032', 'view_permission', 'perm_permission_manage', 'system', NULL, now(), false),
|
||||
-- --
|
||||
-- -- -- 配置管理视图关联配置权限
|
||||
-- -- ('VP-0041', 'view_config', 'perm_config_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0042', 'view_config', 'perm_config_edit', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0043', 'view_config', 'perm_config_export', 'system', NULL, now(), false),
|
||||
-- --
|
||||
-- -- -- 文件管理视图关联文件权限
|
||||
-- -- ('VP-0051', 'view_file', 'perm_file_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0052', 'view_file', 'perm_file_upload', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0053', 'view_file', 'perm_file_download', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0054', 'view_file', 'perm_file_delete', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0055', 'view_file', 'perm_file_export', 'system', NULL, now(), false),
|
||||
-- --
|
||||
-- -- -- 消息管理视图关联消息权限
|
||||
-- -- ('VP-0061', 'view_message', 'perm_message_view', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0062', 'view_message', 'perm_message_send', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0063', 'view_message', 'perm_message_manage', 'system', NULL, now(), false),
|
||||
-- -- ('VP-0064', 'view_message', 'perm_message_export', 'system', NULL, now(), false);
|
||||
@@ -1,64 +0,0 @@
|
||||
-- 初始化用户数据(与 sys schema 对应)
|
||||
-- 创建系统管理员账户和示例用户
|
||||
|
||||
-- =============================
|
||||
-- 1. 创建超级管理员用户
|
||||
-- =============================
|
||||
-- 注意:密码需要使用 bcrypt 加密,这里使用的是 'admin123' 的 bcrypt hash
|
||||
-- 实际部署时应该修改为安全的密码
|
||||
INSERT INTO sys.tb_sys_user (
|
||||
optsn, user_id, usercode, password, email, phone, phone_hash,
|
||||
create_time, status, deleted
|
||||
) VALUES
|
||||
('USER-0001', 'user_admin', 'admin',
|
||||
'$2a$10$XAe0TE2p0ym94bKJ8LJ52el3M4oYyiExVH/kNCh.pWLLGDZWNM9Yu', -- admin123
|
||||
'admin@urbanlifeline.com', 'DAWTIvnCQI/KmtwkBYI5WP2NpnSKTq4kStJpOJKahOeJLNhAQ0s1', '7503bbfc6171077b737cdc4f76e781893a9a474c9ead05b6b946ac936e5a0288',
|
||||
now(), 0, false);
|
||||
|
||||
-- 超级管理员用户信息
|
||||
INSERT INTO sys.tb_sys_user_info (
|
||||
optsn, user_id, username, avatar, gender, level, remark,
|
||||
create_time, deleted
|
||||
) VALUES
|
||||
('UINFO-0001', 'user_admin', '系统管理员',
|
||||
'/static/avatar/admin.png', 1, 10, '系统超级管理员账户',
|
||||
now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 2. 关联超级管理员角色
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_user_role (
|
||||
optsn, user_id, role_id, dept_id, dept_path,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
('UR-0001', 'user_admin', 'role_super_admin', 'dept_root', '/dept_root/',
|
||||
'system', now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 3. 创建示例普通用户(可选)
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_user (
|
||||
optsn, user_id, usercode, password, email, phone, phone_hash,
|
||||
create_time, status, deleted
|
||||
) VALUES
|
||||
('USER-0002', 'user_demo', 'demo',
|
||||
'$2a$10$XAe0TE2p0ym94bKJ8LJ52el3M4oYyiExVH/kNCh.pWLLGDZWNM9Yu', -- admin123
|
||||
'demo@urbanlifeline.com', 'Y9tsAZOppzsxmKvI7iqqRBMDHzvWym2DE5FX1KgEGVBC5Ii1UG68', '4e98ffd0e02a7f746291bff77c6c497225e8884758d503bde2efad64e45ad44b',
|
||||
now(), 0, false);
|
||||
|
||||
-- 示例用户信息
|
||||
INSERT INTO sys.tb_sys_user_info (
|
||||
optsn, user_id, username, avatar, gender, level, remark,
|
||||
create_time, deleted
|
||||
) VALUES
|
||||
('UINFO-0002', 'user_demo', '演示用户',
|
||||
'/static/avatar/demo.png', 0, 1, '系统演示账户',
|
||||
now(), false);
|
||||
|
||||
-- 关联普通用户角色
|
||||
INSERT INTO sys.tb_sys_user_role (
|
||||
optsn, user_id, role_id, dept_id, dept_path,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
('UR-0002', 'user_demo', 'role_user', 'dept_root', '/dept_root/',
|
||||
'system', now(), false);
|
||||
@@ -1,6 +0,0 @@
|
||||
-- 初始化聊天室人员
|
||||
-- user_admin
|
||||
INSERT INTO workcase.tb_chat_room_member(
|
||||
optsn, member_id, room_id, user_id, user_type, user_name, status, unread_count, last_read_time, last_read_msg_id, join_time, leave_time, creator, create_time, update_time
|
||||
) VALUES
|
||||
('MEM-0001', 'member_admin', 'room_0001', 'user_admin', 'staff', '系统管理员', 'active', 0, null, null, now(), null, 'system', now(), null);
|
||||
@@ -1,137 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: redis-config
|
||||
namespace: urban-lifeline
|
||||
data:
|
||||
redis.conf: |
|
||||
bind 0.0.0.0
|
||||
protected-mode no
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
daemonize no
|
||||
supervised no
|
||||
pidfile /var/run/redis_6379.pid
|
||||
loglevel notice
|
||||
logfile ""
|
||||
databases 16
|
||||
always-show-logo yes
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
dir /data
|
||||
maxmemory 512mb
|
||||
maxmemory-policy allkeys-lru
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: urban-lifeline
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
selector:
|
||||
app: redis
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-nodeport
|
||||
namespace: urban-lifeline
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
selector:
|
||||
app: redis
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
nodePort: 30379
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: urban-lifeline
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
serviceName: redis
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-server
|
||||
- /usr/local/etc/redis/redis.conf
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
env:
|
||||
- name: TZ
|
||||
value: "Asia/Shanghai"
|
||||
volumeMounts:
|
||||
- name: redis-data
|
||||
mountPath: /data
|
||||
- name: redis-config
|
||||
mountPath: /usr/local/etc/redis
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- ping
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- ping
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: redis-data
|
||||
hostPath:
|
||||
path: /data/k8s/redis/data
|
||||
type: DirectoryOrCreate
|
||||
- name: redis-config
|
||||
configMap:
|
||||
name: redis-config
|
||||
@@ -1,251 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
networks:
|
||||
urban-lifeline:
|
||||
driver: bridge
|
||||
name: urban-lifeline
|
||||
|
||||
services:
|
||||
nacos:
|
||||
# 保持原有配置不变
|
||||
image: nacos/nacos-server:v3.1.0
|
||||
container_name: urban-lifeline-nacos
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- urban-lifeline
|
||||
ports:
|
||||
- "8081:8080"
|
||||
- "8848:8848"
|
||||
- "9848:9848"
|
||||
- "9849:9849"
|
||||
environment:
|
||||
MODE: standalone
|
||||
SPRING_DATASOURCE_PLATFORM: mysql
|
||||
MYSQL_SERVICE_HOST: host.docker.internal
|
||||
MYSQL_SERVICE_PORT: 3306
|
||||
MYSQL_SERVICE_DB_NAME: nacos_config
|
||||
MYSQL_SERVICE_USER: root
|
||||
MYSQL_SERVICE_PASSWORD: "123456"
|
||||
MYSQL_SERVICE_DB_PARAM: allowPublicKeyRetrieval=true&useSSL=false
|
||||
JVM_XMS: 512m
|
||||
JVM_XMX: 512m
|
||||
JVM_XMN: 256m
|
||||
NACOS_AUTH_ENABLE: "false"
|
||||
NACOS_AUTH_TOKEN: ZlRkR2ZxR3BvZ1F0a3JxY2V6RUx2cUh1Rkx6V1ZQbE9kUVd1R1VOcWFFS2t3dG5hS0E9PQ==
|
||||
NACOS_AUTH_IDENTITY_KEY: ZlRkR2ZxR3BvZ1F0a3JxY2V6RUx2cUh1Rkx6V1ZQbE9kUVd1R1VOcWFFS2t3dG5hS0E9PQ==
|
||||
NACOS_AUTH_IDENTITY_VALUE: ZlRkR2ZxR3BvZ1F0a3JxY2V6RUx2cUh1Rkx6V1ZQbE9kUVd1R1VOcWFFS2t3dG5hS0E9PQ==
|
||||
volumes:
|
||||
- ../../../.data/docker/nacos/data:/home/nacos/data
|
||||
- ../../../.data/docker/nacos/logs:/home/nacos/logs
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
minio:
|
||||
# 保持原有配置不变
|
||||
image: minio/minio:latest
|
||||
container_name: urban-lifeline-minio
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- urban-lifeline
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin123
|
||||
MINIO_CONSOLE_ADDRESS: ":9001"
|
||||
MINIO_ADDRESS: ":9000"
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- ../../../.data/docker/minio/data:/data
|
||||
- ../../../.data/docker/minio/config:/root/.minio
|
||||
command: server /data --console-address ":9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# ====================== Jitsi 核心修改开始 ======================
|
||||
jitsi-web:
|
||||
# ✅ 保持原有配置不变,无需修改
|
||||
image: jitsi/web:stable-9584
|
||||
container_name: urban-lifeline-jitsi-web
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- urban-lifeline
|
||||
ports:
|
||||
- "8280:80"
|
||||
- "8443:443"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
PUBLIC_URL: https://org.xyzh.yslg.jitsi
|
||||
ENABLE_HTTPS: 0
|
||||
ENABLE_HTTP_REDIRECT: 0
|
||||
DISABLE_HTTPS: 1
|
||||
XMPP_DOMAIN: meet.jitsi
|
||||
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
||||
XMPP_BOSH_URL_BASE: http://jitsi-prosody:5280
|
||||
XMPP_MUC_DOMAIN: muc.meet.jitsi
|
||||
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
||||
XMPP_GUEST_DOMAIN: guest.meet.jitsi
|
||||
JICOFO_COMPONENT_SECRET: jicofo-secret
|
||||
JICOFO_AUTH_USER: focus
|
||||
JVB_AUTH_USER: jvb
|
||||
JVB_AUTH_PASSWORD: jvb-password
|
||||
ENABLE_AUTH: 1
|
||||
ENABLE_GUESTS: 0
|
||||
AUTH_TYPE: jwt
|
||||
JWT_APP_ID: urbanLifeline
|
||||
JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256
|
||||
JWT_ACCEPTED_ISSUERS: urbanLifeline
|
||||
JWT_ACCEPTED_AUDIENCES: jitsi
|
||||
JWT_ASAP_KEYSERVER: https://org.xyzh.yslg.jitsi/
|
||||
JWT_ALLOW_EMPTY: 0
|
||||
JWT_AUTH_TYPE: token
|
||||
JWT_TOKEN_AUTH_MODULE: token_verification
|
||||
ENABLE_RECORDING: 0
|
||||
ENABLE_TRANSCRIPTIONS: 0
|
||||
ENABLE_SUBDOMAINS: 0
|
||||
ENABLE_XMPP_WEBSOCKET: 1
|
||||
ENABLE_SCTP: 1
|
||||
ENABLE_LETSENCRYPT: 0
|
||||
LETSENCRYPT_DOMAIN: org.xyzh.yslg.jitsi
|
||||
volumes:
|
||||
- ../../../.data/docker/jitsi/web:/config
|
||||
- ../../../.data/docker/jitsi/web/crontabs:/var/spool/cron/crontabs
|
||||
- ../../../.data/docker/jitsi/transcripts:/usr/share/jitsi-meet/transcripts
|
||||
depends_on:
|
||||
- jitsi-prosody
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
jitsi-prosody:
|
||||
image: jitsi/prosody:stable-9584
|
||||
container_name: urban-lifeline-jitsi-prosody
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- urban-lifeline
|
||||
expose:
|
||||
- "5222"
|
||||
- "5347"
|
||||
- "5280"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
XMPP_DOMAIN: meet.jitsi
|
||||
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
||||
XMPP_MUC_DOMAIN: muc.meet.jitsi
|
||||
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
||||
XMPP_GUEST_DOMAIN: guest.meet.jitsi
|
||||
JICOFO_COMPONENT_SECRET: jicofo-secret
|
||||
JICOFO_AUTH_USER: focus
|
||||
JICOFO_AUTH_PASSWORD: focus-password
|
||||
JVB_AUTH_USER: jvb
|
||||
JVB_AUTH_PASSWORD: jvb-password
|
||||
ENABLE_AUTH: 1
|
||||
ENABLE_GUESTS: 0
|
||||
AUTH_TYPE: jwt
|
||||
JWT_APP_ID: urbanLifeline
|
||||
JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256
|
||||
JWT_ACCEPTED_ISSUERS: urbanLifeline
|
||||
JWT_ACCEPTED_AUDIENCES: jitsi
|
||||
JWT_ALLOW_EMPTY: 0
|
||||
JWT_AUTH_TYPE: token
|
||||
JWT_TOKEN_AUTH_MODULE: token_verification
|
||||
LOG_LEVEL: info
|
||||
PUBLIC_URL: https://org.xyzh.yslg.jitsi
|
||||
# 🔥 新增1 - Prosody层禁用JWT自动授予主持人权限(JWT模式核心!)
|
||||
JWT_DISABLE_AUTO_MODERATOR: true
|
||||
volumes:
|
||||
- ../../../.data/docker/jitsi/prosody/config:/config
|
||||
- ../../../.data/docker/jitsi/prosody/prosody-plugins-custom:/prosody-plugins-custom
|
||||
healthcheck:
|
||||
test: ["CMD", "prosodyctl", "status"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
|
||||
jitsi-jicofo:
|
||||
image: jitsi/jicofo:stable-9584
|
||||
container_name: urban-lifeline-jitsi-jicofo
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- urban-lifeline
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
XMPP_DOMAIN: meet.jitsi
|
||||
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
||||
XMPP_MUC_DOMAIN: muc.meet.jitsi
|
||||
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
||||
XMPP_SERVER: jitsi-prosody
|
||||
JICOFO_COMPONENT_SECRET: jicofo-secret
|
||||
JICOFO_AUTH_USER: focus
|
||||
JICOFO_AUTH_PASSWORD: focus-password
|
||||
AUTH_TYPE: jwt
|
||||
JVB_BREWERY_MUC: jvbbrewery
|
||||
JICOFO_ENABLE_HEALTH_CHECKS: true
|
||||
# 保留原有配置
|
||||
JICOFO_ENABLE_AUTO_OWNER: false
|
||||
JICOFO_ENABLE_AUTO_LOGIN: false
|
||||
# 🔥 新增2 - 兜底:强制清空初始主持人,杜绝所有自动分配可能
|
||||
JICOFO_CONFERENCE_INITIAL_OWNER: ""
|
||||
volumes:
|
||||
- ../../../.data/docker/jitsi/jicofo:/config
|
||||
depends_on:
|
||||
- jitsi-prosody
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8888/about/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
|
||||
jitsi-jvb:
|
||||
# ✅ 保持原有配置不变,无需修改
|
||||
image: jitsi/jvb:stable-9584
|
||||
container_name: urban-lifeline-jitsi-jvb
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- urban-lifeline
|
||||
ports:
|
||||
- "10000:10000/udp"
|
||||
- "4443:4443/tcp"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
XMPP_DOMAIN: meet.jitsi
|
||||
XMPP_AUTH_DOMAIN: auth.meet.jitsi
|
||||
XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
|
||||
XMPP_SERVER: jitsi-prosody
|
||||
JVB_AUTH_USER: jvb
|
||||
JVB_AUTH_PASSWORD: jvb-password
|
||||
JVB_BREWERY_MUC: jvbbrewery
|
||||
JVB_PORT: 10000
|
||||
JVB_STUN_SERVERS: stun.l.google.com:19302,stun1.l.google.com:19302
|
||||
DOCKER_HOST_ADDRESS: 192.168.0.253
|
||||
JVB_ADVERTISE_IPS: 192.168.0.253
|
||||
JVB_ENABLE_APIS: rest,colibri
|
||||
JVB_TCP_HARVESTER_DISABLED: "false"
|
||||
JVB_TCP_PORT: 4443
|
||||
JVB_TCP_MAPPED_PORT: 4443
|
||||
volumes:
|
||||
- ../../../.data/docker/jitsi/jvb:/config
|
||||
depends_on:
|
||||
- jitsi-prosody
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/about/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
@@ -1,3 +0,0 @@
|
||||
# 注意点
|
||||
1. mysql 开发 bind-address 必须是 0.0.0.0 否则无法连接
|
||||
2. root % 密码必须是 123456
|
||||
@@ -1,210 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# urban-lifeline Docker Compose 启动脚本
|
||||
|
||||
set -e
|
||||
|
||||
# If the user invoked this script with `sh start.sh` (which may be dash),
|
||||
# some Bash-specific features (like ${BASH_SOURCE[0]} and [[ .. ]]) will
|
||||
# fail with "Bad substitution". If we're not running under Bash, try to
|
||||
# re-exec the script using `bash` if available.
|
||||
if [ -z "$BASH_VERSION" ]; then
|
||||
if command -v bash >/dev/null 2>&1; then
|
||||
echo "脚本需要 Bash,正在重新以 Bash 执行..."
|
||||
exec bash "$0" "$@"
|
||||
else
|
||||
echo "错误: 需要 Bash 运行此脚本,但系统未安装 Bash。"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
DATA_DIR="$PROJECT_ROOT/.data/docker"
|
||||
|
||||
echo "=========================================="
|
||||
echo "urban-lifeline Docker 开发环境启动"
|
||||
echo "=========================================="
|
||||
echo "项目目录: $PROJECT_ROOT"
|
||||
echo "数据目录: $DATA_DIR"
|
||||
echo ""
|
||||
|
||||
# 1. 检查 Docker
|
||||
echo "1. 检查 Docker..."
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "❌ 错误: 未找到 Docker"
|
||||
echo "请先安装 Docker: https://docs.docker.com/get-docker/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info &> /dev/null; then
|
||||
echo "❌ 错误: Docker 未运行"
|
||||
echo "请启动 Docker 服务"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Docker 检查完成"
|
||||
echo ""
|
||||
|
||||
# 2. 检查 Docker Compose
|
||||
echo "2. 检查 Docker Compose..."
|
||||
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||||
echo "❌ 错误: 未找到 Docker Compose"
|
||||
echo "请先安装 Docker Compose: https://docs.docker.com/compose/install/"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Docker Compose 检查完成"
|
||||
echo ""
|
||||
|
||||
# 3. 检查主机 MySQL
|
||||
echo "3. 检查主机 MySQL..."
|
||||
if command -v mysql &> /dev/null; then
|
||||
echo "请输入 MySQL 连接信息 (按 Enter 使用默认值):"
|
||||
read -p " 主机 [127.0.0.1]: " MYSQL_HOST
|
||||
MYSQL_HOST=${MYSQL_HOST:-127.0.0.1}
|
||||
|
||||
read -p " 端口 [3306]: " MYSQL_PORT
|
||||
MYSQL_PORT=${MYSQL_PORT:-3306}
|
||||
|
||||
read -p " 用户名 [root]: " MYSQL_USER
|
||||
MYSQL_USER=${MYSQL_USER:-root}
|
||||
|
||||
read -s -p " 密码 [123456]: " MYSQL_PASSWORD
|
||||
echo
|
||||
MYSQL_PASSWORD=${MYSQL_PASSWORD:-123456}
|
||||
|
||||
read -p " 数据库名 [nacos_config]: " MYSQL_DB
|
||||
MYSQL_DB=${MYSQL_DB:-nacos_config}
|
||||
|
||||
echo ""
|
||||
echo "测试 MySQL 连接..."
|
||||
if mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "USE $MYSQL_DB;" 2>/dev/null; then
|
||||
echo "✓ MySQL 连接成功"
|
||||
else
|
||||
echo "⚠️ 警告: MySQL 连接失败"
|
||||
echo ""
|
||||
echo "请确保:"
|
||||
echo " 1. MySQL 服务正在运行"
|
||||
echo " 2. 数据库 '$MYSQL_DB' 已创建"
|
||||
echo " 3. 用户 '$MYSQL_USER' 有权限访问该数据库"
|
||||
echo ""
|
||||
read -p "是否继续启动? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "⚠️ 警告: 未找到 mysql 客户端,无法验证数据库连接"
|
||||
echo "将使用默认配置: root:123456@localhost:3306/nacos_config"
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASSWORD=123456
|
||||
MYSQL_DB=nacos_config
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. 创建数据目录
|
||||
echo "4. 创建数据目录..."
|
||||
mkdir -p "$DATA_DIR"/nacos/{data,logs}
|
||||
echo "✓ 数据目录创建完成"
|
||||
echo ""
|
||||
|
||||
# 5. 更新 docker-compose.yml 配置
|
||||
if [ -n "$MYSQL_USER" ] && [ "$MYSQL_USER" != "root" ]; then
|
||||
echo "5. 更新 Docker Compose 配置..."
|
||||
|
||||
# 创建临时 .env 文件
|
||||
cat > "$SCRIPT_DIR/.env" <<EOF
|
||||
MYSQL_SERVICE_USER=$MYSQL_USER
|
||||
MYSQL_SERVICE_PASSWORD=$MYSQL_PASSWORD
|
||||
MYSQL_SERVICE_DB_NAME=$MYSQL_DB
|
||||
EOF
|
||||
|
||||
echo "✓ 配置更新完成"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 6. 拉取镜像
|
||||
echo "6. 拉取 Nacos 镜像..."
|
||||
cd "$SCRIPT_DIR"
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
docker-compose pull
|
||||
else
|
||||
docker compose pull
|
||||
fi
|
||||
echo "✓ 镜像拉取完成"
|
||||
echo ""
|
||||
|
||||
# 7. 启动服务
|
||||
echo "7. 启动服务..."
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
docker-compose up -d
|
||||
else
|
||||
docker compose up -d
|
||||
fi
|
||||
echo "✓ 服务启动完成"
|
||||
echo ""
|
||||
|
||||
# 8. 等待 Nacos 启动
|
||||
echo "8. 等待 Nacos 启动..."
|
||||
echo " 这可能需要 30-60 秒..."
|
||||
|
||||
MAX_WAIT=60
|
||||
WAITED=0
|
||||
while [ $WAITED -lt $MAX_WAIT ]; do
|
||||
if curl -s http://localhost:8848/nacos/ > /dev/null 2>&1; then
|
||||
echo "✓ Nacos 启动成功"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
WAITED=$((WAITED + 2))
|
||||
echo -n "."
|
||||
done
|
||||
echo ""
|
||||
|
||||
if [ $WAITED -ge $MAX_WAIT ]; then
|
||||
echo "⚠️ 警告: Nacos 启动超时"
|
||||
echo ""
|
||||
echo "查看日志:"
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
docker-compose logs nacos
|
||||
else
|
||||
docker compose logs nacos
|
||||
fi
|
||||
echo ""
|
||||
echo "请检查 MySQL 连接配置是否正确"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 9. 显示服务信息
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ urban-lifeline 开发环境启动完成!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "服务访问地址:"
|
||||
echo " - Nacos 控制台: http://localhost:8848/nacos/"
|
||||
echo " - Nacos 默认账号: nacos / nacos"
|
||||
echo ""
|
||||
echo "数据库连接信息:"
|
||||
echo " - 数据库: ${MYSQL_DB:-nacos_config}"
|
||||
echo " - 用户: ${MYSQL_USER:-root}"
|
||||
echo ""
|
||||
echo "数据持久化目录:"
|
||||
echo " - $DATA_DIR/nacos/data"
|
||||
echo " - $DATA_DIR/nacos/logs"
|
||||
echo ""
|
||||
echo "常用命令:"
|
||||
echo " 查看运行状态:"
|
||||
echo " docker ps"
|
||||
echo ""
|
||||
echo " 查看 Nacos 日志:"
|
||||
echo " docker logs -f urban-lifeline-nacos"
|
||||
echo ""
|
||||
echo " 停止服务:"
|
||||
echo " cd $SCRIPT_DIR && docker-compose down"
|
||||
echo ""
|
||||
echo " 重启服务:"
|
||||
echo " cd $SCRIPT_DIR && docker-compose restart"
|
||||
echo ""
|
||||
echo " 完全清理(删除容器和网络):"
|
||||
echo " cd $SCRIPT_DIR && docker-compose down -v"
|
||||
echo ""
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# urban-lifeline Docker Compose 停止脚本
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "=========================================="
|
||||
echo "停止 urban-lifeline 开发环境"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
docker-compose down
|
||||
else
|
||||
docker compose down
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ 服务已停止"
|
||||
echo ""
|
||||
echo "数据已保留在: ../../.data/docker/nacos/"
|
||||
echo ""
|
||||
echo "如需重新启动,请运行: ./start.sh"
|
||||
echo "如需完全清理,请运行: docker-compose down -v"
|
||||
echo ""
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
DROP DATABASE IF EXISTS `nacos_config`;
|
||||
CREATE DATABASE IF NOT EXISTS `nacos_config` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
USE `nacos_config`;
|
||||
|
||||
/******************************************/
|
||||
/* 表名称 = config_info */
|
||||
/******************************************/
|
||||
CREATE TABLE `config_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
|
||||
`group_id` VARCHAR(128) DEFAULT NULL COMMENT 'group_id',
|
||||
`content` longtext NOT NULL COMMENT 'content',
|
||||
`md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',
|
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`src_user` text COMMENT 'source user',
|
||||
`src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',
|
||||
`app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',
|
||||
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',
|
||||
`c_desc` VARCHAR(256) DEFAULT NULL COMMENT 'configuration description',
|
||||
`c_use` VARCHAR(64) DEFAULT NULL COMMENT 'configuration usage',
|
||||
`effect` VARCHAR(64) DEFAULT NULL COMMENT '配置生效的描述',
|
||||
`type` VARCHAR(64) DEFAULT NULL COMMENT '配置的类型',
|
||||
`c_schema` text COMMENT '配置的模式',
|
||||
`encrypted_data_key` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '密钥',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
|
||||
|
||||
/******************************************/
|
||||
/* 表名称 = config_info since 2.5.0 */
|
||||
/******************************************/
|
||||
CREATE TABLE `config_info_gray` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
|
||||
`group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',
|
||||
`content` longtext NOT NULL COMMENT 'content',
|
||||
`md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',
|
||||
`src_user` text COMMENT 'src_user',
|
||||
`src_ip` VARCHAR(100) DEFAULT NULL COMMENT 'src_ip',
|
||||
`gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',
|
||||
`gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',
|
||||
`app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',
|
||||
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',
|
||||
`gray_name` VARCHAR(128) NOT NULL COMMENT 'gray_name',
|
||||
`gray_rule` text NOT NULL COMMENT 'gray_rule',
|
||||
`encrypted_data_key` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),
|
||||
KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),
|
||||
KEY `idx_gmt_modified` (`gmt_modified`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray';
|
||||
|
||||
/******************************************/
|
||||
/* 表名称 = config_tags_relation */
|
||||
/******************************************/
|
||||
CREATE TABLE `config_tags_relation` (
|
||||
`id` bigint(20) NOT NULL COMMENT 'id',
|
||||
`tag_name` VARCHAR(128) NOT NULL COMMENT 'tag_name',
|
||||
`tag_type` VARCHAR(64) DEFAULT NULL COMMENT 'tag_type',
|
||||
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
|
||||
`group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',
|
||||
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',
|
||||
`nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
|
||||
PRIMARY KEY (`nid`),
|
||||
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
|
||||
|
||||
/******************************************/
|
||||
/* 表名称 = group_capacity */
|
||||
/******************************************/
|
||||
CREATE TABLE `group_capacity` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`group_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
|
||||
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
|
||||
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
|
||||
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
||||
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
|
||||
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
|
||||
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
|
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_group_id` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
|
||||
|
||||
/******************************************/
|
||||
/* 表名称 = his_config_info */
|
||||
/******************************************/
|
||||
CREATE TABLE `his_config_info` (
|
||||
`id` bigint(20) unsigned NOT NULL COMMENT 'id',
|
||||
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
|
||||
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
|
||||
`group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',
|
||||
`app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',
|
||||
`content` longtext NOT NULL COMMENT 'content',
|
||||
`md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',
|
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`src_user` text COMMENT 'source user',
|
||||
`src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',
|
||||
`op_type` char(10) DEFAULT NULL COMMENT 'operation type',
|
||||
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',
|
||||
`encrypted_data_key` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '密钥',
|
||||
`publish_type` VARCHAR(50) DEFAULT 'formal' COMMENT 'publish type gray or formal',
|
||||
`gray_name` VARCHAR(50) DEFAULT NULL COMMENT 'gray name',
|
||||
`ext_info` longtext DEFAULT NULL COMMENT 'ext info',
|
||||
PRIMARY KEY (`nid`),
|
||||
KEY `idx_gmt_create` (`gmt_create`),
|
||||
KEY `idx_gmt_modified` (`gmt_modified`),
|
||||
KEY `idx_did` (`data_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
|
||||
|
||||
|
||||
/******************************************/
|
||||
/* 表名称 = tenant_capacity */
|
||||
/******************************************/
|
||||
CREATE TABLE `tenant_capacity` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`tenant_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
|
||||
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
|
||||
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
|
||||
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
||||
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
|
||||
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
|
||||
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
|
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
|
||||
|
||||
|
||||
CREATE TABLE `tenant_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`kp` VARCHAR(128) NOT NULL COMMENT 'kp',
|
||||
`tenant_id` VARCHAR(128) default '' COMMENT 'tenant_id',
|
||||
`tenant_name` VARCHAR(128) default '' COMMENT 'tenant_name',
|
||||
`tenant_desc` VARCHAR(256) DEFAULT NULL COMMENT 'tenant_desc',
|
||||
`create_source` VARCHAR(32) DEFAULT NULL COMMENT 'create_source',
|
||||
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
|
||||
|
||||
CREATE TABLE `users` (
|
||||
`username` VARCHAR(50) NOT NULL PRIMARY KEY COMMENT 'username',
|
||||
`password` VARCHAR(500) NOT NULL COMMENT 'password',
|
||||
`enabled` BOOLEAN NOT NULL COMMENT 'enabled'
|
||||
);
|
||||
|
||||
CREATE TABLE `roles` (
|
||||
`username` VARCHAR(50) NOT NULL COMMENT 'username',
|
||||
`role` VARCHAR(50) NOT NULL COMMENT 'role',
|
||||
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
|
||||
);
|
||||
|
||||
CREATE TABLE `permissions` (
|
||||
`role` VARCHAR(50) NOT NULL COMMENT 'role',
|
||||
`resource` VARCHAR(128) NOT NULL COMMENT 'resource',
|
||||
`action` VARCHAR(8) NOT NULL COMMENT 'action',
|
||||
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
|
||||
);
|
||||
|
||||
28
urbanLifelineServ/.gitignore
vendored
28
urbanLifelineServ/.gitignore
vendored
@@ -1,28 +0,0 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
*/logs
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
.data
|
||||
.idea
|
||||
*/target
|
||||
*/*/target
|
||||
# example/*
|
||||
@@ -1 +0,0 @@
|
||||
1028
|
||||
@@ -1 +0,0 @@
|
||||
1003
|
||||
@@ -1 +0,0 @@
|
||||
1053
|
||||
88
urbanLifelineServ/.vscode/README-snippets.md
vendored
88
urbanLifelineServ/.vscode/README-snippets.md
vendored
@@ -1,88 +0,0 @@
|
||||
# MyBatis XML 代码片段使用说明
|
||||
|
||||
## 安装位置
|
||||
|
||||
代码片段文件已放置在 `.vscode/mybatis-xml.code-snippets`,VS Code 和 Cursor 会自动识别。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 完整 Mapper XML 模板
|
||||
|
||||
在 XML 文件中输入 `mybatis-mapper`,然后按 `Tab` 键,会自动生成完整的 MyBatis Mapper XML 模板。
|
||||
|
||||
**占位符说明:**
|
||||
- `${1}` - Mapper 命名空间(完整包路径)
|
||||
- `${2}` - 模块名称(如:user, role, dept)
|
||||
- `${3}` - 实体类名称(如:TbSysUser)
|
||||
- `${4}` - DTO 完整类路径
|
||||
- `${5}` - 主键数据库字段名(如:user_id)
|
||||
- `${6}` - 主键 Java 属性名(如:userId)
|
||||
- `${7}` - 业务字段数据库字段名
|
||||
- `${8}` - 业务字段 Java 属性名
|
||||
- `${9}` - 实体中文名称(如:用户)
|
||||
- `${10}` - 数据库表名(如:tb_sys_user)
|
||||
|
||||
### 2. 单个 SQL 片段
|
||||
|
||||
#### ResultMap
|
||||
输入 `mybatis-resultmap` 生成结果映射
|
||||
|
||||
#### Insert
|
||||
输入 `mybatis-insert` 生成插入语句
|
||||
|
||||
#### Update
|
||||
输入 `mybatis-update` 生成更新语句
|
||||
|
||||
#### Delete
|
||||
输入 `mybatis-delete` 生成删除语句(逻辑删除)
|
||||
|
||||
#### Select ById
|
||||
输入 `mybatis-select-id` 生成根据ID查询
|
||||
|
||||
#### Select ByFilter
|
||||
输入 `mybatis-select-filter` 生成条件查询
|
||||
|
||||
#### Select Page
|
||||
输入 `mybatis-select-page` 生成分页查询
|
||||
|
||||
#### Select Count
|
||||
输入 `mybatis-select-count` 生成计数查询
|
||||
|
||||
#### Base Column List
|
||||
输入 `mybatis-columns` 生成基础列定义
|
||||
|
||||
## 示例
|
||||
|
||||
### 创建完整的 Mapper XML
|
||||
|
||||
1. 新建文件:`TbSysRoleMapper.xml`
|
||||
2. 输入 `mybatis-mapper` 并按 `Tab`
|
||||
3. 依次填写占位符:
|
||||
- 命名空间:`org.xyzh.system.mapper.role.TbSysRoleMapper`
|
||||
- 模块名:`role`
|
||||
- 实体名:`TbSysRole`
|
||||
- DTO路径:`org.xyzh.common.dto.sys.TbSysRoleDTO`
|
||||
- 主键字段:`role_id`
|
||||
- 主键属性:`roleId`
|
||||
- 业务字段:`role_name`
|
||||
- 业务属性:`roleName`
|
||||
- 实体中文名:`角色`
|
||||
- 表名:`tb_sys_role`
|
||||
|
||||
### 快速添加单个 SQL
|
||||
|
||||
在已有的 Mapper XML 中,输入对应的前缀(如 `mybatis-insert`),按 `Tab` 即可快速插入对应的 SQL 片段。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有模板都包含了 BaseDTO 的通用字段
|
||||
2. 删除操作使用逻辑删除(设置 deleted = true)
|
||||
3. 查询时自动过滤已删除的记录(deleted = false)
|
||||
4. 分页使用 LIMIT 和 OFFSET(PostgreSQL/MySQL 兼容)
|
||||
5. 时间字段使用 TIMESTAMP 类型
|
||||
6. 字符串字段使用 VARCHAR 类型
|
||||
|
||||
## 自定义
|
||||
|
||||
如需修改模板,编辑 `.vscode/mybatis-xml.code-snippets` 文件即可。
|
||||
|
||||
215
urbanLifelineServ/.vscode/launch.json
vendored
215
urbanLifelineServ/.vscode/launch.json
vendored
@@ -1,215 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "AesEncryptUtil",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.common.utils.crypto.AesEncryptUtil",
|
||||
"projectName": "common-utils"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Gateway (8180)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.gateway.GatewayApplication",
|
||||
"projectName": "gateway",
|
||||
"cwd": "${workspaceFolder}/gateway",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8180"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Auth (8181)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.auth.AuthApp",
|
||||
"projectName": "auth",
|
||||
"cwd": "${workspaceFolder}/auth",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8181"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "System (8182)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.system.SystemApp",
|
||||
"projectName": "system",
|
||||
"cwd": "${workspaceFolder}/system",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8182"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Log (8183)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.log.LogApp",
|
||||
"projectName": "log",
|
||||
"cwd": "${workspaceFolder}/log",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8183"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "File (8184)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.file.FileApp",
|
||||
"projectName": "file",
|
||||
"cwd": "${workspaceFolder}/file",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8184"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Message (8185)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.message.MessageApp",
|
||||
"projectName": "message",
|
||||
"cwd": "${workspaceFolder}/message",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8185"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Bidding (8186)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.bidding.BiddingApp",
|
||||
"projectName": "bidding",
|
||||
"cwd": "${workspaceFolder}/bidding",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8186"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Platform (8187)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.platform.PlatformApp",
|
||||
"projectName": "platform",
|
||||
"cwd": "${workspaceFolder}/platform",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8187"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Workcase (8188)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.workcase.WorkcaseApp",
|
||||
"projectName": "workcase",
|
||||
"cwd": "${workspaceFolder}/workcase",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8188"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Crontab (8189)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.crontab.CrontabApp",
|
||||
"projectName": "crontab",
|
||||
"cwd": "${workspaceFolder}/crontab",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8189"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "AI (8190)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.xyzh.ai.AiApp",
|
||||
"projectName": "ai",
|
||||
"cwd": "${workspaceFolder}/ai",
|
||||
"args": [
|
||||
"--spring.profiles.active=dev"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-Dserver.port=8190"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Core Services (Gateway + Auth + System)",
|
||||
"configurations": [
|
||||
"Gateway (8180)",
|
||||
"Auth (8181)",
|
||||
"System (8182)"
|
||||
],
|
||||
"stopAll": true,
|
||||
"presentation": {
|
||||
"hidden": false,
|
||||
"group": "服务组合",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "All Services",
|
||||
"configurations": [
|
||||
"Gateway (8180)",
|
||||
"Auth (8181)",
|
||||
"System (8182)",
|
||||
// "Log (8183)",
|
||||
"File (8184)",
|
||||
// "Message (8185)",
|
||||
// "Bidding (8186)",
|
||||
// "Platform (8187)",
|
||||
"Workcase (8188)",
|
||||
// "Crontab (8189)",
|
||||
"AI (8190)"
|
||||
],
|
||||
"stopAll": true,
|
||||
"presentation": {
|
||||
"hidden": false,
|
||||
"group": "服务组合",
|
||||
"order": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
313
urbanLifelineServ/.vscode/mybatis-xml.code-snippets
vendored
313
urbanLifelineServ/.vscode/mybatis-xml.code-snippets
vendored
@@ -1,313 +0,0 @@
|
||||
{
|
||||
"MyBatis Mapper XML Template": {
|
||||
"prefix": "mybatis-mapper",
|
||||
"body": [
|
||||
"<?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=\"${1:org.xyzh.system.mapper.${2:module}.${3:EntityName}Mapper}\">",
|
||||
"",
|
||||
" <!-- 结果映射 -->",
|
||||
" <resultMap id=\"BaseResultMap\" type=\"${4:org.xyzh.common.dto.sys.${3:EntityName}DTO}\">",
|
||||
" <!-- BaseDTO 字段 -->",
|
||||
" <result column=\"optsn\" property=\"optsn\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"creator\" property=\"creator\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"updater\" property=\"updater\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"dept_path\" property=\"deptPath\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"remark\" property=\"remark\" 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\">",
|
||||
" ${5:entity_id}, ${7:field_name},",
|
||||
" optsn, creator, updater, dept_path, remark, create_time, update_time, delete_time, deleted",
|
||||
" </sql>",
|
||||
"",
|
||||
" <!-- 插入${9:实体名称} -->",
|
||||
" <insert id=\"insert${3:EntityName}\" parameterType=\"${4:org.xyzh.common.dto.sys.${3:EntityName}DTO}\">",
|
||||
" INSERT INTO ${10:tb_entity_name} (",
|
||||
" ${5:entity_id}, ${7:field_name},",
|
||||
" optsn, creator, updater, dept_path, remark, create_time, update_time, deleted",
|
||||
" ) VALUES (",
|
||||
" #{${6:entityId}}, #{${8:fieldName}},",
|
||||
" #{optsn}, #{creator}, #{updater}, #{deptPath}, #{remark}, #{createTime}, #{updateTime}, #{deleted}",
|
||||
" )",
|
||||
" </insert>",
|
||||
"",
|
||||
" <!-- 更新${9:实体名称} -->",
|
||||
" <update id=\"update${3:EntityName}\" parameterType=\"${4:org.xyzh.common.dto.sys.${3:EntityName}DTO}\">",
|
||||
" UPDATE ${10:tb_entity_name}",
|
||||
" <set>",
|
||||
" <if test=\"${8:fieldName} != null and ${8:fieldName} != ''\">",
|
||||
" ${7:field_name} = #{${8:fieldName}},",
|
||||
" </if>",
|
||||
" <if test=\"updater != null and updater != ''\">",
|
||||
" updater = #{updater},",
|
||||
" </if>",
|
||||
" <if test=\"deptPath != null and deptPath != ''\">",
|
||||
" dept_path = #{deptPath},",
|
||||
" </if>",
|
||||
" <if test=\"remark != null\">",
|
||||
" remark = #{remark},",
|
||||
" </if>",
|
||||
" <if test=\"updateTime != null\">",
|
||||
" update_time = #{updateTime},",
|
||||
" </if>",
|
||||
" </set>",
|
||||
" WHERE ${5:entity_id} = #{${6:entityId}}",
|
||||
" <if test=\"deleted != null\">",
|
||||
" AND deleted = #{deleted}",
|
||||
" </if>",
|
||||
" </update>",
|
||||
"",
|
||||
" <!-- 删除${9:实体名称}(逻辑删除) -->",
|
||||
" <update id=\"delete${3:EntityName}\" parameterType=\"${4:org.xyzh.common.dto.sys.${3:EntityName}DTO}\">",
|
||||
" UPDATE ${10:tb_entity_name}",
|
||||
" SET deleted = true,",
|
||||
" delete_time = NOW()",
|
||||
" WHERE ${5:entity_id} = #{${6:entityId}}",
|
||||
" </update>",
|
||||
"",
|
||||
" <!-- 根据ID查询${9:实体名称} -->",
|
||||
" <select id=\"get${3:EntityName}ById\" resultMap=\"BaseResultMap\" parameterType=\"java.lang.String\">",
|
||||
" SELECT",
|
||||
" <include refid=\"Base_Column_List\"/>",
|
||||
" FROM ${10:tb_entity_name}",
|
||||
" WHERE ${5:entity_id} = #{${6:entityId}}",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </select>",
|
||||
"",
|
||||
" <!-- 根据条件查询${9:实体名称}列表 -->",
|
||||
" <select id=\"get${3:EntityName}ByFilter\" resultMap=\"BaseResultMap\" parameterType=\"${4:org.xyzh.common.dto.sys.${3:EntityName}DTO}\">",
|
||||
" SELECT",
|
||||
" <include refid=\"Base_Column_List\"/>",
|
||||
" FROM ${10:tb_entity_name}",
|
||||
" <where>",
|
||||
" <if test=\"filter.${6:entityId} != null and filter.${6:entityId} != ''\">",
|
||||
" AND ${5:entity_id} = #{filter.${6:entityId}}",
|
||||
" </if>",
|
||||
" <if test=\"filter.${8:fieldName} != null and filter.${8:fieldName} != ''\">",
|
||||
" AND ${7:field_name} LIKE CONCAT('%', #{filter.${8:fieldName}}, '%')",
|
||||
" </if>",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </where>",
|
||||
" ORDER BY create_time DESC",
|
||||
" </select>",
|
||||
"",
|
||||
" <!-- 根据条件查询${9:实体名称}分页列表 -->",
|
||||
" <select id=\"get${3:EntityName}PageByFilter\" resultMap=\"BaseResultMap\">",
|
||||
" SELECT",
|
||||
" <include refid=\"Base_Column_List\"/>",
|
||||
" FROM ${10:tb_entity_name}",
|
||||
" <where>",
|
||||
" <if test=\"filter.${6:entityId} != null and filter.${6:entityId} != ''\">",
|
||||
" AND ${5:entity_id} = #{filter.${6:entityId}}",
|
||||
" </if>",
|
||||
" <if test=\"filter.${8:fieldName} != null and filter.${8:fieldName} != ''\">",
|
||||
" AND ${7:field_name} LIKE CONCAT('%', #{filter.${8:fieldName}}, '%')",
|
||||
" </if>",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </where>",
|
||||
" ORDER BY create_time DESC",
|
||||
" LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}",
|
||||
" </select>",
|
||||
"",
|
||||
" <!-- 根据条件查询${9:实体名称}数量 -->",
|
||||
" <select id=\"get${3:EntityName}CountByFilter\" resultType=\"java.lang.Integer\" parameterType=\"${4:org.xyzh.common.dto.sys.${3:EntityName}DTO}\">",
|
||||
" SELECT COUNT(1)",
|
||||
" FROM ${10:tb_entity_name}",
|
||||
" <where>",
|
||||
" <if test=\"filter.${6:entityId} != null and filter.${6:entityId} != ''\">",
|
||||
" AND ${5:entity_id} = #{filter.${6:entityId}}",
|
||||
" </if>",
|
||||
" <if test=\"filter.${8:fieldName} != null and filter.${8:fieldName} != ''\">",
|
||||
" AND ${7:field_name} LIKE CONCAT('%', #{filter.${8:fieldName}}, '%')",
|
||||
" </if>",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </where>",
|
||||
" </select>",
|
||||
"",
|
||||
"</mapper>"
|
||||
],
|
||||
"description": "MyBatis Mapper XML 完整模板"
|
||||
},
|
||||
"MyBatis ResultMap": {
|
||||
"prefix": "mybatis-resultmap",
|
||||
"body": [
|
||||
" <!-- 结果映射 -->",
|
||||
" <resultMap id=\"BaseResultMap\" type=\"${1:org.xyzh.common.dto.sys.${2:EntityName}DTO}\">",
|
||||
" <!-- 主键 -->",
|
||||
" <id column=\"${3:entity_id}\" property=\"${4:entityId}\" jdbcType=\"VARCHAR\"/>",
|
||||
" ",
|
||||
" <!-- 业务字段 -->",
|
||||
" <result column=\"${5:field_name}\" property=\"${6:fieldName}\" jdbcType=\"VARCHAR\"/>",
|
||||
" ",
|
||||
" <!-- BaseDTO 字段 -->",
|
||||
" <result column=\"optsn\" property=\"optsn\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"creator\" property=\"creator\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"updater\" property=\"updater\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"dept_path\" property=\"deptPath\" jdbcType=\"VARCHAR\"/>",
|
||||
" <result column=\"remark\" property=\"remark\" 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>"
|
||||
],
|
||||
"description": "MyBatis ResultMap 映射"
|
||||
},
|
||||
"MyBatis Insert": {
|
||||
"prefix": "mybatis-insert",
|
||||
"body": [
|
||||
" <!-- 插入${1:实体名称} -->",
|
||||
" <insert id=\"insert${2:EntityName}\" parameterType=\"${3:org.xyzh.common.dto.sys.${2:EntityName}DTO}\">",
|
||||
" INSERT INTO ${4:tb_entity_name} (",
|
||||
" ${5:entity_id}, ${6:field_name},",
|
||||
" optsn, creator, updater, dept_path, remark, create_time, update_time, deleted",
|
||||
" ) VALUES (",
|
||||
" #{${7:entityId}}, #{${8:fieldName}},",
|
||||
" #{optsn}, #{creator}, #{updater}, #{deptPath}, #{remark}, #{createTime}, #{updateTime}, #{deleted}",
|
||||
" )",
|
||||
" </insert>"
|
||||
],
|
||||
"description": "MyBatis Insert 语句"
|
||||
},
|
||||
"MyBatis Update": {
|
||||
"prefix": "mybatis-update",
|
||||
"body": [
|
||||
" <!-- 更新${1:实体名称} -->",
|
||||
" <update id=\"update${2:EntityName}\" parameterType=\"${3:org.xyzh.common.dto.sys.${2:EntityName}DTO}\">",
|
||||
" UPDATE ${4:tb_entity_name}",
|
||||
" <set>",
|
||||
" <if test=\"${5:fieldName} != null and ${5:fieldName} != ''\">",
|
||||
" ${6:field_name} = #{${5:fieldName}},",
|
||||
" </if>",
|
||||
" <if test=\"updater != null and updater != ''\">",
|
||||
" updater = #{updater},",
|
||||
" </if>",
|
||||
" <if test=\"deptPath != null and deptPath != ''\">",
|
||||
" dept_path = #{deptPath},",
|
||||
" </if>",
|
||||
" <if test=\"remark != null\">",
|
||||
" remark = #{remark},",
|
||||
" </if>",
|
||||
" <if test=\"updateTime != null\">",
|
||||
" update_time = #{updateTime},",
|
||||
" </if>",
|
||||
" </set>",
|
||||
" WHERE ${7:entity_id} = #{${8:entityId}}",
|
||||
" <if test=\"deleted != null\">",
|
||||
" AND deleted = #{deleted}",
|
||||
" </if>",
|
||||
" </update>"
|
||||
],
|
||||
"description": "MyBatis Update 语句"
|
||||
},
|
||||
"MyBatis Delete": {
|
||||
"prefix": "mybatis-delete",
|
||||
"body": [
|
||||
" <!-- 删除${1:实体名称}(逻辑删除) -->",
|
||||
" <update id=\"delete${2:EntityName}\" parameterType=\"${3:org.xyzh.common.dto.sys.${2:EntityName}DTO}\">",
|
||||
" UPDATE ${4:tb_entity_name}",
|
||||
" SET deleted = true,",
|
||||
" delete_time = NOW()",
|
||||
" WHERE ${5:entity_id} = #{${6:entityId}}",
|
||||
" </update>"
|
||||
],
|
||||
"description": "MyBatis Delete 语句(逻辑删除)"
|
||||
},
|
||||
"MyBatis Select ById": {
|
||||
"prefix": "mybatis-select-id",
|
||||
"body": [
|
||||
" <!-- 根据ID查询${1:实体名称} -->",
|
||||
" <select id=\"get${2:EntityName}ById\" resultMap=\"BaseResultMap\" parameterType=\"java.lang.String\">",
|
||||
" SELECT",
|
||||
" <include refid=\"Base_Column_List\"/>",
|
||||
" FROM ${3:tb_entity_name}",
|
||||
" WHERE ${4:entity_id} = #{${5:entityId}}",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </select>"
|
||||
],
|
||||
"description": "MyBatis Select ById 查询"
|
||||
},
|
||||
"MyBatis Select ByFilter": {
|
||||
"prefix": "mybatis-select-filter",
|
||||
"body": [
|
||||
" <!-- 根据条件查询${1:实体名称}列表 -->",
|
||||
" <select id=\"get${2:EntityName}ByFilter\" resultMap=\"BaseResultMap\" parameterType=\"${3:org.xyzh.common.dto.sys.${2:EntityName}DTO}\">",
|
||||
" SELECT",
|
||||
" <include refid=\"Base_Column_List\"/>",
|
||||
" FROM ${4:tb_entity_name}",
|
||||
" <where>",
|
||||
" <if test=\"filter.${5:entityId} != null and filter.${5:entityId} != ''\">",
|
||||
" AND ${6:entity_id} = #{filter.${5:entityId}}",
|
||||
" </if>",
|
||||
" <if test=\"filter.${7:fieldName} != null and filter.${7:fieldName} != ''\">",
|
||||
" AND ${8:field_name} LIKE CONCAT('%', #{filter.${7:fieldName}}, '%')",
|
||||
" </if>",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </where>",
|
||||
" ORDER BY create_time DESC",
|
||||
" </select>"
|
||||
],
|
||||
"description": "MyBatis Select ByFilter 查询"
|
||||
},
|
||||
"MyBatis Select Page": {
|
||||
"prefix": "mybatis-select-page",
|
||||
"body": [
|
||||
" <!-- 根据条件查询${1:实体名称}分页列表 -->",
|
||||
" <select id=\"get${2:EntityName}PageByFilter\" resultMap=\"BaseResultMap\">",
|
||||
" SELECT",
|
||||
" <include refid=\"Base_Column_List\"/>",
|
||||
" FROM ${3:tb_entity_name}",
|
||||
" <where>",
|
||||
" <if test=\"filter.${4:entityId} != null and filter.${4:entityId} != ''\">",
|
||||
" AND ${5:entity_id} = #{filter.${4:entityId}}",
|
||||
" </if>",
|
||||
" <if test=\"filter.${6:fieldName} != null and filter.${6:fieldName} != ''\">",
|
||||
" AND ${7:field_name} LIKE CONCAT('%', #{filter.${6:fieldName}}, '%')",
|
||||
" </if>",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </where>",
|
||||
" ORDER BY create_time DESC",
|
||||
" LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}",
|
||||
" </select>"
|
||||
],
|
||||
"description": "MyBatis Select Page 分页查询"
|
||||
},
|
||||
"MyBatis Select Count": {
|
||||
"prefix": "mybatis-select-count",
|
||||
"body": [
|
||||
" <!-- 根据条件查询${1:实体名称}数量 -->",
|
||||
" <select id=\"get${2:EntityName}CountByFilter\" resultType=\"java.lang.Integer\" parameterType=\"${3:org.xyzh.common.dto.sys.${2:EntityName}DTO}\">",
|
||||
" SELECT COUNT(1)",
|
||||
" FROM ${4:tb_entity_name}",
|
||||
" <where>",
|
||||
" <if test=\"filter.${5:entityId} != null and filter.${5:entityId} != ''\">",
|
||||
" AND ${6:entity_id} = #{filter.${5:entityId}}",
|
||||
" </if>",
|
||||
" <if test=\"filter.${7:fieldName} != null and filter.${7:fieldName} != ''\">",
|
||||
" AND ${8:field_name} LIKE CONCAT('%', #{filter.${7:fieldName}}, '%')",
|
||||
" </if>",
|
||||
" AND (deleted IS NULL OR deleted = false)",
|
||||
" </where>",
|
||||
" </select>"
|
||||
],
|
||||
"description": "MyBatis Select Count 计数查询"
|
||||
},
|
||||
"MyBatis Base Column List": {
|
||||
"prefix": "mybatis-columns",
|
||||
"body": [
|
||||
" <!-- 基础列 -->",
|
||||
" <sql id=\"Base_Column_List\">",
|
||||
" ${1:entity_id}, ${2:field_name},",
|
||||
" optsn, creator, updater, dept_path, remark, create_time, update_time, delete_time, deleted",
|
||||
" </sql>"
|
||||
],
|
||||
"description": "MyBatis 基础列定义"
|
||||
}
|
||||
}
|
||||
|
||||
34
urbanLifelineServ/.vscode/settings.json
vendored
34
urbanLifelineServ/.vscode/settings.json
vendored
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"maven.view": "hierarchical",
|
||||
"tabSize": 4,
|
||||
"java.debug.settings.onBuildFailureProceed": true,
|
||||
"java.configuration.maven.userSettings": "",
|
||||
"java.import.maven.enabled": true,
|
||||
"java.project.referencedLibraries": [
|
||||
"**/*.jar"
|
||||
],
|
||||
"maven.terminal.useJavaHome": true,
|
||||
"java.configuration.runtimes": [],
|
||||
"Codegeex.RepoIndex": true,
|
||||
"terminal.integrated.defaultProfile.windows": "Command Prompt",
|
||||
"terminal.integrated.profiles.windows": {
|
||||
"PowerShell": {
|
||||
"source": "PowerShell",
|
||||
"args": ["-NoExit", "-Command", "chcp 65001"]
|
||||
},
|
||||
"Command Prompt": {
|
||||
"path": "cmd.exe",
|
||||
"args": ["/k", "chcp 65001"]
|
||||
},
|
||||
"Git Bash": {
|
||||
"path": "F:\\Environment\\Git\\bin\\bash.exe",
|
||||
"args": ["-i"],
|
||||
"env": {
|
||||
"LANG": "zh_CN.UTF-8",
|
||||
"LC_ALL": "zh_CN.UTF-8"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
trigger: manual
|
||||
---
|
||||
|
||||
1. 有BaseDTO基类,DTO\VO在api模块下面
|
||||
2. 用Dubbo注册和,引用服务
|
||||
3. 当个端Insert语句,对应sql表的必填项才生成(有默认值也是if生成),其他应该if条件生成
|
||||
@@ -1,247 +0,0 @@
|
||||
# ================================================
|
||||
# Urban Lifeline 微服务管理 Makefile
|
||||
# ================================================
|
||||
|
||||
# 项目根目录
|
||||
PROJECT_ROOT := $(shell pwd)
|
||||
|
||||
# Maven 命令
|
||||
MAVEN := mvn
|
||||
MAVEN_OPTS := -Dmaven.test.skip=true
|
||||
|
||||
# 服务列表
|
||||
SERVICES := gateway auth system log file message bidding platform workcase crontab agent
|
||||
|
||||
# 服务端口映射
|
||||
PORT_gateway := 8080
|
||||
PORT_auth := 8081
|
||||
PORT_system := 8082
|
||||
PORT_log := 8083
|
||||
PORT_file := 8084
|
||||
PORT_message := 8085
|
||||
PORT_bidding := 8086
|
||||
PORT_platform := 8087
|
||||
PORT_workcase := 8088
|
||||
PORT_crontab := 8089
|
||||
PORT_agent := 8090
|
||||
|
||||
# 服务主类映射
|
||||
MAINCLASS_gateway := org.xyzh.gateway.GatewayApplication
|
||||
MAINCLASS_auth := org.xyzh.auth.AuthApp
|
||||
MAINCLASS_system := org.xyzh.system.SystemApp
|
||||
MAINCLASS_log := org.xyzh.log.LogApp
|
||||
MAINCLASS_file := org.xyzh.file.FileApp
|
||||
MAINCLASS_message := org.xyzh.message.MessageApp
|
||||
MAINCLASS_bidding := org.xyzh.bidding.BiddingApp
|
||||
MAINCLASS_platform := org.xyzh.platform.PlatformApp
|
||||
MAINCLASS_workcase := org.xyzh.workcase.WorkcaseApp
|
||||
MAINCLASS_crontab := org.xyzh.crontab.CrontabApp
|
||||
MAINCLASS_agent := org.xyzh.agent.AgentApp
|
||||
|
||||
# PID 文件目录
|
||||
PID_DIR := $(PROJECT_ROOT)/.pids
|
||||
LOG_DIR := $(PROJECT_ROOT)/.logs
|
||||
|
||||
# 颜色定义
|
||||
COLOR_RESET := \033[0m
|
||||
COLOR_GREEN := \033[0;32m
|
||||
COLOR_YELLOW := \033[0;33m
|
||||
COLOR_BLUE := \033[0;34m
|
||||
COLOR_RED := \033[0;31m
|
||||
|
||||
# ================================================
|
||||
# 帮助信息
|
||||
# ================================================
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE) Urban Lifeline 微服务管理工具$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)构建命令:$(COLOR_RESET)"
|
||||
@echo " make build - 构建所有服务"
|
||||
@echo " make build-<service> - 构建指定服务 (如: make build-system)"
|
||||
@echo " make clean - 清理所有服务"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)启动命令:$(COLOR_RESET)"
|
||||
@echo " make start - 启动所有服务"
|
||||
@echo " make start-<service> - 启动指定服务 (如: make start-system)"
|
||||
@echo " make start-core - 启动核心服务 (gateway, auth, system)"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)停止命令:$(COLOR_RESET)"
|
||||
@echo " make stop - 停止所有服务"
|
||||
@echo " make stop-<service> - 停止指定服务 (如: make stop-system)"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)重启命令:$(COLOR_RESET)"
|
||||
@echo " make restart - 重启所有服务"
|
||||
@echo " make restart-<service> - 重启指定服务 (如: make restart-system)"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)状态查看:$(COLOR_RESET)"
|
||||
@echo " make status - 查看所有服务状态"
|
||||
@echo " make status-<service> - 查看指定服务状态"
|
||||
@echo " make logs-<service> - 查看指定服务日志"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)可用服务:$(COLOR_RESET)"
|
||||
@echo " $(SERVICES)"
|
||||
@echo ""
|
||||
|
||||
# ================================================
|
||||
# 初始化目录
|
||||
# ================================================
|
||||
.PHONY: init-dirs
|
||||
init-dirs:
|
||||
@mkdir -p $(PID_DIR)
|
||||
@mkdir -p $(LOG_DIR)
|
||||
|
||||
# ================================================
|
||||
# 构建相关
|
||||
# ================================================
|
||||
.PHONY: build
|
||||
build: init-dirs
|
||||
@echo "$(COLOR_YELLOW)开始构建所有服务...$(COLOR_RESET)"
|
||||
@$(MAVEN) clean install $(MAVEN_OPTS)
|
||||
@echo "$(COLOR_GREEN)✓ 所有服务构建完成$(COLOR_RESET)"
|
||||
|
||||
.PHONY: $(addprefix build-,$(SERVICES))
|
||||
$(addprefix build-,$(SERVICES)): build-%:
|
||||
@echo "$(COLOR_YELLOW)开始构建 $* 服务...$(COLOR_RESET)"
|
||||
@cd $(PROJECT_ROOT)/$* && $(MAVEN) clean install $(MAVEN_OPTS)
|
||||
@echo "$(COLOR_GREEN)✓ $* 服务构建完成$(COLOR_RESET)"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@echo "$(COLOR_YELLOW)开始清理所有服务...$(COLOR_RESET)"
|
||||
@$(MAVEN) clean
|
||||
@rm -rf $(PID_DIR)
|
||||
@rm -rf $(LOG_DIR)
|
||||
@echo "$(COLOR_GREEN)✓ 清理完成$(COLOR_RESET)"
|
||||
|
||||
# ================================================
|
||||
# 启动服务
|
||||
# ================================================
|
||||
.PHONY: start
|
||||
start: init-dirs $(addprefix start-,$(SERVICES))
|
||||
@echo "$(COLOR_GREEN)✓ 所有服务启动完成$(COLOR_RESET)"
|
||||
|
||||
.PHONY: start-core
|
||||
start-core: init-dirs start-gateway start-auth start-system
|
||||
@echo "$(COLOR_GREEN)✓ 核心服务启动完成$(COLOR_RESET)"
|
||||
|
||||
.PHONY: $(addprefix start-,$(SERVICES))
|
||||
$(addprefix start-,$(SERVICES)): start-%:
|
||||
@if [ -f "$(PID_DIR)/$*.pid" ] && kill -0 $$(cat $(PID_DIR)/$*.pid) 2>/dev/null; then \
|
||||
echo "$(COLOR_YELLOW)⚠ $* 服务已在运行中 (PID: $$(cat $(PID_DIR)/$*.pid))$(COLOR_RESET)"; \
|
||||
else \
|
||||
echo "$(COLOR_BLUE)启动 $* 服务...$(COLOR_RESET)"; \
|
||||
nohup $(MAVEN) -pl $* spring-boot:run > $(LOG_DIR)/$*.log 2>&1 & \
|
||||
echo $$! > $(PID_DIR)/$*.pid; \
|
||||
sleep 2; \
|
||||
if kill -0 $$(cat $(PID_DIR)/$*.pid) 2>/dev/null; then \
|
||||
echo "$(COLOR_GREEN)✓ $* 服务启动成功 (PID: $$(cat $(PID_DIR)/$*.pid), Port: $(PORT_$*))$(COLOR_RESET)"; \
|
||||
else \
|
||||
echo "$(COLOR_RED)✗ $* 服务启动失败$(COLOR_RESET)"; \
|
||||
rm -f $(PID_DIR)/$*.pid; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
# ================================================
|
||||
# 停止服务
|
||||
# ================================================
|
||||
.PHONY: stop
|
||||
stop: $(addprefix stop-,$(SERVICES))
|
||||
@echo "$(COLOR_GREEN)✓ 所有服务停止完成$(COLOR_RESET)"
|
||||
|
||||
.PHONY: $(addprefix stop-,$(SERVICES))
|
||||
$(addprefix stop-,$(SERVICES)): stop-%:
|
||||
@if [ -f "$(PID_DIR)/$*.pid" ]; then \
|
||||
PID=$$(cat $(PID_DIR)/$*.pid); \
|
||||
if kill -0 $$PID 2>/dev/null; then \
|
||||
echo "$(COLOR_YELLOW)停止 $* 服务 (PID: $$PID)...$(COLOR_RESET)"; \
|
||||
kill $$PID; \
|
||||
sleep 2; \
|
||||
if kill -0 $$PID 2>/dev/null; then \
|
||||
echo "$(COLOR_RED)强制停止 $* 服务...$(COLOR_RESET)"; \
|
||||
kill -9 $$PID; \
|
||||
fi; \
|
||||
rm -f $(PID_DIR)/$*.pid; \
|
||||
echo "$(COLOR_GREEN)✓ $* 服务已停止$(COLOR_RESET)"; \
|
||||
else \
|
||||
echo "$(COLOR_YELLOW)⚠ $* 服务未运行$(COLOR_RESET)"; \
|
||||
rm -f $(PID_DIR)/$*.pid; \
|
||||
fi; \
|
||||
else \
|
||||
echo "$(COLOR_YELLOW)⚠ $* 服务未运行$(COLOR_RESET)"; \
|
||||
fi
|
||||
|
||||
# ================================================
|
||||
# 重启服务
|
||||
# ================================================
|
||||
.PHONY: restart
|
||||
restart: stop start
|
||||
@echo "$(COLOR_GREEN)✓ 所有服务重启完成$(COLOR_RESET)"
|
||||
|
||||
.PHONY: $(addprefix restart-,$(SERVICES))
|
||||
$(addprefix restart-,$(SERVICES)): restart-%: stop-% start-%
|
||||
@echo "$(COLOR_GREEN)✓ $* 服务重启完成$(COLOR_RESET)"
|
||||
|
||||
# ================================================
|
||||
# 查看状态
|
||||
# ================================================
|
||||
.PHONY: status
|
||||
status:
|
||||
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE) 服务运行状态$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||
@for service in $(SERVICES); do \
|
||||
printf "%-15s" "$$service:"; \
|
||||
if [ -f "$(PID_DIR)/$$service.pid" ]; then \
|
||||
PID=$$(cat $(PID_DIR)/$$service.pid); \
|
||||
if kill -0 $$PID 2>/dev/null; then \
|
||||
echo "$(COLOR_GREEN)✓ Running (PID: $$PID)$(COLOR_RESET)"; \
|
||||
else \
|
||||
echo "$(COLOR_RED)✗ Stopped$(COLOR_RESET)"; \
|
||||
rm -f $(PID_DIR)/$$service.pid; \
|
||||
fi; \
|
||||
else \
|
||||
echo "$(COLOR_YELLOW)○ Not Started$(COLOR_RESET)"; \
|
||||
fi; \
|
||||
done
|
||||
@echo ""
|
||||
|
||||
.PHONY: $(addprefix status-,$(SERVICES))
|
||||
$(addprefix status-,$(SERVICES)): status-%:
|
||||
@if [ -f "$(PID_DIR)/$*.pid" ]; then \
|
||||
PID=$$(cat $(PID_DIR)/$*.pid); \
|
||||
if kill -0 $$PID 2>/dev/null; then \
|
||||
echo "$(COLOR_GREEN)✓ $* 服务运行中 (PID: $$PID, Port: $(PORT_$*))$(COLOR_RESET)"; \
|
||||
else \
|
||||
echo "$(COLOR_RED)✗ $* 服务已停止$(COLOR_RESET)"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "$(COLOR_YELLOW)○ $* 服务未启动$(COLOR_RESET)"; \
|
||||
fi
|
||||
|
||||
# ================================================
|
||||
# 查看日志
|
||||
# ================================================
|
||||
.PHONY: $(addprefix logs-,$(SERVICES))
|
||||
$(addprefix logs-,$(SERVICES)): logs-%:
|
||||
@if [ -f "$(LOG_DIR)/$*.log" ]; then \
|
||||
tail -f $(LOG_DIR)/$*.log; \
|
||||
else \
|
||||
echo "$(COLOR_YELLOW)⚠ $* 服务日志文件不存在$(COLOR_RESET)"; \
|
||||
fi
|
||||
|
||||
# ================================================
|
||||
# 快捷命令
|
||||
# ================================================
|
||||
.PHONY: dev
|
||||
dev: start-core
|
||||
@echo "$(COLOR_GREEN)✓ 开发环境启动完成 (gateway, auth, system)$(COLOR_RESET)"
|
||||
|
||||
.PHONY: prod
|
||||
prod: start
|
||||
@echo "$(COLOR_GREEN)✓ 生产环境启动完成$(COLOR_RESET)"
|
||||
|
||||
# 默认目标
|
||||
.DEFAULT_GOAL := help
|
||||
@@ -1,51 +0,0 @@
|
||||
# 智能体创建
|
||||
|
||||
说明: 本服务只对智能体进行对话的转发,不能对dify智能体进行修改
|
||||
1. 智能体创建,用户上传tb_agent需要的字段进行智能体创建
|
||||
|
||||
# 智能体更新
|
||||
1. 更新数据库和redis缓存
|
||||
2. 注意加锁,避免并发时,其他线程用了错误数据
|
||||
|
||||
# 智能体删除
|
||||
软删除本服务的智能体数据
|
||||
|
||||
# 智能体对话
|
||||
1. 校验智能体是否可以用
|
||||
2. 用户会先对1个智能体创建一个会话
|
||||
3. 用户先发起对话预处理请求,传入会话携带的对象数据到redis中,产生一个临时的消息id
|
||||
4. 真正发起对话数据请求时,会传入3个必须参数,1智能体id,2会话id,3本次消息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知识库
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<?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>urban-lifeline</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>ai</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.apis</groupId>
|
||||
<artifactId>api-ai</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.apis</groupId>
|
||||
<artifactId>api-system</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</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-jdbc</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>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Cloud Nacos 服务发现(Gateway 路由需要) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-logback-adapter-12</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>logback-adapter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</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>
|
||||
<!-- SpringDoc OpenAPI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>ai</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.xyzh.ai;
|
||||
|
||||
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDubbo // 启用 Dubbo 服务
|
||||
@ComponentScan(basePackages = {
|
||||
"org.xyzh.ai", // 当前ai模块
|
||||
"org.xyzh.common" // 公共模块
|
||||
})
|
||||
public class AiApp {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AiApp.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("======================== AI服务启动中 =========================");
|
||||
SpringApplication.run(AiApp.class, args);
|
||||
logger.info("======================== AI服务启动完成 =========================");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,44 +0,0 @@
|
||||
package org.xyzh.ai.client.callback;
|
||||
|
||||
/**
|
||||
* @description 流式响应回调接口
|
||||
* @filename StreamCallback.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
public interface StreamCallback {
|
||||
|
||||
/**
|
||||
* 接收到消息片段
|
||||
* @param message 消息内容
|
||||
*/
|
||||
void onMessage(String message);
|
||||
|
||||
/**
|
||||
* 消息结束(包含元数据)
|
||||
* @param metadata JSON格式的元数据
|
||||
*/
|
||||
void onMessageEnd(String metadata);
|
||||
|
||||
/**
|
||||
* 接收到Dify原始事件(用于转发完整事件数据)
|
||||
* @param eventType 事件类型(如workflow_started、node_started等)
|
||||
* @param eventData 完整的事件JSON数据
|
||||
*/
|
||||
default void onEvent(String eventType, String eventData) {
|
||||
// 默认实现:不处理
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式响应完成
|
||||
*/
|
||||
void onComplete();
|
||||
|
||||
/**
|
||||
* 发生错误
|
||||
* @param error 错误对象
|
||||
*/
|
||||
void onError(Throwable error);
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.xyzh.api.ai.dto.DifyFileInfo;
|
||||
|
||||
/**
|
||||
* @description 对话请求
|
||||
* @filename ChatRequest.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-15
|
||||
*/
|
||||
@Data
|
||||
public class ChatRequest {
|
||||
|
||||
/**
|
||||
* 输入变量(Dify API 必需字段)
|
||||
*/
|
||||
@JSONField(serializeFeatures = com.alibaba.fastjson2.JSONWriter.Feature.WriteMapNullValue)
|
||||
private Map<String, Object> inputs = new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
* 用户问题
|
||||
*/
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* 响应模式:streaming(流式)、blocking(阻塞)
|
||||
*/
|
||||
@JSONField(name = "response_mode")
|
||||
private String responseMode = "streaming";
|
||||
|
||||
/**
|
||||
* 对话ID(继续对话时传入)
|
||||
*/
|
||||
@JSONField(name = "conversation_id")
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 用户标识
|
||||
*/
|
||||
private String user;
|
||||
|
||||
/**
|
||||
* 上传的文件列表
|
||||
*/
|
||||
private List<DifyFileInfo> files;
|
||||
|
||||
/**
|
||||
* 自动生成标题
|
||||
*/
|
||||
@JSONField(name = "auto_generate_name")
|
||||
private Boolean autoGenerateName = true;
|
||||
|
||||
/**
|
||||
* 指定的数据集ID列表(知识库检索)
|
||||
*/
|
||||
@JSONField(name = "dataset_ids")
|
||||
private List<String> datasetIds;
|
||||
|
||||
/**
|
||||
* 温度参数(0.0-1.0)
|
||||
*/
|
||||
private Double temperature;
|
||||
|
||||
/**
|
||||
* 最大token数
|
||||
*/
|
||||
@JSONField(name = "max_tokens")
|
||||
private Integer maxTokens;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 对话响应(阻塞模式)
|
||||
* @filename ChatResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class ChatResponse {
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
@JSONField(name = "message_id")
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 对话ID
|
||||
*/
|
||||
@JSONField(name = "conversation_id")
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 模式
|
||||
*/
|
||||
private String mode;
|
||||
|
||||
/**
|
||||
* 回答内容
|
||||
*/
|
||||
private String answer;
|
||||
|
||||
/**
|
||||
* 元数据
|
||||
*/
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* Token使用情况
|
||||
*/
|
||||
private Usage usage;
|
||||
|
||||
/**
|
||||
* 检索信息
|
||||
*/
|
||||
@JSONField(name = "retrieval_info")
|
||||
private List<RetrievalInfo> retrievalInfo;
|
||||
|
||||
@Data
|
||||
public static class Usage {
|
||||
@JSONField(name = "prompt_tokens")
|
||||
private Integer promptTokens;
|
||||
|
||||
@JSONField(name = "prompt_unit_price")
|
||||
private String promptUnitPrice;
|
||||
|
||||
@JSONField(name = "prompt_price_unit")
|
||||
private String promptPriceUnit;
|
||||
|
||||
@JSONField(name = "prompt_price")
|
||||
private String promptPrice;
|
||||
|
||||
@JSONField(name = "completion_tokens")
|
||||
private Integer completionTokens;
|
||||
|
||||
@JSONField(name = "completion_unit_price")
|
||||
private String completionUnitPrice;
|
||||
|
||||
@JSONField(name = "completion_price_unit")
|
||||
private String completionPriceUnit;
|
||||
|
||||
@JSONField(name = "completion_price")
|
||||
private String completionPrice;
|
||||
|
||||
@JSONField(name = "total_tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
@JSONField(name = "total_price")
|
||||
private String totalPrice;
|
||||
|
||||
private String currency;
|
||||
|
||||
private Double latency;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RetrievalInfo {
|
||||
@JSONField(name = "dataset_id")
|
||||
private String datasetId;
|
||||
|
||||
@JSONField(name = "dataset_name")
|
||||
private String datasetName;
|
||||
|
||||
@JSONField(name = "document_id")
|
||||
private String documentId;
|
||||
|
||||
@JSONField(name = "document_name")
|
||||
private String documentName;
|
||||
|
||||
@JSONField(name = "segment_id")
|
||||
private String segmentId;
|
||||
|
||||
private Double score;
|
||||
|
||||
private String content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* @description 对话列表响应
|
||||
* @filename ConversationListResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class ConversationListResponse {
|
||||
|
||||
private Integer limit;
|
||||
|
||||
@JSONField(name = "has_more")
|
||||
private Boolean hasMore;
|
||||
|
||||
private List<ConversationInfo> data;
|
||||
|
||||
@Data
|
||||
public static class ConversationInfo {
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private List<InputInfo> inputs;
|
||||
|
||||
private String status;
|
||||
|
||||
private String introduction;
|
||||
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
@JSONField(name = "updated_at")
|
||||
private Long updatedAt;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class InputInfo {
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 对话变量响应
|
||||
* @filename ConversationVariablesResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-29
|
||||
*/
|
||||
@Data
|
||||
public class ConversationVariablesResponse {
|
||||
|
||||
private Integer limit;
|
||||
|
||||
@JSONField(name = "has_more")
|
||||
private Boolean hasMore;
|
||||
|
||||
private List<ConversationVariableItem> data;
|
||||
|
||||
/**
|
||||
* 对话中的变量项
|
||||
*/
|
||||
@Data
|
||||
public static class ConversationVariableItem {
|
||||
|
||||
/**
|
||||
* 变量ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 变量名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 变量类型 (string, number, boolean 等)
|
||||
*/
|
||||
@JSONField(name = "value_type")
|
||||
private String valueType;
|
||||
|
||||
/**
|
||||
* 变量值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 变量描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建时间戳
|
||||
*/
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* 最后更新时间戳
|
||||
*/
|
||||
@JSONField(name = "updated_at")
|
||||
private Long updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 创建知识库请求
|
||||
* @filename DatasetCreateRequest.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DatasetCreateRequest {
|
||||
|
||||
/**
|
||||
* 知识库名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 知识库描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 索引方式:high_quality(高质量)、economy(经济)
|
||||
*/
|
||||
@JSONField(name = "indexing_technique")
|
||||
private String indexingTechnique = "high_quality";
|
||||
|
||||
/**
|
||||
* Embedding模型
|
||||
*/
|
||||
@JSONField(name = "embedding_model")
|
||||
private String embeddingModel;
|
||||
|
||||
/**
|
||||
* Embedding模型提供商
|
||||
*/
|
||||
@JSONField(name = "embedding_model_provider")
|
||||
private String embeddingModelProvider;
|
||||
|
||||
/**
|
||||
* 检索模型配置(包含 Rerank、Top K、Score 阈值等)
|
||||
*/
|
||||
@JSONField(name = "retrieval_model")
|
||||
private RetrievalModel retrievalModel;
|
||||
|
||||
/**
|
||||
* 权限:only_me(仅自己)、all_team_members(团队所有成员)
|
||||
*/
|
||||
private String permission = "only_me";
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 创建知识库响应
|
||||
* @filename DatasetCreateResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DatasetCreateResponse {
|
||||
|
||||
/**
|
||||
* 知识库ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 知识库名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 索引方式
|
||||
*/
|
||||
@JSONField(name = "indexing_technique")
|
||||
private String indexingTechnique;
|
||||
|
||||
/**
|
||||
* Embedding模型
|
||||
*/
|
||||
@JSONField(name = "embedding_model")
|
||||
private String embeddingModel;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@JSONField(name = "created_by")
|
||||
private String createdBy;
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 知识库详情响应
|
||||
* @filename DatasetDetailResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DatasetDetailResponse {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
@JSONField(name = "indexing_technique")
|
||||
private String indexingTechnique;
|
||||
|
||||
@JSONField(name = "embedding_model")
|
||||
private String embeddingModel;
|
||||
|
||||
@JSONField(name = "embedding_model_provider")
|
||||
private String embeddingModelProvider;
|
||||
|
||||
@JSONField(name = "embedding_available")
|
||||
private Boolean embeddingAvailable;
|
||||
|
||||
@JSONField(name = "retrieval_model_dict")
|
||||
private RetrievalModelDict retrievalModelDict;
|
||||
|
||||
@JSONField(name = "document_count")
|
||||
private Integer documentCount;
|
||||
|
||||
@JSONField(name = "word_count")
|
||||
private Integer wordCount;
|
||||
|
||||
@JSONField(name = "app_count")
|
||||
private Integer appCount;
|
||||
|
||||
@JSONField(name = "created_by")
|
||||
private String createdBy;
|
||||
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
@JSONField(name = "updated_at")
|
||||
private Long updatedAt;
|
||||
|
||||
@Data
|
||||
public static class RetrievalModelDict {
|
||||
@JSONField(name = "search_method")
|
||||
private String searchMethod;
|
||||
|
||||
@JSONField(name = "reranking_enable")
|
||||
private Boolean rerankingEnable;
|
||||
|
||||
@JSONField(name = "reranking_model")
|
||||
private RerankingModel rerankingModel;
|
||||
|
||||
@JSONField(name = "top_k")
|
||||
private Integer topK;
|
||||
|
||||
@JSONField(name = "score_threshold_enabled")
|
||||
private Boolean scoreThresholdEnabled;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RerankingModel {
|
||||
@JSONField(name = "reranking_provider_name")
|
||||
private String rerankingProviderName;
|
||||
|
||||
@JSONField(name = "reranking_model_name")
|
||||
private String rerankingModelName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 知识库列表响应
|
||||
* @filename DatasetListResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DatasetListResponse {
|
||||
|
||||
/**
|
||||
* 知识库列表
|
||||
*/
|
||||
private List<DatasetInfo> data;
|
||||
|
||||
/**
|
||||
* 是否有更多
|
||||
*/
|
||||
@JSONField(name = "has_more")
|
||||
private Boolean hasMore;
|
||||
|
||||
/**
|
||||
* 分页限制
|
||||
*/
|
||||
private Integer limit;
|
||||
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Integer total;
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
private Integer page;
|
||||
|
||||
@Data
|
||||
public static class DatasetInfo {
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String permission;
|
||||
@JSONField(name = "document_count")
|
||||
private Integer documentCount;
|
||||
@JSONField(name = "word_count")
|
||||
private Integer wordCount;
|
||||
@JSONField(name = "created_by")
|
||||
private String createdBy;
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
@JSONField(name = "updated_at")
|
||||
private Long updatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description Dify知识库更新请求
|
||||
* @filename DatasetUpdateRequest.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DatasetUpdateRequest {
|
||||
|
||||
/**
|
||||
* 知识库名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 知识库描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 索引方式(high_quality/economy)
|
||||
*/
|
||||
@JSONField(name = "indexing_technique")
|
||||
private String indexingTechnique;
|
||||
|
||||
/**
|
||||
* Embedding模型
|
||||
*/
|
||||
@JSONField(name = "embedding_model")
|
||||
private String embeddingModel;
|
||||
|
||||
/**
|
||||
* Embedding模型提供商
|
||||
*/
|
||||
@JSONField(name = "embedding_model_provider")
|
||||
private String embeddingModelProvider;
|
||||
|
||||
/**
|
||||
* 检索模型配置(包含 Rerank、Top K、Score 阈值等)
|
||||
*/
|
||||
@JSONField(name = "retrieval_model")
|
||||
private RetrievalModel retrievalModel;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 文件ID(Dify返回)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description Dify文档列表响应
|
||||
* @filename DocumentListResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-07
|
||||
*/
|
||||
@Data
|
||||
public class DocumentListResponse {
|
||||
|
||||
private List<Document> data;
|
||||
|
||||
@JSONField(name = "has_more")
|
||||
private Boolean hasMore;
|
||||
|
||||
private Integer limit;
|
||||
|
||||
private Integer total;
|
||||
|
||||
private Integer page;
|
||||
|
||||
/**
|
||||
* 文档信息
|
||||
*/
|
||||
@Data
|
||||
public static class Document {
|
||||
private String id;
|
||||
|
||||
private Integer position;
|
||||
|
||||
@JSONField(name = "data_source_type")
|
||||
private String dataSourceType;
|
||||
|
||||
@JSONField(name = "data_source_info")
|
||||
private DataSourceInfo dataSourceInfo;
|
||||
|
||||
@JSONField(name = "dataset_process_rule_id")
|
||||
private String datasetProcessRuleId;
|
||||
|
||||
private String name;
|
||||
|
||||
@JSONField(name = "created_from")
|
||||
private String createdFrom;
|
||||
|
||||
@JSONField(name = "created_by")
|
||||
private String createdBy;
|
||||
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
private Integer tokens;
|
||||
|
||||
@JSONField(name = "indexing_status")
|
||||
private String indexingStatus;
|
||||
|
||||
private String error;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
@JSONField(name = "disabled_at")
|
||||
private Long disabledAt;
|
||||
|
||||
@JSONField(name = "disabled_by")
|
||||
private String disabledBy;
|
||||
|
||||
private Boolean archived;
|
||||
|
||||
@JSONField(name = "display_status")
|
||||
private String displayStatus;
|
||||
|
||||
@JSONField(name = "word_count")
|
||||
private Integer wordCount;
|
||||
|
||||
@JSONField(name = "hit_count")
|
||||
private Integer hitCount;
|
||||
|
||||
@JSONField(name = "doc_form")
|
||||
private String docForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源信息
|
||||
*/
|
||||
@Data
|
||||
public static class DataSourceInfo {
|
||||
@JSONField(name = "upload_file_id")
|
||||
private String uploadFileId;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 文档处理状态响应
|
||||
* @filename DocumentStatusResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DocumentStatusResponse {
|
||||
|
||||
/**
|
||||
* 文档列表
|
||||
*/
|
||||
private List<DocumentStatus> data;
|
||||
|
||||
@Data
|
||||
public static class DocumentStatus {
|
||||
/**
|
||||
* 文档ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 索引状态:waiting、parsing、cleaning、splitting、indexing、completed、error
|
||||
*/
|
||||
@JSONField(name = "indexing_status")
|
||||
private String indexingStatus;
|
||||
|
||||
/**
|
||||
* 处理开始时间
|
||||
*/
|
||||
@JSONField(name = "processing_started_at")
|
||||
private Long processingStartedAt;
|
||||
|
||||
/**
|
||||
* 解析完成时间
|
||||
*/
|
||||
@JSONField(name = "parsing_completed_at")
|
||||
private Long parsingCompletedAt;
|
||||
|
||||
/**
|
||||
* 清洗完成时间
|
||||
*/
|
||||
@JSONField(name = "cleaning_completed_at")
|
||||
private Long cleaningCompletedAt;
|
||||
|
||||
/**
|
||||
* 分割完成时间
|
||||
*/
|
||||
@JSONField(name = "splitting_completed_at")
|
||||
private Long splittingCompletedAt;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
@JSONField(name = "completed_at")
|
||||
private Long completedAt;
|
||||
|
||||
/**
|
||||
* 暂停时间
|
||||
*/
|
||||
@JSONField(name = "paused_at")
|
||||
private Long pausedAt;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 停止时间
|
||||
*/
|
||||
@JSONField(name = "stopped_at")
|
||||
private Long stoppedAt;
|
||||
|
||||
/**
|
||||
* 分段数量
|
||||
*/
|
||||
@JSONField(name = "completed_segments")
|
||||
private Integer completedSegments;
|
||||
|
||||
/**
|
||||
* 总分段数
|
||||
*/
|
||||
@JSONField(name = "total_segments")
|
||||
private Integer totalSegments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 文档上传请求
|
||||
* @filename DocumentUploadRequest.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DocumentUploadRequest {
|
||||
|
||||
/**
|
||||
* 文档名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 索引方式
|
||||
*/
|
||||
@JSONField(name = "indexing_technique")
|
||||
private String indexingTechnique;
|
||||
|
||||
/**
|
||||
* 处理规则
|
||||
*/
|
||||
@JSONField(name = "process_rule")
|
||||
private ProcessRule processRule;
|
||||
|
||||
@Data
|
||||
public static class ProcessRule {
|
||||
/**
|
||||
* 分段模式:automatic(自动)、custom(自定义)
|
||||
*/
|
||||
private String mode = "automatic";
|
||||
|
||||
/**
|
||||
* 预处理规则
|
||||
*/
|
||||
private Rules rules;
|
||||
|
||||
@Data
|
||||
public static class Rules {
|
||||
/**
|
||||
* 自动分段配置
|
||||
*/
|
||||
@JSONField(name = "pre_processing_rules")
|
||||
private PreProcessingRules preProcessingRules;
|
||||
|
||||
/**
|
||||
* 分段配置
|
||||
*/
|
||||
private Segmentation segmentation;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PreProcessingRules {
|
||||
/**
|
||||
* 移除额外空格
|
||||
*/
|
||||
@JSONField(name = "remove_extra_spaces")
|
||||
private Boolean removeExtraSpaces = true;
|
||||
|
||||
/**
|
||||
* 移除URL和邮箱
|
||||
*/
|
||||
@JSONField(name = "remove_urls_emails")
|
||||
private Boolean removeUrlsEmails = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Segmentation {
|
||||
/**
|
||||
* 分隔符
|
||||
*/
|
||||
private String separator = "\\n";
|
||||
|
||||
/**
|
||||
* 最大分段长度
|
||||
*/
|
||||
@JSONField(name = "max_tokens")
|
||||
private Integer maxTokens = 1000;
|
||||
|
||||
/**
|
||||
* 分段重叠长度
|
||||
*/
|
||||
@JSONField(name = "chunk_overlap")
|
||||
private Integer chunkOverlap = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 文档上传响应(根据 Dify API 返回结构)
|
||||
* @filename DocumentUploadResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class DocumentUploadResponse {
|
||||
|
||||
/**
|
||||
* 文档详细信息
|
||||
*/
|
||||
private Document document;
|
||||
|
||||
/**
|
||||
* 批次ID(用于查询处理状态)
|
||||
*/
|
||||
private String batch;
|
||||
|
||||
/**
|
||||
* 文档详细信息
|
||||
*/
|
||||
@Data
|
||||
public static class Document {
|
||||
/**
|
||||
* 文档ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 文档名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 位置(序号)
|
||||
*/
|
||||
private Integer position;
|
||||
|
||||
/**
|
||||
* 数据源类型
|
||||
*/
|
||||
@JSONField(name = "data_source_type")
|
||||
private String dataSourceType;
|
||||
|
||||
/**
|
||||
* 数据源信息
|
||||
*/
|
||||
@JSONField(name = "data_source_info")
|
||||
private Object dataSourceInfo;
|
||||
|
||||
/**
|
||||
* 数据集处理规则ID
|
||||
*/
|
||||
@JSONField(name = "dataset_process_rule_id")
|
||||
private String datasetProcessRuleId;
|
||||
|
||||
/**
|
||||
* 创建来源
|
||||
*/
|
||||
@JSONField(name = "created_from")
|
||||
private String createdFrom;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@JSONField(name = "created_by")
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间(时间戳)
|
||||
*/
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* Token数量
|
||||
*/
|
||||
private Integer tokens;
|
||||
|
||||
/**
|
||||
* 索引状态
|
||||
*/
|
||||
@JSONField(name = "indexing_status")
|
||||
private String indexingStatus;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 禁用时间
|
||||
*/
|
||||
@JSONField(name = "disabled_at")
|
||||
private Long disabledAt;
|
||||
|
||||
/**
|
||||
* 禁用人
|
||||
*/
|
||||
@JSONField(name = "disabled_by")
|
||||
private String disabledBy;
|
||||
|
||||
/**
|
||||
* 是否归档
|
||||
*/
|
||||
private Boolean archived;
|
||||
|
||||
/**
|
||||
* 显示状态
|
||||
*/
|
||||
@JSONField(name = "display_status")
|
||||
private String displayStatus;
|
||||
|
||||
/**
|
||||
* 字数
|
||||
*/
|
||||
@JSONField(name = "word_count")
|
||||
private Integer wordCount;
|
||||
|
||||
/**
|
||||
* 命中次数
|
||||
*/
|
||||
@JSONField(name = "hit_count")
|
||||
private Integer hitCount;
|
||||
|
||||
/**
|
||||
* 文档形式
|
||||
*/
|
||||
@JSONField(name = "doc_form")
|
||||
private String docForm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description Dify嵌入模型响应
|
||||
* @filename EmbeddingModelResponse.java
|
||||
* @author AI Assistant
|
||||
* @since 2025-11-06
|
||||
*/
|
||||
@Data
|
||||
public class EmbeddingModelResponse {
|
||||
|
||||
/**
|
||||
* 模型提供商列表
|
||||
*/
|
||||
@JSONField(name = "data")
|
||||
private List<ModelProvider> data;
|
||||
|
||||
/**
|
||||
* 模型提供商
|
||||
*/
|
||||
@Data
|
||||
public static class ModelProvider {
|
||||
/**
|
||||
* 提供商标识
|
||||
*/
|
||||
@JSONField(name = "provider")
|
||||
private String provider;
|
||||
|
||||
/**
|
||||
* 提供商标签
|
||||
*/
|
||||
@JSONField(name = "label")
|
||||
private Map<String, String> label;
|
||||
|
||||
/**
|
||||
* 小图标
|
||||
*/
|
||||
@JSONField(name = "icon_small")
|
||||
private Map<String, String> iconSmall;
|
||||
|
||||
/**
|
||||
* 大图标
|
||||
*/
|
||||
@JSONField(name = "icon_large")
|
||||
private Map<String, String> iconLarge;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@JSONField(name = "status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 模型列表
|
||||
*/
|
||||
@JSONField(name = "models")
|
||||
private List<Model> models;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型详情
|
||||
*/
|
||||
@Data
|
||||
public static class Model {
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
@JSONField(name = "model")
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 模型标签
|
||||
*/
|
||||
@JSONField(name = "label")
|
||||
private Map<String, String> label;
|
||||
|
||||
/**
|
||||
* 模型类型
|
||||
*/
|
||||
@JSONField(name = "model_type")
|
||||
private String modelType;
|
||||
|
||||
/**
|
||||
* 特性列表
|
||||
*/
|
||||
@JSONField(name = "features")
|
||||
private List<Object> features;
|
||||
|
||||
/**
|
||||
* 获取来源
|
||||
*/
|
||||
@JSONField(name = "fetch_from")
|
||||
private String fetchFrom;
|
||||
|
||||
/**
|
||||
* 模型属性
|
||||
*/
|
||||
@JSONField(name = "model_properties")
|
||||
private ModelProperties modelProperties;
|
||||
|
||||
/**
|
||||
* 是否已弃用
|
||||
*/
|
||||
@JSONField(name = "deprecated")
|
||||
private Boolean deprecated;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@JSONField(name = "status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 是否启用负载均衡
|
||||
*/
|
||||
@JSONField(name = "load_balancing_enabled")
|
||||
private Boolean loadBalancingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型属性
|
||||
*/
|
||||
@Data
|
||||
public static class ModelProperties {
|
||||
/**
|
||||
* 上下文大小
|
||||
*/
|
||||
@JSONField(name = "context_size")
|
||||
private Integer contextSize;
|
||||
|
||||
/**
|
||||
* 最大分块数
|
||||
*/
|
||||
@JSONField(name = "max_chunks")
|
||||
private Integer maxChunks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 消息历史响应
|
||||
* @filename MessageHistoryResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class MessageHistoryResponse {
|
||||
|
||||
private Integer limit;
|
||||
|
||||
@JSONField(name = "has_more")
|
||||
private Boolean hasMore;
|
||||
|
||||
private List<MessageInfo> data;
|
||||
|
||||
@Data
|
||||
public static class MessageInfo {
|
||||
private String id;
|
||||
|
||||
@JSONField(name = "conversation_id")
|
||||
private String conversationId;
|
||||
|
||||
private List<MessageContent> inputs;
|
||||
|
||||
private String query;
|
||||
|
||||
private String answer;
|
||||
|
||||
@JSONField(name = "message_files")
|
||||
private List<MessageFile> messageFiles;
|
||||
|
||||
private Feedback feedback;
|
||||
|
||||
@JSONField(name = "retriever_resources")
|
||||
private List<RetrieverResource> retrieverResources;
|
||||
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
@JSONField(name = "agent_thoughts")
|
||||
private List<Object> agentThoughts;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MessageContent {
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MessageFile {
|
||||
private String id;
|
||||
private String type;
|
||||
private String url;
|
||||
@JSONField(name = "belongs_to")
|
||||
private String belongsTo;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Feedback {
|
||||
private String rating;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RetrieverResource {
|
||||
@JSONField(name = "dataset_id")
|
||||
private String datasetId;
|
||||
|
||||
@JSONField(name = "dataset_name")
|
||||
private String datasetName;
|
||||
|
||||
@JSONField(name = "document_id")
|
||||
private String documentId;
|
||||
|
||||
@JSONField(name = "document_name")
|
||||
private String documentName;
|
||||
|
||||
@JSONField(name = "segment_id")
|
||||
private String segmentId;
|
||||
|
||||
private Double score;
|
||||
|
||||
private String content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description Dify Rerank模型响应
|
||||
* @filename RerankModelResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-06
|
||||
*/
|
||||
@Data
|
||||
public class RerankModelResponse {
|
||||
private List<ModelProvider> data;
|
||||
|
||||
@Data
|
||||
public static class ModelProvider {
|
||||
private String provider;
|
||||
private Map<String, String> label; // e.g., {"en_US": "Cohere", "zh_Hans": "Cohere"}
|
||||
@JSONField(name = "icon_small")
|
||||
private String iconSmall;
|
||||
@JSONField(name = "icon_large")
|
||||
private String iconLarge;
|
||||
private String status; // e.g., "active"
|
||||
private List<Model> models;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Model {
|
||||
private String model; // e.g., "rerank-multilingual-v3.0"
|
||||
private Map<String, String> label;
|
||||
@JSONField(name = "model_type")
|
||||
private String modelType; // e.g., "rerank"
|
||||
private List<String> features;
|
||||
@JSONField(name = "fetch_from")
|
||||
private String fetchFrom;
|
||||
@JSONField(name = "model_properties")
|
||||
private ModelProperties modelProperties;
|
||||
private Boolean deprecated;
|
||||
private String status; // e.g., "active"
|
||||
private String provider; // 模型提供商(可能在 model 数据中)
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ModelProperties {
|
||||
@JSONField(name = "context_size")
|
||||
private Integer contextSize;
|
||||
@JSONField(name = "max_chunks")
|
||||
private Integer maxChunks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description Dify检索模型配置(Retrieval Model)
|
||||
* @filename RetrievalModel.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-06
|
||||
*/
|
||||
@Data
|
||||
public class RetrievalModel {
|
||||
|
||||
/**
|
||||
* 搜索方法:vector_search(向量搜索)、full_text_search(全文搜索)、hybrid_search(混合搜索)
|
||||
*/
|
||||
@JSONField(name = "search_method")
|
||||
private String searchMethod;
|
||||
|
||||
/**
|
||||
* Rerank是否启用
|
||||
*/
|
||||
@JSONField(name = "reranking_enable")
|
||||
private Boolean rerankingEnable;
|
||||
|
||||
/**
|
||||
* Rerank模式(字符串,值为 "reranking_model")
|
||||
*/
|
||||
@JSONField(name = "reranking_mode")
|
||||
private String rerankingMode;
|
||||
|
||||
/**
|
||||
* Rerank模型配置(当 reranking_enable=true 时必须设置)
|
||||
*/
|
||||
@JSONField(name = "reranking_model")
|
||||
private RerankingModel rerankingModel;
|
||||
|
||||
/**
|
||||
* Top K(返回前K个结果)
|
||||
*/
|
||||
@JSONField(name = "top_k")
|
||||
private Integer topK;
|
||||
|
||||
/**
|
||||
* 分数阈值(0.00-1.00)
|
||||
*/
|
||||
@JSONField(name = "score_threshold")
|
||||
private Double scoreThreshold;
|
||||
|
||||
/**
|
||||
* 是否启用分数阈值
|
||||
*/
|
||||
@JSONField(name = "score_threshold_enabled")
|
||||
private Boolean scoreThresholdEnabled;
|
||||
|
||||
/**
|
||||
* Rerank模型配置(嵌套对象)
|
||||
*/
|
||||
@Data
|
||||
public static class RerankingModel {
|
||||
/**
|
||||
* Rerank模型提供商
|
||||
*/
|
||||
@JSONField(name = "reranking_provider_name")
|
||||
private String rerankingProviderName;
|
||||
|
||||
/**
|
||||
* Rerank模型名称
|
||||
*/
|
||||
@JSONField(name = "reranking_model_name")
|
||||
private String rerankingModelName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 知识库检索请求
|
||||
* @filename RetrievalRequest.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class RetrievalRequest {
|
||||
|
||||
/**
|
||||
* 查询文本
|
||||
*/
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* 返回的最相关结果数量
|
||||
*/
|
||||
@JSONField(name = "top_k")
|
||||
private Integer topK = 3;
|
||||
|
||||
/**
|
||||
* 相似度阈值(0-1)
|
||||
*/
|
||||
@JSONField(name = "score_threshold")
|
||||
private Double scoreThreshold = 0.7;
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 知识库检索响应
|
||||
* @filename RetrievalResponse.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Data
|
||||
public class RetrievalResponse {
|
||||
|
||||
/**
|
||||
* 查询ID
|
||||
*/
|
||||
@JSONField(name = "query_id")
|
||||
private String queryId;
|
||||
|
||||
/**
|
||||
* 检索结果列表
|
||||
*/
|
||||
private List<RetrievalRecord> records;
|
||||
|
||||
@Data
|
||||
public static class RetrievalRecord {
|
||||
/**
|
||||
* 分段内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 相似度分数
|
||||
*/
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 元数据
|
||||
*/
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
/**
|
||||
* 文档ID
|
||||
*/
|
||||
@JSONField(name = "document_id")
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* 文档名称
|
||||
*/
|
||||
@JSONField(name = "document_name")
|
||||
private String documentName;
|
||||
|
||||
/**
|
||||
* 分段ID
|
||||
*/
|
||||
@JSONField(name = "segment_id")
|
||||
private String segmentId;
|
||||
|
||||
/**
|
||||
* 分段位置
|
||||
*/
|
||||
@JSONField(name = "segment_position")
|
||||
private Integer segmentPosition;
|
||||
|
||||
/**
|
||||
* 索引节点ID
|
||||
*/
|
||||
@JSONField(name = "index_node_id")
|
||||
private String indexNodeId;
|
||||
|
||||
/**
|
||||
* 索引节点哈希
|
||||
*/
|
||||
@JSONField(name = "index_node_hash")
|
||||
private String indexNodeHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 工作流执行请求
|
||||
* @filename WorkflowRunRequest.java
|
||||
* @author system
|
||||
* @copyright xyzh
|
||||
* @since 2026-01-01
|
||||
*/
|
||||
@Data
|
||||
public class WorkflowRunRequest {
|
||||
|
||||
/**
|
||||
* 输入变量(Dify 工作流 API 必需字段)
|
||||
*/
|
||||
@JSONField(serializeFeatures = com.alibaba.fastjson2.JSONWriter.Feature.WriteMapNullValue)
|
||||
private Map<String, Object> inputs = new java.util.HashMap<>();
|
||||
|
||||
/**
|
||||
* 响应模式:streaming(流式)、blocking(阻塞)
|
||||
*/
|
||||
@JSONField(name = "response_mode")
|
||||
private String responseMode = "blocking";
|
||||
|
||||
/**
|
||||
* 用户标识
|
||||
*/
|
||||
private String user;
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 工作流执行响应(阻塞模式)
|
||||
* @filename WorkflowRunResponse.java
|
||||
* @author system
|
||||
* @copyright xyzh
|
||||
* @since 2026-01-01
|
||||
*/
|
||||
@Data
|
||||
public class WorkflowRunResponse {
|
||||
|
||||
/**
|
||||
* 工作流执行ID
|
||||
*/
|
||||
@JSONField(name = "workflow_run_id")
|
||||
private String workflowRunId;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
@JSONField(name = "task_id")
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 工作流执行数据
|
||||
*/
|
||||
private WorkflowData data;
|
||||
|
||||
@Data
|
||||
public static class WorkflowData {
|
||||
/**
|
||||
* 执行ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 工作流ID
|
||||
*/
|
||||
@JSONField(name = "workflow_id")
|
||||
private String workflowId;
|
||||
|
||||
/**
|
||||
* 执行状态:running、succeeded、failed、stopped
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 工作流输出结果
|
||||
*/
|
||||
private Map<String, Object> outputs;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 执行耗时(秒)
|
||||
*/
|
||||
@JSONField(name = "elapsed_time")
|
||||
private Double elapsedTime;
|
||||
|
||||
/**
|
||||
* 总Token数
|
||||
*/
|
||||
@JSONField(name = "total_tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JSONField(name = "created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* 完成时间
|
||||
*/
|
||||
@JSONField(name = "finished_at")
|
||||
private Long finishedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
package org.xyzh.ai.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.xyzh.api.system.service.SysConfigService;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @description Dify配置类
|
||||
* @filename DifyConfig.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
@Configuration
|
||||
public class DifyConfig {
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 30000, retries = 0)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
/**
|
||||
* Dify API基础地址
|
||||
*/
|
||||
private String apiBaseUrl = "http://192.168.130.131/v1";
|
||||
|
||||
private String knowledgeApiKey="dataset-nupqKP4LONpzdXmGthIrbjeJ";
|
||||
|
||||
/**
|
||||
* 请求超时时间(秒)
|
||||
*/
|
||||
private Integer timeout = 60;
|
||||
|
||||
/**
|
||||
* 连接超时时间(秒)
|
||||
*/
|
||||
private Integer connectTimeout = 10;
|
||||
|
||||
/**
|
||||
* 读取超时时间(秒)
|
||||
*/
|
||||
private Integer readTimeout = 60;
|
||||
|
||||
/**
|
||||
* 流式响应超时时间(秒)
|
||||
*/
|
||||
private Integer streamTimeout = 300;
|
||||
|
||||
/**
|
||||
* 上传文件配置
|
||||
*/
|
||||
private Upload upload = new Upload();
|
||||
|
||||
/**
|
||||
* 知识库配置
|
||||
*/
|
||||
private Dataset dataset = new Dataset();
|
||||
|
||||
/**
|
||||
* 初始化配置,从数据库加载
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
log.info("开始从数据库加载Dify配置...");
|
||||
|
||||
// 基础配置
|
||||
loadStringConfig("dify.apiBaseUrl", val -> this.apiBaseUrl = val);
|
||||
loadStringConfig("dify.knowledgeApiKey", val -> this.knowledgeApiKey = val);
|
||||
loadIntegerConfig("dify.timeout", val -> this.timeout = val);
|
||||
loadIntegerConfig("dify.connectTimeout", val -> this.connectTimeout = val);
|
||||
loadIntegerConfig("dify.readTimeout", val -> this.readTimeout = val);
|
||||
loadIntegerConfig("dify.streamTimeout", val -> this.streamTimeout = val);
|
||||
|
||||
// Upload配置
|
||||
loadStringConfig("dify.upload.allowedTypes", val -> {
|
||||
if (val != null && !val.trim().isEmpty()) {
|
||||
this.upload.allowedTypes = val.split(",");
|
||||
}
|
||||
});
|
||||
loadIntegerConfig("dify.upload.maxSize", val -> this.upload.maxSize = val);
|
||||
|
||||
// Dataset配置
|
||||
loadStringConfig("dify.dataset.defaultIndexingTechnique", val -> this.dataset.defaultIndexingTechnique = val);
|
||||
loadStringConfig("dify.dataset.defaultEmbeddingModel", val -> this.dataset.defaultEmbeddingModel = val);
|
||||
|
||||
log.info("Dify配置加载完成 - API地址: {}", apiBaseUrl);
|
||||
} catch (Exception e) {
|
||||
log.error("加载Dify配置失败,将使用默认值", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载字符串配置
|
||||
*/
|
||||
private void loadStringConfig(String key, java.util.function.Consumer<String> setter) {
|
||||
try {
|
||||
String value = sysConfigService.getStringConfig(key);
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
setter.accept(value);
|
||||
log.debug("加载配置: {} = {}", key, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("加载配置失败: {}, 错误: {}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载整数配置
|
||||
*/
|
||||
private void loadIntegerConfig(String key, java.util.function.Consumer<Integer> setter) {
|
||||
try {
|
||||
Integer value = sysConfigService.getIntConfig(key);
|
||||
if (value != null) {
|
||||
setter.accept(value);
|
||||
log.debug("加载配置: {} = {}", key, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("加载配置失败: {}, 错误: {}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Upload {
|
||||
/**
|
||||
* 支持的文件类型
|
||||
*/
|
||||
private String[] allowedTypes = {"pdf", "txt", "docx", "doc", "md", "html", "htm"};
|
||||
|
||||
/**
|
||||
* 最大文件大小(MB)
|
||||
*/
|
||||
private Integer maxSize = 50;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Dataset {
|
||||
/**
|
||||
* 默认索引方式(high_quality/economy)
|
||||
*/
|
||||
private String defaultIndexingTechnique = "high_quality";
|
||||
|
||||
/**
|
||||
* 默认Embedding模型
|
||||
*/
|
||||
private String defaultEmbeddingModel = "text-embedding-ada-002";
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置是否有效
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return apiBaseUrl != null && !apiBaseUrl.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新配置(从数据库重新加载)
|
||||
* 由Redis事件监听器调用
|
||||
*/
|
||||
public void refresh() {
|
||||
log.info("收到配置刷新请求,重新加载Dify配置...");
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整的API URL
|
||||
*/
|
||||
public String getFullApiUrl(String endpoint) {
|
||||
String baseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl.substring(0, apiBaseUrl.length() - 1) : apiBaseUrl;
|
||||
String path = endpoint.startsWith("/") ? endpoint : "/" + endpoint;
|
||||
return baseUrl + path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.xyzh.ai.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.xyzh.ai.listener.DifyConfigListener;
|
||||
|
||||
/**
|
||||
* AI模块Redis订阅配置
|
||||
*
|
||||
* @author cascade
|
||||
* @since 2026-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisSubscriberConfig {
|
||||
|
||||
@Autowired
|
||||
private DifyConfigListener difyConfigListener;
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 订阅Dify配置变更频道
|
||||
container.addMessageListener(difyConfigListener,
|
||||
new PatternTopic(difyConfigListener.getChannelPattern()));
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
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.api.ai.service.AgentService;
|
||||
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 AgentService agentService;
|
||||
|
||||
/**
|
||||
* @description 创建智能体
|
||||
* @param agent
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('ai:agent:view')")
|
||||
public ResultDomain<TbAgent> getAgentList(TbAgent tbAgent) {
|
||||
log.info("获取智能体列表");
|
||||
return agentService.getAgentList(tbAgent);
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
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.ai.client.dto.ConversationVariablesResponse;
|
||||
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.core.page.PageDomain;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
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;
|
||||
|
||||
@Autowired
|
||||
private org.xyzh.ai.client.DifyApiClient difyApiClient;
|
||||
|
||||
// ====================== 会话管理 ======================
|
||||
|
||||
/**
|
||||
* @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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
chat.setUserType(true);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("创建会话: agentId={}, title={}, userId={}, userType={}", chat.getAgentId(), chat.getTitle(), chat.getUserId(), chat.getUserType());
|
||||
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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
chat.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("删除会话: chatId={}", chat.getChatId());
|
||||
return chatService.deleteChat(chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取对话列表
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/conversation/list")
|
||||
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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
filter.setUserType(true);
|
||||
}
|
||||
}
|
||||
return chatService.getChatList(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分页获取对话列表
|
||||
* @param pageRequest 分页请求参数
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/conversation/page")
|
||||
public ResultDomain<TbChat> getChatPage(@RequestBody PageRequest<TbChat> pageRequest, @RequestHeader("Authorization") String token) {
|
||||
log.info("分页获取会话列表: agentId={}", pageRequest.getFilter().getAgentId());
|
||||
|
||||
pageRequest.getFilter().setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
pageRequest.getFilter().setUserType(true);
|
||||
}
|
||||
}
|
||||
return chatService.getChatPage(pageRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取对话变量
|
||||
* @param params 请求参数(包含agentId, conversationId, userId, lastId, limit)
|
||||
* @author yslg
|
||||
* @since 2025-12-29
|
||||
*/
|
||||
@PostMapping("/conversation/variables")
|
||||
public ResultDomain<org.xyzh.ai.client.dto.ConversationVariablesResponse> getConversationVariables(
|
||||
@RequestBody Map<String, Object> params,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
|
||||
// 参数验证
|
||||
ValidationResult result = ValidationUtils.validateMap(params, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体ID", 1, 100),
|
||||
ValidationUtils.requiredString("conversationId", "会话ID", 1, 100),
|
||||
ValidationUtils.requiredString("userId", "用户ID", 1, 100)
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
|
||||
String agentId = (String) params.get("agentId");
|
||||
String conversationId = (String) params.get("conversationId");
|
||||
String userId = (String) params.get("userId");
|
||||
String lastId = params.containsKey("lastId") ? (String) params.get("lastId") : null;
|
||||
Integer limit = params.containsKey("limit") ?
|
||||
Integer.parseInt(params.get("limit").toString()) : 20;
|
||||
|
||||
log.info("获取对话变量: agentId={}, conversationId={}, userId={}", agentId, conversationId, userId);
|
||||
|
||||
try {
|
||||
// 获取智能体信息以获取 API Key
|
||||
// 这里需要根据 agentId 获取对应的 API Key
|
||||
// 暂时先使用一个占位符,实际使用时需要从数据库或配置中获取
|
||||
// 或者通过 chatService 获取智能体配置
|
||||
|
||||
// 调用 Dify API 获取会话变量
|
||||
ConversationVariablesResponse response =
|
||||
difyApiClient.getConversationVariables(conversationId, userId, lastId, limit, agentId);
|
||||
|
||||
return ResultDomain.success("获取对话变量成功",response);
|
||||
} catch (Exception e) {
|
||||
log.error("获取对话变量失败", e);
|
||||
return ResultDomain.failure("获取对话变量失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 消息管理 ======================
|
||||
|
||||
/**
|
||||
* @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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
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) && !"guest".equals(loginDomain.getUser().getStatus())) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,379 +0,0 @@
|
||||
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.api.ai.service.DifyProxyService;
|
||||
import org.xyzh.api.ai.service.KnowledgeFileLogService;
|
||||
import org.xyzh.api.ai.service.KnowledgeService;
|
||||
import org.xyzh.api.ai.dto.TbKnowledge;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.vo.KnowledgeFileVO;
|
||||
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 KnowledgeService knowledgeService;
|
||||
|
||||
@Autowired
|
||||
private KnowledgeFileLogService knowledgeFileLogService;
|
||||
|
||||
@Autowired
|
||||
private DifyProxyService difyProxyService;
|
||||
|
||||
// ====================== 知识库管理 ======================
|
||||
|
||||
/**
|
||||
* @description 创建知识库基础信息,包含dify知识库各种参数的配置
|
||||
* @param knowledge
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新知识库,包含dify知识库各种参数的配置
|
||||
* @param knowledge
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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 pageRequest 分页请求
|
||||
* @author yslg
|
||||
* @since 2025-12-20
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@PostMapping("/{knowledgeId}/documents")
|
||||
public ResultDomain<KnowledgeFileVO> getDocumentList(@RequestBody PageRequest<TbKnowledgeFile> pageRequest) {
|
||||
logger.info("获取文档列表: knowledgeId={}", pageRequest.getFilter().getKnowledgeId());
|
||||
return knowledgeService.getDocumentList(pageRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传文件到知识库(同步到Dify)
|
||||
* @param file 上传文件
|
||||
* @param knowledgeId 知识库id
|
||||
* @param indexingTechnique 索引方式
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:upload')")
|
||||
@PostMapping(value = "/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("hasAuthority('ai:knowledge:file:upload')")
|
||||
@PostMapping(value = "/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("hasAuthority('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("hasAuthority('ai:knowledge:file:delete')")
|
||||
@DeleteMapping("/file/{fileRootId}")
|
||||
public ResultDomain<Boolean> deleteFile(@PathVariable("fileRootId") @NotBlank String fileRootId) {
|
||||
logger.info("删除知识库文件: fileId={}", fileRootId);
|
||||
return knowledgeService.deleteKnowledgeFileById(fileRootId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件历史版本,获取fileRootId下所有version
|
||||
* @param fileRootId 文件根ID
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@GetMapping("/file/{fileRootId}/history")
|
||||
public ResultDomain<KnowledgeFileVO> getFileHistory(@PathVariable("fileRootId") @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("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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("hasAuthority('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);
|
||||
}
|
||||
|
||||
// ================================ 知识库文件操作日志 =======================
|
||||
/**
|
||||
* @description 查询知识库操作日志列表
|
||||
* @param fileLog
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@PostMapping("/datasets/log/list")
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogList(@RequestBody TbKnowledgeFileLog fileLog){
|
||||
return knowledgeFileLogService.getKnowledgeFileLogList(fileLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库操作日志分页
|
||||
* @param pageRequest
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@PostMapping("/datasets/log/page")
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogPage(@RequestBody PageRequest<TbKnowledgeFileLog> pageRequest){
|
||||
return knowledgeFileLogService.getKnowledgeFileLogPage(pageRequest);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package org.xyzh.ai.exception;
|
||||
|
||||
/**
|
||||
* @description Dify API调用异常
|
||||
* @filename DifyException.java
|
||||
* @author AI Assistant
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-04
|
||||
*/
|
||||
public class DifyException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer code;
|
||||
|
||||
public DifyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DifyException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public DifyException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DifyException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
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<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.xyzh.ai.listener;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.api.system.constance.SysConfigRedisPrefix;
|
||||
import org.xyzh.ai.config.DifyConfig;
|
||||
import org.xyzh.common.redis.listener.AbstractSysConfigListener;
|
||||
|
||||
/**
|
||||
* Dify配置变更监听器
|
||||
* 监听sys:config:dify频道,接收到事件后延时2秒刷新配置
|
||||
*
|
||||
* @author cascade
|
||||
* @since 2026-01-01
|
||||
*/
|
||||
@Component
|
||||
public class DifyConfigListener extends AbstractSysConfigListener {
|
||||
|
||||
@Autowired
|
||||
private DifyConfig difyConfig;
|
||||
|
||||
@Override
|
||||
protected void doRefresh(String channel, String body) {
|
||||
difyConfig.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelPattern() {
|
||||
return SysConfigRedisPrefix.SYS_CONFIG_DIFY;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.vo.KnowledgeFileVO;
|
||||
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 TbKnowledgeFileLogMapper {
|
||||
|
||||
int addKnowledgeFileLog(TbKnowledgeFileLog tbKnowledgeFileLog);
|
||||
|
||||
List<TbKnowledgeFileLog> getKnowledgeFileLogList(@Param("filter") TbKnowledgeFileLog filter);
|
||||
|
||||
List<TbKnowledgeFileLog> getKnowledgeFileLogPage(@Param("pageParam") PageParam pageParam,@Param("filter") TbKnowledgeFileLog filter);
|
||||
|
||||
|
||||
int countKnowledgeFileLog(@Param("filter") TbKnowledgeFileLog filter);
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
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.api.ai.vo.KnowledgeFileVO;
|
||||
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<KnowledgeFileVO> selectFilesByKnowledgeId(@Param("knowledgeId") String knowledgeId);
|
||||
|
||||
/**
|
||||
* 根据文件根ID查询所有版本
|
||||
*/
|
||||
List<TbKnowledgeFile> selectFileVersions(@Param("fileRootId") String fileRootId);
|
||||
|
||||
/**
|
||||
* 分页查询知识库文件(关联文件详细信息)
|
||||
*/
|
||||
List<KnowledgeFileVO> 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
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据文件根ID查询最大版本的文件
|
||||
*/
|
||||
TbKnowledgeFile selectLatestVersionFile(@Param("fileRootId") String fileRootId);
|
||||
|
||||
/**
|
||||
* 根据文件根ID查询所有版本(包含文件详细信息)
|
||||
*/
|
||||
List<KnowledgeFileVO> selectFileVersionsWithDetail(@Param("fileRootId") String fileRootId);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
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);
|
||||
|
||||
int updateKnowledgeFileCount(@Param("knowledgeId") String knowledgeId, @Param("num") Integer num);
|
||||
|
||||
/**
|
||||
* 根据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);
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
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.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.api.ai.service.AgentService;
|
||||
import org.xyzh.api.file.dto.TbSysFileDTO;
|
||||
import org.xyzh.api.file.service.FileService;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description AI文件上传服务实现(同时上传到MinIO和Dify)
|
||||
* @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 AgentService agentService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "file", timeout = 30000, retries = 0)
|
||||
private FileService fileService;
|
||||
|
||||
// ============================ 对话文件管理 ============================
|
||||
|
||||
@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();
|
||||
|
||||
// 3. 获取当前用户
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
userId = "anonymous";
|
||||
}
|
||||
|
||||
File tempFile = null;
|
||||
String sysFileId = null;
|
||||
String sysFileUrl = null;
|
||||
|
||||
try {
|
||||
// 4. 上传到MinIO(通过FileService,使用字节数组方式)
|
||||
byte[] fileBytes = file.getBytes();
|
||||
String fileName = file.getOriginalFilename();
|
||||
String contentType = file.getContentType();
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileBytes(fileBytes, fileName, contentType, "ai-chat", agentId);
|
||||
if (fileResult.getSuccess() && fileResult.getData() != null) {
|
||||
TbSysFileDTO sysFile = fileResult.getData();
|
||||
sysFileId = sysFile.getFileId();
|
||||
sysFileUrl = sysFile.getUrl();
|
||||
logger.info("上传文件到MinIO成功: fileId={}, url={}", sysFileId, sysFileUrl);
|
||||
} else {
|
||||
logger.warn("上传文件到MinIO失败: {}", fileResult.getMessage());
|
||||
// MinIO上传失败不阻断流程,继续上传到Dify
|
||||
}
|
||||
|
||||
// 5. 将MultipartFile转换为临时File用于Dify上传
|
||||
tempFile = File.createTempFile("upload_", "_" + file.getOriginalFilename());
|
||||
file.transferTo(tempFile);
|
||||
|
||||
// 6. 上传到Dify
|
||||
DifyFileInfo difyFile = difyApiClient.uploadFileForChat(tempFile, file.getOriginalFilename(), userId, agent.getApiKey());
|
||||
if (difyFile != null && StringUtils.hasText(difyFile.getId())) {
|
||||
logger.info("上传对话文件到Dify成功: agentId={}, difyFileId={}", agentId, difyFile.getId());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
// Dify返回的信息
|
||||
result.put("id", difyFile.getId());
|
||||
result.put("name", difyFile.getName());
|
||||
result.put("size", difyFile.getSize());
|
||||
result.put("type", difyFile.getType());
|
||||
result.put("extension", difyFile.getExtension());
|
||||
result.put("mime_type", difyFile.getMimeType());
|
||||
result.put("upload_file_id", difyFile.getUploadFileId());
|
||||
// 系统文件信息(用于前端展示和数据库存储)
|
||||
result.put("sys_file_id", sysFileId);
|
||||
result.put("preview_url", sysFileUrl);
|
||||
result.put("source_url", sysFileUrl);
|
||||
|
||||
return ResultDomain.success("上传成功", result);
|
||||
}
|
||||
return ResultDomain.failure("上传文件到Dify失败");
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,773 +0,0 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
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.client.dto.ChatResponse;
|
||||
import org.xyzh.ai.client.dto.WorkflowRunRequest;
|
||||
import org.xyzh.ai.client.dto.WorkflowRunResponse;
|
||||
import org.xyzh.ai.config.DifyConfig;
|
||||
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.dto.TbKnowledge;
|
||||
import org.xyzh.api.ai.service.AgentChatService;
|
||||
import org.xyzh.api.ai.service.AgentService;
|
||||
import org.xyzh.api.ai.service.KnowledgeService;
|
||||
import org.xyzh.api.system.service.GuestService;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
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.NonUtils;
|
||||
import org.xyzh.common.utils.id.IdUtil;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
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 String WORKFLOW_SESSION_PREFIX = "ai:workflow:session:";
|
||||
private static final long SESSION_TTL = 5 * 60;
|
||||
|
||||
@Autowired
|
||||
private TbChatMapper chatMapper;
|
||||
|
||||
@Autowired
|
||||
private TbChatMessageMapper chatMessageMapper;
|
||||
|
||||
@Autowired
|
||||
private AgentService agentService;
|
||||
|
||||
@Autowired
|
||||
private DifyApiClient difyApiClient;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Autowired
|
||||
private KnowledgeService knowledgeService;
|
||||
|
||||
@Autowired
|
||||
private DifyConfig difyConfig;
|
||||
|
||||
/**
|
||||
* @description 判断智能体是否是outer
|
||||
* @param agentId 智能体ID
|
||||
* @return true-是outer,false-不是outer
|
||||
*/
|
||||
private Boolean isOuterAgent(String agentId){
|
||||
// 智能体必须是outer
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
|
||||
if(!agentResult.getSuccess()|| agentResult.getData() == null || !agentResult.getData().getIsOuter()){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ====================== 智能体会话管理 ======================
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbChat> createChat(TbChat chat) {
|
||||
// 如果是来客(userType=false),校验智能体是否是 isOuter
|
||||
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并校验权限
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
String userId = loginDomain.getUser().getUserId();
|
||||
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并校验权限
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
String userId = loginDomain.getUser().getUserId();
|
||||
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) {
|
||||
|
||||
// 获取用户ID
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
|
||||
List<TbChat> chatList = chatMapper.selectChatList(filter.getAgentId(), userId);
|
||||
return ResultDomain.success("查询成功", chatList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbChat> getChatPage(PageRequest<TbChat> pageRequest) {
|
||||
TbChat filter = pageRequest.getFilter();
|
||||
// 判断agent是否是outer(来客才需要校验)
|
||||
if (!filter.getUserType() && !isOuterAgent(filter.getAgentId())) {
|
||||
return ResultDomain.<TbChat>failure("智能体不可用");
|
||||
}
|
||||
// 获取用户ID
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
return ResultDomain.<TbChat>failure("用户信息获取失败");
|
||||
}
|
||||
filter.setUserId(userId);
|
||||
|
||||
// 分页查询
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
List<TbChat> chatList = chatMapper.selectChatPage(filter, pageParam);
|
||||
long total = chatMapper.countChats(filter);
|
||||
pageParam.setTotal((int) total);
|
||||
|
||||
PageDomain<TbChat> pageDomain = new PageDomain<>(pageParam, chatList);
|
||||
return ResultDomain.<TbChat>success("查询成功", pageDomain);
|
||||
}
|
||||
|
||||
// ====================== 智能体聊天管理 ======================
|
||||
|
||||
@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 = LoginUtil.getCurrentUserId();
|
||||
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().getIsOuter()) {
|
||||
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());
|
||||
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
String userId = loginDomain.getUser().getUserId();
|
||||
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. 生成临时消息ID(sessionId)
|
||||
String sessionId = IdUtil.getSnowflakeId();
|
||||
|
||||
// 5. 准备 inputs 参数
|
||||
Map<String, Object> inputsMap = prepareData.getInputsMap();
|
||||
if (inputsMap == null) {
|
||||
inputsMap = new HashMap<>();
|
||||
}
|
||||
|
||||
// 处理动态知识库
|
||||
Boolean isGuest = "guest".equals(loginDomain.getUser().getStatus());
|
||||
if (agent.getIsOuter() && NonUtils.isNotEmpty(prepareData.getService())) {
|
||||
TbKnowledge filter = new TbKnowledge();
|
||||
filter.setService(prepareData.getService());
|
||||
filter.setCategory(isGuest ? "external" : "internal");
|
||||
ResultDomain<TbKnowledge> knowledgeRD = knowledgeService.listKnowledges(filter);
|
||||
List<String> datasets = new ArrayList<>();
|
||||
if (knowledgeRD.getSuccess()) {
|
||||
datasets = knowledgeRD.getDataList().stream()
|
||||
.map(TbKnowledge::getDifyDatasetId)
|
||||
.toList();
|
||||
}
|
||||
inputsMap.put("datasets", JSON.toJSONString(datasets));
|
||||
inputsMap.put("dataset_apikey", difyConfig.getKnowledgeApiKey());
|
||||
}
|
||||
|
||||
// 6. 存储会话数据到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());
|
||||
sessionData.put("outer", agent.getIsOuter());
|
||||
sessionData.put("service", prepareData.getService());
|
||||
sessionData.put("isGuest", isGuest);
|
||||
sessionData.put("inputsMap", inputsMap); // 存储处理好的 inputs
|
||||
sessionData.put("appType", prepareData.getAppType()); // 存储应用类型
|
||||
|
||||
// 根据应用类型选择不同的Redis key前缀
|
||||
String appType = prepareData.getAppType();
|
||||
String prefix = "workflow".equals(appType) ? WORKFLOW_SESSION_PREFIX : CHAT_SESSION_PREFIX;
|
||||
String cacheKey = prefix + sessionId;
|
||||
redisService.set(cacheKey, sessionData, SESSION_TTL, TimeUnit.SECONDS);
|
||||
|
||||
logger.info("准备{}会话: sessionId={}, agentId={}", appType, 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 = redisService.get(cacheKey, Map.class);
|
||||
|
||||
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")
|
||||
Map<String, Object> inputsMap = (Map<String, Object>) sessionData.get("inputsMap");
|
||||
|
||||
@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);
|
||||
|
||||
// 提取系统文件ID列表保存到消息中
|
||||
if (filesData != null && !filesData.isEmpty()) {
|
||||
List<String> sysFileIds = filesData.stream()
|
||||
.map(DifyFileInfo::getSysFileId)
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
if (!sysFileIds.isEmpty()) {
|
||||
userMessage.setFiles(sysFileIds);
|
||||
}
|
||||
}
|
||||
|
||||
chatMessageMapper.insertChatMessage(userMessage);
|
||||
|
||||
// 5. 构建Dify请求
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
chatRequest.setQuery(query);
|
||||
chatRequest.setUser(userId);
|
||||
chatRequest.setResponseMode("streaming");
|
||||
|
||||
// 使用从Redis获取的inputsMap,如果为空则创建新的
|
||||
if (inputsMap == null) {
|
||||
inputsMap = new HashMap<>();
|
||||
}
|
||||
chatRequest.setInputs(inputsMap); // Dify API 要求 inputs 必传
|
||||
|
||||
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<String> blockingChatMessageWithSession(String sessionId) {
|
||||
try {
|
||||
// 1. 从Redis获取会话数据
|
||||
String cacheKey = CHAT_SESSION_PREFIX + sessionId;
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> sessionData = redisService.get(cacheKey, Map.class);
|
||||
|
||||
if (sessionData == null) {
|
||||
return ResultDomain.failure("会话已过期");
|
||||
}
|
||||
|
||||
// 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")
|
||||
Map<String, Object> inputsMap = (Map<String, Object>) sessionData.get("inputsMap");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<DifyFileInfo> filesData = (List<DifyFileInfo>) sessionData.get("filesData");
|
||||
|
||||
// 3. 删除已使用的会话数据
|
||||
redisService.delete(cacheKey);
|
||||
|
||||
// 4. 保存用户消息(如果有 chatId 的话)
|
||||
if (StringUtils.hasText(chatId)) {
|
||||
String userMessageId = IdUtil.getSnowflakeId();
|
||||
TbChatMessage userMessage = new TbChatMessage();
|
||||
userMessage.setOptsn(IdUtil.getOptsn());
|
||||
userMessage.setMessageId(userMessageId);
|
||||
userMessage.setChatId(chatId);
|
||||
userMessage.setRole("user");
|
||||
userMessage.setContent(query);
|
||||
|
||||
// 提取系统文件ID列表保存到消息中
|
||||
if (filesData != null && !filesData.isEmpty()) {
|
||||
List<String> sysFileIds = filesData.stream()
|
||||
.map(DifyFileInfo::getSysFileId)
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
if (!sysFileIds.isEmpty()) {
|
||||
userMessage.setFiles(sysFileIds);
|
||||
}
|
||||
}
|
||||
|
||||
chatMessageMapper.insertChatMessage(userMessage);
|
||||
}
|
||||
|
||||
// 5. 构建Dify请求
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
chatRequest.setQuery(query);
|
||||
chatRequest.setUser(userId);
|
||||
chatRequest.setResponseMode("blocking");
|
||||
|
||||
// 使用从Redis获取的inputsMap,如果为空则创建新的
|
||||
if (inputsMap == null) {
|
||||
inputsMap = new HashMap<>();
|
||||
}
|
||||
chatRequest.setInputs(inputsMap); // Dify API 要求 inputs 必传
|
||||
|
||||
if (filesData != null && !filesData.isEmpty()) {
|
||||
chatRequest.setFiles(filesData);
|
||||
}
|
||||
|
||||
// 6. 调用Dify阻塞式接口
|
||||
logger.info("调用Dify阻塞式接口: agentId={}, userId={}", agentId, userId);
|
||||
ChatResponse chatResponse = difyApiClient.blockingChat(chatRequest, apiKey);
|
||||
|
||||
if (chatResponse == null || chatResponse.getAnswer() == null) {
|
||||
return ResultDomain.failure("工作流返回结果为空");
|
||||
}
|
||||
|
||||
String answer = chatResponse.getAnswer();
|
||||
|
||||
// 7. 保存AI回复消息(如果有 chatId 的话)
|
||||
if (StringUtils.hasText(chatId)) {
|
||||
String aiMessageId = IdUtil.getSnowflakeId();
|
||||
TbChatMessage aiMessage = new TbChatMessage();
|
||||
aiMessage.setOptsn(IdUtil.getOptsn());
|
||||
aiMessage.setMessageId(aiMessageId);
|
||||
aiMessage.setDifyMessageId(chatResponse.getMessageId());
|
||||
aiMessage.setChatId(chatId);
|
||||
aiMessage.setRole("ai");
|
||||
aiMessage.setContent(answer);
|
||||
chatMessageMapper.insertChatMessage(aiMessage);
|
||||
|
||||
logger.info("阻塞式对话完成: chatId={}, aiMessageId={}", chatId, aiMessageId);
|
||||
} else {
|
||||
logger.info("阻塞式对话完成(无chatId): userId={}", userId);
|
||||
}
|
||||
|
||||
return ResultDomain.success("对话成功", answer);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("阻塞式对话异常: sessionId={}", sessionId, e);
|
||||
return ResultDomain.failure("对话失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@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().getIsOuter()) {
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
}
|
||||
TbAgent agent = agentResult.getData();
|
||||
|
||||
// 2. 获取用户ID
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
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 = LoginUtil.getCurrentUserId();
|
||||
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("评价失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> runWorkflowWithSession(String sessionId) {
|
||||
try {
|
||||
// 1. 从Redis获取会话数据(使用workflow前缀)
|
||||
String cacheKey = WORKFLOW_SESSION_PREFIX + sessionId;
|
||||
Map<String, Object> sessionData = redisService.get(cacheKey, Map.class);
|
||||
|
||||
if (sessionData == null) {
|
||||
return ResultDomain.failure("会话已过期");
|
||||
}
|
||||
|
||||
// 2. 解析会话数据
|
||||
String agentId = (String) sessionData.get("agentId");
|
||||
String userId = (String) sessionData.get("userId");
|
||||
String apiKey = (String) sessionData.get("apiKey");
|
||||
Map<String, Object> inputsMap = (Map<String, Object>) sessionData.get("inputsMap");
|
||||
|
||||
// 3. 删除已使用的会话数据
|
||||
redisService.delete(cacheKey);
|
||||
|
||||
// 4. 构建工作流请求
|
||||
WorkflowRunRequest workflowRequest = new WorkflowRunRequest();
|
||||
workflowRequest.setInputs(inputsMap != null ? inputsMap : new HashMap<>());
|
||||
workflowRequest.setResponseMode("blocking");
|
||||
workflowRequest.setUser(userId);
|
||||
|
||||
logger.info("执行工作流: agentId={}, userId={}, sessionId={}", agentId, userId, sessionId);
|
||||
|
||||
// 5. 调用Dify工作流接口
|
||||
WorkflowRunResponse workflowResponse = difyApiClient.runWorkflowBlocking(workflowRequest, apiKey);
|
||||
|
||||
if (workflowResponse == null || workflowResponse.getData() == null) {
|
||||
return ResultDomain.failure("工作流执行失败:返回结果为空");
|
||||
}
|
||||
|
||||
// 6. 检查工作流执行状态
|
||||
String status = workflowResponse.getData().getStatus();
|
||||
if (!"succeeded".equals(status)) {
|
||||
String error = workflowResponse.getData().getError();
|
||||
logger.error("工作流执行失败: status={}, error={}", status, error);
|
||||
return ResultDomain.failure("工作流执行失败: " + (error != null ? error : status));
|
||||
}
|
||||
|
||||
// 7. 提取outputs
|
||||
Map<String, Object> outputs = workflowResponse.getData().getOutputs();
|
||||
if (outputs == null) {
|
||||
return ResultDomain.failure("工作流执行失败:outputs为空");
|
||||
}
|
||||
|
||||
// 8. 将outputs转为JSON字符串返回
|
||||
String outputsJson = JSON.toJSONString(outputs);
|
||||
logger.info("工作流执行成功: agentId={}, workflowRunId={}", agentId, workflowResponse.getWorkflowRunId());
|
||||
|
||||
return ResultDomain.success("工作流执行成功", outputsJson);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("工作流执行异常: sessionId={}", sessionId, e);
|
||||
return ResultDomain.failure("工作流执行异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> runWorkflowBlocking(String agentId, Map<String, Object> inputs, String userId) {
|
||||
try {
|
||||
// 1. 获取智能体信息
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
|
||||
if (!agentResult.getSuccess() || agentResult.getData() == null) {
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
}
|
||||
TbAgent agent = agentResult.getData();
|
||||
|
||||
// 2. 构建工作流请求
|
||||
WorkflowRunRequest workflowRequest = new WorkflowRunRequest();
|
||||
workflowRequest.setInputs(inputs);
|
||||
workflowRequest.setResponseMode("blocking");
|
||||
workflowRequest.setUser(userId);
|
||||
|
||||
logger.info("执行工作流: agentId={}, userId={}, inputs={}", agentId, userId, JSON.toJSONString(inputs));
|
||||
|
||||
// 3. 调用Dify工作流接口
|
||||
WorkflowRunResponse workflowResponse = difyApiClient.runWorkflowBlocking(workflowRequest, agent.getApiKey());
|
||||
|
||||
if (workflowResponse == null || workflowResponse.getData() == null) {
|
||||
return ResultDomain.failure("工作流执行失败:返回结果为空");
|
||||
}
|
||||
|
||||
// 4. 检查工作流执行状态
|
||||
String status = workflowResponse.getData().getStatus();
|
||||
if (!"succeeded".equals(status)) {
|
||||
String error = workflowResponse.getData().getError();
|
||||
logger.error("工作流执行失败: status={}, error={}", status, error);
|
||||
return ResultDomain.failure("工作流执行失败: " + (error != null ? error : status));
|
||||
}
|
||||
|
||||
// 5. 提取outputs
|
||||
Map<String, Object> outputs = workflowResponse.getData().getOutputs();
|
||||
if (outputs == null) {
|
||||
return ResultDomain.failure("工作流执行失败:outputs为空");
|
||||
}
|
||||
|
||||
// 6. 将outputs转为JSON字符串返回
|
||||
String outputsJson = JSON.toJSONString(outputs);
|
||||
logger.info("工作流执行成功: agentId={}, workflowRunId={}", agentId, workflowResponse.getWorkflowRunId());
|
||||
|
||||
return ResultDomain.success("工作流执行成功", outputsJson);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("工作流执行异常: agentId={}, userId={}", agentId, userId, e);
|
||||
return ResultDomain.failure("工作流执行异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
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 = redisService.get(cacheKey, TbAgent.class);
|
||||
if (agent != null) {
|
||||
return ResultDomain.success("查询成功", agent);
|
||||
}
|
||||
|
||||
// 2. 双检加锁
|
||||
agentLock.lock();
|
||||
try {
|
||||
// 再次检查缓存
|
||||
agent = redisService.get(cacheKey, TbAgent.class);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
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.xyzh.ai.mapper.TbKnowledgeFileLogMapper;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.service.KnowledgeFileLogService;
|
||||
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.utils.NonUtils;
|
||||
import org.xyzh.common.utils.id.IdUtil;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
@DubboService(version="1.0.0", group="ai", timeout=3000, retries=0)
|
||||
public class KnowledgeFileLogServiceImpl implements KnowledgeFileLogService{
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private TbKnowledgeFileLogMapper knowledgeFileLogMapper;
|
||||
|
||||
/**
|
||||
* @description 新增知识库文件操作日志
|
||||
* @param knowledgeFileLog
|
||||
* @return 日志
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFileLog> addKnowledgeFileLog(TbKnowledgeFileLog knowledgeFileLog){
|
||||
knowledgeFileLog.setOptsn(IdUtil.getOptsn());
|
||||
knowledgeFileLog.setLogId(IdUtil.generateID());
|
||||
ValidationResult rt = ValidationUtils.validate(knowledgeFileLog, Arrays.asList(
|
||||
ValidationUtils.requiredString("fileId", "文件id")
|
||||
));
|
||||
if(!rt.isValid()){
|
||||
return ResultDomain.failure("日志参数校验失败");
|
||||
}
|
||||
int result = knowledgeFileLogMapper.addKnowledgeFileLog(knowledgeFileLog);
|
||||
if(result >0){
|
||||
return ResultDomain.success("添加知识库文件日志成功", knowledgeFileLog);
|
||||
}else {
|
||||
return ResultDomain.failure("添加知识库文件日志失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库日志操作列表
|
||||
* @param filter
|
||||
* @return 日志列表
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogList(TbKnowledgeFileLog filter){
|
||||
List<TbKnowledgeFileLog> logs = knowledgeFileLogMapper.getKnowledgeFileLogList(filter);
|
||||
return ResultDomain.success("查询知识库日志成功",logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库日志操作分页
|
||||
* @param pageRequest
|
||||
* @return 日志分页数据
|
||||
* @author yslg
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFileLog> getKnowledgeFileLogPage(PageRequest<TbKnowledgeFileLog> pageRequest){
|
||||
List<TbKnowledgeFileLog> logs = knowledgeFileLogMapper.getKnowledgeFileLogPage(pageRequest.getPageParam(), pageRequest.getFilter());
|
||||
int total = knowledgeFileLogMapper.countKnowledgeFileLog(pageRequest.getFilter());
|
||||
PageDomain<TbKnowledgeFileLog> pageDomain = new PageDomain<>();
|
||||
pageDomain.setDataList(logs);
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
pageParam.setTotal(total);
|
||||
pageDomain.setPageParam(pageParam);
|
||||
return ResultDomain.success("查询知识库日志成功", pageDomain);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,801 +0,0 @@
|
||||
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.dto.TbKnowledgeFileLog;
|
||||
import org.xyzh.api.ai.constance.KnowledgeFileLogAction;
|
||||
import org.xyzh.api.ai.service.AIFileUploadService;
|
||||
import org.xyzh.api.ai.service.KnowledgeFileLogService;
|
||||
import org.xyzh.api.ai.service.KnowledgeService;
|
||||
import org.xyzh.api.ai.vo.KnowledgeFileVO;
|
||||
import org.xyzh.api.file.dto.TbSysFileDTO;
|
||||
import org.xyzh.api.file.service.FileService;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
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.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, retries = 0)
|
||||
private FileService fileService;
|
||||
|
||||
@Autowired
|
||||
private KnowledgeFileLogService knowledgeFileLogService;
|
||||
|
||||
@Autowired
|
||||
private AIFileUploadService aiFileUploadService;
|
||||
|
||||
// ================================= 知识库管理 =================================
|
||||
|
||||
/**
|
||||
* @description 创建知识库基础信息,包含dify知识库各种参数的配置
|
||||
* 注意:涉及外部API调用,不使用@Transactional,采用手动补偿机制
|
||||
* @param knowledge 知识库信息
|
||||
* @return ResultDomain<TbKnowledge> 创建结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> createKnowledge(TbKnowledge knowledge) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledge.getTitle())) {
|
||||
return ResultDomain.failure("知识库标题不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取当前用户
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (loginDomain == null || !StringUtils.hasText(loginDomain.getUser().getUserId())) {
|
||||
return ResultDomain.failure("用户未登录");
|
||||
}
|
||||
String userId = loginDomain.getUser().getUserId();
|
||||
|
||||
// 3. 生成ID
|
||||
knowledge.setDeptPath(loginDomain.getUserDepts().get(0).getDeptPath());
|
||||
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 内部创建知识库(不检查登录状态,用于系统初始化)
|
||||
* 注意:调用方需要自行设置knowledgeId和creator
|
||||
* @param knowledge 知识库信息
|
||||
* @return ResultDomain<TbKnowledge> 创建结果
|
||||
* @author yslg
|
||||
* @since 2025-12-20
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> createKnowledgeInternal(TbKnowledge knowledge) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledge.getTitle())) {
|
||||
return ResultDomain.failure("知识库标题不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(knowledge.getKnowledgeId())) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 设置默认值
|
||||
if (!StringUtils.hasText(knowledge.getOptsn())) {
|
||||
knowledge.setOptsn(IdUtil.getOptsn());
|
||||
}
|
||||
if (!StringUtils.hasText(knowledge.getCreator())) {
|
||||
knowledge.setCreator("system");
|
||||
}
|
||||
|
||||
// 3. 创建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());
|
||||
}
|
||||
|
||||
// 4. 插入数据库
|
||||
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 获取知识库文档列表(查询本地数据库文件)
|
||||
* @param pageRequest 分页请求,filter 中包含 knowledgeId
|
||||
* @return ResultDomain<KnowledgeFileVO> 文档列表
|
||||
* @author yslg
|
||||
* @since 2025-12-20
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<KnowledgeFileVO> getDocumentList(PageRequest<TbKnowledgeFile> pageRequest) {
|
||||
TbKnowledgeFile filter = pageRequest.getFilter();
|
||||
if (filter == null || !StringUtils.hasText(filter.getKnowledgeId())) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
|
||||
String knowledgeId = filter.getKnowledgeId();
|
||||
|
||||
// 验证知识库是否存在
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (knowledge == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
|
||||
try {
|
||||
// 分页查询知识库文件(已通过 SQL JOIN 关联文件详细信息)
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
List<KnowledgeFileVO> files = knowledgeFileMapper.selectFilePage(knowledgeId, pageParam);
|
||||
long total = knowledgeFileMapper.countFiles(knowledgeId);
|
||||
|
||||
// 设置总数
|
||||
pageParam.setTotal((int) total);
|
||||
|
||||
// 创建分页结果
|
||||
PageDomain<KnowledgeFileVO> pageDomain = new PageDomain<>(pageParam, files);
|
||||
|
||||
return ResultDomain.success("查询成功", pageDomain);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取知识库文件列表失败: knowledgeId={}, error={}", knowledgeId, 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(使用字节数组方式,支持跨模块Dubbo调用)
|
||||
byte[] fileBytes;
|
||||
try {
|
||||
fileBytes = file.getBytes();
|
||||
} catch (Exception e) {
|
||||
logger.error("读取文件字节失败", e);
|
||||
return ResultDomain.failure("读取文件失败: " + e.getMessage());
|
||||
}
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileBytes(
|
||||
fileBytes, file.getOriginalFilename(), file.getContentType(), "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);
|
||||
|
||||
knowledgeMapper.updateKnowledgeFileCount(knowledgeId, 1);
|
||||
int rows = knowledgeFileMapper.insertKnowledgeFile(knowledgeFile);
|
||||
if (rows > 0) {
|
||||
logger.info("保存知识库文件记录成功: knowledgeId={}, fileId={}, difyFileId={}", knowledgeId, fileId, difyFileId);
|
||||
// 记录日志
|
||||
TbKnowledgeFileLog log = new TbKnowledgeFileLog();
|
||||
log.setKnowledgeId(knowledgeId);
|
||||
log.setFileRootId(fileId);
|
||||
log.setFileId(fileId);
|
||||
log.setFileName(file.getOriginalFilename());
|
||||
log.setVersion(1);
|
||||
log.setAction(KnowledgeFileLogAction.UPLOAD.getAction());
|
||||
log.setService("workcase");
|
||||
log.setCreator(LoginUtil.getCurrentUserId());
|
||||
log.setCreatorName(LoginUtil.getCurrentUserName());
|
||||
knowledgeFileLogService.addKnowledgeFileLog(log);
|
||||
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. 获取最大版本的旧文件
|
||||
TbKnowledgeFile latestOldFile = knowledgeFileMapper.selectLatestVersionFile(fileRootId);
|
||||
if (latestOldFile == null) {
|
||||
return ResultDomain.failure("原文件不存在");
|
||||
}
|
||||
|
||||
// 4. 上传新版本到minio(使用字节数组避免 Dubbo 序列化问题)
|
||||
byte[] fileBytes;
|
||||
try {
|
||||
fileBytes = file.getBytes();
|
||||
} catch (java.io.IOException e) {
|
||||
logger.error("读取文件字节失败", e);
|
||||
return ResultDomain.failure("读取文件字节失败: " + e.getMessage());
|
||||
}
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileBytesVersion(
|
||||
fileBytes, file.getOriginalFilename(), file.getContentType(), "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最大版本的旧文档
|
||||
if (StringUtils.hasText(latestOldFile.getDifyFileId())) {
|
||||
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), latestOldFile.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);
|
||||
// 记录日志
|
||||
TbKnowledgeFileLog log = new TbKnowledgeFileLog();
|
||||
log.setKnowledgeId(knowledgeId);
|
||||
log.setFileRootId(fileRootId);
|
||||
log.setFileId(newFileId);
|
||||
log.setFileName(file.getOriginalFilename());
|
||||
log.setVersion(newVersion);
|
||||
log.setAction(KnowledgeFileLogAction.UPDATE.getAction());
|
||||
log.setService("workcase");
|
||||
log.setCreator(LoginUtil.getCurrentUserId());
|
||||
log.setCreatorName(LoginUtil.getCurrentUserName());
|
||||
knowledgeFileLogService.addKnowledgeFileLog(log);
|
||||
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);
|
||||
}
|
||||
}else{
|
||||
return ResultDomain.failure("知识库未关联Dify");
|
||||
}
|
||||
|
||||
// 3. 软删除本地记录和minio文件
|
||||
int rows = knowledgeFileMapper.deleteFilesByRootId(fileRootId);
|
||||
knowledgeMapper.updateKnowledgeFileCount(knowledge.getKnowledgeId(), -1);
|
||||
if (rows > 0) {
|
||||
logger.info("删除知识库文件成功: fileRootId={}", fileRootId);
|
||||
for (TbKnowledgeFile file : versions) {
|
||||
fileService.deleteFile(file.getFileId());
|
||||
}
|
||||
// 记录日志
|
||||
TbKnowledgeFileLog log = new TbKnowledgeFileLog();
|
||||
log.setKnowledgeId(knowledge.getKnowledgeId());
|
||||
log.setFileRootId(fileRootId);
|
||||
log.setAction(KnowledgeFileLogAction.DELETE.getAction());
|
||||
log.setService("workcase");
|
||||
log.setCreator(LoginUtil.getCurrentUserId());
|
||||
log.setCreatorName(LoginUtil.getCurrentUserName());
|
||||
knowledgeFileLogService.addKnowledgeFileLog(log);
|
||||
return ResultDomain.success("删除成功", true);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("删除文件失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取文件历史版本
|
||||
* @param fileRootId 文件根ID
|
||||
* @return ResultDomain<KnowledgeFileVO> 文件历史版本列表(dataList)
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<KnowledgeFileVO> getKnowledgeFileHistory(String fileRootId) {
|
||||
if (!StringUtils.hasText(fileRootId)) {
|
||||
return ResultDomain.failure("文件根ID不能为空");
|
||||
}
|
||||
|
||||
List<KnowledgeFileVO> versions = knowledgeFileMapper.selectFileVersionsWithDetail(fileRootId);
|
||||
ResultDomain<KnowledgeFileVO> result = ResultDomain.success("查询成功");
|
||||
result.setDataList(versions);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# ================== AI 服务配置 ==================
|
||||
server:
|
||||
port: 8090
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: ai-service
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 500MB
|
||||
max-request-size: 500MB
|
||||
|
||||
# ================== Auth ==================
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: true
|
||||
whitelist:
|
||||
- /swagger-ui/**
|
||||
- /swagger-ui.html
|
||||
- /v3/api-docs/**
|
||||
- /webjars/**
|
||||
- /favicon.ico
|
||||
- /error
|
||||
- /actuator/health
|
||||
- /actuator/info
|
||||
- /ai/chat/**
|
||||
|
||||
# ================== SpringDoc ==================
|
||||
springdoc:
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
display-name: 'AI代理服务 API'
|
||||
paths-to-match: '/**'
|
||||
|
||||
# ================== Dubbo ==================
|
||||
dubbo:
|
||||
application:
|
||||
name: urban-lifeline-agent
|
||||
qos-enable: false
|
||||
protocol:
|
||||
payload: 110100480
|
||||
scan:
|
||||
base-packages: org.xyzh.ai.service.impl
|
||||
@@ -1,64 +0,0 @@
|
||||
# ================================================
|
||||
# Urban Lifeline - 通用 Bootstrap 配置
|
||||
# 所有微服务共享的基础配置
|
||||
# ================================================
|
||||
|
||||
# ================== Spring Cloud Nacos ==================
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
namespace: ${NACOS_NAMESPACE:dev}
|
||||
group: ${NACOS_GROUP:DEFAULT_GROUP}
|
||||
|
||||
# ================== DataSource ==================
|
||||
datasource:
|
||||
url: ${DB_URL:jdbc:postgresql://127.0.0.1:5432/urban_lifeline}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
# ================== Redis ==================
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:127.0.0.1}
|
||||
port: ${REDIS_PORT:6379}
|
||||
database: ${REDIS_DATABASE:0}
|
||||
password: ${REDIS_PASSWORD:123456}
|
||||
|
||||
# ================== Security AES ==================
|
||||
security:
|
||||
aes:
|
||||
# AES-256 密钥(Base64编码,必须与所有服务保持一致)
|
||||
# 警告:这是开发环境密钥,生产环境请使用密钥管理系统
|
||||
secret-key: ${AES_SECRET_KEY:MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=}
|
||||
|
||||
# ================== Dubbo ==================
|
||||
dubbo:
|
||||
protocol:
|
||||
name: dubbo
|
||||
port: -1
|
||||
registry:
|
||||
address: nacos://${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
|
||||
# ================== MyBatis-Plus ==================
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:mapper/**/*.xml
|
||||
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||
|
||||
# ================== SpringDoc 基础配置 ==================
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
|
||||
# ================== Logging ==================
|
||||
logging:
|
||||
config: classpath:log4j2.xml
|
||||
charset:
|
||||
console: UTF-8
|
||||
file: UTF-8
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration status="WARN" monitorInterval="30">
|
||||
<Properties>
|
||||
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||
<property name="FILE_PATH" value="./logs" />
|
||||
<property name="FILE_NAME" value="ai-service" />
|
||||
<property name="file.encoding" value="UTF-8" />
|
||||
<property name="console.encoding" value="UTF-8" />
|
||||
</Properties>
|
||||
|
||||
<appenders>
|
||||
<console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
</console>
|
||||
|
||||
<RollingFile name="RollingFile" fileName="${FILE_PATH}/${FILE_NAME}.log"
|
||||
filePattern="${FILE_PATH}/${FILE_NAME}-%d{yyyy-MM-dd}_%i.log.gz">
|
||||
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy interval="1"/>
|
||||
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="30"/>
|
||||
</RollingFile>
|
||||
</appenders>
|
||||
|
||||
<loggers>
|
||||
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</logger>
|
||||
|
||||
<logger name="org.mybatis" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</logger>
|
||||
|
||||
<Logger name="org.springframework" level="info" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</Logger>
|
||||
|
||||
<Logger name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" level="TRACE" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</Logger>
|
||||
|
||||
<Logger name="org.xyzh.ai" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</Logger>
|
||||
|
||||
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="RollingFile"/>
|
||||
</Logger>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="Console"/>
|
||||
<appender-ref ref="RollingFile"/>
|
||||
</root>
|
||||
</loggers>
|
||||
</configuration>
|
||||
@@ -1,146 +0,0 @@
|
||||
<?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="is_outer" property="isOuter" 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, is_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="isOuter !=null">, is_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="isOuter !=null">, #{isOuter}</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="isOuter != null">is_outer = #{isOuter},</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>
|
||||
@@ -1,103 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,89 +0,0 @@
|
||||
<?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.jdbc.handler.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.jdbc.handler.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>
|
||||
@@ -1,103 +0,0 @@
|
||||
<?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.TbKnowledgeFileLogMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbKnowledgeFileLog">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="log_id" property="logId" 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="file_name" property="fileName" jdbcType="VARCHAR"/>
|
||||
<result column="action" property="action" jdbcType="VARCHAR"/>
|
||||
<result column="version" property="version" jdbcType="INTEGER"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="creator_name" property="creatorName" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, log_id, knowledge_id, file_root_id, file_id, file_name, version,
|
||||
action, creator, creator_name, create_time
|
||||
</sql>
|
||||
|
||||
<insert id="addKnowledgeFileLog" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFileLog">
|
||||
INSERT INTO ai.tb_knowledge_file_log (
|
||||
optsn, log_id, knowledge_id, file_root_id, file_id, file_name, version,
|
||||
action, service, creator, creator_name, create_time
|
||||
) VALUES (
|
||||
#{optsn}, #{logId}, #{knowledgeId}, #{fileRootId}, #{fileId},
|
||||
<choose>
|
||||
<!-- action为delete时,查询历史fileName,无数据则填空字符串 -->
|
||||
<when test="action == 'delete'">
|
||||
(SELECT COALESCE(file_name, '') FROM ai.tb_knowledge_file_log WHERE file_id = #{fileId} LIMIT 1)
|
||||
</when>
|
||||
<!-- 其他操作直接使用传入的fileName -->
|
||||
<otherwise>
|
||||
#{fileName}
|
||||
</otherwise>
|
||||
</choose>,
|
||||
#{version}, #{action}, #{service}, #{creator}, #{creatorName}, NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="getKnowledgeFileLogList" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file_log
|
||||
WHERE 1=1
|
||||
<if test="filter.knowledgeId != null and filter.knowledgeId != ''">
|
||||
AND knowledge_id = #{filter.knowledgeId}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.fileName != null and filter.fileName != ''">
|
||||
AND file_name LIKE CONCAT('%', #{filter.fileName}, '%')
|
||||
</if>
|
||||
<if test="filter.creatorName != null and filter.creatorName != ''">
|
||||
AND creator_name LIKE CONCAT('%', #{filter.creatorName}, '%')
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="getKnowledgeFileLogPage" resultMap="BaseResultMap">
|
||||
SELECT
|
||||
<include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file_log
|
||||
WHERE 1=1
|
||||
<if test="filter.knowledgeId != null and filter.knowledgeId != ''">
|
||||
AND knowledge_id = #{filter.knowledgeId}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.fileName != null and filter.fileName != ''">
|
||||
AND file_name LIKE CONCAT('%', #{filter.fileName}, '%')
|
||||
</if>
|
||||
<if test="filter.creatorName != null and filter.creatorName != ''">
|
||||
AND creator_name LIKE CONCAT('%', #{filter.creatorName}, '%')
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countKnowledgeFileLog" resultType="int">
|
||||
SELECT
|
||||
count(log_id)
|
||||
FROM ai.tb_knowledge_file_log
|
||||
WHERE 1=1
|
||||
<if test="filter.knowledgeId != null and filter.knowledgeId != ''">
|
||||
AND knowledge_id = #{filter.knowledgeId}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.fileName != null and filter.fileName != ''">
|
||||
AND file_name LIKE CONCAT('%', #{filter.fileName}, '%')
|
||||
</if>
|
||||
<if test="filter.creatorName != null and filter.creatorName != ''">
|
||||
AND creator_name LIKE CONCAT('%', #{filter.creatorName}, '%')
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -1,180 +0,0 @@
|
||||
<?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>
|
||||
|
||||
<resultMap id="KnowledgeFileVOResultMap" type="org.xyzh.api.ai.vo.KnowledgeFileVO">
|
||||
<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"/>
|
||||
<result column="file_name" property="fileName" jdbcType="VARCHAR"/>
|
||||
<result column="file_path" property="filePath" jdbcType="VARCHAR"/>
|
||||
<result column="file_size" property="fileSize" jdbcType="BIGINT"/>
|
||||
<result column="file_mime_type" property="fileMimeType" jdbcType="VARCHAR"/>
|
||||
<result column="file_url" property="fileUrl" jdbcType="VARCHAR"/>
|
||||
<result column="file_extension" property="fileExtension" jdbcType="VARCHAR"/>
|
||||
<result column="file_md5_hash" property="fileMd5Hash" jdbcType="VARCHAR"/>
|
||||
<result column="uploader_name" property="uploaderName" jdbcType="VARCHAR"/>
|
||||
</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="KnowledgeFileVOResultMap">
|
||||
SELECT
|
||||
kf.optsn, kf.knowledge_id, kf.file_root_id, kf.file_id, kf.dify_file_id, kf.version,
|
||||
kf.create_time, kf.update_time, kf.delete_time, kf.deleted,
|
||||
f.name as file_name,
|
||||
f.path as file_path,
|
||||
f.size as file_size,
|
||||
f.mime_type as file_mime_type,
|
||||
f.url as file_url,
|
||||
f.extension as file_extension,
|
||||
f.md5_hash as file_md5_hash,
|
||||
ui.username as uploader_name
|
||||
FROM ai.tb_knowledge_file kf
|
||||
INNER JOIN (
|
||||
SELECT file_root_id, MAX(version) as max_version
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
GROUP BY file_root_id
|
||||
) latest ON kf.file_root_id = latest.file_root_id AND kf.version = latest.max_version
|
||||
LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false
|
||||
LEFT JOIN sys.tb_sys_user_info ui ON f.uploader = ui.user_id AND ui.deleted = false
|
||||
WHERE kf.knowledge_id = #{knowledgeId} AND kf.deleted = false
|
||||
ORDER BY kf.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="KnowledgeFileVOResultMap">
|
||||
SELECT
|
||||
kf.optsn, kf.knowledge_id, kf.file_root_id, kf.file_id, kf.dify_file_id, kf.version,
|
||||
kf.create_time, kf.update_time, kf.delete_time, kf.deleted,
|
||||
f.name as file_name,
|
||||
f.path as file_path,
|
||||
f.size as file_size,
|
||||
f.mime_type as file_mime_type,
|
||||
f.url as file_url,
|
||||
f.extension as file_extension,
|
||||
f.md5_hash as file_md5_hash,
|
||||
ui.username as uploader_name
|
||||
FROM ai.tb_knowledge_file kf
|
||||
INNER JOIN (
|
||||
SELECT file_root_id, MAX(version) as max_version
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
GROUP BY file_root_id
|
||||
) latest ON kf.file_root_id = latest.file_root_id AND kf.version = latest.max_version
|
||||
LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false
|
||||
LEFT JOIN sys.tb_sys_user_info ui ON f.uploader = ui.user_id AND ui.deleted = false
|
||||
WHERE kf.knowledge_id = #{knowledgeId} AND kf.deleted = false
|
||||
ORDER BY kf.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countFiles" resultType="long">
|
||||
SELECT COUNT(DISTINCT file_root_id)
|
||||
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>
|
||||
|
||||
<select id="selectLatestVersionFile" 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
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectFileVersionsWithDetail" resultMap="KnowledgeFileVOResultMap">
|
||||
SELECT
|
||||
kf.optsn, kf.knowledge_id, kf.file_root_id, kf.file_id, kf.dify_file_id, kf.version,
|
||||
kf.create_time, kf.update_time, kf.delete_time, kf.deleted,
|
||||
f.name as file_name,
|
||||
f.path as file_path,
|
||||
f.size as file_size,
|
||||
f.mime_type as file_mime_type,
|
||||
f.url as file_url,
|
||||
f.extension as file_extension,
|
||||
f.md5_hash as file_md5_hash,
|
||||
ui.username as uploader_name
|
||||
FROM ai.tb_knowledge_file kf
|
||||
LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false
|
||||
LEFT JOIN sys.tb_sys_user_info ui ON f.uploader = ui.user_id AND ui.deleted = false
|
||||
WHERE kf.file_root_id = #{fileRootId} AND kf.deleted = false
|
||||
ORDER BY kf.version DESC
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -1,204 +0,0 @@
|
||||
<?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="BOOLEAN"/>
|
||||
<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>
|
||||
|
||||
<update id="updateKnowledgeFileCount">
|
||||
UPDATE ai.tb_knowledge
|
||||
SET document_count = document_count + #{num}
|
||||
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>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?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>apis</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.xyzh.apis</groupId>
|
||||
<artifactId>api-ai</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.xyzh.api.ai.constance;
|
||||
|
||||
|
||||
public enum KnowledgeFileLogAction {
|
||||
UPLOAD("upload", "上传文件"),
|
||||
DELETE("delete", "删除文件"),
|
||||
DOWNLOAD("download", "下载文件"),
|
||||
UPDATE("update", "更新文件"),
|
||||
SEGMENT_CREATE("segment_create", "创建分段"),
|
||||
SEGMENT_UPDATE("segment_update", "更新分段"),
|
||||
SEGMENT_DELETE("segment_delete", "更新分段");
|
||||
|
||||
private String action;
|
||||
private String description;
|
||||
private KnowledgeFileLogAction(String action, String description) {
|
||||
this.action = action;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public String getDescription(){
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package org.xyzh.api.ai.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
|
||||
@Schema(description = "服务名称")
|
||||
private String service;
|
||||
|
||||
@Schema(description = "智能体输入参数,不同智能体可能需要不同的输入参数")
|
||||
private Map<String, Object> inputsMap;
|
||||
|
||||
@Schema(description = "应用类型(chat=对话应用,workflow=工作流应用),默认为chat")
|
||||
private String appType = "chat";
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user