This commit is contained in:
2025-12-02 13:21:18 +08:00
parent fab8c13cb3
commit ee6dd64f98
192 changed files with 25783 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
#!/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}"
# 确保没有活动连接
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();"
# 删除数据库
PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "postgres" -c "DROP DATABASE IF EXISTS $DB_NAME;"
echo -e "${GREEN}Database deleted.${NC}"
}
# 显示帮助信息
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 "$@"

View File

@@ -0,0 +1,27 @@
-- 删除已存在的数据库(如果存在)
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

View File

@@ -0,0 +1,308 @@
-- =============================
-- 智能体管理和平台基础设施模块
-- 支持智能体广场、API集成管理、智能体运维监控
-- =============================
CREATE SCHEMA IF NOT EXISTS agent;
-- 智能体定义表
DROP TABLE IF EXISTS agent.tb_agent CASCADE;
CREATE TABLE agent.tb_agent (
optsn VARCHAR(50) NOT NULL, -- 流水号
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
agent_code VARCHAR(100) NOT NULL, -- 智能体编码(唯一标识)
agent_name VARCHAR(255) NOT NULL, -- 智能体名称
agent_type VARCHAR(50) NOT NULL, -- 智能体类型bidding-招投标/customer_service-客服/knowledge_assistant-知识助手/custom-自定义
display_name VARCHAR(255) NOT NULL, -- 展示名称
description TEXT, -- 智能体描述
icon VARCHAR(500), -- 图标URL
banner VARCHAR(500), -- Banner图URL
version VARCHAR(20) DEFAULT '1.0.0', -- 版本号
model_provider VARCHAR(50), -- 模型提供商openai/anthropic/baidu/aliyun/custom
model_name VARCHAR(100), -- 模型名称
model_config JSONB, -- 模型配置温度、最大tokens等
prompt_template TEXT, -- 提示词模板
system_prompt TEXT, -- 系统提示词
kb_ids VARCHAR(50)[], -- 关联知识库ID数组
tool_ids VARCHAR(50)[], -- 关联工具ID数组
capabilities TEXT[], -- 能力列表
access_level VARCHAR(20) DEFAULT 'private', -- 访问级别public-公开/private-私有/internal-内部
is_published BOOLEAN DEFAULT false, -- 是否发布到智能体广场
usage_count INTEGER DEFAULT 0, -- 使用次数
rating DECIMAL(3,2) DEFAULT 0, -- 评分0-5
rating_count INTEGER DEFAULT 0, -- 评分人数
tags TEXT[], -- 标签数组
category VARCHAR(100), -- 分类
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
owner_user_id VARCHAR(50), -- 所有者用户ID
status VARCHAR(20) DEFAULT 'active', -- 状态active-激活/inactive-停用/under_maintenance-维护中
creator VARCHAR(50) DEFAULT NULL, -- 创建者
updater VARCHAR(50) DEFAULT NULL, -- 更新者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
delete_time timestamptz DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (agent_id),
UNIQUE (optsn),
UNIQUE (agent_code)
);
CREATE INDEX idx_agent_type ON agent.tb_agent(agent_type) WHERE deleted = false;
CREATE INDEX idx_agent_published ON agent.tb_agent(is_published) WHERE deleted = false AND is_published = true;
CREATE INDEX idx_agent_category ON agent.tb_agent(category) WHERE deleted = false;
COMMENT ON TABLE agent.tb_agent IS '智能体定义表';
COMMENT ON COLUMN agent.tb_agent.agent_type IS '智能体类型bidding/customer_service/knowledge_assistant/custom';
-- 智能体会话表
DROP TABLE IF EXISTS agent.tb_agent_session CASCADE;
CREATE TABLE agent.tb_agent_session (
optsn VARCHAR(50) NOT NULL, -- 流水号
session_id VARCHAR(50) NOT NULL, -- 会话ID
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
user_id VARCHAR(50) NOT NULL, -- 用户ID
session_type VARCHAR(30) DEFAULT 'chat', -- 会话类型chat-对话/task-任务/workflow-工作流
session_name VARCHAR(255), -- 会话名称
context JSONB, -- 会话上下文
session_status VARCHAR(20) DEFAULT 'active', -- 会话状态active-活跃/paused-暂停/ended-结束
start_time timestamptz DEFAULT now(), -- 开始时间
end_time timestamptz, -- 结束时间
message_count INTEGER DEFAULT 0, -- 消息数量
token_usage INTEGER DEFAULT 0, -- Token使用量
cost DECIMAL(10,4) DEFAULT 0, -- 成本
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
creator VARCHAR(50) DEFAULT NULL, -- 创建者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (session_id),
UNIQUE (optsn),
FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
);
CREATE INDEX idx_session_agent ON agent.tb_agent_session(agent_id) WHERE deleted = false;
CREATE INDEX idx_session_user ON agent.tb_agent_session(user_id) WHERE deleted = false;
COMMENT ON TABLE agent.tb_agent_session IS '智能体会话表';
-- 智能体消息表
DROP TABLE IF EXISTS agent.tb_agent_message CASCADE;
CREATE TABLE agent.tb_agent_message (
optsn VARCHAR(50) NOT NULL, -- 流水号
message_id VARCHAR(50) NOT NULL, -- 消息ID
session_id VARCHAR(50) NOT NULL, -- 会话ID
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
role VARCHAR(20) NOT NULL, -- 角色user-用户/assistant-助手/system-系统/function-函数
content TEXT, -- 消息内容
content_type VARCHAR(30) DEFAULT 'text', -- 内容类型text-文本/image-图片/file-文件/structured-结构化数据
function_call JSONB, -- 函数调用JSON格式
function_response JSONB, -- 函数响应
token_count INTEGER, -- Token数量
model_name VARCHAR(100), -- 使用的模型
kb_references JSONB, -- 知识库引用JSON数组
metadata JSONB, -- 消息元数据
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (message_id),
UNIQUE (optsn),
FOREIGN KEY (session_id) REFERENCES agent.tb_agent_session(session_id),
FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
);
CREATE INDEX idx_msg_session ON agent.tb_agent_message(session_id, create_time) WHERE deleted = false;
CREATE INDEX idx_msg_agent ON agent.tb_agent_message(agent_id) WHERE deleted = false;
COMMENT ON TABLE agent.tb_agent_message IS '智能体消息表';
-- 智能体工具表
DROP TABLE IF EXISTS agent.tb_agent_tool CASCADE;
CREATE TABLE agent.tb_agent_tool (
optsn VARCHAR(50) NOT NULL, -- 流水号
tool_id VARCHAR(50) NOT NULL, -- 工具ID
tool_code VARCHAR(100) NOT NULL, -- 工具编码
tool_name VARCHAR(255) NOT NULL, -- 工具名称
tool_type VARCHAR(50) NOT NULL, -- 工具类型api-API调用/function-函数/plugin-插件/integration-集成
description TEXT, -- 工具描述
function_schema JSONB, -- 函数SchemaOpenAI function calling格式
api_endpoint VARCHAR(500), -- API端点
api_method VARCHAR(10), -- API方法GET/POST/PUT/DELETE
api_headers JSONB, -- API请求头
auth_type VARCHAR(30), -- 认证类型none/api_key/oauth2/bearer
auth_config JSONB, -- 认证配置(加密存储)
request_template TEXT, -- 请求模板
response_template TEXT, -- 响应模板
timeout_seconds INTEGER DEFAULT 30, -- 超时时间(秒)
retry_count INTEGER DEFAULT 3, -- 重试次数
is_enabled BOOLEAN DEFAULT true, -- 是否启用
usage_count INTEGER DEFAULT 0, -- 使用次数
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
creator VARCHAR(50) DEFAULT NULL, -- 创建者
updater VARCHAR(50) DEFAULT NULL, -- 更新者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
delete_time timestamptz DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (tool_id),
UNIQUE (optsn),
UNIQUE (tool_code)
);
CREATE INDEX idx_tool_type ON agent.tb_agent_tool(tool_type) WHERE deleted = false;
COMMENT ON TABLE agent.tb_agent_tool IS '智能体工具表';
-- API集成注册表
DROP TABLE IF EXISTS agent.tb_api_integration CASCADE;
CREATE TABLE agent.tb_api_integration (
optsn VARCHAR(50) NOT NULL, -- 流水号
integration_id VARCHAR(50) NOT NULL, -- 集成ID
integration_name VARCHAR(255) NOT NULL, -- 集成名称
integration_type VARCHAR(50) NOT NULL, -- 集成类型rest_api/soap/graphql/webhook/mq
provider VARCHAR(100), -- 提供商
base_url VARCHAR(500), -- 基础URL
version VARCHAR(20), -- API版本
auth_type VARCHAR(30) DEFAULT 'none', -- 认证类型
auth_config JSONB, -- 认证配置(加密存储)
endpoints JSONB, -- 端点列表JSON数组
rate_limit INTEGER, -- 速率限制(请求/秒)
timeout_seconds INTEGER DEFAULT 30, -- 超时时间
retry_config JSONB, -- 重试配置
health_check_url VARCHAR(500), -- 健康检查URL
health_status VARCHAR(20) DEFAULT 'unknown', -- 健康状态healthy-健康/unhealthy-不健康/unknown-未知
last_health_check timestamptz, -- 最后健康检查时间
documentation_url VARCHAR(500), -- 文档URL
is_enabled BOOLEAN DEFAULT true, -- 是否启用
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
creator VARCHAR(50) DEFAULT NULL, -- 创建者
updater VARCHAR(50) DEFAULT NULL, -- 更新者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
delete_time timestamptz DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (integration_id),
UNIQUE (optsn)
);
CREATE INDEX idx_integration_type ON agent.tb_api_integration(integration_type) WHERE deleted = false;
CREATE INDEX idx_integration_health ON agent.tb_api_integration(health_status) WHERE deleted = false;
COMMENT ON TABLE agent.tb_api_integration IS 'API集成注册表';
-- API调用日志表
DROP TABLE IF EXISTS agent.tb_api_call_log CASCADE;
CREATE TABLE agent.tb_api_call_log (
optsn VARCHAR(50) NOT NULL, -- 流水号
log_id VARCHAR(50) NOT NULL, -- 日志ID
integration_id VARCHAR(50), -- 集成ID
tool_id VARCHAR(50), -- 工具ID
agent_id VARCHAR(50), -- 智能体ID
session_id VARCHAR(50), -- 会话ID
user_id VARCHAR(50), -- 用户ID
endpoint VARCHAR(500) NOT NULL, -- 请求端点
method VARCHAR(10) NOT NULL, -- 请求方法
request_headers JSONB, -- 请求头
request_body TEXT, -- 请求体
response_status INTEGER, -- 响应状态码
response_headers JSONB, -- 响应头
response_body TEXT, -- 响应体
duration_ms INTEGER, -- 请求耗时(毫秒)
is_success BOOLEAN, -- 是否成功
error_message TEXT, -- 错误信息
retry_count INTEGER DEFAULT 0, -- 重试次数
ip_address VARCHAR(45), -- IP地址
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
PRIMARY KEY (log_id),
UNIQUE (optsn)
);
CREATE INDEX idx_api_log_integration ON agent.tb_api_call_log(integration_id, create_time DESC);
CREATE INDEX idx_api_log_agent ON agent.tb_api_call_log(agent_id, create_time DESC);
CREATE INDEX idx_api_log_status ON agent.tb_api_call_log(is_success, create_time DESC);
COMMENT ON TABLE agent.tb_api_call_log IS 'API调用日志表';
-- 智能体监控指标表
DROP TABLE IF EXISTS agent.tb_agent_metrics CASCADE;
CREATE TABLE agent.tb_agent_metrics (
optsn VARCHAR(50) NOT NULL, -- 流水号
metric_id VARCHAR(50) NOT NULL, -- 指标ID
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
metric_date DATE NOT NULL, -- 指标日期
metric_hour INTEGER, -- 指标小时0-23
total_sessions INTEGER DEFAULT 0, -- 总会话数
active_sessions INTEGER DEFAULT 0, -- 活跃会话数
total_messages INTEGER DEFAULT 0, -- 总消息数
total_tokens BIGINT DEFAULT 0, -- 总Token数
total_cost DECIMAL(10,4) DEFAULT 0, -- 总成本
avg_response_time INTEGER, -- 平均响应时间(毫秒)
success_rate DECIMAL(5,4), -- 成功率
error_count INTEGER DEFAULT 0, -- 错误次数
api_call_count INTEGER DEFAULT 0, -- API调用次数
avg_rating DECIMAL(3,2), -- 平均评分
rating_count INTEGER DEFAULT 0, -- 评分数量
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
PRIMARY KEY (metric_id),
UNIQUE (optsn),
UNIQUE (agent_id, metric_date, metric_hour),
FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
);
CREATE INDEX idx_metrics_agent_date ON agent.tb_agent_metrics(agent_id, metric_date DESC);
COMMENT ON TABLE agent.tb_agent_metrics IS '智能体监控指标表';
-- 智能体异常日志表
DROP TABLE IF EXISTS agent.tb_agent_error_log CASCADE;
CREATE TABLE agent.tb_agent_error_log (
optsn VARCHAR(50) NOT NULL, -- 流水号
log_id VARCHAR(50) NOT NULL, -- 日志ID
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
session_id VARCHAR(50), -- 会话ID
error_type VARCHAR(50) NOT NULL, -- 错误类型model_error-模型错误/api_error-API错误/timeout-超时/rate_limit-限流/other-其他
error_code VARCHAR(50), -- 错误代码
error_message TEXT NOT NULL, -- 错误信息
stack_trace TEXT, -- 堆栈跟踪
request_context JSONB, -- 请求上下文
severity VARCHAR(20) DEFAULT 'error', -- 严重级别critical-致命/error-错误/warning-警告
is_resolved BOOLEAN DEFAULT false, -- 是否已解决
resolution_notes TEXT, -- 解决方案备注
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
resolve_time timestamptz, -- 解决时间
PRIMARY KEY (log_id),
UNIQUE (optsn),
FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
);
CREATE INDEX idx_error_agent ON agent.tb_agent_error_log(agent_id, create_time DESC);
CREATE INDEX idx_error_severity ON agent.tb_agent_error_log(severity) WHERE is_resolved = false;
COMMENT ON TABLE agent.tb_agent_error_log IS '智能体异常日志表';
-- 智能体评价表
DROP TABLE IF EXISTS agent.tb_agent_rating CASCADE;
CREATE TABLE agent.tb_agent_rating (
optsn VARCHAR(50) NOT NULL, -- 流水号
rating_id VARCHAR(50) NOT NULL, -- 评价ID
agent_id VARCHAR(50) NOT NULL, -- 智能体ID
session_id VARCHAR(50), -- 会话ID
user_id VARCHAR(50) NOT NULL, -- 用户ID
rating INTEGER NOT NULL, -- 评分1-5星
dimensions JSONB, -- 分维度评分(准确性、速度、友好度等)
feedback TEXT, -- 评价反馈
tags TEXT[], -- 标签
is_anonymous BOOLEAN DEFAULT false, -- 是否匿名
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (rating_id),
UNIQUE (optsn),
FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id)
);
CREATE INDEX idx_rating_agent ON agent.tb_agent_rating(agent_id) WHERE deleted = false;
COMMENT ON TABLE agent.tb_agent_rating IS '智能体评价表';

View File

@@ -0,0 +1,209 @@
-- =============================
-- 泰豪电源AI数智化平台 - 完整数据库初始化脚本
-- 包含所有业务模块的表结构
-- =============================
-- 安装必要的扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID生成
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 全文搜索
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- GIN索引支持
-- CREATE EXTENSION IF NOT EXISTS "vector"; -- pgvector向量检索需要单独安装
-- =============================
-- 1. 系统基础模块(权限、用户、部门、角色)
-- =============================
\i createTablePermission.sql
\i createTableUser.sql
-- =============================
-- 2. 文件管理模块
-- =============================
\i createTableFile.sql
-- =============================
-- 3. 消息通知模块
-- =============================
\i createTableMessage.sql
-- =============================
-- 4. 日志模块
-- =============================
\i createTableLog.sql
-- =============================
-- 5. 配置管理模块
-- =============================
\i createTableConfig.sql
-- =============================
-- 6. 知识库管理模块
-- =============================
\i createTableKnowledge.sql
-- =============================
-- 7. 招投标智能体业务模块
-- =============================
\i createTableBidding.sql
-- =============================
-- 8. 智能客服系统业务模块
-- =============================
\i createTableCustomerService.sql
-- =============================
-- 9. 智能体管理和平台基础设施模块
-- =============================
\i createTableAgent.sql
-- =============================
-- 创建通用触发器函数
-- =============================
-- 自动更新update_time的触发器函数
CREATE OR REPLACE FUNCTION public.update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.update_time = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 为所有表添加update_time触发器的辅助函数
CREATE OR REPLACE FUNCTION public.create_update_triggers()
RETURNS void AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname IN ('sys', 'file', 'message', 'log', 'config', 'knowledge', 'bidding', 'customer_service', 'agent')
AND tablename LIKE 'tb_%'
LOOP
-- 检查表是否有update_time列
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = r.schemaname
AND table_name = r.tablename
AND column_name = 'update_time'
) THEN
-- 删除已存在的触发器
EXECUTE format('DROP TRIGGER IF EXISTS trg_%s_update_time ON %I.%I',
r.tablename, r.schemaname, r.tablename);
-- 创建新触发器
EXECUTE format('CREATE TRIGGER trg_%s_update_time
BEFORE UPDATE ON %I.%I
FOR EACH ROW
EXECUTE FUNCTION public.update_modified_column()',
r.tablename, r.schemaname, r.tablename);
RAISE NOTICE 'Created trigger for %.%', r.schemaname, r.tablename;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- 执行触发器创建
SELECT public.create_update_triggers();
-- =============================
-- 创建视图
-- =============================
-- 用户完整信息视图
CREATE OR REPLACE VIEW sys.v_user_full_info AS
SELECT
u.user_id,
u.email,
u.phone,
u.wechat_id,
u.status,
ui.avatar,
ui.full_name,
ui.gender,
ui.level,
u.create_time,
u.update_time
FROM sys.tb_sys_user u
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id
WHERE u.deleted = false AND (ui.deleted = false OR ui.deleted IS NULL);
-- 用户角色权限视图
CREATE OR REPLACE VIEW sys.v_user_role_permission AS
SELECT DISTINCT
ur.user_id,
r.role_id,
r.name AS role_name,
p.permission_id,
p.code AS permission_code,
p.name AS permission_name,
m.module_id,
m.name AS module_name
FROM sys.tb_sys_user_role ur
JOIN sys.tb_sys_role r ON ur.role_id = r.role_id
JOIN sys.tb_sys_role_permission rp ON r.role_id = rp.role_id
JOIN sys.tb_sys_permission p ON rp.permission_id = p.permission_id
LEFT JOIN sys.tb_sys_module m ON p.module_id = m.module_id
WHERE ur.deleted = false
AND r.deleted = false
AND rp.deleted = false
AND p.deleted = false;
-- 智能体使用统计视图
CREATE OR REPLACE VIEW agent.v_agent_usage_stats AS
SELECT
a.agent_id,
a.agent_name,
a.agent_type,
COUNT(DISTINCT s.session_id) AS total_sessions,
COUNT(DISTINCT s.user_id) AS unique_users,
SUM(s.message_count) AS total_messages,
AVG(s.token_usage) AS avg_token_usage,
AVG(r.rating) AS avg_rating,
COUNT(r.rating_id) AS rating_count,
MAX(s.start_time) AS last_used_time
FROM agent.tb_agent a
LEFT JOIN agent.tb_agent_session s ON a.agent_id = s.agent_id AND s.deleted = false
LEFT JOIN agent.tb_agent_rating r ON a.agent_id = r.agent_id AND r.deleted = false
WHERE a.deleted = false
GROUP BY a.agent_id, a.agent_name, a.agent_type;
-- 客服工单统计视图
CREATE OR REPLACE VIEW customer_service.v_ticket_stats AS
SELECT
t.ticket_status,
t.priority,
t.ticket_type,
COUNT(*) AS ticket_count,
AVG(EXTRACT(EPOCH FROM (t.resolution_time - t.create_time))/3600) AS avg_resolution_hours,
AVG(t.customer_rating) AS avg_rating,
COUNT(CASE WHEN t.is_overdue THEN 1 END) AS overdue_count
FROM customer_service.tb_ticket t
WHERE t.deleted = false
GROUP BY t.ticket_status, t.priority, t.ticket_type;
-- 招投标项目统计视图
CREATE OR REPLACE VIEW bidding.v_project_stats AS
SELECT
p.project_status,
p.project_type,
COUNT(*) AS project_count,
SUM(p.budget_amount) AS total_budget,
SUM(CASE WHEN p.winning_status = 'won' THEN 1 ELSE 0 END) AS won_count,
SUM(CASE WHEN p.winning_status = 'won' THEN p.winning_amount ELSE 0 END) AS total_won_amount,
AVG(CASE WHEN p.winning_status = 'won' THEN p.winning_amount / NULLIF(p.budget_amount, 0) ELSE NULL END) AS avg_win_rate
FROM bidding.tb_bidding_project p
WHERE p.deleted = false
GROUP BY p.project_status, p.project_type;
COMMENT ON VIEW sys.v_user_full_info IS '用户完整信息视图';
COMMENT ON VIEW sys.v_user_role_permission IS '用户角色权限视图';
COMMENT ON VIEW agent.v_agent_usage_stats IS '智能体使用统计视图';
COMMENT ON VIEW customer_service.v_ticket_stats IS '客服工单统计视图';
COMMENT ON VIEW bidding.v_project_stats IS '招投标项目统计视图';
-- =============================
-- 数据库初始化完成
-- =============================

View File

@@ -0,0 +1,264 @@
-- =============================
-- 招投标智能体业务模块
-- 支持:招标文件管理、投标文件生成、评分分析、流程跟踪
-- =============================
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 VARCHAR(20) DEFAULT '1.0', -- 版本号
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 VARCHAR(20) DEFAULT '1.0', -- 版本号
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 '投标模板表';

View File

@@ -0,0 +1,51 @@
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 '是否删除';

View File

@@ -0,0 +1,289 @@
-- =============================
-- 智能客服系统业务模块
-- 支持微信小程序客户咨询、智能问答、工单管理、CRM集成
-- =============================
CREATE SCHEMA IF NOT EXISTS customer_service;
-- 客户信息表
DROP TABLE IF EXISTS customer_service.tb_customer CASCADE;
CREATE TABLE customer_service.tb_customer (
optsn VARCHAR(50) NOT NULL, -- 流水号
customer_id VARCHAR(50) NOT NULL, -- 客户ID
customer_no VARCHAR(100), -- 客户编号
customer_name VARCHAR(255), -- 客户姓名
customer_type VARCHAR(30) DEFAULT 'individual', -- 客户类型individual-个人/enterprise-企业
company_name VARCHAR(255), -- 公司名称
phone VARCHAR(20), -- 电话
email VARCHAR(100), -- 邮箱
wechat_openid VARCHAR(100), -- 微信OpenID
wechat_unionid VARCHAR(100), -- 微信UnionID
avatar VARCHAR(500), -- 头像URL
gender INTEGER DEFAULT 0, -- 性别0-未知/1-男/2-女
address VARCHAR(500), -- 地址
customer_level VARCHAR(20) DEFAULT 'normal', -- 客户等级vip/important/normal/potential
customer_source VARCHAR(50), -- 客户来源wechat-微信/web-网站/phone-电话/referral-推荐
tags TEXT[], -- 客户标签数组
notes TEXT, -- 备注
crm_customer_id VARCHAR(50), -- CRM系统客户ID外部系统
last_contact_time timestamptz, -- 最后联系时间
total_consultations INTEGER DEFAULT 0, -- 咨询总次数
total_orders INTEGER DEFAULT 0, -- 订单总数
total_amount DECIMAL(18,2) DEFAULT 0, -- 总消费金额
satisfaction_score DECIMAL(3,2), -- 满意度评分1-5
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
status VARCHAR(20) DEFAULT 'active', -- 状态active-活跃/inactive-非活跃/blacklist-黑名单
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 (customer_id),
UNIQUE (optsn),
UNIQUE (wechat_openid),
UNIQUE (phone),
UNIQUE (email)
);
CREATE INDEX idx_customer_type ON customer_service.tb_customer(customer_type) WHERE deleted = false;
CREATE INDEX idx_customer_level ON customer_service.tb_customer(customer_level) WHERE deleted = false;
CREATE INDEX idx_customer_wechat ON customer_service.tb_customer(wechat_openid) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_customer IS '客户信息表';
COMMENT ON COLUMN customer_service.tb_customer.customer_level IS '客户等级vip/important/normal/potential';
-- 会话表
DROP TABLE IF EXISTS customer_service.tb_conversation CASCADE;
CREATE TABLE customer_service.tb_conversation (
optsn VARCHAR(50) NOT NULL, -- 流水号
conversation_id VARCHAR(50) NOT NULL, -- 会话ID
customer_id VARCHAR(50) NOT NULL, -- 客户ID
conversation_type VARCHAR(30) DEFAULT 'ai', -- 会话类型ai-AI客服/human-人工客服/transfer-转接
channel VARCHAR(20) DEFAULT 'wechat', -- 渠道wechat-微信/web-网页/app-应用/phone-电话
agent_id VARCHAR(50), -- 智能体ID或客服人员ID
agent_type VARCHAR(20) DEFAULT 'ai', -- 座席类型ai-AI/human-人工
session_start_time timestamptz DEFAULT now(), -- 会话开始时间
session_end_time timestamptz, -- 会话结束时间
duration_seconds INTEGER, -- 会话时长(秒)
message_count INTEGER DEFAULT 0, -- 消息数量
conversation_status VARCHAR(20) DEFAULT 'active', -- 会话状态active-进行中/closed-已结束/transferred-已转接/timeout-超时
satisfaction_rating INTEGER, -- 满意度评分1-5星
satisfaction_feedback TEXT, -- 满意度反馈
summary TEXT, -- 会话摘要AI生成
tags TEXT[], -- 会话标签
metadata JSONB, -- 会话元数据
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 (conversation_id),
UNIQUE (optsn),
FOREIGN KEY (customer_id) REFERENCES customer_service.tb_customer(customer_id)
);
CREATE INDEX idx_conv_customer ON customer_service.tb_conversation(customer_id, session_start_time DESC) WHERE deleted = false;
CREATE INDEX idx_conv_status ON customer_service.tb_conversation(conversation_status) WHERE deleted = false;
CREATE INDEX idx_conv_agent ON customer_service.tb_conversation(agent_id) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_conversation IS '会话表';
COMMENT ON COLUMN customer_service.tb_conversation.conversation_type IS '会话类型ai/human/transfer';
-- 会话消息表
DROP TABLE IF EXISTS customer_service.tb_conversation_message CASCADE;
CREATE TABLE customer_service.tb_conversation_message (
optsn VARCHAR(50) NOT NULL, -- 流水号
message_id VARCHAR(50) NOT NULL, -- 消息ID
conversation_id VARCHAR(50) NOT NULL, -- 所属会话ID
sender_type VARCHAR(20) NOT NULL, -- 发送者类型customer-客户/agent-座席/system-系统
sender_id VARCHAR(50), -- 发送者ID
message_type VARCHAR(30) NOT NULL DEFAULT 'text',-- 消息类型text-文本/image-图片/voice-语音/video-视频/file-文件/card-卡片
content TEXT, -- 消息内容
content_url VARCHAR(500), -- 内容URL图片、文件等
is_ai_generated BOOLEAN DEFAULT false, -- 是否AI生成
ai_model VARCHAR(100), -- 使用的AI模型
kb_references VARCHAR(50)[], -- 引用的知识库文档ID数组
confidence_score DECIMAL(5,4), -- AI回答置信度
sentiment VARCHAR(20), -- 情感分析positive-正面/neutral-中性/negative-负面
intent VARCHAR(100), -- 意图识别结果
is_sensitive BOOLEAN DEFAULT false, -- 是否敏感信息
read_status BOOLEAN DEFAULT false, -- 已读状态
read_time timestamptz, -- 阅读时间
metadata JSONB, -- 消息元数据
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间(发送时间)
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (message_id),
UNIQUE (optsn),
FOREIGN KEY (conversation_id) REFERENCES customer_service.tb_conversation(conversation_id)
);
CREATE INDEX idx_msg_conversation ON customer_service.tb_conversation_message(conversation_id, create_time) WHERE deleted = false;
CREATE INDEX idx_msg_sender ON customer_service.tb_conversation_message(sender_id) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_conversation_message IS '会话消息表';
COMMENT ON COLUMN customer_service.tb_conversation_message.sentiment IS '情感分析positive/neutral/negative';
-- 工单表
DROP TABLE IF EXISTS customer_service.tb_ticket CASCADE;
CREATE TABLE customer_service.tb_ticket (
optsn VARCHAR(50) NOT NULL, -- 流水号
ticket_id VARCHAR(50) NOT NULL, -- 工单ID
ticket_no VARCHAR(100) NOT NULL, -- 工单编号
customer_id VARCHAR(50) NOT NULL, -- 客户ID
conversation_id VARCHAR(50), -- 关联会话ID
ticket_type VARCHAR(50) NOT NULL, -- 工单类型consultation-咨询/complaint-投诉/suggestion-建议/repair-维修/installation-安装/other-其他
ticket_category VARCHAR(100), -- 工单分类(具体业务分类)
priority VARCHAR(20) DEFAULT 'normal', -- 优先级urgent-紧急/high-高/normal-普通/low-低
title VARCHAR(500) NOT NULL, -- 工单标题
description TEXT NOT NULL, -- 问题描述
attachments VARCHAR(50)[], -- 附件ID数组
ticket_source VARCHAR(30) DEFAULT 'ai', -- 工单来源ai-AI生成/manual-人工创建/system-系统自动
assigned_to VARCHAR(50), -- 分配给(处理人)
assigned_dept VARCHAR(50), -- 分配部门
ticket_status VARCHAR(30) DEFAULT 'pending', -- 工单状态pending-待处理/processing-处理中/resolved-已解决/closed-已关闭/cancelled-已取消
resolution TEXT, -- 解决方案
resolution_time timestamptz, -- 解决时间
close_time timestamptz, -- 关闭时间
response_time timestamptz, -- 首次响应时间
sla_deadline timestamptz, -- SLA截止时间
is_overdue BOOLEAN DEFAULT false, -- 是否逾期
customer_rating INTEGER, -- 客户评分1-5星
customer_feedback TEXT, -- 客户反馈
crm_ticket_id VARCHAR(50), -- CRM系统工单ID外部系统
sync_status VARCHAR(20) DEFAULT 'pending', -- 同步状态pending-待同步/synced-已同步/failed-失败
tags TEXT[], -- 工单标签
metadata JSONB, -- 工单元数据
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 (ticket_id),
UNIQUE (optsn),
UNIQUE (ticket_no),
FOREIGN KEY (customer_id) REFERENCES customer_service.tb_customer(customer_id)
);
CREATE INDEX idx_ticket_customer ON customer_service.tb_ticket(customer_id) WHERE deleted = false;
CREATE INDEX idx_ticket_status ON customer_service.tb_ticket(ticket_status) WHERE deleted = false;
CREATE INDEX idx_ticket_assigned ON customer_service.tb_ticket(assigned_to) WHERE deleted = false;
CREATE INDEX idx_ticket_priority ON customer_service.tb_ticket(priority) WHERE deleted = false;
CREATE INDEX idx_ticket_sla ON customer_service.tb_ticket(sla_deadline) WHERE deleted = false AND is_overdue = false;
COMMENT ON TABLE customer_service.tb_ticket IS '工单表';
COMMENT ON COLUMN customer_service.tb_ticket.ticket_type IS '工单类型consultation/complaint/suggestion/repair/installation/other';
-- 工单处理记录表
DROP TABLE IF EXISTS customer_service.tb_ticket_log CASCADE;
CREATE TABLE customer_service.tb_ticket_log (
optsn VARCHAR(50) NOT NULL, -- 流水号
log_id VARCHAR(50) NOT NULL, -- 日志ID
ticket_id VARCHAR(50) NOT NULL, -- 工单ID
action_type VARCHAR(50) NOT NULL, -- 操作类型create-创建/assign-分配/update-更新/comment-评论/resolve-解决/close-关闭/reopen-重开
action_content TEXT, -- 操作内容
old_value TEXT, -- 旧值
new_value TEXT, -- 新值
operator_id VARCHAR(50), -- 操作人ID
operator_name VARCHAR(100), -- 操作人姓名
attachments VARCHAR(50)[], -- 附件ID数组
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
PRIMARY KEY (log_id),
UNIQUE (optsn),
FOREIGN KEY (ticket_id) REFERENCES customer_service.tb_ticket(ticket_id)
);
CREATE INDEX idx_ticket_log_ticket ON customer_service.tb_ticket_log(ticket_id, create_time DESC);
COMMENT ON TABLE customer_service.tb_ticket_log IS '工单处理记录表';
-- FAQ表常见问题
DROP TABLE IF EXISTS customer_service.tb_faq CASCADE;
CREATE TABLE customer_service.tb_faq (
optsn VARCHAR(50) NOT NULL, -- 流水号
faq_id VARCHAR(50) NOT NULL, -- FAQ ID
kb_id VARCHAR(50), -- 关联知识库ID
category VARCHAR(100) NOT NULL, -- 分类
question TEXT NOT NULL, -- 问题
answer TEXT NOT NULL, -- 答案
similar_questions TEXT[], -- 相似问题数组
keywords TEXT[], -- 关键词数组
hit_count INTEGER DEFAULT 0, -- 命中次数
helpful_count INTEGER DEFAULT 0, -- 有帮助次数
unhelpful_count INTEGER DEFAULT 0, -- 无帮助次数
is_published BOOLEAN DEFAULT false, -- 是否发布
priority INTEGER DEFAULT 0, -- 优先级
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
creator VARCHAR(50) DEFAULT NULL, -- 创建者
updater VARCHAR(50) DEFAULT NULL, -- 更新者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
delete_time timestamptz DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (faq_id),
UNIQUE (optsn)
);
CREATE INDEX idx_faq_category ON customer_service.tb_faq(category) WHERE deleted = false;
CREATE INDEX idx_faq_published ON customer_service.tb_faq(is_published) WHERE deleted = false AND is_published = true;
COMMENT ON TABLE customer_service.tb_faq IS 'FAQ常见问题表';
-- 客服评价表
DROP TABLE IF EXISTS customer_service.tb_service_evaluation CASCADE;
CREATE TABLE customer_service.tb_service_evaluation (
optsn VARCHAR(50) NOT NULL, -- 流水号
evaluation_id VARCHAR(50) NOT NULL, -- 评价ID
customer_id VARCHAR(50) NOT NULL, -- 客户ID
conversation_id VARCHAR(50), -- 会话ID
ticket_id VARCHAR(50), -- 工单ID
evaluation_type VARCHAR(30) NOT NULL, -- 评价类型conversation-会话/ticket-工单/overall-整体服务
rating INTEGER NOT NULL, -- 评分1-5星
dimensions JSONB, -- 分维度评分JSON格式响应速度、专业性、态度等
feedback TEXT, -- 评价反馈
tags TEXT[], -- 评价标签
is_anonymous BOOLEAN DEFAULT false, -- 是否匿名
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (evaluation_id),
UNIQUE (optsn),
FOREIGN KEY (customer_id) REFERENCES customer_service.tb_customer(customer_id)
);
CREATE INDEX idx_eval_customer ON customer_service.tb_service_evaluation(customer_id) WHERE deleted = false;
CREATE INDEX idx_eval_rating ON customer_service.tb_service_evaluation(rating) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_service_evaluation IS '客服评价表';
-- CRM集成配置表
DROP TABLE IF EXISTS customer_service.tb_crm_config CASCADE;
CREATE TABLE customer_service.tb_crm_config (
optsn VARCHAR(50) NOT NULL, -- 流水号
config_id VARCHAR(50) NOT NULL, -- 配置ID
crm_system VARCHAR(50) NOT NULL, -- CRM系统名称
api_endpoint VARCHAR(500) NOT NULL, -- API端点
api_key VARCHAR(500), -- API密钥加密存储
auth_type VARCHAR(30) DEFAULT 'api_key', -- 认证类型api_key/oauth2/basic_auth
sync_interval INTEGER DEFAULT 3600, -- 同步间隔(秒)
sync_direction VARCHAR(30) DEFAULT 'bidirectional',-- 同步方向to_crm-单向到CRM/from_crm-单向从CRM/bidirectional-双向
field_mapping JSONB, -- 字段映射配置
sync_enabled BOOLEAN DEFAULT false, -- 是否启用同步
last_sync_time timestamptz, -- 最后同步时间
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 (config_id),
UNIQUE (optsn)
);
COMMENT ON TABLE customer_service.tb_crm_config IS 'CRM集成配置表';

View File

@@ -0,0 +1,42 @@
CREATE SCHEMA IF NOT EXISTS file;
DROP TABLE IF EXISTS file.tb_sys_file CASCADE;
CREATE TABLE file.tb_sys_file (
optsn VARCHAR(50) NOT NULL, -- 流水号
file_id VARCHAR(50) NOT NULL, -- 文件ID
name VARCHAR(255) NOT NULL, -- 文件名
path VARCHAR(255) NOT NULL, -- 文件路径
size BIGINT NOT NULL, -- 文件大小
type VARCHAR(50) NOT NULL, -- 文件类型
storage_type VARCHAR(50) NOT NULL, -- 存储类型
mime_type VARCHAR(255) NOT NULL, -- 文件MIME类型
url VARCHAR(255) NOT NULL, -- 文件URL
status VARCHAR(50) NOT NULL, -- 文件状态
dept_path VARCHAR(255) 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 (file_id),
UNIQUE (optsn)
);
COMMENT ON TABLE file.tb_sys_file IS '文件表';
COMMENT ON COLUMN file.tb_sys_file.optsn IS '流水号';
COMMENT ON COLUMN file.tb_sys_file.file_id IS '文件ID';
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 '文件URL';
COMMENT ON COLUMN file.tb_sys_file.status IS '文件状态';
COMMENT ON COLUMN file.tb_sys_file.dept_path 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.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 '是否删除';

View File

@@ -0,0 +1,138 @@
-- =============================
-- 知识库管理模块
-- 支持:招投标知识库、客服知识库、企业内部知识库
-- =============================
CREATE SCHEMA IF NOT EXISTS knowledge;
-- 知识库表(多租户知识库定义)
DROP TABLE IF EXISTS knowledge.tb_knowledge_base CASCADE;
CREATE TABLE knowledge.tb_knowledge_base (
optsn VARCHAR(50) NOT NULL, -- 流水号
kb_id VARCHAR(50) NOT NULL, -- 知识库ID
name VARCHAR(255) NOT NULL, -- 知识库名称
kb_type VARCHAR(50) NOT NULL, -- 知识库类型bidding-招投标/customer_service-客服/internal-内部协同
access_level VARCHAR(20) NOT NULL DEFAULT 'private', -- 访问级别public-公开/private-私有/internal-内部
description TEXT, -- 知识库描述
storage_path VARCHAR(500), -- 存储路径
version VARCHAR(20) DEFAULT '1.0', -- 当前版本号
config JSONB DEFAULT NULL, -- 知识库配置JSON格式索引配置、检索参数等
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态active-激活/inactive-停用/archived-归档
creator VARCHAR(50) DEFAULT NULL, -- 创建者
updater VARCHAR(50) DEFAULT NULL, -- 更新者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
delete_time timestamptz DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (kb_id),
UNIQUE (optsn)
);
CREATE INDEX idx_kb_type ON knowledge.tb_knowledge_base(kb_type) WHERE deleted = false;
CREATE INDEX idx_kb_dept_path ON knowledge.tb_knowledge_base(dept_path) WHERE deleted = false;
COMMENT ON TABLE knowledge.tb_knowledge_base IS '知识库表';
COMMENT ON COLUMN knowledge.tb_knowledge_base.kb_type IS '知识库类型bidding-招投标/customer_service-客服/internal-内部协同';
COMMENT ON COLUMN knowledge.tb_knowledge_base.access_level IS '访问级别public-公开/private-私有/internal-内部';
-- 知识文档表
DROP TABLE IF EXISTS knowledge.tb_knowledge_document CASCADE;
CREATE TABLE knowledge.tb_knowledge_document (
optsn VARCHAR(50) NOT NULL, -- 流水号
doc_id VARCHAR(50) NOT NULL, -- 文档ID
kb_id VARCHAR(50) NOT NULL, -- 所属知识库ID
title VARCHAR(500) NOT NULL, -- 文档标题
doc_type VARCHAR(50) NOT NULL, -- 文档类型text-文本/pdf/word/excel/image/video
category VARCHAR(100), -- 文档分类(自动或手动分类)
content TEXT, -- 文档内容(文本类型)
content_summary TEXT, -- 内容摘要AI生成
file_id VARCHAR(50), -- 关联文件表ID
file_path VARCHAR(500), -- 文件路径
file_size BIGINT, -- 文件大小(字节)
mime_type VARCHAR(100), -- MIME类型
version VARCHAR(20) DEFAULT '1.0', -- 文档版本号
parent_doc_id VARCHAR(50), -- 父文档ID用于版本管理
tags TEXT[], -- 文档标签数组
keywords TEXT[], -- 关键词数组AI提取
embedding_status VARCHAR(20) DEFAULT 'pending', -- 向量化状态pending-待处理/processing-处理中/completed-完成/failed-失败
embedding_model VARCHAR(100), -- 使用的向量化模型
chunk_count INTEGER DEFAULT 0, -- 切片数量
metadata JSONB DEFAULT NULL, -- 文档元数据JSON格式
source_url VARCHAR(500), -- 来源URL
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态active-激活/inactive-停用/archived-归档
creator VARCHAR(50) DEFAULT NULL, -- 创建者
updater VARCHAR(50) DEFAULT NULL, -- 更新者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
delete_time timestamptz DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (doc_id),
UNIQUE (optsn),
FOREIGN KEY (kb_id) REFERENCES knowledge.tb_knowledge_base(kb_id)
);
CREATE INDEX idx_doc_kb ON knowledge.tb_knowledge_document(kb_id) WHERE deleted = false;
CREATE INDEX idx_doc_category ON knowledge.tb_knowledge_document(category) WHERE deleted = false;
CREATE INDEX idx_doc_embedding_status ON knowledge.tb_knowledge_document(embedding_status) WHERE deleted = false;
CREATE INDEX idx_doc_tags ON knowledge.tb_knowledge_document USING GIN(tags) WHERE deleted = false;
COMMENT ON TABLE knowledge.tb_knowledge_document IS '知识文档表';
COMMENT ON COLUMN knowledge.tb_knowledge_document.embedding_status IS '向量化状态pending/processing/completed/failed';
-- 知识文档片段表用于RAG检索
DROP TABLE IF EXISTS knowledge.tb_knowledge_chunk CASCADE;
CREATE TABLE knowledge.tb_knowledge_chunk (
optsn VARCHAR(50) NOT NULL, -- 流水号
chunk_id VARCHAR(50) NOT NULL, -- 片段ID
doc_id VARCHAR(50) NOT NULL, -- 所属文档ID
kb_id VARCHAR(50) NOT NULL, -- 所属知识库ID
chunk_index INTEGER NOT NULL, -- 片段索引(在文档中的顺序)
content TEXT NOT NULL, -- 片段内容
content_length INTEGER, -- 内容长度
embedding vector(1536), -- 向量嵌入假设使用OpenAI 1536维
chunk_type VARCHAR(20) DEFAULT 'text', -- 片段类型text-文本/table-表格/image-图片
position_info JSONB, -- 位置信息(页码、坐标等)
metadata JSONB, -- 片段元数据
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
creator VARCHAR(50) DEFAULT NULL, -- 创建者
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
update_time timestamptz DEFAULT NULL, -- 更新时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (chunk_id),
UNIQUE (optsn),
FOREIGN KEY (doc_id) REFERENCES knowledge.tb_knowledge_document(doc_id),
FOREIGN KEY (kb_id) REFERENCES knowledge.tb_knowledge_base(kb_id)
);
CREATE INDEX idx_chunk_doc ON knowledge.tb_knowledge_chunk(doc_id) WHERE deleted = false;
CREATE INDEX idx_chunk_kb ON knowledge.tb_knowledge_chunk(kb_id) WHERE deleted = false;
-- 向量检索索引需要安装pgvector扩展
-- CREATE INDEX idx_chunk_embedding ON knowledge.tb_knowledge_chunk USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
COMMENT ON TABLE knowledge.tb_knowledge_chunk IS '知识文档片段表RAG检索';
COMMENT ON COLUMN knowledge.tb_knowledge_chunk.embedding IS '向量嵌入需要pgvector扩展';
-- 知识访问日志表
DROP TABLE IF EXISTS knowledge.tb_knowledge_access_log CASCADE;
CREATE TABLE knowledge.tb_knowledge_access_log (
optsn VARCHAR(50) NOT NULL, -- 流水号
log_id VARCHAR(50) NOT NULL, -- 日志ID
kb_id VARCHAR(50), -- 知识库ID
doc_id VARCHAR(50), -- 文档ID
user_id VARCHAR(50) NOT NULL, -- 用户ID
access_type VARCHAR(20) NOT NULL, -- 访问类型view-查看/download-下载/search-搜索/edit-编辑
query_text TEXT, -- 搜索查询文本
result_count INTEGER, -- 搜索结果数量
ip_address VARCHAR(45), -- IP地址
user_agent TEXT, -- 用户代理
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
create_time timestamptz NOT NULL DEFAULT now(), -- 创建时间
PRIMARY KEY (log_id),
UNIQUE (optsn)
);
CREATE INDEX idx_access_log_user ON knowledge.tb_knowledge_access_log(user_id, create_time DESC);
CREATE INDEX idx_access_log_kb ON knowledge.tb_knowledge_access_log(kb_id, create_time DESC);
COMMENT ON TABLE knowledge.tb_knowledge_access_log IS '知识访问日志表';

View File

@@ -0,0 +1,39 @@
CREATE SCHEMA IF NOT EXISTS log;
DROP TABLE IF EXISTS log.tb_sys_log CASCADE;
CREATE TABLE log.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, -- 创建者
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 log.tb_sys_log IS '系统日志表';
COMMENT ON COLUMN log.tb_sys_log.optsn IS '流水号';
COMMENT ON COLUMN log.tb_sys_log.log_id IS '日志ID';
COMMENT ON COLUMN log.tb_sys_log.type IS '日志类型';
COMMENT ON COLUMN log.tb_sys_log.level IS '日志级别';
COMMENT ON COLUMN log.tb_sys_log.module IS '日志模块';
COMMENT ON COLUMN log.tb_sys_log.message IS '日志消息';
COMMENT ON COLUMN log.tb_sys_log.data IS '日志数据';
COMMENT ON COLUMN log.tb_sys_log.creator IS '创建者';
COMMENT ON COLUMN log.tb_sys_log.dept_path IS '部门全路径';
COMMENT ON COLUMN log.tb_sys_log.updater IS '更新者';
COMMENT ON COLUMN log.tb_sys_log.create_time IS '日志创建时间';
COMMENT ON COLUMN log.tb_sys_log.update_time IS '日志更新时间';
COMMENT ON COLUMN log.tb_sys_log.delete_time IS '日志删除时间';
COMMENT ON COLUMN log.tb_sys_log.deleted IS '是否删除';

View File

@@ -0,0 +1,161 @@
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, -- 消息状态
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.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等
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等';
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(20) NOT NULL, -- 渠道编码app/sms/email/wechat/dingtalk等
channel_name VARCHAR(100) NOT NULL, -- 渠道名称
channel_desc VARCHAR(255) DEFAULT NULL, -- 渠道描述
config TEXT DEFAULT NULL, -- 渠道配置JSON格式如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 '是否删除';
-- 插入默认渠道配置
INSERT INTO message.tb_message_channel (optsn, channel_id, channel_code, channel_name, channel_desc, status, priority)
VALUES
('CHANNEL_APP_001', 'CH_APP', 'app', '应用内消息', '系统内部消息推送', 'enabled', 100),
('CHANNEL_SMS_001', 'CH_SMS', 'sms', '短信通知', '手机短信推送', 'disabled', 80),
('CHANNEL_EMAIL_001', 'CH_EMAIL', 'email', '邮件通知', '电子邮件推送', 'disabled', 60),
('CHANNEL_WECHAT_001', 'CH_WECHAT', 'wechat', '微信通知', '微信公众号/企业微信推送', 'disabled', 70),
('CHANNEL_DINGTALK_001', 'CH_DINGTALK', 'dingtalk', '钉钉通知', '钉钉工作通知推送', 'disabled', 70);

View File

@@ -0,0 +1,461 @@
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 '部门私有角色的所属部门IDscope=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_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_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, -- 视图类型
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)
);
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 '视图类型';
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 时,是否包含其子级(便于“所有子级可查看”场景)
-- allowtrue=允许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;

View File

@@ -0,0 +1,120 @@
-- 创建 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
password varchar(128) NOT NULL, -- 密码(建议存储 bcrypt/argon2 哈希)
email varchar(100), -- 电子邮件
phone varchar(20), -- 电话号码
wechat_id varchar(50), -- 微信ID
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, -- 状态
PRIMARY KEY (user_id),
UNIQUE (optsn),
UNIQUE (email),
UNIQUE (phone),
UNIQUE (wechat_id)
);
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, -- 性别
family_name varchar(50), -- 姓
given_name varchar(50), -- 名
full_name varchar(100), -- 全名
level integer DEFAULT 1, -- 等级
id_card varchar(50), -- 身份证号
address varchar(255), -- 地址
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.family_name IS '';
COMMENT ON COLUMN sys.tb_sys_user_info.given_name IS '';
COMMENT ON COLUMN sys.tb_sys_user_info.full_name 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.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 btreePostgres 默认即为 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 '创建时间';

View File

@@ -0,0 +1,11 @@
-- 按顺序执行初始化
\i createDB.sql -- 创建数据库和基础扩展
\i createTableUser.sql -- 用户表相关(核心表)
\i createTablePermission.sql -- 权限相关(核心表)
\i createTableFile.sql -- 文件管理
\i createTableMessage.sql -- 消息管理
\i createTableConfig.sql -- 配置管理
\i createTableLog.sql -- 日志管理
-- 初始化基础数据(如果有)
-- \i initBaseData.sql -- 取消注释如果需要初始化基础数据

View File

@@ -0,0 +1,43 @@
-- 初始化系统常用配置(与 config.tb_sys_config 对应)
-- 仅插入常用示例,可按需调整 value/remark
INSERT INTO config.tb_sys_config (
optsn, config_id, key, value, type, description, "group", "order", status, remark,
creator, dept_path, updater, create_time, update_time, delete_time, deleted
) VALUES
-- 站点与品牌
('CFG-0001', 'cfg_site_name', 'site.name', 'urban-lifeline 平台', 'string', '站点名称', 'site', 10, 'enabled', '展示在标题/登录/页脚', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0002', 'cfg_site_logo', 'site.logo', '/static/logo.png', 'string', '站点Logo地址', 'site', 20, 'enabled', '相对或绝对URL', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0003', 'cfg_site_icp', 'site.icp', '', 'string', 'ICP备案号', 'site', 30, 'enabled', '页脚展示', 'system', NULL, NULL, now(), NULL, NULL, false),
-- 国际化与时区
('CFG-0101', 'cfg_i18n_locale', 'i18n.defaultLocale', 'zh-CN', 'string', '默认语言', 'i18n', 10, 'enabled', '如 zh-CN/en-US', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0102', 'cfg_timezone', 'system.timezone', 'Asia/Shanghai', 'string', '系统默认时区', 'i18n', 20, 'enabled', '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}', 'json', '密码策略', 'security', 10, 'enabled', 'JSON结构', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0202', 'cfg_jwt_exp', 'security.jwt.expireSeconds','86400', 'number', 'JWT过期秒数', 'security', 20, 'enabled', '默认24小时', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0203', 'cfg_session_timeout', 'security.session.timeoutMinutes','30', 'number', '会话超时(分钟)', 'security', 30, 'enabled', '空闲登出', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0204', 'cfg_signup_enabled', 'security.signup.enabled','false', 'bool', '是否开放注册', 'security', 40, 'enabled', '生产建议关闭', 'system', NULL, NULL, now(), NULL, NULL, false),
-- 存储与上传
('CFG-0301', 'cfg_upload_max', 'upload.maxSizeMB', '50', 'number', '单文件最大上传(MB)', 'storage', 10, 'enabled', '前后端需一致校验', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0302', 'cfg_storage_backend', 'storage.backend', 'local', 'string', '存储后端(local/minio/s3)', 'storage', 20, 'enabled', '本地/MinIO/S3等', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0303', 'cfg_storage_base', 'storage.basePath', '/data/urban-lifeline', 'string', '本地存储基路径', 'storage', 30, 'enabled', '当 backend=local', 'system', NULL, NULL, now(), NULL, NULL, false),
-- 通知(邮件/SMS
('CFG-0401', 'cfg_mail_host', 'mail.smtp.host', '', 'string', 'SMTP主机', 'notify', 10, 'disabled','留空为未配置', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0402', 'cfg_mail_port', 'mail.smtp.port', '465', 'number', 'SMTP端口', 'notify', 20, 'disabled','SSL常用465', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0403', 'cfg_mail_from', 'mail.from', '', 'string', '发件人邮箱', 'notify', 30, 'disabled','如 no-reply@x.com', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0411', 'cfg_sms_provider', 'sms.provider', '', 'string', '短信服务商', 'notify', 40, 'disabled','如 aliyun/tencent', 'system', NULL, NULL, now(), NULL, NULL, false),
-- 日志与审计
('CFG-0501', 'cfg_log_level', 'log.level', 'INFO', 'string', '系统日志级别', 'log', 10, 'enabled', 'DEBUG/INFO/WARN/ERROR', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0502', 'cfg_audit_retention', 'audit.retentionDays', '90', 'number', '审计日志保留天数', 'log', 20, 'enabled', '合规按需调整', 'system', NULL, NULL, now(), NULL, NULL, false),
-- 平台特性
('CFG-0601', 'cfg_maintenance', 'platform.maintenance', 'false', 'bool', '维护模式开关', 'platform', 10, 'enabled', 'true时仅管理员可用', 'system', NULL, NULL, now(), NULL, NULL, false),
('CFG-0602', 'cfg_feature_acl_policy', 'feature.acl.policy', 'enabled', 'string', 'ACL策略开关', 'platform', 20, 'enabled', 'enabled/disabled', 'system', NULL, NULL, now(), NULL, NULL, false);

View File

@@ -0,0 +1,549 @@
-- =============================
-- 数据库优化补丁脚本
-- 基于现有表结构的增强和修改
-- =============================
-- =============================
-- 1. 用户模块优化
-- =============================
-- 1.1 移除登录日志表中的敏感密码字段
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'sys'
AND table_name = 'tb_sys_login_log'
AND column_name = 'password'
) THEN
ALTER TABLE sys.tb_sys_login_log DROP COLUMN password;
RAISE NOTICE '已移除登录日志表的密码字段';
END IF;
END $$;
-- 1.2 创建用户部门关联表
CREATE TABLE IF NOT EXISTS sys.tb_sys_user_dept (
optsn VARCHAR(50) NOT NULL,
user_id VARCHAR(50) NOT NULL,
dept_id VARCHAR(50) NOT NULL,
is_primary BOOLEAN DEFAULT false,
position VARCHAR(100),
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, dept_id),
UNIQUE (optsn),
FOREIGN KEY (user_id) REFERENCES sys.tb_sys_user(user_id),
FOREIGN KEY (dept_id) REFERENCES sys.tb_sys_dept(dept_id)
);
COMMENT ON TABLE sys.tb_sys_user_dept IS '用户部门关联表';
COMMENT ON COLUMN sys.tb_sys_user_dept.is_primary IS '是否主部门';
COMMENT ON COLUMN sys.tb_sys_user_dept.position IS '职位';
CREATE INDEX IF NOT EXISTS idx_user_dept_user ON sys.tb_sys_user_dept(user_id) WHERE deleted = false;
CREATE INDEX IF NOT EXISTS idx_user_dept_dept ON sys.tb_sys_user_dept(dept_id) WHERE deleted = false;
-- 1.3 用户表添加主部门字段
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'sys'
AND table_name = 'tb_sys_user'
AND column_name = 'primary_dept_id'
) THEN
ALTER TABLE sys.tb_sys_user ADD COLUMN primary_dept_id VARCHAR(50);
COMMENT ON COLUMN sys.tb_sys_user.primary_dept_id IS '主部门ID';
END IF;
END $$;
-- =============================
-- 2. 权限模块优化
-- =============================
-- 2.1 角色表添加排序字段
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'sys'
AND table_name = 'tb_sys_role'
AND column_name = 'order_num'
) THEN
ALTER TABLE sys.tb_sys_role ADD COLUMN order_num INTEGER DEFAULT 0;
COMMENT ON COLUMN sys.tb_sys_role.order_num IS '排序号';
END IF;
END $$;
-- 2.2 权限表添加权限类型字段
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'sys'
AND table_name = 'tb_sys_permission'
AND column_name = 'permission_type'
) THEN
ALTER TABLE sys.tb_sys_permission ADD COLUMN permission_type VARCHAR(20) DEFAULT 'action';
COMMENT ON COLUMN sys.tb_sys_permission.permission_type IS '权限类型action-操作权限/data-数据权限/menu-菜单权限';
END IF;
END $$;
-- =============================
-- 3. 文件模块扩展
-- =============================
-- 3.1 文件表添加版本管理字段
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'file'
AND table_name = 'tb_sys_file'
AND column_name = 'version'
) THEN
ALTER TABLE file.tb_sys_file
ADD COLUMN version VARCHAR(20) DEFAULT '1.0',
ADD COLUMN parent_file_id VARCHAR(50),
ADD COLUMN is_latest BOOLEAN DEFAULT true;
COMMENT ON COLUMN file.tb_sys_file.version IS '文件版本号';
COMMENT ON COLUMN file.tb_sys_file.parent_file_id IS '父文件ID版本链';
COMMENT ON COLUMN file.tb_sys_file.is_latest IS '是否最新版本';
END IF;
END $$;
-- 3.2 文件表添加分类和标签
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'file'
AND table_name = 'tb_sys_file'
AND column_name = 'category'
) THEN
ALTER TABLE file.tb_sys_file
ADD COLUMN category VARCHAR(100),
ADD COLUMN tags TEXT[],
ADD COLUMN metadata JSONB;
COMMENT ON COLUMN file.tb_sys_file.category IS '文件分类';
COMMENT ON COLUMN file.tb_sys_file.tags IS '文件标签数组';
COMMENT ON COLUMN file.tb_sys_file.metadata IS '文件元数据';
END IF;
END $$;
-- 3.3 创建文件关联表
CREATE TABLE IF NOT EXISTS file.tb_file_relation (
optsn VARCHAR(50) NOT NULL,
relation_id VARCHAR(50) NOT NULL,
file_id VARCHAR(50) NOT NULL,
object_type VARCHAR(50) NOT NULL,
object_id VARCHAR(50) NOT NULL,
relation_type VARCHAR(30) DEFAULT 'attachment',
order_num INTEGER DEFAULT 0,
creator VARCHAR(50) DEFAULT NULL,
updater VARCHAR(50) DEFAULT NULL,
create_time timestamptz NOT NULL DEFAULT now(),
update_time timestamptz DEFAULT NULL,
delete_time timestamptz DEFAULT NULL,
deleted BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (relation_id),
UNIQUE (optsn),
FOREIGN KEY (file_id) REFERENCES file.tb_sys_file(file_id)
);
COMMENT ON TABLE file.tb_file_relation IS '文件关联表';
COMMENT ON COLUMN file.tb_file_relation.object_type IS '对象类型bidding_project/ticket/document等';
COMMENT ON COLUMN file.tb_file_relation.object_id IS '对象ID';
COMMENT ON COLUMN file.tb_file_relation.relation_type IS '关联类型attachment-附件/avatar-头像/banner-横幅';
CREATE INDEX IF NOT EXISTS idx_file_relation_object
ON file.tb_file_relation(object_type, object_id) WHERE deleted = false;
CREATE INDEX IF NOT EXISTS idx_file_relation_file
ON file.tb_file_relation(file_id) WHERE deleted = false;
-- =============================
-- 4. 消息模块增强
-- =============================
-- 4.1 创建消息模板表
CREATE TABLE IF NOT EXISTS 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,
title_template TEXT,
content_template TEXT NOT NULL,
variables JSONB,
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.template_type IS '模板类型system-系统/business-业务';
COMMENT ON COLUMN message.tb_message_template.variables IS '模板变量定义';
CREATE INDEX IF NOT EXISTS idx_template_type
ON message.tb_message_template(template_type) WHERE deleted = false;
-- =============================
-- 5. 日志模块优化
-- =============================
-- 5.1 日志表添加trace_id用于链路追踪
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'log'
AND table_name = 'tb_sys_log'
AND column_name = 'trace_id'
) THEN
ALTER TABLE log.tb_sys_log
ADD COLUMN trace_id VARCHAR(50),
ADD COLUMN span_id VARCHAR(50),
ADD COLUMN parent_span_id VARCHAR(50);
COMMENT ON COLUMN log.tb_sys_log.trace_id IS '追踪ID用于分布式追踪';
COMMENT ON COLUMN log.tb_sys_log.span_id IS '跨度ID';
COMMENT ON COLUMN log.tb_sys_log.parent_span_id IS '父跨度ID';
CREATE INDEX idx_log_trace ON log.tb_sys_log(trace_id) WHERE trace_id IS NOT NULL;
END IF;
END $$;
-- =============================
-- 6. 全文搜索索引
-- =============================
-- 6.1 为知识文档标题创建全文搜索索引
CREATE INDEX IF NOT EXISTS idx_knowledge_doc_title_trgm
ON knowledge.tb_knowledge_document USING gin(title gin_trgm_ops)
WHERE deleted = false;
-- 6.2 为招标项目名称创建全文搜索索引
CREATE INDEX IF NOT EXISTS idx_project_name_trgm
ON bidding.tb_bidding_project USING gin(project_name gin_trgm_ops)
WHERE deleted = false;
-- 6.3 为客户姓名创建全文搜索索引
CREATE INDEX IF NOT EXISTS idx_customer_name_trgm
ON customer_service.tb_customer USING gin(customer_name gin_trgm_ops)
WHERE deleted = false;
-- =============================
-- 7. JSONB字段优化索引
-- =============================
-- 7.1 智能体配置JSONB索引
CREATE INDEX IF NOT EXISTS idx_agent_model_config_gin
ON agent.tb_agent USING gin(model_config)
WHERE deleted = false;
-- 7.2 工单元数据JSONB索引
CREATE INDEX IF NOT EXISTS idx_ticket_metadata_gin
ON customer_service.tb_ticket USING gin(metadata)
WHERE deleted = false;
-- =============================
-- 8. 性能优化视图
-- =============================
-- 8.1 用户完整权限视图包含ACL
CREATE OR REPLACE VIEW sys.v_user_full_permissions AS
WITH user_roles AS (
-- 用户直接拥有的角色权限
SELECT DISTINCT
ur.user_id,
p.permission_id,
p.code AS permission_code,
p.name AS permission_name,
'role' AS source_type,
r.role_id AS source_id
FROM sys.tb_sys_user_role ur
JOIN sys.tb_sys_role r ON ur.role_id = r.role_id
JOIN sys.tb_sys_role_permission rp ON r.role_id = rp.role_id
JOIN sys.tb_sys_permission p ON rp.permission_id = p.permission_id
WHERE ur.deleted = false
AND r.deleted = false
AND rp.deleted = false
AND p.deleted = false
),
user_acls AS (
-- 用户的ACL权限
SELECT DISTINCT
principal_id AS user_id,
object_type || ':' || object_id AS permission_id,
object_type || '_' ||
CASE
WHEN (permission & 1) = 1 THEN 'read'
WHEN (permission & 2) = 2 THEN 'write'
WHEN (permission & 4) = 4 THEN 'exec'
END AS permission_code,
'ACL permission on ' || object_type AS permission_name,
'acl' AS source_type,
acl_id AS source_id
FROM sys.tb_sys_acl
WHERE principal_type = 'user'
AND allow = true
AND deleted = false
)
SELECT * FROM user_roles
UNION ALL
SELECT * FROM user_acls;
COMMENT ON VIEW sys.v_user_full_permissions IS '用户完整权限视图包含角色权限和ACL权限';
-- 8.2 智能体实时状态视图
CREATE OR REPLACE VIEW agent.v_agent_realtime_status AS
SELECT
a.agent_id,
a.agent_name,
a.agent_type,
a.status,
COUNT(DISTINCT s.session_id) FILTER (WHERE s.session_status = 'active') AS active_sessions,
COUNT(DISTINCT s.user_id) FILTER (WHERE s.start_time > now() - interval '24 hours') AS daily_users,
COALESCE(SUM(s.message_count) FILTER (WHERE s.start_time > now() - interval '1 hour'), 0) AS hourly_messages,
COALESCE(AVG(r.rating) FILTER (WHERE r.create_time > now() - interval '7 days'), 0) AS weekly_avg_rating
FROM agent.tb_agent a
LEFT JOIN agent.tb_agent_session s ON a.agent_id = s.agent_id AND s.deleted = false
LEFT JOIN agent.tb_agent_rating r ON a.agent_id = r.agent_id AND r.deleted = false
WHERE a.deleted = false
GROUP BY a.agent_id, a.agent_name, a.agent_type, a.status;
COMMENT ON VIEW agent.v_agent_realtime_status IS '智能体实时状态视图';
-- 8.3 工单处理效率视图
CREATE OR REPLACE VIEW customer_service.v_ticket_efficiency AS
SELECT
DATE(t.create_time) AS stat_date,
t.ticket_type,
t.priority,
COUNT(*) AS total_tickets,
COUNT(*) FILTER (WHERE t.ticket_status = 'resolved') AS resolved_tickets,
COUNT(*) FILTER (WHERE t.is_overdue) AS overdue_tickets,
AVG(EXTRACT(EPOCH FROM (t.response_time - t.create_time))/60) AS avg_response_minutes,
AVG(EXTRACT(EPOCH FROM (t.resolution_time - t.create_time))/3600) FILTER (WHERE t.resolution_time IS NOT NULL) AS avg_resolution_hours,
AVG(t.customer_rating) FILTER (WHERE t.customer_rating IS NOT NULL) AS avg_rating
FROM customer_service.tb_ticket t
WHERE t.deleted = false
AND t.create_time > now() - interval '90 days'
GROUP BY DATE(t.create_time), t.ticket_type, t.priority;
COMMENT ON VIEW customer_service.v_ticket_efficiency IS '工单处理效率统计视图';
-- =============================
-- 9. 审计触发器增强
-- =============================
-- 9.1 创建审计日志函数
CREATE OR REPLACE FUNCTION public.audit_trigger_func()
RETURNS TRIGGER AS $$
DECLARE
audit_data JSONB;
BEGIN
IF (TG_OP = 'DELETE') THEN
audit_data := jsonb_build_object(
'operation', 'DELETE',
'table', TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
'old_data', row_to_json(OLD)
);
INSERT INTO log.tb_sys_log (
optsn, log_id, type, level, module, message, data, creator
) VALUES (
'AUDIT_' || gen_random_uuid()::text,
gen_random_uuid()::text,
'audit',
'info',
TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
'Record deleted',
audit_data,
COALESCE(current_setting('app.current_user_id', true), 'system')
);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
audit_data := jsonb_build_object(
'operation', 'UPDATE',
'table', TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
'old_data', row_to_json(OLD),
'new_data', row_to_json(NEW)
);
INSERT INTO log.tb_sys_log (
optsn, log_id, type, level, module, message, data, creator
) VALUES (
'AUDIT_' || gen_random_uuid()::text,
gen_random_uuid()::text,
'audit',
'info',
TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
'Record updated',
audit_data,
COALESCE(current_setting('app.current_user_id', true), 'system')
);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
audit_data := jsonb_build_object(
'operation', 'INSERT',
'table', TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
'new_data', row_to_json(NEW)
);
INSERT INTO log.tb_sys_log (
optsn, log_id, type, level, module, message, data, creator
) VALUES (
'AUDIT_' || gen_random_uuid()::text,
gen_random_uuid()::text,
'audit',
'info',
TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
'Record created',
audit_data,
COALESCE(current_setting('app.current_user_id', true), 'system')
);
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION public.audit_trigger_func() IS '审计日志触发器函数';
-- =============================
-- 10. 数据归档函数
-- =============================
-- 10.1 创建日志归档函数
CREATE OR REPLACE FUNCTION public.archive_old_logs(
p_months_to_keep INTEGER DEFAULT 6
)
RETURNS TABLE(
archived_count INTEGER,
deleted_count INTEGER
) AS $$
DECLARE
v_cutoff_date TIMESTAMPTZ;
v_archived INTEGER := 0;
v_deleted INTEGER := 0;
BEGIN
v_cutoff_date := now() - (p_months_to_keep || ' months')::INTERVAL;
-- 归档系统日志
CREATE TABLE IF NOT EXISTS log.tb_sys_log_archived (LIKE log.tb_sys_log INCLUDING ALL);
WITH moved_rows AS (
INSERT INTO log.tb_sys_log_archived
SELECT * FROM log.tb_sys_log
WHERE create_time < v_cutoff_date
RETURNING *
)
SELECT COUNT(*) INTO v_archived FROM moved_rows;
DELETE FROM log.tb_sys_log
WHERE create_time < v_cutoff_date;
GET DIAGNOSTICS v_deleted = ROW_COUNT;
RETURN QUERY SELECT v_archived, v_deleted;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION public.archive_old_logs IS '归档旧日志数据';
-- 10.2 创建API调用日志归档函数
CREATE OR REPLACE FUNCTION agent.archive_api_logs(
p_months_to_keep INTEGER DEFAULT 3
)
RETURNS INTEGER AS $$
DECLARE
v_cutoff_date TIMESTAMPTZ;
v_archived INTEGER;
BEGIN
v_cutoff_date := now() - (p_months_to_keep || ' months')::INTERVAL;
CREATE TABLE IF NOT EXISTS agent.tb_api_call_log_archived (LIKE agent.tb_api_call_log INCLUDING ALL);
WITH moved_rows AS (
INSERT INTO agent.tb_api_call_log_archived
SELECT * FROM agent.tb_api_call_log
WHERE create_time < v_cutoff_date
RETURNING *
)
SELECT COUNT(*) INTO v_archived FROM moved_rows;
DELETE FROM agent.tb_api_call_log
WHERE create_time < v_cutoff_date;
RETURN v_archived;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION agent.archive_api_logs IS '归档旧API调用日志';
-- =============================
-- 11. 性能监控函数
-- =============================
-- 11.1 表膨胀检查函数
CREATE OR REPLACE FUNCTION public.check_table_bloat()
RETURNS TABLE(
schemaname TEXT,
tablename TEXT,
table_size_mb NUMERIC,
bloat_size_mb NUMERIC,
bloat_ratio NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
s.schemaname::TEXT,
s.tablename::TEXT,
ROUND(pg_total_relation_size(s.schemaname || '.' || s.tablename)::NUMERIC / 1024 / 1024, 2) AS table_size_mb,
ROUND((pg_total_relation_size(s.schemaname || '.' || s.tablename) - pg_relation_size(s.schemaname || '.' || s.tablename))::NUMERIC / 1024 / 1024, 2) AS bloat_size_mb,
ROUND(((pg_total_relation_size(s.schemaname || '.' || s.tablename) - pg_relation_size(s.schemaname || '.' || s.tablename))::NUMERIC / NULLIF(pg_total_relation_size(s.schemaname || '.' || s.tablename), 0) * 100), 2) AS bloat_ratio
FROM pg_tables s
WHERE s.schemaname IN ('sys', 'file', 'message', 'log', 'config', 'knowledge', 'bidding', 'customer_service', 'agent')
ORDER BY pg_total_relation_size(s.schemaname || '.' || s.tablename) DESC;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION public.check_table_bloat IS '检查表膨胀情况';
-- =============================
-- 执行完成提示
-- =============================
DO $$
BEGIN
RAISE NOTICE '==============================';
RAISE NOTICE '数据库优化补丁执行完成!';
RAISE NOTICE '==============================';
RAISE NOTICE '已完成以下优化:';
RAISE NOTICE '1. 用户模块:移除敏感字段、添加部门关联';
RAISE NOTICE '2. 权限模块:添加排序和类型字段';
RAISE NOTICE '3. 文件模块:版本管理、分类标签、关联表';
RAISE NOTICE '4. 消息模块:消息模板表';
RAISE NOTICE '5. 日志模块:链路追踪支持';
RAISE NOTICE '6. 全文搜索添加GIN索引';
RAISE NOTICE '7. JSONB优化添加GIN索引';
RAISE NOTICE '8. 性能视图:用户权限、智能体状态、工单效率';
RAISE NOTICE '9. 审计增强:完整审计触发器';
RAISE NOTICE '10. 数据归档日志和API调用归档函数';
RAISE NOTICE '11. 监控工具:表膨胀检查函数';
RAISE NOTICE '==============================';
END $$;

View File

@@ -0,0 +1,137 @@
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

View File

@@ -0,0 +1,62 @@
version: '3.8'
# urban-lifeline 开发环境 Docker Compose 配置
# 使用主机的 MySQL 数据库
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" # Nacos Console (Web UI) - 映射到主机 8081
- "8848:8848" # Nacos HTTP API
- "9848:9848" # Nacos gRPC 客户端请求
- "9849:9849" # Nacos gRPC 服务间同步
environment:
# 运行模式
MODE: standalone
# 数据库配置 - 使用主机 MySQL
SPRING_DATASOURCE_PLATFORM: mysql
MYSQL_SERVICE_HOST: host.docker.internal # Docker Desktop
# MYSQL_SERVICE_HOST: 172.17.0.1 # Linux 使用此行,注释上一行
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 配置
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
# Linux 需要添加 extra_hosts 来访问主机服务
extra_hosts:
- "host.docker.internal:host-gateway"

View File

@@ -0,0 +1,3 @@
# 注意点
1. mysql 开发 bind-address 必须是 0.0.0.0 否则无法连接
2. root % 密码必须是 123456

View File

@@ -0,0 +1,210 @@
#!/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 ""

View File

@@ -0,0 +1,27 @@
#!/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 ""

View File

@@ -0,0 +1,182 @@
/*
* 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
);

27
urbanLifelineServ/.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Compiled class file
*.class
# Log file
*.log
# 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

View File

@@ -0,0 +1,88 @@
# 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 和 OFFSETPostgreSQL/MySQL 兼容)
5. 时间字段使用 TIMESTAMP 类型
6. 字符串字段使用 VARCHAR 类型
## 自定义
如需修改模板,编辑 `.vscode/mybatis-xml.code-snippets` 文件即可。

23
urbanLifelineServ/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "SystemApp",
"request": "launch",
"mainClass": "org.xyzh.SystemApp",
"projectName": "system",
"cwd": "${workspaceFolder}/system",
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dspring.profiles.active=dev"
],
"env": {
"SPRING_PROFILES_ACTIVE": "dev"
},
"console": "integratedTerminal"
}
]
}

View File

@@ -0,0 +1,313 @@
{
"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 基础列定义"
}
}

15
urbanLifelineServ/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "automatic",
"maven.view": "hierarchical",
"tabSize": 4,
"java.debug.settings.onBuildFailureProceed": true,
"java.configuration.maven.userSettings": null,
"java.import.maven.enabled": true,
"java.project.referencedLibraries": [
"**/*.jar"
],
"maven.terminal.useJavaHome": true,
"java.configuration.runtimes": [],
"Codegeex.RepoIndex": true
}

View File

@@ -0,0 +1,41 @@
<?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-all</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-auth</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-file</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-message</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-system</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
<?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-auth</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,74 @@
package org.xyzh.api.auth.service;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.core.domain.LoginParam;
import org.xyzh.common.core.domain.ResultDomain;
import jakarta.servlet.http.HttpServletRequest;
/**
* @description 认证服务接口
* @filename AuthService.java
* @author yslg
* @copyright yslg
* @since 2025-11-03
*/
public interface AuthService {
/**
* @description 登录
* @param LoginParam loginParam 登录参数
* @param HttpServletRequest request 请求
* @return ResultDomain<LoginDomain> 登录结果
* @author yslg
* @since 2025-11-03
*/
ResultDomain<LoginDomain> login(LoginParam loginParam, HttpServletRequest request);
/**
* @description 刷新token
* @param HttpServletRequest request 请求
* @return ResultDomain<LoginDomain> 刷新token结果
* @author yslg
* @since 2025-11-03
*/
ResultDomain<LoginDomain> refreshToken(HttpServletRequest request);
/**
* @description 登出
* @param HttpServletRequest request 请求
* @return ResultDomain<LoginDomain> 登出结果
* @author yslg
* @since 2025-11-03
*/
ResultDomain<LoginDomain> logout(HttpServletRequest request);
/**
* @description 根据验证码类型获取验证码
* @param LoginParam loginParam 登录参数
* @return ResultDomain<LoginDomain> 获取验证码结果
* @author yslg
* @since 2025-11-03
*/
ResultDomain<LoginDomain> getCaptcha(LoginParam loginParam);
/**
* @description 根据 token 获取登录信息,供其他服务调用以判定用户登录状态
* @param token 鉴权 token例如 JWT 或会话 token
* @return ResultDomain<LoginDomain> 登录信息,若 token 无效或未登录则在 ResultDomain 中返回相应状态/错误
* @author yslg
* @since 2025-11-03
*/
ResultDomain<LoginDomain> getLoginByToken(String token);
/**
* @description 简单校验 token 是否有效(用于快速判断是否已登录)
* @param token 鉴权 token
* @return ResultDomain<Boolean> true 表示有效/已登录false 表示无效/未登录
* @author yslg
* @since 2025-11-03
*/
ResultDomain<Boolean> isTokenValid(String token);
}

View File

@@ -0,0 +1,22 @@
<?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-file</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,49 @@
package org.xyzh.api.file.dto;
import org.xyzh.common.dto.BaseDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统文件DTO
* @filename TbSysFileDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统文件DTO")
public class TbSysFileDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "文件ID (主键)")
private String fileId;
@Schema(description = "文件名")
private String name;
@Schema(description = "文件路径")
private String path;
@Schema(description = "文件大小(字节)")
private Long size;
@Schema(description = "文件类型")
private String type;
@Schema(description = "存储类型")
private String storageType;
@Schema(description = "MIME 类型")
private String mimeType;
@Schema(description = "文件访问 URL")
private String url;
@Schema(description = "文件状态")
private String status;
}

View File

@@ -0,0 +1,86 @@
package org.xyzh.api.file.service;
import org.springframework.web.multipart.MultipartFile;
import org.xyzh.api.file.dto.TbSysFileDTO;
import org.xyzh.common.core.domain.ResultDomain;
/**
* @description 文件服务接口
* @filename FileService.java
* @author yslg
* @copyright yslg
* @since 2025-11-03
*/
public interface FileService {
/**
* @description 上传文件
* @param file 文件对象
* @param module 所属模块
* @param businessId 业务ID
* @return ResultDomain<TbSysFile> 上传结果,包含文件信息
* @author yslg
* @since 2025-10-16
*/
ResultDomain<TbSysFileDTO> uploadFile(MultipartFile file, String module, String businessId);
/**
* @description 批量上传文件
* @param files 文件对象列表
* @param module 所属模块
* @param businessId 业务ID
* @param uploader 上传者用户ID可选
* @return ResultDomain<TbSysFile> 上传结果,包含文件信息列表
* @author yslg
* @since 2025-11-03
*/
ResultDomain<TbSysFileDTO> batchUploadFiles(MultipartFile[] files, String module, String businessId, String uploader);
/**
* @description 删除文件
* @param fileId 文件ID
* @return ResultDomain<String> 删除结果
* @author yslg
* @since 2025-11-03
*/
ResultDomain<Boolean> deleteFile(String fileId);
/**
* @description 批量删除文件(逻辑删除)
* @param fileIds 文件ID列表
* @return ResultDomain<TbSysFileDTO> 删除结果
* @author yslg
* @since 2025-10-16
*/
ResultDomain<TbSysFileDTO> batchDeleteFiles(String[] fileIds);
/**
* @description 下载文件
* @param fileId 文件ID
* @return ResultDomain<byte[]> 文件字节数组
* @author yslg
* @since 2025-11-03
*/
ResultDomain<byte[]> downloadFile(String fileId);
/**
* @description 根据文件ID查询文件信息
* @param fileId 文件ID
* @return ResultDomain<TbSysFile> 文件信息
* @author yslg
* @since 2025-11-03
*/
ResultDomain<TbSysFileDTO> getFileById(String fileId);
/**
* @description 保存临时文件
* @param file 文件对象
* @param module 所属模块
* @param businessId 业务ID
* @return ResultDomain<TbSysFileDTO> 保存结果
* @author yslg
* @since 2025-11-03
*/
ResultDomain<TbSysFileDTO> saveTempFile(MultipartFile file, String module, String businessId);
}

View File

@@ -0,0 +1,22 @@
<?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-log</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,51 @@
package org.xyzh.api.log.dto;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
/**
* @description 系统日志DTO
* @filename TbSysLogDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统日志DTO")
public class TbSysLogDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "日志ID")
private String logId;
@Schema(description = "日志类型")
private Integer type;
@Schema(description = "日志级别")
private String level;
@Schema(description = "模块")
private String module;
@Schema(description = "IP地址")
private String ipAddress;
@Schema(description = "IP来源")
private String ip_source;
@Schema(description = "浏览器")
private String browser;
@Schema(description = "操作系统")
private String os;
@Schema(description = "日志消息")
private String message;
@Schema(description = "日志数据")
private JSONObject data;
}

View File

@@ -0,0 +1,56 @@
package org.xyzh.api.log.dto;
import java.util.Date;
import com.alibaba.fastjson2.annotation.JSONField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
/**
* @description 系统登录日志DTO
* @filename TbSysLoginLogDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统登录日志DTO")
public class TbSysLoginLogDTO extends BaseDTO {
@Schema(description = "用户ID")
private String userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "IP地址")
private String ipAddress;
@Schema(description = "IP来源")
private String ipSource;
@Schema(description = "浏览器")
private String browser;
@Schema(description = "操作系统")
private String os;
@Schema(description = "密码")
private String password;
@Schema(description = "登录时间")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date loginTime;
@Schema(description = "状态")
private Integer status;
@Schema(description = "错误次数")
private Integer errorCount;
@Schema(description = "消息")
private String message;
}

View File

@@ -0,0 +1,22 @@
<?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-message</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,43 @@
package org.xyzh.api.message.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 消息渠道配置DTO
* @filename TbMessageChannelDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "消息渠道配置DTO")
public class TbMessageChannelDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "渠道ID")
private String channelId;
@Schema(description = "渠道编码app/sms/email/wechat/dingtalk等")
private String channelCode;
@Schema(description = "渠道名称")
private String channelName;
@Schema(description = "渠道描述")
private String channelDesc;
@Schema(description = "渠道配置JSON格式")
private String config;
@Schema(description = "渠道状态enabled-启用/disabled-禁用/maintenance-维护中")
private String status;
@Schema(description = "优先级(数字越大优先级越高)")
private Integer priority;
}

View File

@@ -0,0 +1,37 @@
package org.xyzh.api.message.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 消息DTO
* @filename TbMessageDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "消息DTO")
public class TbMessageDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "消息ID")
private String messageId;
@Schema(description = "消息标题")
private String title;
@Schema(description = "消息内容")
private String content;
@Schema(description = "消息类型")
private String type;
@Schema(description = "消息状态")
private String status;
}

View File

@@ -0,0 +1,33 @@
package org.xyzh.api.message.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 消息发送范围DTO
* @filename TbMessageRangeDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "消息发送范围DTO")
public class TbMessageRangeDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "消息ID")
private String messageId;
@Schema(description = "目标类型user-指定用户/dept-部门/role-角色/all-全员")
private String targetType;
@Schema(description = "目标ID用户、部门、角色ID等all类型时为空")
private String targetId;
@Schema(description = "发送渠道app/sms/email/wechat/dingtalk等")
private String channel;
}

View File

@@ -0,0 +1,41 @@
package org.xyzh.api.message.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
/**
* @description 用户消息接收记录DTO
* @filename TbMessageReceiverDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户消息接收记录DTO")
public class TbMessageReceiverDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "消息ID")
private String messageId;
@Schema(description = "用户ID")
private String userId;
@Schema(description = "接收渠道app/sms/email/wechat/dingtalk等")
private String channel;
@Schema(description = "消息状态unread-未读/read-已读/handled-已处理/deleted-已删除")
private String status;
@Schema(description = "阅读时间")
private ZonedDateTime readTime;
@Schema(description = "处理时间")
private ZonedDateTime handleTime;
}

View File

@@ -0,0 +1,135 @@
package org.xyzh.api.message.service;
import org.xyzh.api.message.dto.TbMessageDTO;
import org.xyzh.api.message.vo.MessageVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageParam;
/**
* @description 消息服务接口
* @filename MessageService.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
public interface MessageService {
//================= 用户查看消息列表 =================
/**
* @description 获取我的消息列表
* @param userId 用户ID
* @return ResultDomain<TbMessageDTO> 消息列表
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> getMyMessageList(TbMessageDTO filter);
/**
* @description 获取我的消息分页列表
* @param TbMessageDTO filter 消息过滤条件
* @param PageParam pageParam 分页参数
* @return ResultDomain<TbMessageDTO> 消息分页列表
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> getMyMessagePage(TbMessageDTO filter, PageParam pageParam);
/**
* @description 获取我的消息详情
* @param messageId 消息ID
* @return ResultDomain<TbMessageDTO> 消息详情
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> getMyMessageDetail(String messageId);
// ================= 用户处理消息 =================
/**
* @description 用户处理消息
* @param messageId 消息ID
* @param status 消息状态
* @return ResultDomain<TbMessageDTO> 消息处理结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> handleMessage(String messageId, String status);
// ================= 管理员查看消息列表 =================
/**
* @description 获取消息列表
* @param TbMessageDTO filter 消息过滤条件
* @return ResultDomain<TbMessageDTO> 消息列表
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> getMessageList(TbMessageDTO filter);
/**
* @description 获取消息分页列表
* @param TbMessageDTO filter 消息过滤条件
* @param PageParam pageParam 分页参数
* @return ResultDomain<TbMessageDTO> 消息分页列表
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> getMessagePage(TbMessageDTO filter, PageParam pageParam);
/**
* @description 获取消息详情
* @param messageId 消息ID
* @return ResultDomain<TbMessageDTO> 消息详情
* @author yslg
* @since 2025-11-05
*/
ResultDomain<MessageVO> getMessageDetail(String messageId);
// ================= 管理员处理消息 =================
/**
* @description 创建消息
* @param MessageVO messageVO 消息VO
* @return ResultDomain<TbMessageDTO> 创建结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> createMessage(MessageVO messageVO);
/**
* @description 更新消息
* @param MessageVO messageVO 消息VO
* @return ResultDomain<TbMessageDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> updateMessage(MessageVO messageVO);
/**
* @description 删除消息
* @param messageId 消息ID
* @return ResultDomain<TbMessageDTO> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteMessage(String messageId);
/**
* @description 发送消息
* @param MessageVO messageVO 消息VO
* @return ResultDomain<MessageVO> 发送结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<MessageVO> sendMessage(MessageVO messageVO);
/**
* @description 撤回消息
* @param messageId 消息ID
* @return ResultDomain<TbMessageDTO> 撤回结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbMessageDTO> withdrawMessage(String messageId);
}

View File

@@ -0,0 +1,48 @@
package org.xyzh.api.message.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
/**
* @description 消息发送范围和渠道VORange和Channel的平铺组合
* @filename MessageRangeChannelVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "消息发送范围和渠道VO")
public class MessageRangeChannelVO extends BaseVO {
// ========== Range相关字段 ==========
@Schema(description = "消息ID")
private String messageId;
@Schema(description = "目标类型user-指定用户/dept-部门/role-角色/all-全员")
private String targetType;
@Schema(description = "目标ID用户、部门、角色ID等all类型时为空")
private String targetId;
@Schema(description = "目标名称(用户名、部门名、角色名等,用于前端展示)")
private String targetName;
// ========== Channel相关字段 ==========
@Schema(description = "渠道编码app/sms/email/wechat/dingtalk等")
private String channelCode;
@Schema(description = "渠道名称")
private String channelName;
@Schema(description = "渠道描述")
private String channelDesc;
@Schema(description = "渠道状态enabled-启用/disabled-禁用/maintenance-维护中")
private String channelStatus;
@Schema(description = "渠道优先级(数字越大优先级越高)")
private Integer channelPriority;
}

View File

@@ -0,0 +1,44 @@
package org.xyzh.api.message.vo;
import java.util.List;
import org.xyzh.api.message.dto.TbMessageReceiverDTO;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @description 消息VO包含消息详情和发送范围
* @filename MessageVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "消息VO")
public class MessageVO extends BaseVO {
@Schema(description = "消息ID")
private String messageId;
@Schema(description = "消息标题")
private String title;
@Schema(description = "消息内容")
private String content;
@Schema(description = "消息类型")
private String type;
@Schema(description = "消息状态")
private String status;
@Schema(description = "消息发送范围列表")
private List<MessageRangeChannelVO> messageRanges;
@Schema(description = "消息接收记录列表(管理员查看时使用)")
private List<TbMessageReceiverDTO> messageReceivers;
}

View File

@@ -0,0 +1,238 @@
# 消息系统设计说明
## 📊 数据表结构
### 1. tb_message - 消息主表
存储消息的基本信息
**主要字段:**
- `message_id` - 消息ID主键
- `title` - 消息标题
- `content` - 消息内容
- `type` - 消息类型
- `status` - 消息状态
### 2. tb_message_range - 消息发送范围定义表
定义消息要发送给哪些对象,通过什么渠道
**主要字段:**
- `message_id` - 消息ID
- `target_type` - 目标类型user/dept/role/all
- `target_id` - 目标ID用户、部门、角色ID等
- `channel` - 发送渠道app/sms/email/wechat等
- **唯一约束:** (message_id, target_type, target_id, channel)
**使用示例:**
- 发送给部门001通过app`{target_type: 'dept', target_id: 'D001', channel: 'app'}`
- 发送给部门001通过sms`{target_type: 'dept', target_id: 'D001', channel: 'sms'}`
- 发送给全员通过app`{target_type: 'all', target_id: null, channel: 'app'}`
### 3. tb_message_receiver - 用户消息接收记录表
记录每个用户实际收到的消息及处理状态
**主要字段:**
- `message_id` - 消息ID
- `user_id` - 用户ID
- `channel` - 接收渠道
- `status` - 消息状态unread/read/handled/deleted
- `read_time` - 阅读时间
- `handle_time` - 处理时间
- **唯一约束:** (message_id, user_id, channel)
**索引:**
- `idx_message_user_user_status` - 快速查询用户消息列表
- `idx_message_user_message` - 快速查询消息的接收情况
### 4. tb_message_channel - 消息渠道配置表
管理各种消息发送渠道的配置
**主要字段:**
- `channel_id` - 渠道ID主键
- `channel_code` - 渠道编码app/sms/email/wechat/dingtalk
- `channel_name` - 渠道名称
- `config` - 渠道配置JSON格式存储API密钥等
- `status` - 渠道状态enabled/disabled/maintenance
- `priority` - 优先级
**预置渠道:**
- app - 应用内消息(已启用)
- sms - 短信通知(已禁用)
- email - 邮件通知(已禁用)
- wechat - 微信通知(已禁用)
- dingtalk - 钉钉通知(已禁用)
## 📦 DTO/VO 结构
### TbMessageDTO
消息基本信息DTO
**字段:**
- messageId
- title
- content
- type
- status
### TbMessageRangeDTO
消息发送范围DTO
**字段:**
- messageId
- targetType - 目标类型
- targetId - 目标ID
- channel - 发送渠道
### TbMessageReceiverDTO
用户消息接收记录DTO
**字段:**
- messageId
- userId
- channel
- status
- readTime
- handleTime
### TbMessageChannelDTO
消息渠道配置DTO
**字段:**
- channelId
- channelCode
- channelName
- channelDesc
- config
- status
- priority
### MessageVO
消息视图对象(用于创建和查看消息)
**字段:**
- messageId
- title
- content
- type
- status
- createTime
- creator
- messageRanges - 消息发送范围列表
- messageReceivers - 消息接收记录列表(管理员查看时使用)
## 🔄 业务流程
### 1. 创建并发送消息
```java
// 创建消息
MessageVO messageVO = new MessageVO();
messageVO.setTitle("系统维护通知");
messageVO.setContent("系统将于今晚22:00进行维护");
messageVO.setType("notice");
// 定义发送范围发给IT部门通过app和email
List<TbMessageRangeDTO> ranges = new ArrayList<>();
ranges.add(new TbMessageRangeDTO() {{
setTargetType("dept");
setTargetId("DEPT_IT");
setChannel("app");
}});
ranges.add(new TbMessageRangeDTO() {{
setTargetType("dept");
setTargetId("DEPT_IT");
setChannel("email");
}});
messageVO.setMessageRanges(ranges);
// 发送消息
messageService.sendMessage(messageVO);
```
**系统处理:**
1.`tb_message` 中创建消息记录
2.`tb_message_range` 中保存发送范围
3. 根据 `target_type``target_id` 查询具体用户列表
4.`tb_message_receiver` 中为每个用户创建接收记录
5. 根据 `channel` 调用相应的渠道服务发送消息
### 2. 用户查看消息列表
```sql
-- 查询用户未读消息
SELECT m.*, r.status, r.read_time, r.channel
FROM message.tb_message m
JOIN message.tb_message_receiver r ON m.message_id = r.message_id
WHERE r.user_id = 'USER_001'
AND r.status = 'unread'
AND r.deleted = false
ORDER BY m.create_time DESC;
```
### 3. 用户处理消息
```java
// 标记消息为已读
messageService.handleMessage(messageId, "read");
// 标记消息为已处理
messageService.handleMessage(messageId, "handled");
```
**系统处理:**
- 更新 `tb_message_receiver` 表的 `status` 字段
- 根据状态更新 `read_time``handle_time`
### 4. 管理员查看消息统计
```sql
-- 查询某条消息的发送统计
SELECT
r.channel,
r.status,
COUNT(*) as count
FROM message.tb_message_receiver r
WHERE r.message_id = 'MSG_001'
AND r.deleted = false
GROUP BY r.channel, r.status;
```
## 🎯 设计优势
1. **职责分离**
- `tb_message_range` - 定义发送规则
- `tb_message_receiver` - 记录实际接收情况
2. **多渠道支持**
- 同一消息可通过多个渠道发送
- 渠道配置独立管理
- 易于扩展新渠道
3. **灵活的目标定义**
- 支持按用户、部门、角色、全员发送
- 同一目标可使用不同渠道
4. **完整的状态跟踪**
- 记录阅读时间、处理时间
- 支持已读/未读/已处理/已删除等状态
5. **性能优化**
- 合理的索引设计
- 支持高效的用户消息查询
## 📝 注意事项
1. **数据一致性**
- 发送消息时,确保 `tb_message_range``tb_message_receiver` 的事务一致性
2. **渠道验证**
- 发送前检查 `tb_message_channel` 中渠道是否启用
- 根据 `priority` 选择备用渠道
3. **性能考虑**
- 全员消息target_type='all')需要异步处理
- 大量用户时分批创建 `tb_message_receiver` 记录
4. **软删除**
- 所有表都使用软删除deleted字段
- 查询时注意添加 `WHERE deleted = false` 条件

View File

@@ -0,0 +1,22 @@
<?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-system</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,230 @@
package org.xyzh.api.system.service;
import org.xyzh.api.system.vo.PermissionVO;
import org.xyzh.api.system.vo.UserDeptRoleVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbSysDeptDTO;
import org.xyzh.common.dto.sys.TbSysDeptRoleDTO;
import org.xyzh.common.dto.sys.TbSysRoleDTO;
/**
* @description 部门角色服务接口
* @filename DeptRoleService.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
public interface DeptRoleService {
// ================= 部门管理 =================
/**
* @description 插入部门
* @param deptDTO 部门DTO
* @return ResultDomain<TbSysDeptDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysDeptDTO> insertDept(TbSysDeptDTO deptDTO);
/**
* @description 更新部门
* @param deptDTO 部门DTO
* @return ResultDomain<TbSysDeptDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysDeptDTO> updateDept(TbSysDeptDTO deptDTO);
/**
* @description 根据ID删除部门
* @param deptDTO 部门DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteDept(TbSysDeptDTO deptDTO);
/**
* @description 根据ID查询部门
* @param filter 部门VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDept(UserDeptRoleVO filter);
/**
* @description 根据条件查询部门列表
* @param filter 部门VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptList(UserDeptRoleVO filter);
/**
* @description 根据条件查询部门分页列表
* @param pageRequest 部门VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptPage(PageRequest<UserDeptRoleVO> pageRequest);
/**
* @description 获取部门树
* @param filter 部门VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptTree(UserDeptRoleVO filter);
// ================= 角色管理 =================
/**
* @description 插入角色
* @param roleDTO 角色DTO
* @return ResultDomain<TbSysRoleDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysRoleDTO> insertRole(TbSysRoleDTO roleDTO);
/**
* @description 更新角色
* @param roleDTO 角色DTO
* @return ResultDomain<TbSysRoleDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysRoleDTO> updateRole(TbSysRoleDTO roleDTO);
/**
* @description 根据ID删除角色
* @param roleDTO 角色DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteRole(TbSysRoleDTO roleDTO);
/**
* @description 根据ID查询角色
* @param filter 角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getRole(UserDeptRoleVO filter);
/**
* @description 根据条件查询角色列表
* @param filter 角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getRoleList(UserDeptRoleVO filter);
/**
* @description 根据条件查询角色分页列表
* @param pageRequest 角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getRolePage(PageRequest<UserDeptRoleVO> pageRequest);
/**
* @description 根据部门ID获取角色列表
* @param deptId 部门ID
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getRoleListByDeptId(String deptId);
/**
* @description 根据用户ID获取角色列表
* @param userId 用户ID
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getRoleListByUserId(String userId);
// ================= 部门角色关联管理 =================
/**
* @description 插入部门角色关联
* @param deptRoleDTO 部门角色DTO
* @return ResultDomain<TbSysDeptRoleDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysDeptRoleDTO> insertDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 更新部门角色关联
* @param deptRoleDTO 部门角色DTO
* @return ResultDomain<TbSysDeptRoleDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysDeptRoleDTO> updateDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 根据ID删除部门角色关联
* @param deptRoleDTO 部门角色DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 根据ID查询部门角色关联
* @param filter 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptRole(UserDeptRoleVO filter);
/**
* @description 根据条件查询部门角色关联列表
* @param filter 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptRoleList(UserDeptRoleVO filter);
/**
* @description 根据条件查询部门角色关联分页列表
* @param pageRequest 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptRolePage(PageRequest<UserDeptRoleVO> pageRequest);
// ==================== 角色权限关联 ================================
/**
* @description 设置角色的权限
* @param permissionVO 权限VO roleId对应多个permissionId
* @return 返回值描述
* @author yslg
* @since 2025-11-10
*/
ResultDomain<PermissionVO> setRolePermission(PermissionVO permissionVO);
/**
* @description 获取角色的权限列表
* @param permissionVO 权限VO
* @return 返回值描述
* @author yslg
* @since 2025-11-10
*/
ResultDomain<PermissionVO> getRolePermissionList(PermissionVO permissionVO);
}

View File

@@ -0,0 +1,148 @@
package org.xyzh.api.system.service;
import org.xyzh.api.system.vo.PermissionVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.dto.sys.TbSysModuleDTO;
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysRolePermissionDTO;
import org.xyzh.common.core.page.PageRequest;
/**
* @description 模块权限服务接口
* @filename ModulePermissionService.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
public interface ModulePermissionService {
// ================= 模块管理 =================
/**
* @description 插入模块
* @param moduleDTO 模块DTO
* @return ResultDomain<TbSysModuleDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysModuleDTO> insertModule(TbSysModuleDTO moduleDTO);
/**
* @description 更新模块
* @param moduleDTO 模块DTO
* @return ResultDomain<TbSysModuleDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysModuleDTO> updateModule(TbSysModuleDTO moduleDTO);
/**
* @description 根据ID删除模块
* @param moduleDTO 模块DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteModule(TbSysModuleDTO moduleDTO);
/**
* @description 获取模块分页数据
* @param
* @return 返回值描述
* @author yslg
* @since 2025-11-10
*/
ResultDomain<PermissionVO> getModulePage(PageRequest<PermissionVO> pageRequest);
/**
* @description 查询模块列表
* @param filter 模块VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getModuleList(PermissionVO filter);
// ================= 权限管理 =================
/**
* @description 插入权限
* @param permissionDTO 权限DTO
* @return ResultDomain<TbSysPermissionDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysPermissionDTO> insertPermission(TbSysPermissionDTO permissionDTO);
/**
* @description 更新权限
* @param permissionDTO 权限DTO
* @return ResultDomain<TbSysPermissionDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysPermissionDTO> updatePermission(TbSysPermissionDTO permissionDTO);
/**
* @description 根据ID删除权限
* @param permissionDTO 权限DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deletePermission(TbSysPermissionDTO permissionDTO);
/**
* @description 根据模块ID获取权限列表
* @param moduleId 模块ID
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getPermissionListByModuleId(String moduleId);
// ================= 模块权限查询 =================
/**
* @description 根据条件查询模块权限
* @param filter 模块权限VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getModulePermission(PermissionVO filter);
/**
* @description 根据条件查询模块权限列表
* @param filter 模块权限VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getModulePermissionList(PermissionVO filter);
/**
* @description 根据条件查询模块权限分页列表
* @param pageRequest 模块权限VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getModulePermissionPage(PageRequest<PermissionVO> pageRequest);
/**
* @description 根据角色ID获取模块权限列表
* @param roleId 角色ID
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getModulePermissionListByRoleId(String roleId);
/**
* @description 根据用户ID获取用户的所有权限
* @param userId 用户ID
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getUserPermissions(String userId);
}

View File

@@ -0,0 +1,88 @@
package org.xyzh.api.system.service;
import org.xyzh.api.system.vo.SysConfigVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbSysConfigDTO;
/**
* @description 系统配置服务接口
* @filename SysConfigService.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
public interface SysConfigService {
/**
* @description 插入系统配置
* @param configDTO 系统配置DTO
* @return ResultDomain<TbSysConfigDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysConfigDTO> insertConfig(TbSysConfigDTO configDTO);
/**
* @description 更新系统配置
* @param configDTO 系统配置DTO
* @return ResultDomain<TbSysConfigDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysConfigDTO> updateConfig(TbSysConfigDTO configDTO);
/**
* @description 根据ID删除系统配置
* @param configDTO 系统配置DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteConfig(TbSysConfigDTO configDTO);
/**
* @description 根据ID查询系统配置
* @param filter 系统配置VO
* @return ResultDomain<SysConfigVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysConfigVO> getConfig(SysConfigVO filter);
/**
* @description 根据条件查询系统配置列表
* @param filter 系统配置VO
* @return ResultDomain<SysConfigVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysConfigVO> getConfigList(SysConfigVO filter);
/**
* @description 根据条件查询系统配置分页列表
* @param filter 系统配置VO
* @return ResultDomain<SysConfigVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysConfigVO> getConfigPage(PageRequest<SysConfigVO> filter);
/**
* @description 根据配置键获取配置值
* @param key 配置键
* @return ResultDomain<String> 配置值
* @author yslg
* @since 2025-11-05
*/
ResultDomain<String> getConfigValueByKey(String key);
/**
* @description 根据模块ID获取配置列表
* @param moduleId 模块ID
* @return ResultDomain<SysConfigVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysConfigVO> getConfigListByModuleId(String moduleId);
}

View File

@@ -0,0 +1,180 @@
package org.xyzh.api.system.service;
import org.xyzh.api.system.vo.SysUserVO;
import org.xyzh.api.system.vo.UserDeptRoleVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
/**
* @description 用户服务接口
* @filename SysUserService.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
public interface SysUserService {
// ================= 用户基本信息管理 =================
/**
* @description 插入用户
* @param userVO 用户VO
* @return ResultDomain<TbSysUserDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysUserDTO> insertUser(SysUserVO userVO);
/**
* @description 更新用户
* @param userVO 用户VO
* @return ResultDomain<TbSysUserDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysUserDTO> updateUser(SysUserVO userVO);
/**
* @description 根据ID删除用户
* @param TbSysUserDTO userDTO 用户DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteUser(TbSysUserDTO userDTO);
/**
* @description 根据ID查询用户
* @param filter 用户VO
* @return ResultDomain<SysUserVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysUserVO> getUser(SysUserVO filter);
/**
* @description 用户登录查询
* @param filter 用户VO
* @return ResultDomain<SysUserVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysUserVO> getLoginUser(SysUserVO filter);
/**
* @description 根据条件查询用户列表
* @param filter 用户VO
* @return ResultDomain<SysUserVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysUserVO> getUserList(SysUserVO filter);
/**
* @description 根据条件查询用户分页列表
* @param filter 用户VO
* @return ResultDomain<SysUserVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysUserVO> getUserPage(PageRequest<SysUserVO> pageRequest);
/**
* @description 根据用户名查询用户
* @param username 用户名
* @return ResultDomain<SysUserVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysUserVO> getUserByUsername(String username);
/**
* @description 更新用户密码
* @param userId 用户ID
* @param oldPassword 旧密码
* @param newPassword 新密码
* @return ResultDomain<Boolean> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> updateUserPassword(String userId, String oldPassword, String newPassword);
/**
* @description 重置用户密码
* @param userId 用户ID
* @return ResultDomain<String> 新密码
* @author yslg
* @since 2025-11-05
*/
ResultDomain<String> resetUserPassword(String userId);
/**
* @description 更新用户状态
* @param userId 用户ID
* @param status 状态
* @return ResultDomain<Boolean> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> updateUserStatus(String userId, String status);
// ================= 用户详细信息管理 =================
/**
* @description 更新用户详细信息
* @param userInfoDTO 用户详细信息DTO
* @return ResultDomain<TbSysUserInfoDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysUserInfoDTO> updateUserInfo(TbSysUserInfoDTO userInfoDTO);
/**
* @description 根据用户ID获取用户详细信息
* @param userId 用户ID
* @return ResultDomain<SysUserVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<SysUserVO> getUserInfo(String userId);
// ================= 用户角色关联管理 =================
/**
* @description 添加用户角色关联
* @param userRoleDTO 用户角色DTO
* @return ResultDomain<TbSysUserRoleDTO> 添加结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysUserRoleDTO> addUserRole(TbSysUserRoleDTO userRoleDTO);
/**
* @description 删除用户角色关联
* @param userRoleDTO 用户角色DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> removeUserRole(TbSysUserRoleDTO userRoleDTO);
/**
* @description 批量设置用户角色
* @param userId 用户ID
* @param[] roleIds 角色ID数组
* @return ResultDomain<Boolean> 设置结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> setUserRoles(String userId, String[] roleIds);
// ================= 用户完整信息查询 =================
/**
* @description 获取用户完整信息(包含部门和角色)
* @param userId 用户ID
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getUserWithDeptRole(String userId);
}

View File

@@ -0,0 +1,99 @@
package org.xyzh.api.system.service;
import org.xyzh.api.system.vo.PermissionVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.dto.sys.TbSysViewDTO;
import org.xyzh.common.dto.sys.TbSysViewPermissionDTO;
import org.xyzh.common.core.page.PageRequest;
/**
* @description 视图服务接口
* @filename ViewService.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
public interface ViewService {
// ================= 视图管理 =================
/**
* @description 插入视图
* @param viewDTO 视图DTO
* @return ResultDomain<TbSysViewDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysViewDTO> insertView(TbSysViewDTO viewDTO);
/**
* @description 更新视图
* @param viewDTO 视图DTO
* @return ResultDomain<TbSysViewDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysViewDTO> updateView(TbSysViewDTO viewDTO);
/**
* @description 根据ID删除视图
* @param viewDTO 视图DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteView(TbSysViewDTO viewDTO);
/**
* @description 根据ID查询视图
* @param filter 视图VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getView(PermissionVO filter);
/**
* @description 根据条件查询视图列表
* @param filter 视图VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getViewList(PermissionVO filter);
/**
* @description 根据条件查询视图分页列表
* @param filter 视图VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getViewPage(PageRequest<PermissionVO> filter);
/**
* @description 获取视图树(包含子视图)
* @param filter 视图VO
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getViewTree(PermissionVO filter);
// ================= 视图权限关联管理 =================
/**
* @description 设置视图权限
* @param permissionVO 视图ID 和权限ID数组
* @return ResultDomain<PermissionVO> 设置结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> setViewPermissions(PermissionVO permissionVO);
/**
* @description 获取视图的权限列表
* @param permissionVO 视图
* @return ResultDomain<PermissionVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<PermissionVO> getViewPermissionList(PermissionVO permissionVO);
}

View File

@@ -0,0 +1,71 @@
package org.xyzh.api.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
/**
* @description 访问控制列表视图对象
* @filename AclVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "访问控制列表视图对象")
public class AclVO extends BaseVO {
// TbSysAclDTO对应字段
@Schema(description = "权限ID")
private String aclId;
@Schema(description = "对象类型article/file/course/...")
private String objectType;
@Schema(description = "对象ID")
private String objectId;
@Schema(description = "主体类型user/dept/role")
private String principalType;
@Schema(description = "主体ID")
private String principalId;
@Schema(description = "当主体为role且限定到某部门时的部门ID支持某部门的某角色")
private String principalDeptId;
@Schema(description = "权限位1读 2写 4执行")
private Integer permission;
@Schema(description = "允许或显式拒绝", defaultValue = "true")
private Boolean allow = true;
@Schema(description = "是否包含子级对dept/role生效", defaultValue = "false")
private Boolean includeDescendants = false;
// TbSysAclPolicyDTO对应字段
@Schema(description = "策略ID")
private String policyId;
@Schema(description = "策略名称")
private String policyName;
@Schema(description = "对象类型article/file/course/..")
private String policyObjectType;
@Schema(description = "编辑层级规则parent_only/parent_or_same_admin/owner_only/none")
private String editHierarchyRule;
@Schema(description = "可见层级规则 children_all/children_specified/none")
private String viewHierarchyRule;
@Schema(description = "默认权限无显式ACL时应用", defaultValue = "0")
private Integer defaultPermission = 0;
@Schema(description = "默认是否允许", defaultValue = "true")
private boolean defaultAllow = true;
@Schema(description = "是否默认应用到子级", defaultValue = "true")
private boolean applyToChildren = true;
}

View File

@@ -0,0 +1,221 @@
package org.xyzh.api.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import java.util.List;
import org.xyzh.common.dto.sys.TbSysModuleDTO;
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysViewDTO;
/**
* @description 权限视图对象
* @filename PermissionVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "权限视图对象")
public class PermissionVO extends BaseVO {
// TbSysDeptDTO对应字段
@Schema(description = "部门ID")
private String deptId;
@Schema(description = "部门名称")
private String deptName;
@Schema(description = "父级部门ID")
private String deptParentId;
@Schema(description = "部门描述")
private String deptDescription;
// TbSysRoleDTO对应字段
@Schema(description = "角色ID")
private String roleId;
@Schema(description = "角色名称")
private String roleName;
@Schema(description = "角色描述")
private String roleDescription;
@Schema(description = "角色作用域")
private String roleScope;
@Schema(description = "所属部门ID")
private String roleOwnerDeptId;
@Schema(description = "角色状态")
private boolean roleStatus;
// TbSysModuleDTO对应字段
@Schema(description = "模块ID")
private String moduleId;
@Schema(description = "模块名称")
private String moduleName;
@Schema(description = "模块描述")
private String moduleDescription;
// TbSysPermissionDTO对应字段
@Schema(description = "权限ID")
private String permissionId;
@Schema(description = "权限名称")
private String permissionName;
@Schema(description = "权限代码")
private String permissionCode;
@Schema(description = "权限描述")
private String permissionDescription;
@Schema(description = "权限状态")
private String permissionStatus;
// TbSysViewDTO对应字段
@Schema(description = "视图ID")
private String viewId;
@Schema(description = "视图名称")
private String viewName;
@Schema(description = "父视图ID")
private String viewParentId;
@Schema(description = "URL")
private String viewUrl;
@Schema(description = "组件")
private String viewComponent;
@Schema(description = "图标")
private String viewIcon;
@Schema(description = "类型")
private Integer viewType;
@Schema(description = "布局")
private String viewLayout;
@Schema(description = "排序")
private Integer viewOrderNum;
@Schema(description = "视图描述")
private String viewDescription;
// 角色权限数组
@Schema(description = "用户视图权限列表")
private List<String> permissionIdList;
public static TbSysModuleDTO toModuleDTO(PermissionVO vo) {
if (vo == null) {
return null;
}
TbSysModuleDTO dto = new TbSysModuleDTO();
dto.setModuleId(vo.getModuleId());
dto.setName(vo.getModuleName());
dto.setDescription(vo.getModuleDescription());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static TbSysPermissionDTO toPermissionDTO(PermissionVO vo) {
if (vo == null) {
return null;
}
TbSysPermissionDTO dto = new TbSysPermissionDTO();
dto.setPermissionId(vo.getPermissionId());
dto.setModuleId(vo.getModuleId());
dto.setCode(vo.getPermissionCode());
dto.setName(vo.getPermissionName());
dto.setDescription(vo.getPermissionDescription());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static TbSysViewDTO toViewDTO(PermissionVO vo) {
if (vo == null) {
return null;
}
TbSysViewDTO dto = new TbSysViewDTO();
dto.setViewId(vo.getViewId());
dto.setName(vo.getViewName());
dto.setParentId(vo.getViewParentId());
dto.setUrl(vo.getViewUrl());
dto.setComponent(vo.getViewComponent());
dto.setIcon(vo.getViewIcon());
dto.setType(vo.getViewType());
dto.setLayout(vo.getViewLayout());
dto.setOrderNum(vo.getViewOrderNum());
dto.setDescription(vo.getViewDescription());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static PermissionVO fromViewDTO(TbSysViewDTO dto) {
if (dto == null) {
return null;
}
PermissionVO vo = new PermissionVO();
vo.setViewId(dto.getViewId());
vo.setViewName(dto.getName());
vo.setViewParentId(dto.getParentId());
vo.setViewUrl(dto.getUrl());
vo.setViewComponent(dto.getComponent());
vo.setViewIcon(dto.getIcon());
vo.setViewType(dto.getType());
vo.setViewLayout(dto.getLayout());
vo.setViewOrderNum(dto.getOrderNum());
vo.setViewDescription(dto.getDescription());
vo.setOptsn(dto.getOptsn());
vo.setCreator(dto.getCreator());
vo.setUpdater(dto.getUpdater());
vo.setDeptPath(dto.getDeptPath());
vo.setRemark(dto.getRemark());
vo.setCreateTime(dto.getCreateTime());
vo.setUpdateTime(dto.getUpdateTime());
vo.setDeleteTime(dto.getDeleteTime());
vo.setDeleted(dto.getDeleted());
return vo;
}
public static java.util.List<PermissionVO> fromViewDTOList(java.util.List<TbSysViewDTO> dtoList) {
if (dtoList == null || dtoList.isEmpty()) {
return java.util.Collections.emptyList();
}
return dtoList.stream()
.map(PermissionVO::fromViewDTO)
.toList();
}
}

View File

@@ -0,0 +1,100 @@
package org.xyzh.api.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import org.xyzh.common.dto.sys.TbSysConfigDTO;
import com.alibaba.fastjson2.JSONObject;
/**
* @description 系统配置视图对象
* @filename SysConfigVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统配置视图对象")
public class SysConfigVO extends BaseVO {
@Schema(description = "配置ID")
private String configId;
@Schema(description = "配置键")
private String key;
@Schema(description = "配置名称")
private String name;
@Schema(description = "配置值")
private String value;
@Schema(description = "数据类型(String, Integer, Boolean, Float, Double)")
private String configType;
@Schema(description = "配置渲染类型(select, input, textarea, checkbox, radio, switch)")
private String renderType;
@Schema(description = "配置描述")
private String description;
@Schema(description = "正则表达式校验规则(JSON)")
private JSONObject re;
@Schema(description = "可选项(JSON)render_type为select、checkbox、radio时使用")
private JSONObject options;
@Schema(description = "配置组")
private String group;
@Schema(description = "模块ID")
private String moduleId;
@Schema(description = "模块名称")
private String moduleName;
@Schema(description = "模块描述")
private String moduleDescription;
@Schema(description = "配置顺序")
private Integer orderNum;
@Schema(description = "状态")
private Integer status;
public static TbSysConfigDTO toDTO(SysConfigVO vo) {
if (vo == null) {
return null;
}
TbSysConfigDTO dto = new TbSysConfigDTO();
dto.setConfigId(vo.getConfigId());
dto.setKey(vo.getKey());
dto.setName(vo.getName());
dto.setValue(vo.getValue());
dto.setConfigType(vo.getConfigType());
dto.setRenderType(vo.getRenderType());
dto.setDescription(vo.getDescription());
dto.setRe(vo.getRe());
dto.setOptions(vo.getOptions());
dto.setGroup(vo.getGroup());
dto.setModuleId(vo.getModuleId());
dto.setOrderNum(vo.getOrderNum());
dto.setStatus(vo.getStatus());
// 基础字段
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
}

View File

@@ -0,0 +1,113 @@
package org.xyzh.api.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import java.util.List;
/**
* @description 系统用户视图对象
* @filename SysUserVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统用户视图对象")
public class SysUserVO extends BaseVO {
// TbSysUserDTO对应字段
@Schema(description = "用户ID")
private String userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码(敏感信息,仅用于创建/修改)")
private String password;
@Schema(description = "邮箱")
private String email;
@Schema(description = "手机")
private String phone;
@Schema(description = "微信ID")
private String wechatId;
@Schema(description = "用户状态")
private String status;
@Schema(description = "用户类型")
private String userType;
// TbSysUserInfoDTO对应字段
@Schema(description = "头像")
private String avatar;
@Schema(description = "性别")
private Integer gender;
@Schema(description = "")
private String familyName;
@Schema(description = "")
private String givenName;
@Schema(description = "全名")
private String fullName;
@Schema(description = "等级")
private Integer level;
@Schema(description = "身份证号")
private String idCard;
@Schema(description = "地址")
private String address;
// 关联信息
@Schema(description = "用户部门角色列表")
private List<UserDeptRoleVO> deptRoles;
@Schema(description = "用户角色权限列表")
private List<PermissionVO> rolePermissions;
@Schema(description = "用户视图权限列表")
private List<PermissionVO> viewPermissions;
public static TbSysUserDTO toDTO(SysUserVO vo) {
if (vo == null) {
return null;
}
TbSysUserDTO dto = new TbSysUserDTO();
dto.setUserId(vo.getUserId());
dto.setUsername(vo.getUsername());
dto.setPassword(vo.getPassword());
dto.setEmail(vo.getEmail());
dto.setPhone(vo.getPhone());
dto.setWechatId(vo.getWechatId());
dto.setStatus(vo.getStatus());
dto.setUserType(vo.getUserType());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static TbSysUserDTO toFilter(SysUserVO vo) {
return toDTO(vo);
}
}

View File

@@ -0,0 +1,195 @@
package org.xyzh.api.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import org.xyzh.common.dto.sys.TbSysDeptDTO;
import org.xyzh.common.dto.sys.TbSysRoleDTO;
import org.xyzh.common.dto.sys.TbSysDeptRoleDTO;
/**
* @description 用户部门角色视图对象
* @filename UserDeptRoleVO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户部门角色视图对象")
public class UserDeptRoleVO extends BaseVO {
// TbSysUserDTO对应字段
@Schema(description = "用户ID")
private String userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "邮箱")
private String email;
@Schema(description = "手机")
private String phone;
@Schema(description = "微信ID")
private String wechatId;
@Schema(description = "用户状态")
private String status;
@Schema(description = "用户类型")
private String userType;
// TbSysUserInfoDTO对应字段
@Schema(description = "头像")
private String avatar;
@Schema(description = "性别")
private Integer gender;
@Schema(description = "")
private String familyName;
@Schema(description = "")
private String givenName;
@Schema(description = "全名")
private String fullName;
@Schema(description = "等级")
private Integer level;
@Schema(description = "身份证号")
private String idCard;
@Schema(description = "地址")
private String address;
// TbSysDeptDTO对应字段
@Schema(description = "部门ID")
private String deptId;
@Schema(description = "部门名称")
private String deptName;
@Schema(description = "父级部门ID")
private String parentId;
@Schema(description = "部门描述")
private String deptDescription;
// TbSysRoleDTO对应字段SysRoleVO的字段
@Schema(description = "角色ID")
private String roleId;
@Schema(description = "角色名称")
private String roleName;
@Schema(description = "角色描述")
private String roleDescription;
@Schema(description = "角色作用域")
private String scope;
@Schema(description = "所属部门ID")
private String ownerDeptId;
@Schema(description = "角色状态")
private boolean roleStatus;
public static TbSysDeptDTO toDeptDTO(UserDeptRoleVO vo) {
if (vo == null) {
return null;
}
TbSysDeptDTO dto = new TbSysDeptDTO();
dto.setDeptId(vo.getDeptId());
dto.setName(vo.getDeptName());
dto.setParentId(vo.getParentId());
dto.setDescription(vo.getDeptDescription());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static TbSysRoleDTO toRoleDTO(UserDeptRoleVO vo) {
if (vo == null) {
return null;
}
TbSysRoleDTO dto = new TbSysRoleDTO();
dto.setRoleId(vo.getRoleId());
dto.setName(vo.getRoleName());
dto.setDescription(vo.getRoleDescription());
dto.setScope(vo.getScope());
dto.setOwnerDeptId(vo.getOwnerDeptId());
dto.setStatus(vo.isRoleStatus());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static TbSysDeptRoleDTO toDeptRoleDTO(UserDeptRoleVO vo) {
if (vo == null) {
return null;
}
TbSysDeptRoleDTO dto = new TbSysDeptRoleDTO();
dto.setDeptId(vo.getDeptId());
dto.setRoleId(vo.getRoleId());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
dto.setUpdateTime(vo.getUpdateTime());
dto.setDeleteTime(vo.getDeleteTime());
dto.setDeleted(vo.getDeleted());
return dto;
}
public static UserDeptRoleVO fromPermission(PermissionVO permissionVO) {
if (permissionVO == null) {
return null;
}
UserDeptRoleVO vo = new UserDeptRoleVO();
vo.setDeptId(permissionVO.getDeptId());
vo.setDeptName(permissionVO.getDeptName());
vo.setParentId(permissionVO.getDeptParentId());
vo.setDeptDescription(permissionVO.getDeptDescription());
vo.setRoleId(permissionVO.getRoleId());
vo.setRoleName(permissionVO.getRoleName());
vo.setRoleDescription(permissionVO.getRoleDescription());
vo.setScope(permissionVO.getRoleScope());
vo.setOwnerDeptId(permissionVO.getRoleOwnerDeptId());
vo.setRoleStatus(permissionVO.isRoleStatus());
vo.setOptsn(permissionVO.getOptsn());
vo.setCreator(permissionVO.getCreator());
vo.setUpdater(permissionVO.getUpdater());
vo.setDeptPath(permissionVO.getDeptPath());
vo.setRemark(permissionVO.getRemark());
vo.setCreateTime(permissionVO.getCreateTime());
vo.setUpdateTime(permissionVO.getUpdateTime());
vo.setDeleteTime(permissionVO.getDeleteTime());
vo.setDeleted(permissionVO.getDeleted());
return vo;
}
}

View File

@@ -0,0 +1,82 @@
<?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>apis</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>api-all</module>
<module>api-auth</module>
<module>api-file</module>
<module>api-message</module>
<module>api-log</module>
<module>api-system</module>
</modules>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-all</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh.apis</groupId>
<artifactId>api-auth</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.apis</groupId>
<artifactId>api-message</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-log</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-dto</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
<?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>auth</artifactId>
<version>${urban-lifeline.version}</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-auth</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,24 @@
package org.xyzh.auth;
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.auth", // 当前auth模块
"org.xyzh.common" // 公共模块
})
public class AuthApp {
private static final Logger logger = LoggerFactory.getLogger(AuthApp.class);
public static void main(String[] args) {
logger.info("======================== AuthApp 启动中 =========================");
SpringApplication.run(AuthApp.class, args);
logger.info("======================== AuthApp 启动成功 =========================");
}
}

View File

@@ -0,0 +1,58 @@
package org.xyzh.auth.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* OpenAPI 配置类 - Auth 服务
* 配置 Swagger/OpenAPI 文档,方便 Apifox 导入接口和对象进行测试
*
* @author yslg
*/
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI authOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("认证服务 API 文档")
.description("""
认证服务接口文档包括登录、登出、Token刷新等功能。
## 使用说明
1. 访问 Swagger UI: http://localhost:8081/urban-lifeline/auth/swagger-ui.html
2. 访问 OpenAPI JSON: http://localhost:8081/urban-lifeline/auth/v3/api-docs
3. 在 Apifox 中导入 OpenAPI JSON 进行接口测试
""")
.version("1.0.0")
.contact(new Contact()
.name("yslg")
.email("3401275564@qq.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")))
.servers(List.of(
new Server().url("http://localhost:8081/urban-lifeline/auth").description("本地开发环境")
))
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
.components(new Components()
.addSecuritySchemes("Bearer Authentication",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("请输入JWT Token格式Bearer {token}")));
}
}

View File

@@ -0,0 +1,62 @@
package org.xyzh.auth.service.impl;
import org.xyzh.api.auth.service.AuthService;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.core.domain.LoginParam;
import org.xyzh.common.core.domain.ResultDomain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.servlet.http.HttpServletRequest;
/**
* @description AuthServiceImpl.java文件描述 认证服务实现类
* @filename AuthServiceImpl.java
* @author yslg
* @copyright yslg
* @since 2025-11-09
*/
public class AuthServiceImpl implements AuthService{
private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
@Override
public ResultDomain<LoginDomain> getCaptcha(LoginParam arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public ResultDomain<LoginDomain> getLoginByToken(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public ResultDomain<Boolean> isTokenValid(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public ResultDomain<LoginDomain> login(LoginParam arg0, HttpServletRequest arg1) {
// TODO Auto-generated method stub
return null;
}
@Override
public ResultDomain<LoginDomain> logout(HttpServletRequest arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public ResultDomain<LoginDomain> refreshToken(HttpServletRequest arg0) {
// TODO Auto-generated method stub
return null;
}
}

View File

@@ -0,0 +1,36 @@
server:
port: 8081
servlet:
context-path: /urban-lifeline/auth
springdoc:
api-docs:
# 启用 API 文档
enabled: true
# API 文档路径
path: /v3/api-docs
swagger-ui:
# 启用 Swagger UI
enabled: true
# Swagger UI 路径
path: /swagger-ui.html
# 尝试请求超时时间(毫秒)
try-it-out-enabled: true
# 显示请求执行时间
show-common-extensions: true
# 显示请求头部
show-extensions: true
# 显示模型
show-request-duration: true
# 过滤开关
filter: true
# 标签排序
tags-sorter: alpha
# 操作排序
operations-sorter: alpha
# 分组配置(可选)
group-configs:
- group: 'default'
display-name: '认证服务 API'
paths-to-match: '/**'

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh.common</groupId>
<artifactId>common-all</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-dto</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-utils</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh.common</groupId>
<artifactId>common-auth</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- Common Core dependency -->
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-utils</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-dto</artifactId>
</dependency>
<!-- JWT Dependencies -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除默认的logback依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<!-- 排除默认的logback依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Log4j2 日志依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- JWT 依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 配置处理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
<!-- 排除默认的logback依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
package org.xyzh.common.auth.annotation;
import java.lang.annotation.*;
/**
* @description HttpLogin.java文件描述 HTTP登录注解
* @filename HttpLogin.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpLogin {
/**
* @description 是否必需默认为true
* @return boolean
* @author yslg
* @since 2025-11-02
*/
boolean required() default true;
/**
* @description 当token无效时的错误消息
* @return String
* @author yslg
* @since 2025-11-02
*/
String message() default "用户未登录或登录已过期";
}

View File

@@ -0,0 +1,135 @@
package org.xyzh.common.auth.annotation.resovler;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.xyzh.common.auth.annotation.HttpLogin;
import org.xyzh.common.auth.token.TokenParser;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.utils.NonUtils;
import org.xyzh.redis.service.RedisService;
import jakarta.servlet.http.HttpServletRequest;
/**
* @description HttpLoginArgumentResolver.java文件描述 HTTP登录参数解析器
* @filename HttpLoginArgumentResolver.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Component
public class HttpLoginArgumentResolver implements HandlerMethodArgumentResolver {
private final TokenParser tokenParser;
private final RedisService redisService;
/**
* 使用构造器注入依赖TokenParser接口而非具体实现
* 这样避免了与auth模块的直接依赖解决循环依赖问题
*/
public HttpLoginArgumentResolver(TokenParser tokenParser,
RedisService redisService) {
this.tokenParser = tokenParser;
this.redisService = redisService;
}
private static final String TOKEN_PREFIX = "Bearer ";
private static final String REDIS_LOGIN_PREFIX = "login:token:";
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(HttpLogin.class)
&& LoginDomain.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpLogin httpLogin = parameter.getParameterAnnotation(HttpLogin.class);
if (httpLogin == null) {
return null;
}
// 从请求头中获取token
String token = extractTokenFromRequest(webRequest);
if (NonUtils.isEmpty(token)) {
if (httpLogin.required()) {
throw new IllegalArgumentException(httpLogin.message());
}
return null;
}
try {
// 验证token格式和有效性
if (!tokenParser.validateToken(token, tokenParser.getUserIdFromToken(token))) {
if (httpLogin.required()) {
throw new IllegalArgumentException(httpLogin.message());
}
return null;
}
// 从Redis中获取LoginDomain
String userId = tokenParser.getUserIdFromToken(token);
String redisKey = REDIS_LOGIN_PREFIX + userId;
LoginDomain loginDomain = (LoginDomain) redisService.get(redisKey);
if (loginDomain == null) {
if (httpLogin.required()) {
throw new IllegalArgumentException(httpLogin.message());
}
return null;
}
// 更新token信息
loginDomain.setToken(token);
return loginDomain;
} catch (Exception e) {
if (httpLogin.required()) {
throw new IllegalArgumentException(httpLogin.message());
}
return null;
}
}
/**
* @description 从请求中提取token
* @param webRequest 请求对象
* @return String token
* @author yslg
* @since 2025-11-02
*/
private String extractTokenFromRequest(NativeWebRequest webRequest) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
return null;
}
// 优先从Authorization头获取
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (NonUtils.isNotEmpty(authHeader) && authHeader.startsWith(TOKEN_PREFIX)) {
return authHeader.substring(TOKEN_PREFIX.length());
}
// 从请求参数中获取token
String token = request.getParameter("token");
if (NonUtils.isNotEmpty(token)) {
return token;
}
// 从请求头中获取token
token = request.getHeader("token");
if (NonUtils.isNotEmpty(token)) {
return token;
}
return null;
}
}

View File

@@ -0,0 +1,160 @@
package org.xyzh.common.auth.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 认证配置属性类
* 用于配置认证相关的属性,包括白名单路径
*
* @author yslg
*/
@Component
@ConfigurationProperties(prefix = "urban-lifeline.auth")
public class AuthProperties {
/**
* 是否启用认证过滤器
* 默认启用
*/
private boolean enabled = true;
/**
* 登录接口路径
* 支持不同服务自定义登录地址
*/
private String loginPath = "/urban-lifeline/auth/login";
/**
* 登出接口路径
*/
private String logoutPath = "/urban-lifeline/auth/logout";
/**
* 验证码获取接口路径
*/
private String captchaPath = "/urban-lifeline/auth/captcha";
/**
* 刷新 Token 接口路径
*/
private String refreshPath = "/urban-lifeline/auth/refresh";
/**
* 通用白名单路径列表(非认证接口)
* 例如Swagger、静态资源、健康检查等
*/
private List<String> whitelist = new ArrayList<>();
/**
* Token 请求头名称
* 默认: Authorization
*/
private String tokenHeader = "Authorization";
/**
* Token 前缀
* 默认: Bearer
*/
private String tokenPrefix = "Bearer ";
public AuthProperties() {
// 默认通用白名单Swagger 及静态资源等
whitelist.add("/swagger-ui/**");
whitelist.add("/swagger-ui.html");
whitelist.add("/v3/api-docs/**");
whitelist.add("/webjars/**");
whitelist.add("/favicon.ico");
whitelist.add("/error");
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getLoginPath() {
return loginPath;
}
public void setLoginPath(String loginPath) {
this.loginPath = loginPath;
}
public String getLogoutPath() {
return logoutPath;
}
public void setLogoutPath(String logoutPath) {
this.logoutPath = logoutPath;
}
public String getCaptchaPath() {
return captchaPath;
}
public void setCaptchaPath(String captchaPath) {
this.captchaPath = captchaPath;
}
public String getRefreshPath() {
return refreshPath;
}
public void setRefreshPath(String refreshPath) {
this.refreshPath = refreshPath;
}
public List<String> getWhitelist() {
return whitelist;
}
public void setWhitelist(List<String> whitelist) {
this.whitelist = whitelist;
}
/**
* 认证相关接口路径集合login / logout / captcha / refresh
* 供 SecurityConfig 和 JwtAuthenticationFilter 统一放行
*/
public List<String> getAuthPaths() {
List<String> authPaths = new ArrayList<>();
if (StringUtils.hasText(loginPath)) {
authPaths.add(loginPath);
}
if (StringUtils.hasText(logoutPath)) {
authPaths.add(logoutPath);
}
if (StringUtils.hasText(captchaPath)) {
authPaths.add(captchaPath);
}
if (StringUtils.hasText(refreshPath)) {
authPaths.add(refreshPath);
}
return authPaths;
}
public String getTokenHeader() {
return tokenHeader;
}
public void setTokenHeader(String tokenHeader) {
this.tokenHeader = tokenHeader;
}
public String getTokenPrefix() {
return tokenPrefix;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
}

View File

@@ -0,0 +1,55 @@
package org.xyzh.common.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.xyzh.common.auth.filter.JwtAuthenticationFilter;
import org.xyzh.common.auth.token.TokenParser;
import org.xyzh.redis.service.RedisService;
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(TokenParser tokenParser,
AuthProperties authProperties,
RedisService redisService) {
return new JwtAuthenticationFilter(tokenParser, authProperties, redisService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
AuthProperties authProperties,
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf(csrf -> csrf.disable())
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> {
// 认证接口放行login / logout / captcha / refresh
if (authProperties.getAuthPaths() != null) {
authProperties.getAuthPaths().forEach(path -> authz.requestMatchers(path).permitAll());
}
// 通用白名单放行Swagger、静态资源等
if (authProperties.getWhitelist() != null) {
authProperties.getWhitelist().forEach(path -> authz.requestMatchers(path).permitAll());
}
authz
.requestMatchers("/error", "/favicon.ico").permitAll()
.anyRequest().authenticated();
})
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

View File

@@ -0,0 +1,34 @@
package org.xyzh.common.auth.config;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.xyzh.common.auth.annotation.resovler.HttpLoginArgumentResolver;
/**
* @description WebMvcConfig.java文件描述 WebMVC配置类
* @filename WebMvcConfig.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final HandlerMethodArgumentResolver httpLoginArgumentResolver;
/**
* 使用构造器注入
* 通过接口抽象解决了循环依赖问题,不再需要@Lazy注解
*/
public WebMvcConfig(HttpLoginArgumentResolver httpLoginArgumentResolver) {
this.httpLoginArgumentResolver = httpLoginArgumentResolver;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(httpLoginArgumentResolver);
}
}

View File

@@ -0,0 +1,43 @@
package org.xyzh.common.auth.contants;
/**
* @description 认证相关常量类
* @filename AuthContants.java
* @author yslg
* @copyright yslg
* @since 2025-11-09
*/
public class AuthContants {
/**
* 用户ID请求属性键
*/
public static final String USER_ID_ATTRIBUTE = "userId";
/**
* 用户名请求属性键
*/
public static final String USERNAME_ATTRIBUTE = "username";
/**
* Token请求属性键
*/
public static final String TOKEN_ATTRIBUTE = "token";
/**
* JWT Claims 中的用户名键
*/
public static final String CLAIMS_USERNAME_KEY = "username";
/**
* JWT Claims 中的用户ID键
*/
public static final String CLAIMS_USER_ID_KEY = "userId";
/**
* 私有构造函数,防止实例化
*/
private AuthContants() {
throw new UnsupportedOperationException("常量类不允许实例化");
}
}

View File

@@ -0,0 +1,237 @@
package org.xyzh.common.auth.filter;
import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.xyzh.common.auth.config.AuthProperties;
import org.xyzh.common.auth.contants.AuthContants;
import org.xyzh.common.auth.token.TokenParser;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.redis.service.RedisService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
/**
* @description JWT认证过滤器用于检测用户请求是否登录支持白名单配置
* @filename JwtAuthenticationFilter.java
* @author yslg
* @copyright yslg
* @since 2025-11-09
*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
private final TokenParser tokenParser;
private final AuthProperties authProperties;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final RedisService redisService;
private static final String REDIS_LOGIN_PREFIX = "login:token:";
public JwtAuthenticationFilter(TokenParser tokenParser, AuthProperties authProperties) {
this.tokenParser = tokenParser;
this.authProperties = authProperties;
this.redisService = null; // 占位,使用另一个构造函数注入
}
public JwtAuthenticationFilter(TokenParser tokenParser, AuthProperties authProperties, RedisService redisService) {
this.tokenParser = tokenParser;
this.authProperties = authProperties;
this.redisService = redisService;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
throws ServletException, IOException {
// 如果认证过滤器未启用,直接放行
if (!authProperties.isEnabled()) {
filterChain.doFilter(request, response);
return;
}
String requestPath = request.getRequestURI();
if (requestPath == null) {
requestPath = "";
}
// 去掉 context-path仅保留业务路径用于白名单匹配
String contextPath = request.getContextPath();
if (contextPath != null && !contextPath.isEmpty() && requestPath.startsWith(contextPath)) {
requestPath = requestPath.substring(contextPath.length());
}
// 检查是否在白名单中
if (isWhitelisted(requestPath)) {
log.debug("请求路径在白名单中,跳过认证: {}", requestPath);
filterChain.doFilter(request, response);
return;
}
// 从请求头获取Token
String token = extractToken(request);
if (!StringUtils.hasText(token)) {
log.warn("请求缺少Token: {}", requestPath);
handleUnauthorized(response, "未提供认证令牌,请先登录");
return;
}
try {
// 验证Token
if (tokenParser.isTokenExpired(token)) {
log.warn("Token已过期: {}", requestPath);
handleUnauthorized(response, "认证令牌已过期,请重新登录");
return;
}
// 获取用户ID
String userId = tokenParser.getUserIdFromToken(token);
if (!StringUtils.hasText(userId)) {
log.warn("Token中未找到用户ID: {}", requestPath);
handleUnauthorized(response, "认证令牌无效");
return;
}
// 验证Token有效性
if (!tokenParser.validateToken(token, userId)) {
log.warn("Token验证失败: userId={}, path={}", userId, requestPath);
handleUnauthorized(response, "认证令牌验证失败");
return;
}
// 将用户信息放入请求属性中,供后续使用
Claims claims = tokenParser.getAllClaimsFromToken(token);
request.setAttribute(AuthContants.USER_ID_ATTRIBUTE, userId);
request.setAttribute(AuthContants.USERNAME_ATTRIBUTE, claims.get(AuthContants.CLAIMS_USERNAME_KEY, String.class));
request.setAttribute(AuthContants.TOKEN_ATTRIBUTE, token);
// 从Redis加载 LoginDomain并将权限装配到 Spring Security 上下文
if (redisService != null) {
Object obj = redisService.get(REDIS_LOGIN_PREFIX + userId);
if (obj instanceof LoginDomain loginDomain) {
// 组装权限码 authorities已存在
List<SimpleGrantedAuthority> permAuthorities = null;
if (loginDomain.getUserPermissions() != null) {
permAuthorities = loginDomain.getUserPermissions().stream()
.filter(Objects::nonNull)
.map(TbSysPermissionDTO::getCode)
.filter(StringUtils::hasText)
.map(SimpleGrantedAuthority::new)
.toList();
}
// 组装角色 authorities关键ROLE_ 前缀)
List<SimpleGrantedAuthority> roleAuthorities = null;
if (loginDomain.getUserRoles() != null) {
roleAuthorities = loginDomain.getUserRoles().stream()
.filter(Objects::nonNull)
.map(r -> r.getRoleId()) // 若有角色code/名称,可替换为对应字段
.filter(StringUtils::hasText)
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList();
}
// 合并权限与角色
List<SimpleGrantedAuthority> authorities = Stream
.concat(
permAuthorities != null ? permAuthorities.stream() : Stream.empty(),
roleAuthorities != null ? roleAuthorities.stream() : Stream.empty()
)
.toList();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(loginDomain, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
log.debug("Token验证成功: userId={}, path={}", userId, requestPath);
// 继续执行过滤器链
filterChain.doFilter(request, response);
} catch (Exception e) {
log.error("Token解析或验证异常: path={}, error={}", requestPath, e.getMessage(), e);
handleUnauthorized(response, "认证令牌解析失败: " + e.getMessage());
}
}
/**
* 检查路径是否在白名单中
*/
private boolean isWhitelisted(@NonNull String path) {
// 1. 先检查认证相关接口login / logout / captcha / refresh
if (authProperties.getAuthPaths() != null) {
for (String pattern : authProperties.getAuthPaths()) {
if (pattern != null && pathMatcher.match(pattern, path)) {
return true;
}
}
}
// 2. 再检查通用白名单
if (authProperties.getWhitelist() != null) {
for (String pattern : authProperties.getWhitelist()) {
if (pattern != null && pathMatcher.match(pattern, path)) {
return true;
}
}
}
return false;
}
/**
* 从请求头中提取Token
*/
private String extractToken(HttpServletRequest request) {
String header = request.getHeader(authProperties.getTokenHeader());
if (!StringUtils.hasText(header)) {
return null;
}
// 支持 Bearer 前缀
String prefix = authProperties.getTokenPrefix();
if (StringUtils.hasText(prefix) && header.startsWith(prefix)) {
return header.substring(prefix.length()).trim();
}
// 也支持直接传递Token不带前缀
return header.trim();
}
/**
* 处理未授权响应
*/
private void handleUnauthorized(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
ResultDomain<Object> result = ResultDomain.failure(HttpStatus.UNAUTHORIZED.value(), message);
String json = JSON.toJSONString(result);
response.getWriter().write(json);
response.getWriter().flush();
}
}

View File

@@ -0,0 +1,46 @@
package org.xyzh.common.auth.token;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.xyzh.common.auth.utils.JwtTokenUtil;
/**
* @description JwtTokenParser.java文件描述 JWT令牌解析器实现类
* @filename JwtTokenParser.java
* @author yslg
* @copyright yslg
* @since 2025-11-07
*/
@Component
public class JwtTokenParser implements TokenParser {
private final JwtTokenUtil jwtTokenUtil;
/**
* Spring会自动注入无需@Autowired注解
*/
public JwtTokenParser(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
public String getUserIdFromToken(String token) {
return jwtTokenUtil.getUserIdFromToken(token);
}
@Override
public Claims getAllClaimsFromToken(String token) {
return jwtTokenUtil.getAllClaimsFromToken(token);
}
@Override
public boolean validateToken(String token, String userId) {
return jwtTokenUtil.validateToken(token, userId);
}
@Override
public boolean isTokenExpired(String token) {
return jwtTokenUtil.isTokenExpired(token);
}
}

View File

@@ -0,0 +1,51 @@
package org.xyzh.common.auth.token;
import io.jsonwebtoken.Claims;
/**
* @description TokenParser.java文件描述 令牌解析器接口
* @filename TokenParser.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
public interface TokenParser {
/**
* @description 从令牌中获取用户ID
* @param token 令牌
* @return String 用户ID
* @author yslg
* @since 2025-11-02
*/
String getUserIdFromToken(String token);
/**
* @description 从令牌中获取所有声明信息
* @param token 令牌
* @return Claims 所有声明信息
* @author yslg
* @since 2025-11-02
*/
Claims getAllClaimsFromToken(String token);
/**
* @description 验证令牌
* @param token 令牌
* @param userId 用户ID
* @return boolean 是否有效
* @author yslg
* @since 2025-11-02
*/
boolean validateToken(String token, String userId);
/**
* @description 检查令牌是否过期
* @param token 令牌
* @return boolean 是否过期
* @author yslg
* @since 2025-11-02
*/
boolean isTokenExpired(String token);
}

View File

@@ -0,0 +1,151 @@
package org.xyzh.common.auth.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.utils.IDUtils;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @description JwtTokenUtil.java文件描述 JWT工具类
* @filename JwtTokenUtil.java
* @author yslg
* @copyright yslg
* @since 2025-11-07
*/
@Component
public class JwtTokenUtil {
@Value("${urban-lifeline.auth.jwt-secret:schoolNewsDefaultSecretKeyForJWT2025}")
private String secret;
@Value("${urban-lifeline.auth.jwt-expiration:86400}")
private Long expiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
/**
* @description 生成JWT令牌
* @param loginDomain 登录域对象
* @return String JWT令牌
* @author yslg
* @since 2025-11-07
*/
public String generateToken(LoginDomain loginDomain) {
Map<String, Object> claims = new HashMap<>();
TbSysUserDTO user = loginDomain.getUser();
claims.put("userId", user.getUserId());
claims.put("username", user.getUsername());
claims.put("email", user.getEmail());
claims.put("loginType", loginDomain.getLoginType());
claims.put("ipAddress", loginDomain.getIpAddress());
return Jwts.builder()
.setClaims(claims)
.setSubject(user.getUserId())
.setId(IDUtils.generateID()) // 使用IDUtils生成JWT ID
.setIssuedAt(new Date())
.setExpiration(generateExpirationDate())
.signWith(getSigningKey())
.compact();
}
/**
* @description 从令牌中获取用户ID
* @param token JWT令牌
* @return String 用户ID
* @author yslg
* @since 2025-11-07
*/
public String getUserIdFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* @description 从令牌中获取过期时间
* @param token JWT令牌
* @return Date 过期时间
* @author yslg
* @since 2025-11-07
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* @description 从令牌中获取指定信息
* @param token JWT令牌
* @param claimsResolver 信息解析器
* @return T 指定信息
* @author yslg
* @since 2025-11-07
*/
public <T> T getClaimFromToken(String token, java.util.function.Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* @description 从令牌中获取所有信息
* @param token JWT令牌
* @return Claims 所有信息
* @author yslg
* @since 2025-11-07
*/
public Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* @description 验证令牌
* @param token JWT令牌
* @param userId 用户ID
* @return boolean 是否有效
* @author yslg
* @since 2025-11-07
*/
public boolean validateToken(String token, String userId) {
try {
final String tokenUserId = getUserIdFromToken(token);
return (userId.equals(tokenUserId) && !isTokenExpired(token));
} catch (Exception e) {
return false;
}
}
/**
* @description 检查令牌是否过期
* @param token JWT令牌
* @return boolean 是否过期
* @author yslg
* @since 2025-11-07
*/
public boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* @description 生成过期时间
* @return Date 过期时间
* @author yslg
* @since 2025-11-07
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
}

View File

@@ -0,0 +1,33 @@
# 认证配置示例文件
# 将此配置添加到各服务的 application.yml 中
urban-lifeline:
auth:
enabled: true
# 认证接口:可以按服务自定义
login-path: /urban-lifeline/auth/login
logout-path: /urban-lifeline/auth/logout
captcha-path: /urban-lifeline/auth/captcha
refresh-path: /urban-lifeline/auth/refresh
# 通用白名单(非认证接口)
whitelist:
# Swagger/OpenAPI 文档相关(建议不带 context-path
- /swagger-ui/**
- /swagger-ui.html
- /v3/api-docs/**
- /webjars/**
# 静态资源
- /favicon.ico
- /error
# 健康检查
- /actuator/health
- /actuator/info
# 其他需要放行的路径
# - /public/**
# - /api/public/**

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh.common</groupId>
<artifactId>common-core</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-dto</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,28 @@
package org.xyzh.common.core.constant;
/**
* @description Constants.java文件描述 常量类
* @filename Constants.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
public class Constants {
/**
* @description 令牌前缀
* @filename Constants.java
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* @description JSON_WHITELIST_STR JSON白名单
* @author yslg
* @since 2025-11-02
*/
public static final String JSON_WHITELIST_STR = "org.xyzh";
}

View File

@@ -0,0 +1,48 @@
package org.xyzh.common.core.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
import org.xyzh.common.dto.sys.TbSysDeptDTO;
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysViewDTO;
import lombok.Data;
/**
* @description 登录域
* @filename 登录域
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Data
public class LoginDomain implements Serializable{
private static final long serialVersionUID = 1L;
private TbSysUserDTO user;
private TbSysUserInfoDTO userInfo;
private List<TbSysUserRoleDTO> userRoles;
private List<TbSysDeptDTO> userDepts;
private List<TbSysPermissionDTO> userPermissions;
private List<TbSysViewDTO> userViews;
private String token;
private Date tokenExpireTime;
private String loginTime;
private String ipAddress;
private String loginType;
}

View File

@@ -0,0 +1,84 @@
package org.xyzh.common.core.domain;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* @description 登录参数
* @filename 登录参数
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Data
public class LoginParam implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
* @author yslg
* @since 2025-11-02
*/
private String username;
/**
* 密码
* @author yslg
* @since 2025-11-02
*/
private String password;
/**
* 邮箱
* @author yslg
* @since 2025-11-02
*/
private String email;
/**
* 手机号
* @author yslg
* @since 2025-11-02
*/
private String phone;
/**
* 微信ID
* @author yslg
* @since 2025-11-02
*/
private String wechatId;
/**
* 验证码类型
* @author yslg
* @since 2025-11-02
*/
private String captchaType;
/**
* 验证码
* @author yslg
* @since 2025-11-02
*/
private String captcha;
/**
* 验证码ID
* @author yslg
* @since 2025-11-02
*/
private String captchaId;
/**
* 登录类型
* @author yslg
* @since 2025-11-02
*/
private String loginType;
/**
* 是否记住我
* @author yslg
* @since 2025-11-02
*/
private Boolean rememberMe;
}

View File

@@ -0,0 +1,110 @@
package org.xyzh.common.core.domain;
import java.io.Serializable;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.xyzh.common.core.page.PageDomain;
import lombok.Data;
/**
* @description 结果域 通用返回结果
* @filename 结果域
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Data
public class ResultDomain<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Integer code;
private boolean success;
private String message;
private T data;
private List<T> dataList;
private PageDomain<T> pageDomain;
public ResultDomain(){
}
public ResultDomain(int code, String message) {
this.code = code;
this.message = message;
this.success = false;
}
public ResultDomain(int code, String message, T data) {
this.code = code;
this.message = message;
this.success = false;
this.data = data;
}
public ResultDomain(int code, String message, List<T> dataList) {
this.code = code;
this.message = message;
this.success = false;
this.dataList = dataList;
}
public ResultDomain(int code, String message, PageDomain<T> pageDomain) {
this.code = code;
this.message = message;
this.success = false;
this.pageDomain = pageDomain;
}
// 静态工厂方法 - 推荐使用(简洁、清晰)
public static <R> ResultDomain<R> success(String message) {
ResultDomain<R> result = new ResultDomain<>();
result.success = true;
result.message = message;
result.code = HttpStatus.OK.value();
return result;
}
public static <R> ResultDomain<R> success(String message, R data) {
ResultDomain<R> result = new ResultDomain<>();
result.success = true;
result.message = message;
result.data = data;
result.code = HttpStatus.OK.value();
return result;
}
public static <R> ResultDomain<R> success(String message, List<R> dataList) {
ResultDomain<R> result = new ResultDomain<>();
result.success = true;
result.message = message;
result.dataList = dataList;
result.code = HttpStatus.OK.value();
return result;
}
public static <R> ResultDomain<R> success(String message, PageDomain<R> pageDomain) {
ResultDomain<R> result = new ResultDomain<>();
result.success = true;
result.message = message;
result.pageDomain = pageDomain;
result.code = HttpStatus.OK.value();
return result;
}
public static <R> ResultDomain<R> failure(String message) {
ResultDomain<R> result = new ResultDomain<>();
result.success = false;
result.message = message;
result.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
return result;
}
public static <R> ResultDomain<R> failure(int code, String message) {
ResultDomain<R> result = new ResultDomain<>();
result.success = false;
result.message = message;
result.code = code;
return result;
}
}

View File

@@ -0,0 +1,65 @@
package org.xyzh.common.core.enums;
/**
* @description 验证码类型
* @filename CaptchaType.java
* @author yslg
* @copyright yslg
* @since 2025-11-03
*/
public enum CaptchaType {
EMAIL(1, "EMAIL", "邮箱验证码"),
SMS(2, "SMS", "短信验证码"),
IMAGE(3, "IMAGE", "图形验证码");
/**
* 验证码类型编码
*/
private int code;
/**
* 验证码类型名称
*/
private String name;
/**
* 验证码类型描述
*/
private String description;
private CaptchaType(int code, String name, String description) {
this.code = code;
this.name = name;
this.description = description;
}
public int getCode() {
return code;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
/**
* @description 根据名称获取验证码类型
* @param name 验证码类型名称
* @return 验证码类型
* @author yslg
* @since 2025-11-03
*/
public static CaptchaType fromName(String name) {
for (CaptchaType type : CaptchaType.values()) {
if (type.getName().equalsIgnoreCase(name)) {
return type;
}
}
return null;
}
}

View File

@@ -0,0 +1,34 @@
package org.xyzh.common.core.page;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
@Data
public class PageDomain<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分页参数
* @author yslg
* @since 2025-11-02
*/
private PageParam pageParam;
/**
* 数据列表
* @author yslg
* @since 2025-11-02
*/
private List<T> dataList;
public PageDomain(PageParam pageParam, List<T> dataList) {
if (pageParam == null) {
throw new IllegalArgumentException("分页参数不能为空");
}
this.pageParam = pageParam;
this.dataList = dataList;
}
}

View File

@@ -0,0 +1,76 @@
package org.xyzh.common.core.page;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @description 分页参数
* @filename 分页参数
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Data
@NoArgsConstructor
public class PageParam implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 页码
* @author yslg
* @since 2025-11-02
*/
private int page;
/**
* 每页条数
* @author yslg
* @since 2025-11-02
*/
private int pageSize;
private int total;
/**
* 总页数
* @author yslg
* @since 2025-11-02
*/
private int totalPages;
private int offset;
public PageParam(int page, int pageSize) {
if (page <= 0) {
throw new IllegalArgumentException("页码必须大于0");
}
if (pageSize <= 0) {
throw new IllegalArgumentException("每页条数必须大于0");
}
this.page = page;
this.pageSize = pageSize;
this.offset = (page - 1) * pageSize;
}
public void setPage(int page){
if (page <= 0) {
throw new IllegalArgumentException("页码必须大于0");
}
this.page = page;
if (this.pageSize <= 0) {
this.pageSize = 10;
}
this.offset = (page - 1) * this.pageSize;
}
public void setPageSize(int pageSize){
if (pageSize <= 0) {
throw new IllegalArgumentException("每页条数必须大于0");
}
this.pageSize = pageSize;
if (this.page <= 0) {
this.page = 1;
}
this.offset = (this.page - 1) * this.pageSize;
}
}

View File

@@ -0,0 +1,17 @@
package org.xyzh.common.core.page;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageRequest<T> implements Serializable {
private static final long serialVersionUID = 1L;
private PageParam pageParam;
private T filter;
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<groupId>org.xyzh.common</groupId>
<artifactId>common-dto</artifactId>
<version>${urban-lifeline.version}</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- Existing dependencies -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,44 @@
package org.xyzh.common.dto;
import java.io.Serializable;
import java.util.Date;
import com.alibaba.fastjson2.annotation.JSONField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "基础数据传输对象")
public class BaseDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "操作流水号")
private String optsn;
@Schema(description = "创建人")
private String creator;
@Schema(description = "更新人")
private String updater;
@Schema(description = "部门路径")
private String deptPath;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "更新时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@Schema(description = "删除时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date deleteTime;
@Schema(description = "是否已删除", defaultValue = "false")
private Boolean deleted = false;
}

View File

@@ -0,0 +1,45 @@
package org.xyzh.common.dto.sys;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
/**
* @description 系统访问控制列表DTO
* @filename TbSysAclDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统访问控制列表DTO")
public class TbSysAclDTO extends BaseDTO {
@Schema(description = "权限ID")
private String aclId;
@Schema(description = "对象类型article/file/course/...")
private String objectType;
@Schema(description = "对象ID")
private String objectId;
@Schema(description = "主体类型user/dept/role")
private String principalType;
@Schema(description = "主体ID")
private String principalId;
@Schema(description = "当主体为role且限定到某部门时的部门ID支持“某部门的某角色”")
private String principalDeptId;
@Schema(description = "权限位1读 2写 4执行")
private Integer permission;
@Schema(description = "允许或显式拒绝", defaultValue = "true")
private Boolean allow = true;
@Schema(description = "是否包含子级对dept/role生效", defaultValue = "false")
private Boolean includeDescendants = false;
}

View File

@@ -0,0 +1,43 @@
package org.xyzh.common.dto.sys;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
/**
* @description ACL 策略表:定义对象类型的层级可见/可编辑规则
* @filename 系统权限策略DTO
* @author yslg
* @copyright yslg
* @since 2025-11-02
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统访问控制策略DTO")
public class TbSysAclPolicyDTO extends BaseDTO {
@Schema(description = "策略ID")
private String policyId;
@Schema(description = "策略名称")
private String name;
@Schema(description = "对象类型article/file/course/..")
private String objectType;
@Schema(description = "编辑层级规则parent_only/parent_or_same_admin/owner_only/none")
private String editHierarchyRule;
@Schema(description = "可见层级规则 children_all/children_specified/none")
private String viewHierarchyRule;
@Schema(description = "默认权限无显式ACL时应用", defaultValue = "0")
private Integer defaultPermission=0;
@Schema(description = "默认是否允许", defaultValue = "true")
private boolean defaultAllow=true;
@Schema(description = "是否默认应用到子级", defaultValue = "true")
private boolean applyToChildren=true;
}

View File

@@ -0,0 +1,61 @@
package org.xyzh.common.dto.sys;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import com.alibaba.fastjson2.JSONObject;
/**
* @description 系统配置DTO
* @filename TbSysConfigDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统配置DTO")
public class TbSysConfigDTO extends BaseDTO {
// optsn、creator、updater、deptPath、remark、createTime、updateTime、deleteTime、deleted 继承自BaseDTO
@Schema(description = "配置ID")
private String configId;
@Schema(description = "配置键")
private String key;
@Schema(description = "配置名称")
private String name;
@Schema(description = "配置值")
private String value;
@Schema(description = "数据类型(String, Integer, Boolean, Float, Double)")
private String configType;
@Schema(description = "配置渲染类型(select, input, textarea, checkbox, radio, switch)")
private String renderType;
@Schema(description = "配置描述")
private String description;
@Schema(description = "正则表达式校验规则(JSON)")
private JSONObject re;
@Schema(description = "可选项(JSON)render_type为select、checkbox、radio时使用")
private JSONObject options;
@Schema(description = "配置组")
private String group;
@Schema(description = "模块id")
private String moduleId;
@Schema(description = "配置顺序")
private Integer orderNum;
@Schema(description = "配置状态 0:启用 1:禁用", defaultValue = "0")
private Integer status = 0;
}

View File

@@ -0,0 +1,30 @@
package org.xyzh.common.dto.sys;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
/**
* @description 系统部门DTO
* @filename TbSysDeptDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统部门DTO")
public class TbSysDeptDTO extends BaseDTO {
@Schema(description = "部门ID")
private String deptId;
@Schema(description = "部门名称")
private String name;
@Schema(description = "父级部门ID")
private String parentId;
@Schema(description = "部门描述")
private String description;
}

View File

@@ -0,0 +1,25 @@
package org.xyzh.common.dto.sys;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
/**
* @description 系统部门角色关系DTO
* @filename TbSysDeptRoleDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统部门角色关系DTO")
public class TbSysDeptRoleDTO extends BaseDTO {
@Schema(description = "部门ID")
private String deptId;
@Schema(description = "角色ID")
private String roleId;
}

View File

@@ -0,0 +1,29 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统模块DTO
* @filename TbSysModuleDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统模块DTO")
public class TbSysModuleDTO extends BaseDTO {
@Schema(description = "模块ID")
private String moduleId;
@Schema(description = "模块名称")
private String name;
@Schema(description = "模块描述")
private String description;
}

View File

@@ -0,0 +1,37 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统权限DTO
* @filename TbSysPermissionDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统权限DTO")
public class TbSysPermissionDTO extends BaseDTO {
@Schema(description = "权限ID")
private String permissionId;
@Schema(description = "权限名称")
private String name;
@Schema(description = "权限代码")
private String code;
@Schema(description = "权限描述")
private String description;
@Schema(description = "模块ID")
private String moduleId;
@Schema(description = "状态")
private String status;
}

View File

@@ -0,0 +1,37 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统角色DTO
* @filename TbSysRoleDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统角色DTO")
public class TbSysRoleDTO extends BaseDTO {
@Schema(description = "角色ID")
private String roleId;
@Schema(description = "角色名称")
private String name;
@Schema(description = "角色描述")
private String description;
@Schema(description = "角色作用域 global 全局角色, dept 部门角色")
private String scope;
@Schema(description = "所属部门ID")
private String ownerDeptId;
@Schema(description = "角色状态 true 有效, false 无效")
private boolean status;
}

View File

@@ -0,0 +1,25 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统角色权限关系DTO
* @filename TbSysRolePermissionDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统角色权限关系DTO")
public class TbSysRolePermissionDTO extends BaseDTO {
@Schema(description = "角色ID")
private String roleId;
@Schema(description = "权限ID")
private String permissionId;
}

View File

@@ -0,0 +1,44 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统用户DTO
* @filename TbSysUserDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统用户DTO")
public class TbSysUserDTO extends BaseDTO {
@Schema(description = "用户ID")
private String userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "邮箱")
private String email;
@Schema(description = "手机")
private String phone;
@Schema(description = "微信ID")
private String wechatId;
@Schema(description = "用户状态")
private String status;
@Schema(description = "用户类型")
private String userType;
}

View File

@@ -0,0 +1,46 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统用户信息DTO
* @filename TbSysUserInfoDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统用户信息DTO")
public class TbSysUserInfoDTO extends BaseDTO {
@Schema(description = "用户ID")
private String userId;
@Schema(description = "头像")
private String avatar;
@Schema(description = "性别")
private Integer gender;
@Schema(description = "")
private String familyName;
@Schema(description = "")
private String givenName;
@Schema(description = "全名")
private String fullName;
@Schema(description = "等级")
private Integer level;
@Schema(description = "身份证号")
private String idCard;
@Schema(description = "地址")
private String address;
}

View File

@@ -0,0 +1,26 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统用户角色关系DTO
* @filename TbSysUserRoleDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统用户角色关系DTO")
public class TbSysUserRoleDTO extends BaseDTO {
@Schema(description = "用户ID")
private String userId;
@Schema(description = "角色ID")
private String roleId;
}

View File

@@ -0,0 +1,50 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统视图DTO
* @filename TbSysViewDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统视图DTO")
public class TbSysViewDTO extends BaseDTO {
@Schema(description = "视图ID")
private String viewId;
@Schema(description = "视图名称")
private String name;
@Schema(description = "父视图ID")
private String parentId;
@Schema(description = "URL")
private String url;
@Schema(description = "组件")
private String component;
@Schema(description = "图标")
private String icon;
@Schema(description = "类型")
private Integer type;
@Schema(description = "布局")
private String layout;
@Schema(description = "排序")
private Integer orderNum;
@Schema(description = "描述")
private String description;
}

View File

@@ -0,0 +1,26 @@
package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @description 系统视图权限关系DTO
* @filename TbSysViewPermissionDTO.java
* @author yslg
* @copyright yslg
* @since 2025-11-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统视图权限关系DTO")
public class TbSysViewPermissionDTO extends BaseDTO {
@Schema(description = "视图ID")
private String viewId;
@Schema(description = "权限ID")
private String permissionId;
}

Some files were not shown because too many files have changed in this diff Show More