Shell实现数据库巡检

当我们对数据库进行巡检,比如表空间、磁盘空间使用情况等,如果要在文档中记录,需要每一项查询出来以后单独放到文档中,会比较繁琐。对此本文提供一个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检查

可以根据实际需求进行调整和扩展

【巡检报告样式案例】

相关推荐
福大大架构师每日一题2 小时前
redis 8.4.1 正式发布:安全升级、性能强化与多模块重大修复详解
数据库·redis·安全
魑-魅-魍-魉2 小时前
金仓数据库(KingbaseES)Windows 安装避坑指南:从 Connection Refused 到服务启动的完整实战记录
数据库·金仓
chlk1233 小时前
聊聊索引:为何 B + 树能撑起数据库的半壁江山?
数据库·mysql
宁酱醇3 小时前
ORACLE_建表+增改查+删
数据库·oracle
爬山算法3 小时前
MongoDB(8)什么是聚合(Aggregation)?
数据库·mongodb
Tinyundg3 小时前
CentOS安装Oracle 19C 数据库
数据库·oracle·centos
IT_Octopus4 小时前
AI 工程 生产级别向量数据库Milvus2.6.10性能测试报告
数据库·人工智能·milvus
JosieBook4 小时前
【数据库】时序数据库选型指南:从大数据角度解析IoTDB的优势
大数据·数据库·时序数据库
码农学院4 小时前
查找sql server 2014存储过程是否包含某个关键字
数据库