当我们对数据库进行巡检,比如表空间、磁盘空间使用情况等,如果要在文档中记录,需要每一项查询出来以后单独放到文档中,会比较繁琐。对此本文提供一个Shell脚本对数据库进行一般的巡检,并自动生成HTML巡检报告。
1.创建主脚本【db_inspect.sh】
bash
#!/bin/bash
# ============================================
# 数据库巡检脚本 v2.0
# 支持: Oracle, MySQL, PostgreSQL, SQL Server
# 特性: 完整的参数验证、错误处理、HTML报告生成
# ============================================
# 初始化变量
CONFIG_FILE=""
HTML_REPORT=""
TEMP_DIR=""
LOG_FILE=""
DB_TYPE=""
DB_HOST=""
DB_PORT=""
DB_USER=""
DB_PASSWORD=""
DB_NAME=""
SCRIPT_VERSION="2.0"
SCRIPT_AUTHOR="数据库运维团队"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
BOLD='\033[1m'
# 参数验证状态
PARAM_ERROR=0
CONNECTION_ERROR=0
WARNING_COUNT=0
ERROR_MESSAGES=()
WARNING_MESSAGES=()
# 创建必要的目录结构
init_directories() {
local timestamp=$(date +%Y%m%d_%H%M%S)
TEMP_DIR="./.db_inspect_temp_${timestamp}"
LOG_FILE="./logs/db_inspect_$(date +%Y%m%d).log"
HTML_REPORT="./reports/db_insp_report_${timestamp}.html"
# 创建目录
mkdir -p $TEMP_DIR 2>/dev/null
mkdir -p ./logs 2>/dev/null
mkdir -p ./reports 2>/dev/null
mkdir -p ./config 2>/dev/null
# 设置默认配置文件
if [ -z "$CONFIG_FILE" ]; then
CONFIG_FILE="./config/db_inspect.conf"
if [ ! -f "$CONFIG_FILE" ]; then
CONFIG_FILE=""
fi
fi
}
# 日志函数
log() {
local level=$1
local message=$2
local print=$3
local color=$NC
local log_level="INFO"
case $level in
"ERROR")
color=$RED
log_level="ERROR"
;;
"WARN")
color=$YELLOW
log_level="WARN"
WARNING_COUNT=$((WARNING_COUNT + 1))
WARNING_MESSAGES+=("$message")
;;
"SUCCESS")
color=$GREEN
log_level="SUCCESS"
;;
"INFO")
color=$BLUE
log_level="INFO"
;;
"DEBUG")
color=$PURPLE
log_level="DEBUG"
;;
esac
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
if [[ $print == "0" ]]; then
echo -e "${color}[${timestamp}] [${log_level}] ${message}${NC}" >> $LOG_FILE
else
echo -e "${color}[${timestamp}] [${log_level}] ${message}${NC}" | tee -a $LOG_FILE
fi
}
# 错误处理
error_exit() {
log "ERROR" "脚本执行失败: $1"
echo -e "\n${RED}❌ 错误总结:${NC}"
printf '%s\n' "${ERROR_MESSAGES[@]}" | while read error; do
echo -e " ${RED}•${NC} $error"
done
if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
echo -e "\n${YELLOW}⚠️ 警告信息 (${#WARNING_MESSAGES[@]}个):${NC}"
printf '%s\n' "${WARNING_MESSAGES[@]}" | head -5 | while read warning; do
echo -e " ${YELLOW}•${NC} $warning"
done
if [ ${#WARNING_MESSAGES[@]} -gt 5 ]; then
echo -e " ${YELLOW}... 还有$(( ${#WARNING_MESSAGES[@]} - 5 ))个警告${NC}"
fi
fi
echo -e "\n${CYAN}💡 建议:${NC}"
echo -e " 1. 使用 ${BOLD}--help${NC} 查看帮助"
echo -e " 2. 检查所有参数是否正确"
echo -e " 3. 查看日志文件: $LOG_FILE"
echo -e " 4. 确保有足够的权限执行检查"
cleanup
exit 1
}
# 添加错误信息
add_error() {
PARAM_ERROR=1
ERROR_MESSAGES+=("$1")
log "ERROR" "$1"
}
# 添加警告信息
add_warning() {
#log函数中已增加,此处便不再重复操作
#WARNING_MESSAGES+=("$1")
log "WARN" "$1" "$2"
}
# 显示横幅
show_banner() {
clear
echo -e "${BLUE}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║${NC}${BOLD} 数据库巡检脚本 v${SCRIPT_VERSION} ${NC}${BLUE}║${NC}"
echo -e "${BLUE}║${NC}${CYAN} 全面支持 Oracle, MySQL, PostgreSQL, SQL Server ${NC}${BLUE}║${NC}"
echo -e "${BLUE}╠══════════════════════════════════════════════════════════╣${NC}"
echo -e "${BLUE}║${NC}${YELLOW} 🔍 系统检查 | 📊 数据库检查 | 📄 HTML报告生成 ${NC}${BLUE}║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
}
# 检查命令是否存在
check_command() {
local cmd=$1
local required=$2
local package=$3
if ! command -v $cmd &> /dev/null; then
if [ "$required" = "required" ]; then
add_error "必需的命令 '$cmd' 未找到"
if [ -n "$package" ]; then
echo -e " 请安装: ${BOLD}$package${NC}"
fi
return 1
else
add_warning "可选命令 '$cmd' 未找到,部分功能可能受限"
return 0
fi
fi
return 0
}
# 检查文件是否存在
check_file() {
local file=$1
local description=$2
if [ ! -f "$file" ]; then
add_error "${description:-文件}不存在: $file"
return 1
fi
return 0
}
# 检查目录是否存在
check_directory() {
local dir=$1
local description=$2
if [ ! -d "$dir" ]; then
add_error "${description:-目录}不存在: $dir"
return 1
fi
return 0
}
# 检查端口是否有效
check_port() {
local port=$1
if [ -n "$port" ]; then
if ! [[ "$port" =~ ^[0-9]+$ ]]; then
add_error "端口号必须是数字,当前值: '$port'"
return 1
fi
if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
add_error "端口号必须在 1-65535 范围内,当前值: $port"
return 1
fi
fi
return 0
}
# 检查IP地址或主机名
check_host() {
local host=$1
if [ -z "$host" ]; then
add_error "主机地址不能为空"
return 1
fi
# 检查是否是有效的IP地址
if [[ $host =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
local IFS='.'
read -r i1 i2 i3 i4 <<< "$host"
if [ $i1 -gt 255 ] || [ $i2 -gt 255 ] || [ $i3 -gt 255 ] || [ $i4 -gt 255 ]; then
add_warning "主机地址 '$host' 可能不是有效的IP地址"
fi
fi
return 0
}
# 检查用户名
check_username() {
local username=$1
local db_type=$2
if [ -z "$username" ]; then
add_error "数据库用户名不能为空"
return 1
fi
# 检查用户名长度
if [ ${#username} -lt 1 ]; then
add_error "用户名太短"
return 1
fi
if [ ${#username} -gt 30 ]; then
add_warning "用户名 '${username}' 过长,某些数据库可能不支持"
fi
# 检查特殊字符
if [[ "$username" =~ [^a-zA-Z0-9_] ]]; then
add_warning "用户名 '${username}' 包含特殊字符,某些数据库可能不支持"
fi
}
# 检查密码
check_password() {
local password=$1
local db_type=$2
if [ -z "$password" ]; then
add_warning "密码为空,将尝试无密码连接或操作系统认证"
return 0
fi
# 检查密码长度
if [ ${#password} -lt 4 ]; then
add_warning "密码长度过短(建议至少8个字符)"
fi
if [ ${#password} -gt 128 ]; then
add_warning "密码过长(超过128字符)"
fi
# 密码复杂度检查(可选)
local complexity=0
[[ "$password" =~ [A-Z] ]] && complexity=$((complexity + 1))
[[ "$password" =~ [a-z] ]] && complexity=$((complexity + 1))
[[ "$password" =~ [0-9] ]] && complexity=$((complexity + 1))
[[ "$password" =~ [^a-zA-Z0-9] ]] && complexity=$((complexity + 1))
if [ $complexity -lt 3 ]; then
add_warning "密码复杂度较低,建议包含大小写字母、数字和特殊字符"
fi
}
# 显示帮助信息
show_help() {
cat << EOF
${BOLD}数据库巡检脚本 v${SCRIPT_VERSION} - 使用说明${NC}
${CYAN}📖 命令语法:${NC}
$(basename $0) [选项]
${CYAN}🎯 主要选项:${NC}
${BOLD}-t, --type TYPE${NC} 数据库类型 (必需)
oracle - Oracle Database
mysql - MySQL / MariaDB
pgsql - PostgreSQL
sqlserver - Microsoft SQL Server
${BOLD}-h, --host HOST${NC} 数据库主机地址 (默认: localhost)
${BOLD}-p, --port PORT${NC} 数据库端口号
${BOLD}-u, --user USER${NC} 数据库用户名 (必需)
${BOLD}-P, --password PASS${NC} 数据库密码
${BOLD}-d, --database DB${NC} 数据库名称
${BOLD}-c, --config FILE${NC} 配置文件路径
${BOLD}-o, --output FILE${NC} 输出报告文件路径
${BOLD}--help${NC} 显示此帮助信息
${BOLD}--version${NC} 显示版本信息
${CYAN}🔧 数据库特定默认端口:${NC}
Oracle: 1521 MySQL: 3306
PostgreSQL: 5432 SQL Server: 1433
${CYAN}🚀 使用示例:${NC}
1. ${GREEN}基本用法 (交互式输入密码):${NC}
$(basename $0) -t mysql -u root -h localhost
2. ${GREEN}完整参数示例:${NC}
$(basename $0) -t oracle -h dbserver -p 1521 -u system -d ORCL
3. ${GREEN}使用配置文件:${NC}
$(basename $0) -t mysql -c ./config/my_config.conf
4. ${GREEN}指定输出报告:${NC}
$(basename $0) -t pgsql -o /var/www/reports/db_check.html
${CYAN}📋 配置文件格式 (db_inspect.conf):${NC}
DB_HOST="localhost"
DB_PORT="3306"
DB_USER="username"
DB_PASSWORD="password"
DB_NAME="database"
${CYAN}⚠️ 安全建议:${NC}
1. 使用配置文件存储密码,并设置权限: chmod 600 config.conf
2. 不要在命令行中直接输入密码
3. 定期清理报告和日志文件
4. 限制脚本执行权限
${CYAN}📊 输出文件:${NC}
报告文件: ./reports/db_inspection_report_YYYYMMDD_HHMMSS.html
日志文件: ./logs/db_inspect_YYYYMMDD.log
临时文件: ./.db_inspect_temp_*/ (自动清理)
${CYAN}🔍 检查内容:${NC}
✓ 系统信息 (CPU, 内存, 磁盘, 网络)
✓ 数据库状态和版本
✓ 连接数和性能指标
✓ 表空间和存储使用
✓ 等待事件和锁信息
${CYAN}📞 退出代码:${NC}
0 - 成功完成
1 - 参数错误或配置错误
2 - 系统检查失败
3 - 数据库连接失败
4 - 权限不足
99 - 用户取消
EOF
exit 0
}
# 显示版本信息
show_version() {
cat << EOF
${BOLD}数据库巡检脚本 v${SCRIPT_VERSION}${NC}
${CYAN}作者: ${SCRIPT_AUTHOR}${NC}
${YELLOW}发布日期: 2024-01-20${NC}
${GREEN}支持数据库:${NC}
• Oracle Database 11g/12c/19c/21c
• MySQL 5.7/8.0, MariaDB 10+
• PostgreSQL 9.6/10/11/12/13/14
• SQL Server 2012/2014/2016/2017/2019
${BLUE}主要特性:${NC}
✓ 完整的参数验证和错误处理
✓ 交互式输入缺失参数
✓ 美观的HTML报告生成
✓ 详细的系统资源检查
✓ 数据库性能指标收集
✓ 自动清理临时文件
✓ 多级日志记录
${PURPLE}许可证: MIT${NC}
${YELLOW}更多信息: 使用 --help 查看详细帮助${NC}
EOF
exit 0
}
# 解析命令行参数
parse_arguments() {
local has_type=0
local has_user=0
# 如果没有参数,显示帮助
if [ $# -eq 0 ]; then
show_help
fi
while [[ $# -gt 0 ]]; do
case $1 in
-t|--type)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--type 参数需要指定一个值"
else
DB_TYPE="$2"
has_type=1
fi
shift 2
;;
-h|--host)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--host 参数需要指定一个值"
else
DB_HOST="$2"
fi
shift 2
;;
-p|--port)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--port 参数需要指定一个值"
else
DB_PORT="$2"
check_port "$DB_PORT"
fi
shift 2
;;
-u|--user)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--user 参数需要指定一个值"
else
DB_USER="$2"
has_user=1
fi
shift 2
;;
-P|--password)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--password 参数需要指定一个值"
else
DB_PASSWORD="$2"
fi
shift 2
;;
-d|--database)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--database 参数需要指定一个值"
else
DB_NAME="$2"
fi
shift 2
;;
-c|--config)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--config 参数需要指定一个值"
else
CONFIG_FILE="$2"
check_file "$CONFIG_FILE" "配置文件"
fi
shift 2
;;
-o|--output)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
add_error "--output 参数需要指定一个值"
else
HTML_REPORT="$2"
local output_dir=$(dirname "$HTML_REPORT")
if [ ! -d "$output_dir" ] && [ "$output_dir" != "." ]; then
mkdir -p "$output_dir" 2>/dev/null ||
add_warning "输出目录 '$output_dir' 不存在,将尝试创建"
fi
fi
shift 2
;;
--help)
show_help
;;
--version)
show_version
;;
-*)
add_error "未知选项: $1"
shift
;;
*)
add_error "未知参数: $1"
shift
;;
esac
done
# 检查必需参数
if [ $has_type -eq 0 ]; then
add_error "必须指定数据库类型 (使用 -t 或 --type 参数)"
fi
if [ $has_user -eq 0 ] && [ "$DB_TYPE" != "oracle" ]; then
add_warning "建议提供数据库用户名 (使用 -u 或 --user 参数)"
fi
}
# 读取配置文件
load_configuration() {
if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
log "INFO" "加载配置文件: $CONFIG_FILE"
# 检查配置文件权限
local file_perm=$(stat -c %a "$CONFIG_FILE" 2>/dev/null || stat -f %p "$CONFIG_FILE" 2>/dev/null)
if [[ $file_perm =~ ^[0-9]+$ ]] && [ $((file_perm % 10)) -gt 4 ]; then
add_warning "配置文件 '$CONFIG_FILE' 权限过宽 (建议 chmod 600)"
fi
# 安全地source配置文件
if source "$CONFIG_FILE" 2>/dev/null; then
# 从配置文件覆盖参数(如果命令行未指定)
DB_HOST=${DB_HOST:-${CONFIG_DB_HOST:-"localhost"}}
DB_PORT=${DB_PORT:-${CONFIG_DB_PORT:-}}
DB_USER=${DB_USER:-${CONFIG_DB_USER:-}}
DB_PASSWORD=${DB_PASSWORD:-${CONFIG_DB_PASSWORD:-}}
DB_NAME=${DB_NAME:-${CONFIG_DB_NAME:-}}
# 记录从配置文件读取的值
[ -n "$CONFIG_DB_HOST" ] && log "DEBUG" "从配置读取主机: $CONFIG_DB_HOST"
[ -n "$CONFIG_DB_USER" ] && log "DEBUG" "从配置读取用户: $CONFIG_DB_USER"
[ -n "$CONFIG_DB_NAME" ] && log "DEBUG" "从配置读取数据库: $CONFIG_DB_NAME"
else
add_error "无法读取配置文件: $CONFIG_FILE"
return 1
fi
else
if [ -n "$CONFIG_FILE" ]; then
add_warning "配置文件不存在: $CONFIG_FILE"
fi
fi
return 0
}
# 验证数据库类型
validate_database_type() {
log "INFO" "验证数据库类型: $DB_TYPE"
case $(echo "$DB_TYPE" | tr '[:upper:]' '[:lower:]') in
oracle|ora)
DB_TYPE="oracle"
log "SUCCESS" "数据库类型: Oracle"
# Oracle特定检查
if [ -z "$ORACLE_HOME" ]; then
add_warning "ORACLE_HOME 环境变量未设置"
elif [ ! -d "$ORACLE_HOME" ]; then
add_warning "ORACLE_HOME 目录不存在: $ORACLE_HOME"
fi
# 设置默认端口
DB_PORT=${DB_PORT:-1521}
# Oracle通常不需要指定数据库名
if [ -z "$DB_NAME" ]; then
DB_NAME=""
log "INFO" "Oracle数据库名未指定,将使用默认服务"
fi
;;
mysql|mariadb)
DB_TYPE="mysql"
log "SUCCESS" "数据库类型: MySQL/MariaDB"
# MySQL特定检查
if [ -z "$DB_USER" ]; then
add_error "MySQL需要指定用户名 (使用 -u 参数)"
else
check_username "$DB_USER" "mysql"
fi
# 设置默认值
DB_PORT=${DB_PORT:-3306}
DB_NAME=${DB_NAME:-"mysql"}
;;
pgsql|postgresql|postgres)
DB_TYPE="pgsql"
log "SUCCESS" "数据库类型: PostgreSQL"
# PostgreSQL特定检查
if [ -z "$DB_USER" ]; then
add_error "PostgreSQL需要指定用户名 (使用 -u 参数)"
else
check_username "$DB_USER" "pgsql"
fi
# 设置默认值
DB_PORT=${DB_PORT:-5432}
DB_NAME=${DB_NAME:-"postgres"}
;;
sqlserver|mssql|sqlsrv)
DB_TYPE="sqlserver"
log "SUCCESS" "数据库类型: SQL Server"
# SQL Server特定检查
if [ -z "$DB_USER" ]; then
add_warning "SQL Server用户名未指定,将尝试Windows认证"
else
check_username "$DB_USER" "sqlserver"
fi
# 设置默认值
DB_PORT=${DB_PORT:-1433}
DB_NAME=${DB_NAME:-"master"}
;;
*)
add_error "不支持的数据库类型: $DB_TYPE"
log "INFO" "支持的数据库类型: oracle, mysql, pgsql, sqlserver"
return 1
;;
esac
# 验证端口号
check_port "$DB_PORT"
# 验证主机地址
DB_HOST=${DB_HOST:-"localhost"}
check_host "$DB_HOST"
# 验证密码
check_password "$DB_PASSWORD" "$DB_TYPE"
return 0
}
# 交互式输入缺失参数
interactive_input() {
echo -e "\n${CYAN}🔄 交互式参数输入${NC}"
echo -e "${BLUE}══════════════════════════════════════════════════════════${NC}"
# 如果用户名未提供,提示输入
if [ -z "$DB_USER" ] && [ "$DB_TYPE" != "oracle" ]; then
echo -e "${YELLOW}请输入数据库用户名:${NC}"
read -p "> " DB_USER
if [ -z "$DB_USER" ]; then
add_error "用户名不能为空"
return 1
fi
check_username "$DB_USER" "$DB_TYPE"
log "INFO" "用户输入用户名: $DB_USER"
fi
# 如果密码未提供,提示输入
if [ -z "$DB_PASSWORD" ]; then
echo -e "\n${YELLOW}请输入数据库密码 (输入时不显示字符):${NC}"
echo -e "${CYAN}提示: 直接按回车键将尝试无密码连接或操作系统认证${NC}"
stty -echo
read -p "> " DB_PASSWORD
stty echo
echo ""
check_password "$DB_PASSWORD" "$DB_TYPE"
if [ -n "$DB_PASSWORD" ]; then
log "INFO" "用户输入了密码 (长度: ${#DB_PASSWORD} 字符)"
else
log "INFO" "用户未输入密码"
fi
fi
# 如果数据库名未提供,提示输入
if [ -z "$DB_NAME" ] && [ "$DB_TYPE" != "oracle" ]; then
local default_db=""
case $DB_TYPE in
mysql) default_db="mysql" ;;
pgsql) default_db="postgres" ;;
sqlserver) default_db="master" ;;
esac
echo -e "\n${YELLOW}请输入数据库名 [${default_db}]:${NC}"
read -p "> " input_db
DB_NAME=${input_db:-$default_db}
log "INFO" "用户输入数据库名: $DB_NAME"
fi
# 确认参数
echo -e "\n${GREEN}✅ 参数输入完成${NC}"
return 0
}
# 显示参数摘要
show_parameter_summary() {
echo -e "\n${CYAN}📋 参数配置摘要${NC}"
echo -e "${BLUE}══════════════════════════════════════════════════════════${NC}"
printf "%-20s: ${BOLD}%s${NC}\n" "数据库类型" "$DB_TYPE"
printf "%-20s: ${BOLD}%s${NC}\n" "主机地址" "$DB_HOST"
printf "%-20s: ${BOLD}%s${NC}\n" "端口号" "$DB_PORT"
if [ -n "$DB_USER" ]; then
printf "%-20s: ${BOLD}%s${NC}\n" "用户名" "$DB_USER"
else
printf "%-20s: ${YELLOW}%s${NC}\n" "用户名" "未指定 (使用OS认证)"
fi
if [ -n "$DB_PASSWORD" ]; then
printf "%-20s: ${BOLD}%s${NC}\n" "密码" "****** (已设置)"
else
printf "%-20s: ${YELLOW}%s${NC}\n" "密码" "未设置"
fi
if [ -n "$DB_NAME" ]; then
printf "%-20s: ${BOLD}%s${NC}\n" "数据库名" "$DB_NAME"
else
printf "%-20s: ${YELLOW}%s${NC}\n" "数据库名" "未指定"
fi
printf "%-20s: ${BOLD}%s${NC}\n" "配置文件" "${CONFIG_FILE:-未使用}"
printf "%-20s: ${BOLD}%s${NC}\n" "输出报告" "$HTML_REPORT"
printf "%-20s: ${BOLD}%s${NC}\n" "日志文件" "$LOG_FILE"
echo -e "\n${YELLOW}📊 统计信息:${NC}"
printf " %-25s: ${BOLD}%d 个${NC}\n" "错误" "${#ERROR_MESSAGES[@]}"
printf " %-25s: ${BOLD}%d 个${NC}\n" "警告" "${#WARNING_MESSAGES[@]}"
if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
echo -e "\n${RED}❌ 发现错误,请先解决问题:${NC}"
for error in "${ERROR_MESSAGES[@]}"; do
echo -e " ${RED}•${NC} $error"
done
return 1
fi
if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
echo -e "\n${YELLOW}⚠️ 发现警告 (不影响继续执行):${NC}"
for warning in "${WARNING_MESSAGES[@]:0:3}"; do
echo -e " ${YELLOW}•${NC} $warning"
done
if [ ${#WARNING_MESSAGES[@]} -gt 3 ]; then
echo -e " ${YELLOW}... 还有$(( ${#WARNING_MESSAGES[@]} - 3 ))个警告${NC}"
fi
fi
return 0
}
# 检查系统命令依赖
check_system_dependencies() {
log "INFO" "检查系统命令依赖..."
# 必需的系统命令
local required_commands="awk sed grep date ps"
for cmd in $required_commands; do
check_command "$cmd" "required"
done
# 可选的系统命令(增强功能)
local optional_commands="free df uptime vmstat iostat netstat ss"
for cmd in $optional_commands; do
check_command "$cmd" "optional"
done
# 数据库客户端检查
case $DB_TYPE in
oracle)
check_command "sqlplus" "required" "Oracle Instant Client"
;;
mysql)
check_command "mysql" "required" "mysql-client"
check_command "mysqladmin" "optional" "mysql-client"
;;
pgsql)
check_command "psql" "required" "postgresql-client"
;;
sqlserver)
check_command "sqlcmd" "required" "mssql-tools"
;;
esac
if [ $PARAM_ERROR -eq 1 ]; then
error_exit "系统依赖检查失败"
fi
}
# 确认开始检查
confirm_execution() {
echo -e "\n${CYAN}🚀 准备开始巡检${NC}"
echo -e "${BLUE}══════════════════════════════════════════════════════════${NC}"
if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
echo -e "${RED}存在错误,无法继续执行:${NC}"
for error in "${ERROR_MESSAGES[@]}"; do
echo -e " ${RED}•${NC} $error"
done
return 1
fi
echo -e "${YELLOW}即将执行以下操作:${NC}"
echo -e " 1. 📊 系统资源检查 (CPU, 内存, 磁盘, 网络)"
echo -e " 2. 🗄️ 数据库状态检查 ($DB_TYPE)"
echo -e " 3. 📄 生成HTML报告"
echo -e " 4. 🧹 清理临时文件"
echo -e "\n${GREEN}预计耗时: 1-3分钟${NC}"
local attempts=0
while [ $attempts -lt 3 ]; do
echo -e "\n${CYAN}是否开始巡检?(y/n):${NC}"
read -p "> " confirm
case $confirm in
y|Y|yes|YES|Yes)
log "INFO" "用户确认开始巡检"
echo -e "\n${GREEN}✅ 开始执行巡检...${NC}"
return 0
;;
n|N|no|NO|No)
log "INFO" "用户取消巡检"
echo -e "\n${YELLOW}巡检已取消${NC}"
cleanup
exit 99
;;
*)
attempts=$((attempts + 1))
if [ $attempts -lt 3 ]; then
echo -e "${RED}无效输入,请输入 y(是) 或 n(否)${NC}"
else
echo -e "${RED}输入错误次数过多,取消执行${NC}"
cleanup
exit 99
fi
;;
esac
done
}
# ============================================
# 系统巡检部分
# ============================================
# 检查系统信息
check_system_info() {
log "INFO" "检查系统信息..."
cat > $TEMP_DIR/system.html << 'EOF'
<h2>📱 系统信息</h2>
<div class="section-content">
<table>
EOF
# 主机名
local hostname=$(hostname 2>/dev/null || echo "未知")
echo "<tr><td>主机名</td><td>$hostname</td></tr>" >> $TEMP_DIR/system.html
# 操作系统
if [ -f /etc/os-release ]; then
local os_name=$(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '"' 2>/dev/null || echo "未知")
echo "<tr><td>操作系统</td><td>$os_name</td></tr>" >> $TEMP_DIR/system.html
elif [ -f /etc/redhat-release ]; then
local os_name=$(cat /etc/redhat-release 2>/dev/null || echo "未知")
echo "<tr><td>操作系统</td><td>$os_name</td></tr>" >> $TEMP_DIR/system.html
fi
# 内核版本
local kernel=$(uname -r 2>/dev/null || echo "未知")
echo "<tr><td>内核版本</td><td>$kernel</td></tr>" >> $TEMP_DIR/system.html
# 系统架构
local arch=$(uname -m 2>/dev/null || echo "未知")
echo "<tr><td>系统架构</td><td>$arch</td></tr>" >> $TEMP_DIR/system.html
# 系统运行时间
if command -v uptime &> /dev/null; then
local uptime_info=$(uptime 2>/dev/null | awk -F',' '{print $1}' | sed 's/^ *//')
echo "<tr><td>运行时间</td><td>$uptime_info</td></tr>" >> $TEMP_DIR/system.html
fi
echo "</table></div>" >> $TEMP_DIR/system.html
}
# 检查内存使用情况
check_memory() {
log "INFO" "检查内存使用情况..."
cat > $TEMP_DIR/memory.html << 'EOF'
<h2>🧠 内存信息</h2>
<div class="section-content">
<table>
<tr><th>项目</th><th>值</th><th>状态</th></tr>
EOF
if command -v free &> /dev/null; then
# Linux系统
local mem_info=$(free -b 2>/dev/null)
if [ -n "$mem_info" ]; then
local mem_total=$(echo "$mem_info" | grep Mem | awk '{print $2}')
local mem_used=$(echo "$mem_info" | grep Mem | awk '{print $3}')
local mem_available=$(echo "$mem_info" | grep Mem | awk '{print $7}')
if [ -n "$mem_total" ] && [ "$mem_total" -gt 0 ]; then
# 计算使用率
local mem_usage=$(echo "scale=1; $mem_used * 100 / $mem_total" | bc 2>/dev/null || echo "0")
# 格式化显示
local mem_total_gb=$(echo "scale=2; $mem_total / 1024 / 1024 / 1024" | bc 2>/dev/null)
local mem_used_gb=$(echo "scale=2; $mem_used / 1024 / 1024 / 1024" | bc 2>/dev/null)
local mem_available_gb=$(echo "scale=2; $mem_available / 1024 / 1024 / 1024" | bc 2>/dev/null)
echo "<tr><td>总内存</td><td>${mem_total_gb} GB</td><td class='info'>正常</td></tr>" >> $TEMP_DIR/memory.html
echo "<tr><td>已用内存</td><td>${mem_used_gb} GB</td><td class='info'>正常</td></tr>" >> $TEMP_DIR/memory.html
echo "<tr><td>可用内存</td><td>${mem_available_gb} GB</td><td class='info'>正常</td></tr>" >> $TEMP_DIR/memory.html
# 内存使用率状态
local mem_status="good"
if [ $(echo "$mem_usage > 90" | bc 2>/dev/null) -eq 1 ]; then
mem_status="critical"
add_warning "内存使用率过高: ${mem_usage}%"
elif [ $(echo "$mem_usage > 80" | bc 2>/dev/null) -eq 1 ]; then
mem_status="warning"
add_warning "内存使用率较高: ${mem_usage}%"
fi
echo "<tr><td>内存使用率</td><td>${mem_usage}%</td><td class='${mem_status}'>${mem_status}</td></tr>" >> $TEMP_DIR/memory.html
# 检查Swap
local swap_total=$(echo "$mem_info" | grep Swap | awk '{print $2}')
if [ -n "$swap_total" ] && [ "$swap_total" -gt 0 ]; then
local swap_used=$(echo "$mem_info" | grep Swap | awk '{print $3}')
local swap_usage=$(echo "scale=1; $swap_used * 100 / $swap_total" | bc 2>/dev/null || echo "0")
local swap_status="good"
if [ $(echo "$swap_usage > 50" | bc 2>/dev/null) -eq 1 ]; then
swap_status="warning"
add_warning "Swap使用率较高: ${swap_usage}%"
fi
local swap_total_gb=$(echo "scale=2; $swap_total / 1024 / 1024 / 1024" | bc 2>/dev/null)
local swap_used_gb=$(echo "scale=2; $swap_used / 1024 / 1024 / 1024" | bc 2>/dev/null)
echo "<tr><td>Swap总量</td><td>${swap_total_gb} GB</td><td class='info'>正常</td></tr>" >> $TEMP_DIR/memory.html
echo "<tr><td>Swap已用</td><td>${swap_used_gb} GB (${swap_usage}%)</td><td class='${swap_status}'>${swap_status}</td></tr>" >> $TEMP_DIR/memory.html
fi
fi
fi
fi
echo "</table></div>" >> $TEMP_DIR/memory.html
}
# 检查磁盘空间
check_disk() {
log "INFO" "检查磁盘空间..."
cat > $TEMP_DIR/disk.html << 'EOF'
<h2>💾 磁盘空间</h2>
<div class="section-content">
<table>
<tr><th>文件系统</th><th>大小</th><th>已用</th><th>可用</th><th>使用率</th><th>挂载点</th><th>状态</th></tr>
EOF
if command -v df &> /dev/null; then
# 排除特殊文件系统
df -h 2>/dev/null | grep -E '^/dev/|^/mmt/|^/opt/|^/u01/|^/oracle' | while read line; do
local filesystem=$(echo $line | awk '{print $1}')
local size=$(echo $line | awk '{print $2}')
local used=$(echo $line | awk '{print $3}')
local avail=$(echo $line | awk '{print $4}')
local use_pct=$(echo $line | awk '{print $5}' | sed 's/%//')
local mount=$(echo $line | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')
# 确定状态
local status="good"
if [ "$use_pct" -ge 95 ]; then
status="critical"
add_warning "磁盘空间严重不足: $filesystem ($mount) 使用率 ${use_pct}%"
elif [ "$use_pct" -ge 85 ]; then
status="warning"
add_warning "磁盘空间不足: $filesystem ($mount) 使用率 ${use_pct}%"
fi
echo "<tr><td>$filesystem</td><td>$size</td><td>$used</td><td>$avail</td><td>${use_pct}%</td><td>$mount</td><td class='$status'>$status</td></tr>" >> $TEMP_DIR/disk.html
done
fi
echo "</table></div>" >> $TEMP_DIR/disk.html
}
# 检查CPU使用率
check_cpu() {
log "INFO" "检查CPU使用率..."
cat > $TEMP_DIR/cpu.html << 'EOF'
<h2>⚡ CPU信息</h2>
<div class="section-content">
<table>
EOF
# CPU核心数
if command -v nproc &> /dev/null; then
local cpu_cores=$(nproc 2>/dev/null)
echo "<tr><td>CPU核心数</td><td>$cpu_cores</td></tr>" >> $TEMP_DIR/cpu.html
elif command -v sysctl &> /dev/null; then
local cpu_cores=$(sysctl -n hw.ncpu 2>/dev/null)
echo "<tr><td>CPU核心数</td><td>$cpu_cores</td></tr>" >> $TEMP_DIR/cpu.html
fi
# 负载平均值
if command -v uptime &> /dev/null; then
local load_avg=$(uptime 2>/dev/null | awk -F'load average:' '{print $2}' | sed 's/^ *//')
echo "<tr><td>负载平均值</td><td>$load_avg</td></tr>" >> $TEMP_DIR/cpu.html
# 检查负载是否过高
local load1=$(echo $load_avg | awk -F',' '{print $1}' | sed 's/ //g')
if [ -n "$cpu_cores" ] && [ -n "$load1" ]; then
if [ $(echo "$load1 > $cpu_cores * 2" | bc 2>/dev/null) -eq 1 ]; then
add_warning "系统负载较高: $load_avg"
fi
fi
fi
# CPU使用率(需要top或mpstat)
if command -v mpstat &> /dev/null; then
local cpu_idle=$(mpstat 1 1 2>/dev/null | awk '/Average:/ {print $NF}')
if [ -n "$cpu_idle" ]; then
local cpu_usage=$(echo "scale=1; 100 - $cpu_idle" | bc 2>/dev/null)
local cpu_status="good"
if [ $(echo "$cpu_usage > 90" | bc 2>/dev/null) -eq 1 ]; then
cpu_status="critical"
add_warning "CPU使用率过高: ${cpu_usage}%"
elif [ $(echo "$cpu_usage > 80" | bc 2>/dev/null) -eq 1 ]; then
cpu_status="warning"
fi
echo "<tr><td>CPU使用率</td><td>${cpu_usage}%</td><td class='$cpu_status'>$cpu_status</td></tr>" >> $TEMP_DIR/cpu.html
fi
fi
echo "</table></div>" >> $TEMP_DIR/cpu.html
}
# 检查网络连接
check_network() {
log "INFO" "检查网络连接..."
cat > $TEMP_DIR/network.html << 'EOF'
<h2>🌐 网络连接</h2>
<div class="section-content">
<h3>监听端口</h3>
<table>
<tr><th>端口</th><th>服务</th><th>状态</th></tr>
EOF
# 检查数据库端口
local port_status="down"
if command -v nc &> /dev/null; then
if nc -z -w 2 "$DB_HOST" "$DB_PORT" 2>/dev/null; then
port_status="up"
fi
elif command -v telnet &> /dev/null; then
if echo "" | telnet "$DB_HOST" "$DB_PORT" 2>&1 | grep -q "Connected"; then
port_status="up"
fi
fi
if [ "$port_status" = "up" ]; then
echo "<tr><td>$DB_PORT</td><td>$DB_TYPE</td><td class='good'>✓ 监听中</td></tr>" >> $TEMP_DIR/network.html
else
echo "<tr><td>$DB_PORT</td><td>$DB_TYPE</td><td class='critical'>✗ 未监听</td></tr>" >> $TEMP_DIR/network.html
add_warning "数据库端口 $DB_PORT 未监听"
fi
echo "</table>" >> $TEMP_DIR/network.html
# 网络接口信息
echo "<h3>网络接口</h3><table><tr><th>接口</th><th>IP地址</th><th>状态</th></tr>" >> $TEMP_DIR/network.html
if command -v ip &> /dev/null; then
ip -br addr show 2>/dev/null | while read line; do
local interface=$(echo $line | awk '{print $1}')
local state=$(echo $line | awk '{print $2}')
local ip=$(echo $line | awk '{print $3}')
echo "<tr><td>$interface</td><td>$ip</td><td>$state</td></tr>" >> $TEMP_DIR/network.html
done
elif command -v ifconfig &> /dev/null; then
ifconfig 2>/dev/null | grep -E "^(eth|en|bond|br|lo|vlan)" | awk '{print $1}' | while read interface; do
local ip=$(ifconfig $interface 2>/dev/null | grep 'inet ' | awk '{print $2}')
echo "<tr><td>$interface</td><td>${ip:-未配置}</td><td>unknown</td></tr>" >> $TEMP_DIR/network.html
done
fi
echo "</table></div>" >> $TEMP_DIR/network.html
}
# ============================================
# 数据库巡检部分
# ============================================
# 测试数据库连接
test_database_connection() {
log "INFO" "测试数据库连接..."
case $DB_TYPE in
mysql)
local mysql_cmd="mysql"
[ -n "$DB_USER" ] && mysql_cmd="$mysql_cmd -u$DB_USER"
[ -n "$DB_PASSWORD" ] && mysql_cmd="$mysql_cmd -p$DB_PASSWORD"
[ -n "$DB_HOST" ] && mysql_cmd="$mysql_cmd -h$DB_HOST"
[ -n "$DB_PORT" ] && mysql_cmd="$mysql_cmd -P$DB_PORT"
[ -n "$DB_NAME" ] && mysql_cmd="$mysql_cmd $DB_NAME"
if $mysql_cmd -e "SELECT 1;" 2>/dev/null | grep -q "1"; then
log "SUCCESS" "MySQL连接测试成功"
return 0
else
add_warning "MySQL连接测试失败"
CONNECTION_ERROR=1
return 1
fi
;;
oracle)
# Oracle连接测试
if command -v tnsping &> /dev/null; then
if tnsping $DB_HOST:$DB_PORT 2>/dev/null | grep -q "OK"; then
log "SUCCESS" "Oracle网络连接测试成功"
else
add_warning "Oracle网络连接测试失败"
fi
fi
return 0
;;
pgsql)
local pgsql_cmd="psql"
[ -n "$DB_USER" ] && pgsql_cmd="$pgsql_cmd -U $DB_USER"
[ -n "$DB_HOST" ] && pgsql_cmd="$pgsql_cmd -h $DB_HOST"
[ -n "$DB_PORT" ] && pgsql_cmd="$pgsql_cmd -p $DB_PORT"
[ -n "$DB_NAME" ] && pgsql_cmd="$pgsql_cmd -d $DB_NAME"
if $pgsql_cmd -c "SELECT 1;" 2>/dev/null | grep -q "1"; then
log "SUCCESS" "PostgreSQL连接测试成功"
return 0
else
add_warning "PostgreSQL连接测试失败"
CONNECTION_ERROR=1
return 1
fi
;;
sqlserver)
# SQL Server连接测试
log "INFO" "SQL Server连接测试需要Windows环境或配置"
return 0
;;
esac
return 0
}
# Oracle数据库巡检
check_oracle() {
log "INFO" "执行Oracle数据库巡检..."
cat > $TEMP_DIR/database.html << 'EOF'
<h2>🗃️ Oracle数据库巡检</h2>
<div class="section-content">
EOF
# 检查sqlplus是否存在
if ! command -v sqlplus &> /dev/null; then
echo "<p class='warning'>⚠️ sqlplus未找到,跳过Oracle检查</p>" >> $TEMP_DIR/database.html
add_warning "sqlplus未安装,跳过Oracle数据库检查"
return
fi
# 创建检查SQL
cat > $TEMP_DIR/oracle_check.sql << 'ORACLE_EOF'
set pagesize 0
set linesize 32767
set feedback off
set heading off
SET TRIMSPOOL ON
SET WRAP OFF
--set termout off
-- 数据库版本和状态
prompt ===数据库信息===
select '实例名: ' || instance_name from v$instance;
select '版本: ' || version from v$instance;
select '状态: ' || status || ', 启动时间: ' ||
to_char(startup_time, 'YYYY-MM-DD HH24:MI:SS') from v$instance;
-- 表空间使用
prompt ===表空间使用情况===
/*select '表空间【' || tablespace_name || '】:【数据文件数】' || file_count || ',【已分配大小】' ||
round(total_mb, 2) || 'MB , 【已使用】' || round(used_mb, 2) || 'MB (' ||
round(used_percent, 2) || '%)' || case
when used_percent >= 90 then
'占用高'
end || ' ,【数据文件还可增加分配大小】' || round(unmakespace_mb, 2) || 'MB' as space_info
from (select a.tablespace_name,
a.file_count,
a.bytes / 1024 / 1024 total_mb,
(a.bytes - nvl(b.free_bytes, 0)) / 1024 / 1024 used_mb,
((decode(nvl(a.maxbytes, 0), 0, a.bytes, nvl(a.maxbytes, 0))) -
a.bytes) / 1024 / 1024 unmakespace_mb,
(a.bytes - nvl(b.free_bytes, 0)) / a.bytes * 100 used_percent
from (select tablespace_name,
count(file_id) file_count,
sum(bytes) bytes,
sum(maxbytes) maxbytes
from dba_data_files
group by tablespace_name) a,
(select tablespace_name, sum(bytes) free_bytes
from dba_free_space
group by tablespace_name) b
where a.tablespace_name = b.tablespace_name(+)
order by used_percent desc);*/
select '<th>表空间</th>'||
'<th>数据文件数</th>'||
'<th>已分配大小</th>'||
'<th>已使用大小</th>'||
'<th>数据文件还可增加分配大小</th>' as space_info
from dual
union all
select '<td>' || tablespace_name ||
'</td><td>' || file_count ||
'</td><td>' || round(total_mb, 2) ||
'MB</td><td>' || round(used_mb, 2) ||
'MB (' || to_char(round(used_percent, 2),'FM990.90') || '%)' ||
case
when used_percent >= 90 then
'占用高'
end ||
'</td><td>' ||
round(unmakespace_mb, 2) || 'MB</td>' as space_info
from (select a.tablespace_name,
a.file_count,
a.bytes / 1024 / 1024 total_mb,
(a.bytes - nvl(b.free_bytes, 0)) / 1024 / 1024 used_mb,
((decode(nvl(a.maxbytes, 0), 0, a.bytes, nvl(a.maxbytes, 0))) -
a.bytes) / 1024 / 1024 unmakespace_mb,
(a.bytes - nvl(b.free_bytes, 0)) / a.bytes * 100 used_percent
from (select tablespace_name,
count(file_id) file_count,
sum(bytes) bytes,
sum(maxbytes) maxbytes
from dba_data_files
group by tablespace_name) a,
(select tablespace_name, sum(bytes) free_bytes
from dba_free_space
group by tablespace_name) b
where a.tablespace_name = b.tablespace_name(+)
order by used_percent desc);
-- 会话信息
prompt ===会话统计===
select '活动会话数: ' || count(*) from v$session where status = 'ACTIVE';
select '总会话数: ' || count(*) from v$session;
-- 等待事件
prompt ===等待事件【前五】===
select '【事件】' || event || ': 【数量】' || count(*)
from v$session_wait
where event not like '%SQL%'
group by event
order by count(1) desc fetch first 5 rows only;
-- 无效对象
prompt ===对象状态===
select '无效对象【数量】: ' || count(*) from dba_objects where status != 'VALID';
exit;
ORACLE_EOF
# 执行SQL检查
local sql_output=$TEMP_DIR/oracle_output.txt
local connect_string=""
if [ -n "$DB_USER" ] && [ -n "$DB_PASSWORD" ]; then
if [[ $DB_PASSWORD =~ @ ]]; then
connect_string="$DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
else
connect_string="$DB_USER/$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME"
fi
else
connect_string="/ as sysdba"
fi
log "INFO" "Oracle数据库连接串:$connect_string"
if sqlplus -S "$connect_string" @$TEMP_DIR/oracle_check.sql > $sql_output 2>&1; then
# 转换输出为HTML表格
echo "<table>" >> $TEMP_DIR/database.html
local current_section=""
local current_line=""
while read line; do
if [[ "$line" == ===*=== ]]; then
current_section=${line//=/}
echo "<tr><td colspan='3'><h4>$current_section</h4></td></tr>" >> $TEMP_DIR/database.html
elif [[ -n $line && $line != $'\n' && $line != $'\r' && $line != $'\r\n' ]]; then
if [[ $line =~ "</" ]]; then
echo "<tr>$line</tr>" >> $TEMP_DIR/database.html
else
echo "<tr><td>$line</td></tr>" >> $TEMP_DIR/database.html
fi
fi
if [[ $line =~ 占用高 ]]; then
current_line="${line//<\/td>/</p>}"
current_line="${current_line//<td/<p}"
current_line="${current_line//center/left}"
add_warning "<p>表空间占用较高:</p> ${current_line}" "0"
fi
done < $sql_output
echo "</table>" >> $TEMP_DIR/database.html
log "SUCCESS" "Oracle数据库检查完成"
else
echo "<p class='critical'>❌ Oracle数据库检查失败</p>" >> $TEMP_DIR/database.html
add_warning "Oracle数据库检查失败"
fi
echo "</div>" >> $TEMP_DIR/database.html
}
# MySQL数据库巡检
check_mysql() {
log "INFO" "执行MySQL数据库巡检..."
cat > $TEMP_DIR/database.html << 'EOF'
<h2>🗃️ MySQL数据库巡检</h2>
<div class="section-content">
EOF
if ! command -v mysql &> /dev/null; then
echo "<p class='warning'>⚠️ mysql客户端未找到,跳过MySQL检查</p>" >> $TEMP_DIR/database.html
add_warning "mysql未安装,跳过MySQL数据库检查"
return
fi
# 构建mysql命令
local mysql_cmd="mysql"
[ -n "$DB_USER" ] && mysql_cmd="$mysql_cmd -u$DB_USER"
[ -n "$DB_PASSWORD" ] && mysql_cmd="$mysql_cmd -p$DB_PASSWORD"
[ -n "$DB_HOST" ] && mysql_cmd="$mysql_cmd -h$DB_HOST"
[ -n "$DB_PORT" ] && mysql_cmd="$mysql_cmd -P$DB_PORT"
[ -n "$DB_NAME" ] && mysql_cmd="$mysql_cmd $DB_NAME"
# 创建检查SQL
cat > $TEMP_DIR/mysql_check.sql << 'MYSQL_EOF'
-- 数据库信息
SELECT '===数据库信息===' AS '';
SELECT CONCAT('版本: ', @@version) AS '';
SELECT CONCAT('运行时间: ',
FLOOR(@@GLOBAL.Uptime/86400), '天 ',
FLOOR((@@GLOBAL.Uptime%86400)/3600), '小时 ',
FLOOR((@@GLOBAL.Uptime%3600)/60), '分钟') AS '';
-- 数据库大小
SELECT '===数据库大小===' AS '';
SELECT
table_schema AS '数据库',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS '大小(MB)',
COUNT(*) AS '表数量'
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'performance_schema', 'sys')
GROUP BY table_schema
ORDER BY SUM(data_length + index_length) DESC
LIMIT 10;
-- 连接信息
SELECT '===连接信息===' AS '';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';
SELECT CONCAT('连接使用率: ',
ROUND(Threads_connected / @@max_connections * 100, 2), '%') AS ''
FROM (SELECT @@max_connections AS max_connections) vars,
(SELECT VARIABLE_VALUE AS Threads_connected
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME = 'Threads_connected') stats;
-- 表空间
SELECT '===大表统计===' AS '';
SELECT
table_schema AS '数据库',
table_name AS '表名',
ROUND((data_length + index_length) / 1024 / 1024, 2) AS '大小(MB)',
table_rows AS '行数'
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
ORDER BY (data_length + index_length) DESC
LIMIT 10;
-- 状态信息
SELECT '===状态信息===' AS '';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
MYSQL_EOF
# 执行SQL检查
local sql_output=$TEMP_DIR/mysql_output.txt
if $mysql_cmd < $TEMP_DIR/mysql_check.sql > $sql_output 2>&1; then
echo "<table>" >> $TEMP_DIR/database.html
local current_section=""
while read line; do
if [[ "$line" == ===*=== ]]; then
current_section=${line//=/}
echo "<tr><td colspan='3'><h4>$current_section</h4></td></tr>" >> $TEMP_DIR/database.html
elif [[ -n "$line" ]]; then
echo "<tr><td>$line</td></tr>" >> $TEMP_DIR/database.html
fi
done < $sql_output
echo "</table>" >> $TEMP_DIR/database.html
log "SUCCESS" "MySQL数据库检查完成"
else
echo "<p class='critical'>❌ MySQL数据库检查失败</p>" >> $TEMP_DIR/database.html
add_warning "MySQL数据库检查失败"
fi
echo "</div>" >> $TEMP_DIR/database.html
}
# PostgreSQL数据库巡检
check_postgresql() {
log "INFO" "执行PostgreSQL数据库巡检..."
cat > $TEMP_DIR/database.html << 'EOF'
<h2>🗃️ PostgreSQL数据库巡检</h2>
<div class="section-content">
EOF
if ! command -v psql &> /dev/null; then
echo "<p class='warning'>⚠️ psql客户端未找到,跳过PostgreSQL检查</p>" >> $TEMP_DIR/database.html
add_warning "psql未安装,跳过PostgreSQL数据库检查"
return
fi
# 构建psql命令
local pgsql_cmd="psql"
[ -n "$DB_USER" ] && pgsql_cmd="$pgsql_cmd -U $DB_USER"
[ -n "$DB_HOST" ] && pgsql_cmd="$pgsql_cmd -h $DB_HOST"
[ -n "$DB_PORT" ] && pgsql_cmd="$pgsql_cmd -p $DB_PORT"
[ -n "$DB_NAME" ] && pgsql_cmd="$pgsql_cmd -d $DB_NAME"
pgsql_cmd="$pgsql_cmd -q -t"
# 创建检查SQL
cat > $TEMP_DIR/pgsql_check.sql << 'PGSQL_EOF'
-- 数据库信息
\echo ===数据库信息===
SELECT '版本: ' || version();
SELECT '运行时间: ' || now() - pg_postmaster_start_time();
-- 数据库大小
\echo ===数据库大小===
SELECT
datname as "数据库",
pg_size_pretty(pg_database_size(datname)) as "大小",
numbackends as "连接数"
FROM pg_database
LEFT JOIN pg_stat_database ON pg_database.oid = pg_stat_database.datid
WHERE datname NOT LIKE 'template%'
ORDER BY pg_database_size(datname) DESC;
-- 连接信息
\echo ===连接信息===
SELECT '总连接数: ' || count(*) FROM pg_stat_activity;
SELECT '活动连接: ' || count(*) FROM pg_stat_activity WHERE state = 'active';
-- 表空间
\echo ===大表统计===
SELECT
schemaname as "模式",
relname as "表名",
pg_size_pretty(pg_total_relation_size(relid)) as "总大小",
n_live_tup as "行数"
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;
-- 锁信息
\echo ===锁信息===
SELECT count(*) || ' 个等待锁' FROM pg_locks WHERE NOT granted;
PGSQL_EOF
# 执行SQL检查
local sql_output=$TEMP_DIR/pgsql_output.txt
if $pgsql_cmd -f $TEMP_DIR/pgsql_check.sql > $sql_output 2>&1; then
echo "<table>" >> $TEMP_DIR/database.html
local current_section=""
while read line; do
if [[ "$line" == ===*=== ]]; then
current_section=${line//=/}
echo "<tr><td colspan='3'><h4>$current_section</h4></td></tr>" >> $TEMP_DIR/database.html
elif [[ -n "$line" ]]; then
echo "<tr><td>$line</td></tr>" >> $TEMP_DIR/database.html
fi
done < $sql_output
echo "</table>" >> $TEMP_DIR/database.html
log "SUCCESS" "PostgreSQL数据库检查完成"
else
echo "<p class='critical'>❌ PostgreSQL数据库检查失败</p>" >> $TEMP_DIR/database.html
add_warning "PostgreSQL数据库检查失败"
fi
echo "</div>" >> $TEMP_DIR/database.html
}
# SQL Server数据库巡检
check_sqlserver() {
log "INFO" "执行SQL Server数据库巡检..."
cat > $TEMP_DIR/database.html << 'EOF'
<h2>🗃️ SQL Server数据库巡检</h2>
<div class="section-content">
EOF
if ! command -v sqlcmd &> /dev/null; then
echo "<p class='warning'>⚠️ sqlcmd未找到,跳过SQL Server检查</p>" >> $TEMP_DIR/database.html
add_warning "sqlcmd未安装,跳过SQL Server数据库检查"
return
fi
# 构建sqlcmd命令
local sqlcmd_opts="-W -h-1"
if [ -n "$DB_USER" ] && [ -n "$DB_PASSWORD" ]; then
sqlcmd_opts="$sqlcmd_opts -U $DB_USER -P $DB_PASSWORD"
else
sqlcmd_opts="$sqlcmd_opts -E" # Windows认证
fi
[ -n "$DB_HOST" ] && sqlcmd_opts="$sqlcmd_opts -S $DB_HOST"
[ -n "$DB_PORT" ] && sqlcmd_opts="$sqlcmd_opts,$DB_PORT"
[ -n "$DB_NAME" ] && sqlcmd_opts="$sqlcmd_opts -d $DB_NAME"
# 创建检查SQL
cat > $TEMP_DIR/sqlserver_check.sql << 'SQLSERVER_EOF'
-- 数据库信息
PRINT '===数据库信息==='
SELECT '版本: ' + @@VERSION
SELECT '实例名: ' + @@SERVERNAME
SELECT '数据库: ' + DB_NAME()
-- 数据库状态
PRINT '===数据库状态==='
SELECT
name as '数据库',
state_desc as '状态',
recovery_model_desc as '恢复模式'
FROM sys.databases
WHERE name = DB_NAME()
-- 空间使用
PRINT '===空间使用==='
SELECT
'总空间(MB): ' + CAST(SUM(size) * 8.0 / 1024 AS VARCHAR(50))
FROM sys.master_files
WHERE database_id = DB_ID()
-- 连接信息
PRINT '===连接信息==='
SELECT '连接数: ' + CAST(COUNT(*) AS VARCHAR(10)) FROM sys.dm_exec_connections
-- 性能信息
PRINT '===性能信息==='
SELECT '等待任务数: ' + CAST(SUM(waiting_tasks_count) AS VARCHAR(50))
FROM sys.dm_os_wait_stats
SQLSERVER_EOF
# 执行SQL检查
local sql_output=$TEMP_DIR/sqlserver_output.txt
if sqlcmd $sqlcmd_opts -i $TEMP_DIR/sqlserver_check.sql -o $sql_output 2>&1; then
echo "<table>" >> $TEMP_DIR/database.html
local current_section=""
while read line; do
if [[ "$line" == ===*=== ]]; then
current_section=${line//=/}
echo "<tr><td colspan='3'><h4>$current_section</h4></td></tr>" >> $TEMP_DIR/database.html
elif [[ -n "$line" ]]; then
echo "<tr><td>$line</td></tr>" >> $TEMP_DIR/database.html
fi
done < $sql_output
echo "</table>" >> $TEMP_DIR/database.html
log "SUCCESS" "SQL Server数据库检查完成"
else
echo "<p class='critical'>❌ SQL Server数据库检查失败</p>" >> $TEMP_DIR/database.html
add_warning "SQL Server数据库检查失败"
fi
echo "</div>" >> $TEMP_DIR/database.html
}
# ============================================
# 报告生成部分
# ============================================
# 生成HTML报告
generate_html_report() {
log "INFO" "生成HTML报告..."
# 汇总统计信息
local total_checks=0
local passed_checks=0
local failed_checks=0
local warning_checks=${#WARNING_MESSAGES[@]}
cat > $HTML_REPORT << HTML_HEAD
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据库巡检报告</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #4A00E0 0%, #8E2DE2 100%);
color: white;
padding: 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px);
background-size: 50px 50px;
animation: float 20s linear infinite;
}
@keyframes float {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.header h1 {
font-size: 2.8rem;
margin-bottom: 10px;
position: relative;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.header .subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 20px;
position: relative;
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
padding: 30px;
background: #f8f9fa;
}
.card {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
text-align: center;
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card.success {
border-left: 5px solid #4CAF50;
}
.card.warning {
border-left: 5px solid #FF9800;
}
.card.error {
border-left: 5px solid #F44336;
}
.card.info {
border-left: 5px solid #2196F3;
}
.card-icon {
font-size: 2.5rem;
margin-bottom: 15px;
}
.card-title {
font-size: 1rem;
color: #666;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.card-value {
font-size: 2.2rem;
font-weight: bold;
margin: 10px 0;
}
.card-value.success { color: #4CAF50; }
.card-value.warning { color: #FF9800; }
.card-value.error { color: #F44336; }
.card-value.info { color: #2196F3; }
.section {
padding: 30px;
border-bottom: 1px solid #eee;
}
.section:last-child {
border-bottom: none;
}
.section-title {
font-size: 1.8rem;
color: #333;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 3px solid;
display: flex;
align-items: center;
gap: 10px;
}
.section-title.system { border-color: #4CAF50; }
.section-title.database { border-color: #2196F3; }
.section-title.issues { border-color: #FF9800; }
.section-title.recommend { border-color: #9C27B0; }
.section-content {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-top: 15px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
border-radius: 8px;
overflow: hidden;
}
th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 16px;
text-align: left;
font-weight: 600;
//vertical-align: middle;
}
td {
padding: 14px 16px;
border-bottom: 1px solid #eee;
text-align: left;
//vertical-align: middle;
}
tr:last-child td {
border-bottom: none;
}
tr:hover {
background-color: #f5f7ff;
}
.status {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status.good { background: #E8F5E9; color: #2E7D32; }
.status.warning { background: #FFF3E0; color: #EF6C00; }
.status.critical { background: #FFEBEE; color: #C62828; }
.status.info { background: #E3F2FD; color: #1565C0; }
.issue-list {
list-style: none;
}
.issue-list li {
padding: 12px 15px;
margin: 8px 0;
background: white;
border-left: 4px solid;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.issue-list li.error { border-color: #F44336; }
.issue-list li.warning { border-color: #FF9800; }
.recommendation-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.recommendation-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
transition: all 0.3s ease;
}
.recommendation-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.recommendation-card h4 {
color: #333;
margin-bottom: 10px;
font-size: 1.1rem;
}
.recommendation-card p {
color: #666;
font-size: 0.95rem;
line-height: 1.5;
}
.footer {
background: #2C3E50;
color: white;
padding: 25px;
text-align: center;
border-radius: 0 0 15px 15px;
}
.footer-info {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.footer-item {
text-align: center;
}
.footer-label {
font-size: 0.9rem;
opacity: 0.8;
margin-bottom: 5px;
}
.footer-value {
font-size: 1.1rem;
font-weight: 600;
}
.copyright {
font-size: 0.9rem;
opacity: 0.7;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.progress-bar {
height: 8px;
background: #e0e0e0;
border-radius: 4px;
margin: 10px 0;
overflow: hidden;
}
.progress-fill {
height: 100%;
border-radius: 4px;
transition: width 0.5s ease;
}
.progress-fill.success { background: #4CAF50; }
.progress-fill.warning { background: #FF9800; }
.progress-fill.error { background: #F44336; }
.collapsible {
margin: 10px 0;
}
.collapsible-btn {
background: #2196F3;
color: white;
border: none;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
width: 100%;
text-align: left;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.3s ease;
}
.collapsible-btn:hover {
background: #1976D2;
}
.collapsible-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.5s ease;
background: #f8f9fa;
border-radius: 0 0 6px 6px;
}
.collapsible-content.expanded {
max-height: 2000px;
padding: 20px;
}
@media (max-width: 768px) {
.header h1 { font-size: 2rem; }
.summary-cards { grid-template-columns: 1fr; }
.recommendation-grid { grid-template-columns: 1fr; }
.footer-info { flex-direction: column; }
}
.print-btn {
position: fixed;
bottom: 30px;
right: 30px;
background: #4CAF50;
color: white;
border: none;
padding: 12px 24px;
border-radius: 50px;
cursor: pointer;
font-size: 1rem;
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
transition: all 0.3s ease;
z-index: 1000;
}
.print-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
}
</style>
</head>
<body>
<button class="print-btn" onclick="window.print()">🖨️ 打印报告</button>
<div class="container">
<div class="header">
<h1>📊 数据库巡检报告</h1>
<div class="subtitle">全面检查系统资源与数据库性能状态</div>
</div>
<div class="summary-cards">
<div class="card success">
<div class="card-icon">✅</div>
<div class="card-title">检查项目</div>
<div class="card-value success">6</div>
<div class="progress-bar">
<div class="progress-fill success" style="width: 100%"></div>
</div>
</div>
<div class="card info">
<div class="card-icon">📅</div>
<div class="card-title">生成时间</div>
<div class="card-value info">$(date '+%H:%M')</div>
<div>$(date '+%Y-%m-%d')</div>
</div>
<div class="card warning">
<div class="card-icon">⚠️</div>
<div class="card-title">警告数量</div>
<div class="card-value warning">${warning_checks}</div>
<div>需要关注的问题</div>
</div>
<div class="card info">
<div class="card-icon">🗃️</div>
<div class="card-title">数据库类型</div>
<div class="card-value info">${DB_TYPE^^}</div>
<div>$DB_HOST:$DB_PORT</div>
</div>
</div>
HTML_HEAD
# 添加参数配置部分
cat >> $HTML_REPORT << HTML_PARAMS
<div class="section">
<h2 class="section-title">🔧 检查参数配置</h2>
<div class="section-content">
<table>
<tr><th>参数</th><th>值</th><th>说明</th></tr>
<tr><td>数据库类型</td><td><span class="status info">${DB_TYPE^^}</span></td><td>本次检查的数据库类型</td></tr>
<tr><td>主机地址</td><td>$DB_HOST</td><td>数据库服务器地址</td></tr>
<tr><td>端口号</td><td>$DB_PORT</td><td>数据库服务端口</td></tr>
<tr><td>用户名</td><td>${DB_USER:-未指定}</td><td>数据库连接用户</td></tr>
<tr><td>数据库名</td><td>${DB_NAME:-未指定}</td><td>目标数据库名称</td></tr>
<tr><td>配置文件</td><td>${CONFIG_FILE:-未使用}</td><td>配置参数来源</td></tr>
</table>
</div>
</div>
HTML_PARAMS
# 添加系统检查部分
for file in system.html memory.html cpu.html disk.html network.html; do
if [ -f $TEMP_DIR/$file ]; then
cat $TEMP_DIR/$file >> $HTML_REPORT
fi
done
# 添加数据库检查部分
if [ -f $TEMP_DIR/database.html ]; then
cat $TEMP_DIR/database.html >> $HTML_REPORT
fi
# 添加问题汇总部分
if [ ${#ERROR_MESSAGES[@]} -gt 0 ] || [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
cat >> $HTML_REPORT << HTML_ISSUES
<div class="section">
<h2 class="section-title issues">⚠️ 问题汇总</h2>
<div class="section-content">
HTML_ISSUES
if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
cat >> $HTML_REPORT << HTML_ERRORS
<h3>❌ 错误 ($((total_checks - passed_checks)))</h3>
<ul class="issue-list">
HTML_ERRORS
for error in "${ERROR_MESSAGES[@]}"; do
echo "<li class='error'>$error</li>" >> $HTML_REPORT
done
echo "</ul>" >> $HTML_REPORT
fi
if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; then
cat >> $HTML_REPORT << HTML_WARNINGS
<h3>⚠️ 警告 ($warning_checks)</h3>
<ul class="issue-list">
HTML_WARNINGS
for warning in "${WARNING_MESSAGES[@]}"; do
echo "<li class='warning'>$warning</li>" >> $HTML_REPORT
done
echo "</ul>" >> $HTML_REPORT
fi
echo "</div></div>" >> $HTML_REPORT
fi
# 添加建议部分
cat >> $HTML_REPORT << HTML_RECOMMEND
<div class="section">
<h2 class="section-title recommend">💡 优化建议</h2>
<div class="section-content">
<div class="recommendation-grid">
<div class="recommendation-card">
<h4>🔍 定期监控</h4>
<p>建议设置自动化巡检任务,每天检查关键指标,每周生成完整报告。</p>
</div>
<div class="recommendation-card">
<h4>💾 容量规划</h4>
<p>根据磁盘使用趋势,提前规划存储扩展,确保至少20%的可用空间。</p>
</div>
<div class="recommendation-card">
<h4>⚡ 性能优化</h4>
<p>针对发现的性能瓶颈,优化数据库配置和查询语句。</p>
</div>
<div class="recommendation-card">
<h4>🔒 安全加固</h4>
<p>定期检查数据库权限设置,确保符合最小权限原则。</p>
</div>
<div class="recommendation-card">
<h4>📋 备份策略</h4>
<p>制定并测试备份恢复方案,确保数据安全。</p>
</div>
<div class="recommendation-card">
<h4>📈 趋势分析</h4>
<p>收集历史数据,分析性能趋势,预测未来需求。</p>
</div>
</div>
</div>
</div>
<div class="footer">
<div class="footer-info">
<div class="footer-item">
<div class="footer-label">报告文件</div>
<div class="footer-value">$(basename $HTML_REPORT)</div>
</div>
<div class="footer-item">
<div class="footer-label">生成时间</div>
<div class="footer-value">$(date '+%Y-%m-%d %H:%M:%S')</div>
</div>
<div class="footer-item">
<div class="footer-label">数据库类型</div>
<div class="footer-value">${DB_TYPE^^}</div>
</div>
<div class="footer-item">
<div class="footer-label">脚本版本</div>
<div class="footer-value">v$SCRIPT_VERSION</div>
</div>
</div>
<div class="copyright">
© 2024 数据库运维团队 | 巡检脚本 v$SCRIPT_VERSION | 报告仅供参考,请结合实际环境进行调整
</div>
</div>
</div>
<script>
// 展开/收起功能
document.querySelectorAll('.collapsible-btn').forEach(button => {
button.addEventListener('click', () => {
const content = button.nextElementSibling;
const isExpanded = content.classList.contains('expanded');
// 切换所有同级的展开状态
document.querySelectorAll('.collapsible-content').forEach(item => {
item.classList.remove('expanded');
});
if (!isExpanded) {
content.classList.add('expanded');
}
});
});
// 自动滚动到问题区域
window.addEventListener('load', () => {
const issueCount = ${#ERROR_MESSAGES[@]} + ${#WARNING_MESSAGES[@]};
if (issueCount > 0) {
setTimeout(() => {
document.querySelector('.section-title.issues').scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 1000);
}
});
// 打印优化
window.addEventListener('beforeprint', () => {
document.querySelector('.print-btn').style.display = 'none';
});
window.addEventListener('afterprint', () => {
document.querySelector('.print-btn').style.display = 'block';
});
</script>
</body>
</html>
HTML_RECOMMEND
# 设置文件权限
chmod 644 "$HTML_REPORT"
log "SUCCESS" "HTML报告生成完成: $HTML_REPORT"
# 显示报告摘要
echo -e "\n${GREEN}══════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}✨ 巡检报告生成成功!${NC}"
echo -e "${BLUE}══════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN}📊 报告摘要:${NC}"
echo -e " 📄 报告文件: ${BOLD}$HTML_REPORT${NC}"
echo -e " 📋 日志文件: ${BOLD}$LOG_FILE${NC}"
echo -e " ⚠️ 警告数量: ${BOLD}$warning_checks 个${NC}"
echo -e " ⏱️ 生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo -e "${BLUE}══════════════════════════════════════════════════════════${NC}"
if [ $warning_checks -gt 0 ]; then
echo -e "${YELLOW}⚠️ 注意: 发现 $warning_checks 个需要关注的问题${NC}"
echo -e " 请查看报告中的问题汇总部分"
fi
if [ $CONNECTION_ERROR -eq 1 ]; then
echo -e "${YELLOW}🔗 注意: 数据库连接测试失败,部分检查可能不完整${NC}"
fi
echo -e "\n${GREEN}💡 提示:${NC}"
echo -e " 1. 使用浏览器打开报告文件查看详细内容"
echo -e " 2. 报告右下角有打印按钮,可打印纸质报告"
echo -e " 3. 临时文件已自动清理"
echo -e "${GREEN}══════════════════════════════════════════════════════════${NC}"
}
# 清理临时文件
cleanup() {
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR" 2>/dev/null
log "INFO" "清理临时目录: $TEMP_DIR"
fi
}
# 主执行流程
main() {
# 显示横幅
show_banner
# 初始化目录
init_directories
# 解析命令行参数
parse_arguments "$@"
# 如果有参数错误,显示帮助并退出
if [ $PARAM_ERROR -eq 1 ]; then
echo -e "\n${RED}❌ 参数验证失败,请修正以下错误:${NC}"
for error in "${ERROR_MESSAGES[@]}"; do
echo -e " ${RED}•${NC} $error"
done
echo -e "\n${CYAN}💡 使用 --help 查看完整的帮助信息${NC}"
cleanup
exit 1
fi
# 加载配置文件
if ! load_configuration; then
error_exit "配置文件加载失败"
fi
# 验证数据库类型和参数
if ! validate_database_type; then
error_exit "数据库类型验证失败"
fi
# 交互式输入缺失参数
if ! interactive_input; then
error_exit "参数输入失败"
fi
# 显示参数摘要
if ! show_parameter_summary; then
error_exit "参数验证失败"
fi
# 检查系统依赖
check_system_dependencies
# 确认执行
if ! confirm_execution; then
exit 0
fi
# 测试数据库连接
test_database_connection
# 执行系统检查
log "INFO" "开始系统资源检查..."
check_system_info
check_memory
check_cpu
check_disk
check_network
# 执行数据库检查
log "INFO" "开始数据库状态检查..."
case $DB_TYPE in
oracle) check_oracle ;;
mysql) check_mysql ;;
pgsql) check_postgresql ;;
sqlserver) check_sqlserver ;;
esac
# 生成HTML报告
generate_html_report
# 清理临时文件
cleanup
# 显示完成消息
log "SUCCESS" "数据库巡检流程完成!"
echo -e "\n${GREEN}✅ 所有检查已完成!${NC}"
# 返回适当的退出码
if [ ${#ERROR_MESSAGES[@]} -gt 0 ]; then
exit 1
elif [ $CONNECTION_ERROR -eq 1 ]; then
exit 3
else
exit 0
fi
}
# 设置信号处理
trap 'log "ERROR" "脚本被用户中断"; cleanup; exit 99' INT TERM
trap 'cleanup' EXIT
# 执行主函数
main "$@"
2.配置文件【db_inspect.conf 补充扩展参数】
默认读取路径:./config/db_inspect.conf
bash
# 数据库巡检配置文件
# 注意: 设置文件权限为 600: chmod 600 config/db_inspect.conf
# ============================================
# 数据库连接配置
# ============================================
# Oracle 数据库配置
# CONFIG_DB_HOST="oracle-server"
# CONFIG_DB_PORT="1521"
# CONFIG_DB_USER="system"
# CONFIG_DB_PASSWORD="your_oracle_password"
# CONFIG_DB_NAME="ORCL"
# MySQL 数据库配置
# CONFIG_DB_HOST="localhost"
# CONFIG_DB_PORT="3306"
# CONFIG_DB_USER="root"
# CONFIG_DB_PASSWORD="your_mysql_password"
# CONFIG_DB_NAME="mysql"
# PostgreSQL 数据库配置
# CONFIG_DB_HOST="localhost"
# CONFIG_DB_PORT="5432"
# CONFIG_DB_USER="postgres"
# CONFIG_DB_PASSWORD="your_postgres_password"
# CONFIG_DB_NAME="postgres"
# SQL Server 数据库配置
# CONFIG_DB_HOST="sqlserver-host"
# CONFIG_DB_PORT="1433"
# CONFIG_DB_USER="sa"
# CONFIG_DB_PASSWORD="your_sqlserver_password"
# CONFIG_DB_NAME="master"
# ============================================
# 巡检配置选项
# ============================================
# 报告保留天数
REPORT_RETENTION_DAYS=30
# 日志级别: DEBUG, INFO, WARN, ERROR
LOG_LEVEL="INFO"
# 最大日志文件大小 (MB)
MAX_LOG_SIZE=10
# 检查超时时间 (秒)
CHECK_TIMEOUT=300
# 是否检查表空间使用率阈值
CHECK_TABLESPACE_THRESHOLD=true
TABLESPACE_WARNING=80
TABLESPACE_CRITICAL=95
# 是否检查连接数阈值
CHECK_CONNECTION_THRESHOLD=true
CONNECTION_WARNING=80
CONNECTION_CRITICAL=90
# ============================================
# 通知配置 (可选)
# ============================================
# 邮件通知
# EMAIL_ENABLED=false
# EMAIL_RECIPIENTS="admin@example.com"
# EMAIL_SMTP_SERVER="smtp.example.com"
# EMAIL_SMTP_PORT="587"
# EMAIL_USERNAME="sender@example.com"
# EMAIL_PASSWORD="email_password"
# 企业微信通知
# WECHAT_ENABLED=false
# WECHAT_WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key"
# Slack通知
# SLACK_ENABLED=false
# SLACK_WEBHOOK_URL="https://hooks.slack.com/services/your/webhook/url"
# ============================================
# 自定义检查项 (可选)
# ============================================
# 自定义SQL检查文件路径
# CUSTOM_CHECKS_DIR="./custom_checks"
# 是否执行慢查询检查
# CHECK_SLOW_QUERIES=true
# 是否检查锁等待
# CHECK_LOCK_WAITS=true
# 是否检查备份状态
# CHECK_BACKUP_STATUS=true
# ============================================
# 性能阈值配置
# ============================================
# CPU使用率阈值 (%)
CPU_WARNING=80
CPU_CRITICAL=90
# 内存使用率阈值 (%)
MEMORY_WARNING=80
MEMORY_CRITICAL=90
# 磁盘使用率阈值 (%)
DISK_WARNING=85
DISK_CRITICAL=95
# 负载平均值阈值 (相对于CPU核心数)
LOAD_WARNING=1.5
LOAD_CRITICAL=2.0
# 网络连接超时 (秒)
NETWORK_TIMEOUT=5
# ============================================
# 安全配置
# ============================================
# 是否检查密码过期
# CHECK_PASSWORD_EXPIRY=false
# 是否检查用户权限
# CHECK_USER_PRIVILEGES=false
# 是否检查安全补丁
# CHECK_SECURITY_PATCHES=false
# ============================================
# 高级配置 (一般不需要修改)
# ============================================
# 临时目录路径
# TEMP_DIR="/tmp/db_inspect"
# 报告输出目录
# REPORT_DIR="./reports"
# 日志目录
# LOG_DIR="./logs"
# 并发检查线程数
# CONCURRENT_CHECKS=1
# 数据库连接池大小
# CONNECTION_POOL_SIZE=5
# 查询超时时间 (秒)
# QUERY_TIMEOUT=30
【安装步骤】
保存脚本:
bash
保存主脚本
chmod +x db_inspect.sh
保存配置文件
编辑配置文件,设置数据库连接信息
安装必要的工具:
bash
对于Linux系统
sudo apt-get update
sudo apt-get install -y bc net-tools # Ubuntu/Debian
或者
sudo yum install -y bc net-tools # CentOS/RHEL
安装数据库客户端(根据需要选择)
Oracle: 需要安装Oracle Instant Client
MySQL: sudo apt-get install mysql-client
PostgreSQL: sudo apt-get install postgresql-client
SQL Server: 安装mssql-tools
【使用方法】
1. 基本用法(会提示输入缺失参数)
./db_inspect.sh -t mysql
2. 完整参数
./db_inspect.sh -t mysql -h 192.168.1.100 -P 3306 -u admin -d mydb
3. 使用配置文件
./db_inspect.sh -t mysql -c /path/to/config.conf
4. 指定输出文件
./db_inspect.sh -t oracle -o /var/www/reports/db_check.html
5. 查看帮助
./db_inspect.sh --help
6. 查看版本
./db_inspect.sh --version
基本用法:
bash
检查MySQL数据库
./db_inspect.sh -t mysql -u root -p your_password
检查Oracle数据库
./db_inspect.sh -t oracle -u system
检查PostgreSQL
./db_inspect.sh -t pgsql -U postgres -d postgres
检查SQL Server
./db_inspect.sh -t sqlserver -u sa -p your_password
带参数的完整示例:
bash
./db_inspect.sh -t mysql -h 192.168.1.100 -P 3306 -u admin -d mydatabase
使用配置文件:
db_inspect.conf
MySQL 配置示例
CONFIG_DB_HOST="localhost"
CONFIG_DB_PORT="3306"
CONFIG_DB_USER="dbadmin"
CONFIG_DB_PASSWORD="SecurePass123!"
CONFIG_DB_NAME="production_db"
巡检选项
CHECK_INTERVAL=3600
KEEP_REPORTS=30
LOG_LEVEL="INFO"
bash
先编辑配置文件设置连接信息
vi db_inspect.conf
然后运行脚本
./db_inspect.sh -t MySQL
【脚本功能说明】
系统检查:
内存使用情况(包括Swap)
CPU使用率和负载
磁盘空间使用
网络连接和端口监听
系统基本信息
数据库检查:
Oracle:表空间、会话、等待事件等
MySQL:连接数、数据库大小、InnoDB状态等
PostgreSQL:数据库大小、连接数、表空间等
SQL Server:数据库状态、连接数、等待统计等
报告生成:
生成美观的HTML报告
包含时间戳和主机信息
颜色标记重要信息
提供检查建议
【注意事项】
权限要求:
脚本需要执行权限
数据库用户需要有适当的查询权限
系统命令需要相应的执行权限
安全性:
不要在配置文件中明文存储密码
建议使用环境变量或密码文件
定期清理生成的报告文件
扩展性:
可以添加更多的检查项目
支持自定义SQL检查
可以根据实际需求进行调整和扩展
【巡检报告样式案例】



