顾乔芝士网

持续更新的前后端开发技术栈

写了一个小工具查询java服务有没有被人远程debug

最近参加了一个项目开发,程序没办法在本地启动调试。所有人要进行debug需要远程连接到一个公共的环境。

但有人在debug,就会影响其它人使用。

今天感觉系统卡住了,应该是又有人在debug,但群里喊了一声,没人承认。所以我决定登录到服务器,看能不能查出谁在debug。

原理很简单,远程debug是需要通过tcp服务连接到服务器ip的,如果有人在debug,我应该可以通过端口连接信息查出对方的ip。

登录上系统后,使用netstat -anp | grep 远程debug端口,本来想着肯定能看到ESTABLISHED字样的连接,结果发现没有,只有LISTEN的进程

tcp        0      0 0.0.0.0:5381            0.0.0.0:*               LISTEN      2047419/docker-prox
tcp6       0      0 :::5381                 :::*                    LISTEN      2047431/docker-prox

理论上只要有客户端连接上来,应该就能看到ESTABLISHED的连接呀。我觉得有些奇怪,就问ai,ai说可能是因为我的java服务部署在容器,由于网络栈隔离原因,我在宿主机上看不到这个客户端连接信息。

按ai指导,我登录到容器里面,再查,发现还真是这样。在容器里面可以看到客户端的连接信息,在容器外面看不到。

我一直以为docker的网络只是宿主机的一部分,如果宿主机之外有第三方机器连接进来,宿主机应该能看到呢。

不过有一些容器里面没有netsta,为了方便检查我让豆包给我写了一个脚本

parallel_check_java_debug.sh

#!/bin/bash
# 功能:检查Java远程Debug服务,先收集所有进程再分成4组并行处理
# 特点:进程列表全量收集后分组,每组由1个进程处理;严格使用预生成临时文件
# 依赖:需要docker命令执行权限,部分操作可能需要root权限

# 1. 预生成所有临时文件(先生成文件名再使用)
ALL_PROCESSES_FILE=$(mktemp /tmp/java_all_processes.XXXXXX)  # 存储所有进程列表
GROUP_FILE_1=$(mktemp /tmp/java_group_1.XXXXXX)             # 第1组进程
GROUP_FILE_2=$(mktemp /tmp/java_group_2.XXXXXX)             # 第2组进程
GROUP_FILE_3=$(mktemp /tmp/java_group_3.XXXXXX)             # 第3组进程
GROUP_FILE_4=$(mktemp /tmp/java_group_4.XXXXXX)             # 第4组进程
RESULT_FILE_1=$(mktemp /tmp/java_result_1.XXXXXX)           # 第1组结果
RESULT_FILE_2=$(mktemp /tmp/java_result_2.XXXXXX)           # 第2组结果
RESULT_FILE_3=$(mktemp /tmp/java_result_3.XXXXXX)           # 第3组结果
RESULT_FILE_4=$(mktemp /tmp/java_result_4.XXXXXX)           # 第4组结果

# 脚本退出时清理所有临时文件
trap 'rm -f "$ALL_PROCESSES_FILE" "$GROUP_FILE_1" "$GROUP_FILE_2" "$GROUP_FILE_3" "$GROUP_FILE_4" "$RESULT_FILE_1" "$RESULT_FILE_2" "$RESULT_FILE_3" "$RESULT_FILE_4"' EXIT

# 函数:检查命令是否存在
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# 函数:转换十六进制IP到十进制
hex_to_ip() {
    local hex=$1
    if [[ $hex =~ ^[0-9A-Fa-f]{8}$ ]]; then
        printf "%d.%d.%d.%d" \
            0x${hex:6:2} \
            0x${hex:4:2} \
            0x${hex:2:2} \
            0x${hex:0:2}
        return 0
    else
        echo "无效IP"
        return 1
    fi
}

# 函数:处理一组进程(从文件读取),结果写入指定文件
process_group() {
    local group_file=$1    # 输入:包含本组进程列表的文件
    local result_file=$2   # 输出:本组处理结果的文件

    # 遍历本组所有进程
    while IFS= read -r line; do
        local pid=$(echo "$line" | awk '{print $2}')
        local cmd_line=$(echo "$line" | awk '{$1=$2=""; print $0}')
        local debug_port=$(echo "$cmd_line" | grep -oP 'address=\K\d+')
        local user=$(echo "$line" | awk '{print $1}')

        # 获取容器ID
        local cgroup_path="/proc/$pid/cgroup"
        local container_id=""
        if [ -f "$cgroup_path" ]; then
            container_id=$(grep -oP -- 'docker/[^/]+' "$cgroup_path" | cut -d '/' -f2 | head -n1)
            if [ -z "$container_id" ]; then
                container_id=$(grep -oP -- 'containerd/[^/]+' "$cgroup_path" | cut -d '/' -f2 | head -n1 | cut -c 1-12)
            fi
        fi

        # 获取容器名称
        local container_name="N/A"
        if [ -n "$container_id" ]; then
            container_name=$(docker inspect --format '{{.Name}}' "$container_id" 2>/dev/null | sed 's/^\///')
            [ -z "$container_name" ] && container_name="未知容器($container_id)"
        fi

        # 检查连接
        local connections=""
        if [ -n "$debug_port" ]; then
            if [ -n "$container_id" ]; then
                # 优先使用netstat
                local has_netstat=$(docker exec "$container_id" sh -c "command -v netstat >/dev/null 2>&1 && echo 1 || echo 0")
                if [ "$has_netstat" -eq 1 ]; then
                    connections=$(docker exec "$container_id" netstat -anp 2>/dev/null | grep ":$debug_port" | grep ESTABLISHED)
                else
                    # 其次使用ss
                    local has_ss=$(docker exec "$container_id" sh -c "command -v ss >/dev/null 2>&1 && echo 1 || echo 0")
                    if [ "$has_ss" -eq 1 ]; then
                        connections=$(docker exec "$container_id" ss -antp 2>/dev/null | grep ":$debug_port" | grep ESTAB)
                    else
                        # 最后解析/proc/net/tcp
                        local hex_port=$(printf "%04X" "$debug_port")
                        local proc_tcp=$(docker exec "$container_id" cat /proc/net/tcp 2>/dev/null | grep ":$hex_port" | grep " 01 ")
                        if [ -n "$proc_tcp" ]; then
                            while read -r tcp_line; do
                                local fields=($tcp_line)
                                local remote_addr=${fields[2]}  # 远程地址在第3字段
                                local client_hex_ip=$(echo "$remote_addr" | cut -d: -f1)
                                local client_ip=$(hex_to_ip "$client_hex_ip")
                                if [ "$client_ip" != "无效IP" ]; then
                                    connections+="ESTABLISHED  $client_ip:$debug_port"#39;\n'
                                fi
                            done <<< "$proc_tcp"
                        fi
                    fi
                fi
            else
                # 主机上的检查
                if command_exists netstat; then
                    connections=$(netstat -anp 2>/dev/null | grep ":$debug_port" | grep ESTABLISHED)
                elif command_exists ss; then
                    connections=$(ss -antp 2>/dev/null | grep ":$debug_port" | grep ESTAB)
                fi
            fi
        fi

        # 有连接才写入结果文件
        if [ -n "$connections" ]; then
            {
                echo "进程ID: $pid"
                echo "运行用户: $user"
                echo "容器名称: $container_name"
                echo "容器ID: ${container_id:-N/A}"
                echo "Debug端口: $debug_port"
                echo "启动命令片段: $(echo "$cmd_line" | cut -c 1-150)..."
                echo "远程连接状态:"
                echo "  发现以下远程连接:"
                local client_ips=$(echo "$connections" | awk '{
                    if ($1 == "ESTABLISHED") print $2;
                    else if ($5 ~ /:/) print $5;
                    else print $4;
                }' | cut -d: -f1 | sort | uniq | grep -v '^#39;)
                for ip in $client_ips; do
                    echo "    - 客户端IP: $ip"
                done
                echo "======================================================================"
            } >> "$result_file"  # 追加到本组结果文件
        fi
    done < "$group_file"
}

# 主逻辑:收集所有进程并分组处理
echo "正在查找启用了远程Debug的Java服务..."
ps -ef | grep 'agentlib:jdwp=' | grep -v grep > "$ALL_PROCESSES_FILE"

# 检查是否有进程
if [ ! -s "$ALL_PROCESSES_FILE" ]; then
    echo "未找到启用远程Debug的Java服务"
    exit 0
fi

# 计算总进程数和每组进程数
total_processes=$(wc -l < "$ALL_PROCESSES_FILE")
group_size=$(( (total_processes + 3) / 4 ))  # 向上取整,确保分配均匀

# 分割进程列表到4个组文件(不足4组时自动为空)
split -l "$group_size" "$ALL_PROCESSES_FILE" -d -a 1 "$GROUP_FILE_1"_  # 临时中间文件
mv "$GROUP_FILE_1"_0 "$GROUP_FILE_1" 2>/dev/null || true
mv "$GROUP_FILE_1"_1 "$GROUP_FILE_2" 2>/dev/null || true
mv "$GROUP_FILE_1"_2 "$GROUP_FILE_3" 2>/dev/null || true
mv "$GROUP_FILE_1"_3 "$GROUP_FILE_4" 2>/dev/null || true

echo "找到以下有远程Debug连接的Java服务(总进程数: $total_processes,分组处理):"
echo "======================================================================"

# 启动分组处理(根据实际进程数决定启动几个后台任务)
bg_pids=()
if [ -s "$GROUP_FILE_1" ]; then
    process_group "$GROUP_FILE_1" "$RESULT_FILE_1" &
    bg_pids+=($!)
fi
if [ -s "$GROUP_FILE_2" ]; then
    process_group "$GROUP_FILE_2" "$RESULT_FILE_2" &
    bg_pids+=($!)
fi
if [ -s "$GROUP_FILE_3" ]; then
    process_group "$GROUP_FILE_3" "$RESULT_FILE_3" &
    bg_pids+=($!)
fi
if [ -s "$GROUP_FILE_4" ]; then
    process_group "$GROUP_FILE_4" "$RESULT_FILE_4" &
    bg_pids+=($!)
fi

# 等待所有后台任务完成
if [ ${#bg_pids[@]} -gt 0 ]; then
    wait "${bg_pids[@]}"
fi

# 按分组顺序合并结果(保持原始进程顺序)
cat "$RESULT_FILE_1" "$RESULT_FILE_2" "$RESULT_FILE_3" "$RESULT_FILE_4"

echo "检查完成"

写完脚本,考虑到不是所有人都有权限登录到后台服务机器上执行,我又让豆包帮我写了一个python脚本,可以在网页上执行脚本,并查看脚本输出结果

web_shell_invoker.py

# -*- coding: utf-8 -*-

from flask import Flask, request, jsonify, send_from_directory
import subprocess
import os
import sys
import argparse

# 获取当前脚本所在目录
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, static_folder=APP_ROOT)

# 配置不同接口对应的shell脚本路径
SHELL_SCRIPTS = {
    "system-debug-info": "/root/hch/bin/parallel_check_java_debug.sh",  # 检查有哪些java服务正在被debug
    "disk-usage": "/usr/local/bin/disk_usage.sh",    # 示例:磁盘使用脚本
    "custom-script": "/path/to/your/custom_script.sh"   # 自定义脚本
}

def run_shell_script(script_path, args=None):
    """执行shell脚本并返回结果"""
    # 检查脚本是否存在
    if not os.path.exists(script_path):
        return {
            "success": False,
            "error": f"脚本不存在: {script_path}",
            "output": ""
        }
    
    # 检查是否有执行权限
    if not os.access(script_path, os.X_OK):
        return {
            "success": False,
            "error": f"没有执行权限: {script_path}",
            "output": ""
        }
    
    try:
        # 构建命令列表
        cmd = [script_path]
        if args:
            cmd.extend(args)
        
        # 执行命令
        result = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            timeout=30  # 超时时间30秒
        )
        
        # 检查执行结果
        if result.returncode != 0:
            return {
                "success": False,
                "error": f"脚本执行出错: {result.stderr}",
                "output": result.stdout
            }
        
        return {
            "success": True,
            "error": "",
            "output": result.stdout
        }
        
    except subprocess.TimeoutExpired:
        return {
            "success": False,
            "error": "脚本执行超时",
            "output": ""
        }
    except Exception as e:
        return {
            "success": False,
            "error": f"执行过程出错: {str(e)}",
            "output": ""
        }

# 示例接口:带参数的文件查询接口
@app.route('/file-query', methods=['GET'])
def file_query():
    """查询指定目录下的文件"""
    path = request.args.get('path', '/')
    pattern = request.args.get('pattern', '*')
    return jsonify(run_shell_script('/bin/ls', [f'--color=never', '-l', f'{path}/{pattern}']))

# 系统信息接口
@app.route('/system-debug-info', methods=['GET'])
def system_info():
    return jsonify(run_shell_script(SHELL_SCRIPTS["system-debug-info"]))

# 磁盘使用接口
@app.route('/disk-usage', methods=['GET'])
def disk_usage():
    path = request.args.get('path', '/')
    return jsonify(run_shell_script(SHELL_SCRIPTS["disk-usage"], [path]))

# 自定义脚本接口
@app.route('/custom-script', methods=['GET'])
def custom_script():
    args = [f"{k}={v}" for k, v in request.args.items()]
    return jsonify(run_shell_script(SHELL_SCRIPTS["custom-script"], args))

# 首页:返回同目录下的index.html
@app.route('/', methods=['GET'])
def index():
    return send_from_directory(APP_ROOT, 'index.html')

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Web接口调用Shell脚本服务')
    parser.add_argument('--port', type=int, default=5555, help='服务端口号,默认5555')
    args = parser.parse_args()
    
    app.run(host='0.0.0.0', port=args.port, debug=False)

脚本配套的网页(需要放在python脚本同目录下)

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Shell脚本调用服务</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: #333;
            background-color: #f5f7fa;
            min-height: 100vh;
        }
        
        .app-container {
            display: flex;
            max-width: 1400px;
            margin: 0 auto;
            padding: 20px;
            gap: 20px;
            height: calc(100vh - 40px);
        }
        
        header {
            background-color: #2c3e50;
            color: white;
            padding: 15px 20px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        
        header h1 {
            font-size: 1.8rem;
            font-weight: 600;
        }
        
        /* 左侧脚本列表 */
        .script-list {
            width: 300px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
            overflow: hidden;
            display: flex;
            flex-direction: column;
        }
        
        .list-header {
            padding: 15px 20px;
            background-color: #34495e;
            color: white;
            font-size: 1.1rem;
            font-weight: 500;
        }
        
        .script-items {
            list-style: none;
            overflow-y: auto;
            flex-grow: 1;
        }
        
        .script-item {
            padding: 15px 20px;
            border-bottom: 1px solid #f1f1f1;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        
        .script-item:hover {
            background-color: #f8f9fa;
        }
        
        .script-item.active {
            background-color: #e3f2fd;
            border-left: 4px solid #2196f3;
        }
        
        .script-item h3 {
            font-size: 1rem;
            margin-bottom: 5px;
            color: #2c3e50;
        }
        
        .script-item p {
            font-size: 0.85rem;
            color: #7f8c8d;
        }
        
        /* 右侧内容区 */
        .content-area {
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        
        /* 参数输入区 */
        .param-input {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
            padding: 20px;
        }
        
        .param-input h2 {
            color: #2c3e50;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
            font-size: 1.4rem;
        }
        
        .form-group {
            margin-bottom: 15px;
        }
        
        label {
            display: block;
            margin-bottom: 6px;
            font-weight: 500;
            color: #34495e;
        }
        
        input {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 1rem;
            transition: border-color 0.2s;
        }
        
        input:focus {
            outline: none;
            border-color: #2196f3;
            box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
        }
        
        button {
            background-color: #2196f3;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 10px 20px;
            font-size: 1rem;
            font-weight: 500;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        
        button:hover {
            background-color: #1976d2;
        }
        
        button:disabled {
            background-color: #bdbdbd;
            cursor: not-allowed;
        }
        
        /* 结果展示区 */
        .result-display {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
            padding: 20px;
            flex-grow: 1;
            display: flex;
            flex-direction: column;
        }
        
        .result-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
        }
        
        .result-header h2 {
            color: #2c3e50;
            font-size: 1.4rem;
        }
        
        .result-timestamp {
            font-size: 0.85rem;
            color: #7f8c8d;
        }
        
        .result-content {
            flex-grow: 1;
            overflow-y: auto;
            padding: 15px;
            border-radius: 4px;
            background-color: #f8f9fa;
            font-family: 'Consolas', 'Monaco', monospace;
            white-space: pre-wrap;
            line-height: 1.5;
        }
        
        .result-content.loading {
            color: #7f8c8d;
        }
        
        .result-content.success {
            background-color: #f1f8e9;
            border: 1px solid #dcedc8;
        }
        
        .result-content.error {
            background-color: #ffebee;
            border: 1px solid #ffcdd2;
        }
        
        /* 响应式调整 */
        @media (max-width: 900px) {
            .app-container {
                flex-direction: column;
                height: auto;
            }
            
            .script-list {
                width: 100%;
                max-height: 300px;
            }
        }
    </style>
</head>
<body>
    <header>
        <h1>Shell脚本调用服务</h1>
    </header>
    
    <div class="app-container">
        <!-- 左侧脚本列表 -->
        <div class="script-list">
            <div class="list-header">可用脚本</div>
            <ul class="script-items">
                <!-- li class="script-item active" data-script="file-query">
                    <h3>文件查询</h3>
                    <p>/file-query</p>
                    <p>查询指定目录下的文件</p>
                </li>
                <li class="script-item" data-script="disk-usage">
                    <h3>磁盘使用</h3>
                    <p>/disk-usage</p>
                    <p>查看指定路径的磁盘使用情况</p>
                </li -->
                <li class="script-item active" data-script="system-debug-info">
                    <h3>debug信息</h3>
                    <p>/system-debug-info</p>
                    <p>获取正在debug的java服务</p>
                </li>
                <!-- li class="script-item" data-script="custom-script">
                    <h3>自定义脚本</h3>
                    <p>/custom-script</p>
                    <p>执行自定义脚本并传递参数</p>
                </li -->
            </ul>
        </div>
        
        <!-- 右侧内容区 -->
        <div class="content-area">
            <!-- 参数输入区 -->
            <div class="param-input">
                <h2 id="currentScriptTitle">debug信息 (/system-debug-info)</h2>
                
                <!-- 文件查询表单 -->
                <div id="file-query-form" class="script-form" style="display: none;">
                    <div class="form-group">
                        <label for="path">目录路径:</label>
                        <input type="text" id="path" name="path" value="/tmp" placeholder="例如:/tmp">
                    </div>
                    <div class="form-group">
                        <label for="pattern">文件匹配模式:</label>
                        <input type="text" id="pattern" name="pattern" value="*.log" placeholder="例如:*.log 或 *">
                    </div>
                    <button type="button" onclick="executeScript('file-query')">执行查询</button>
                </div>
                
                <!-- 磁盘使用表单 -->
                <div id="disk-usage-form" class="script-form" style="display: none;">
                    <div class="form-group">
                        <label for="diskPath">查询路径:</label>
                        <input type="text" id="diskPath" name="path" value="/" placeholder="例如:/">
                    </div>
                    <button type="button" onclick="executeScript('disk-usage')">查询磁盘使用</button>
                </div>
                
                <!-- debug信息表单 -->
                <div id="system-debug-info-form" class="script-form">
                    <p>点击下方按钮检查有哪些java服务正在被debug</p>
                    <button type="button" onclick="executeScript('system-debug-info')">开始检查</button>
                </div>
                
                <!-- 自定义脚本表单 -->
                <div id="custom-script-form" class="script-form" style="display: none;">
                    <div class="form-group">
                        <label for="param1">参数1:</label>
                        <input type="text" id="param1" name="param1" placeholder="参数1名称=值">
                    </div>
                    <div class="form-group">
                        <label for="param2">参数2:</label>
                        <input type="text" id="param2" name="param2" placeholder="参数2名称=值">
                    </div>
                    <button type="button" onclick="executeScript('custom-script')">执行自定义脚本</button>
                </div>
            </div>
            
            <!-- 结果展示区 -->
            <div class="result-display">
                <div class="result-header">
                    <h2>执行结果</h2>
                    <div class="result-timestamp" id="resultTimestamp">-</div>
                </div>
                <div class="result-content" id="resultContent">
                    请选择左侧的脚本,在上方填写参数并点击执行按钮
                </div>
            </div>
        </div>
    </div>

    <script>
        // 脚本信息映射
        const scriptInfo = {
            "file-query": {
                title: "文件查询 (/file-query)",
                description: "查询指定目录下的文件"
            },
            "disk-usage": {
                title: "磁盘使用 (/disk-usage)",
                description: "查看指定路径的磁盘使用情况"
            },
            "system-debug-info": {
                title: "debug信息 (/system-debug-info)",
                description: "获取系统正在被debug的java服务"
            },
            "custom-script": {
                title: "自定义脚本 (/custom-script)",
                description: "执行自定义脚本并传递参数"
            }
        };

        // 切换脚本
        document.querySelectorAll('.script-item').forEach(item => {
            item.addEventListener('click', function() {
                // 移除所有active类
                document.querySelectorAll('.script-item').forEach(i => i.classList.remove('active'));
                // 给当前点击项添加active类
                this.classList.add('active');
                
                // 获取脚本名称
                const scriptName = this.getAttribute('data-script');
                
                // 更新标题
                document.getElementById('currentScriptTitle').textContent = scriptInfo[scriptName].title;
                
                // 隐藏所有表单
                document.querySelectorAll('.script-form').forEach(form => {
                    form.style.display = 'none';
                });
                
                // 显示当前脚本对应的表单
                document.getElementById(`${scriptName}-form`).style.display = 'block';
            });
        });

        // 获取当前时间格式化字符串
        function getCurrentTime() {
            const now = new Date();
            return now.toLocaleString('zh-CN', {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit',
                hour12: false
            }).replace(',', ' ');
        }

        // 执行脚本
        async function executeScript(scriptName) {
            const resultContent = document.getElementById('resultContent');
            const resultTimestamp = document.getElementById('resultTimestamp');
            let url = `/${scriptName}`;
            let params = {};
            
            // 根据脚本类型收集参数
            switch(scriptName) {
                case 'file-query':
                    params = {
                        path: document.getElementById('path').value,
                        pattern: document.getElementById('pattern').value
                    };
                    break;
                case 'disk-usage':
                    params = {
                        path: document.getElementById('diskPath').value
                    };
                    break;
                case 'system-debug-info':
                    // 无参数
                    break;
                case 'custom-script':
                    if (document.getElementById('param1').value) {
                        const [key, value] = document.getElementById('param1').value.split('=');
                        if (key) params[key] = value || '';
                    }
                    if (document.getElementById('param2').value) {
                        const [key, value] = document.getElementById('param2').value.split('=');
                        if (key) params[key] = value || '';
                    }
                    break;
            }
            
            // 显示加载状态
            resultContent.textContent = "正在执行...";
            resultContent.className = "result-content loading";
            resultTimestamp.textContent = "执行中...";
            
            try {
                // 构建带参数的URL
                const queryString = new URLSearchParams(params).toString();
                if (queryString) url += `?${queryString}`;
                
                // 发送请求
                const response = await fetch(url);
                const data = await response.json();
                
                // 更新时间戳
                resultTimestamp.textContent = `更新时间: ${getCurrentTime()}`;
                
                // 显示结果
                if (data.success) {
                    resultContent.textContent = data.output || "执行成功,无返回内容";
                    resultContent.className = "result-content success";
                } else {
                    resultContent.textContent = `错误:${data.error}\n输出:${data.output}`;
                    resultContent.className = "result-content error";
                }
            } catch (error) {
                resultTimestamp.textContent = `更新时间: ${getCurrentTime()}`;
                resultContent.textContent = `请求失败:${error.message}`;
                resultContent.className = "result-content error";
            }
        }
    </script>
</body>
</html>
    

运行说明:

版权声明

本项目的版权归原作者 [皇家救星] 所有 。在项目存续期间,作者保留对代码及相关文档的版权权益。

开源协议核心条款

  1. 使用与分发:您有权免费使用本项目代码,无论是个人学习、内部业务应用还是商业用途。同时,您可以将本项目的副本进行分发,但需完整保留项目中的版权声明及开源协议文件。例如,如果您在自己的项目中引用了本项目的部分代码,在您项目的相关文件中应清晰注明代码来源及本项目的开源协议。
  2. 修改权限:您可以对本项目代码进行修改以满足自身需求。但修改后的代码在分发时,同样需要遵循本开源协议。若您对代码进行了重大修改,建议在相关文件头部或说明文档中,简要描述修改的内容及目的。
  3. 专利与商标:本开源协议不涉及对专利及商标使用的额外授权。在使用本项目过程中,如涉及专利及商标相关问题,需遵循相关法律法规及第三方权利声明。
  4. 免责声明:本项目代码按 “原样” 提供,作者不提供任何形式的明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权保证。在任何情况下,作者对因使用本项目代码所产生的任何直接、间接、偶然、特殊或后果性损害概不负责。

安装说明

1 首先安装Miniconda,后面需要依赖它创建python环境

mkdir -p ~/hch/src
cd ~/hch/src

# 下载最新 Miniconda3(适用于 Python 3)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh

# 运行安装脚本,指定安装路径(例如 /opt/miniconda3,避免权限问题)
bash Miniconda3-latest-Linux-x86_64.sh 
(执行后按提示安装)

# 初始化环境(可选,让终端能识别 conda 命令)
/opt/miniconda3/bin/conda init

source ~/.bash_profile

# 关闭base显示
conda config --set auto_activate_base false

2 放置项目文件

mkdir -p ~/hch/web_shell_invoker
上传web_shell_invoker.python和index.html

mkdir -p ~/hch/bin
上传parallel_check_java_debug.sh

chmod +x ~/hch/bin/parallel_check_java_debug.sh

3 创建python运行环境

# 接受main和r两个仓库的条款
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
cd ~/hch/web_shell_invoker
conda create -n web_shell_env python=3.9 -y

conda activate web_shell_env

# 安装脚本所需的 flask
pip install flask

4 开启程序

# 放开访问端口
sudo iptables -A INPUT -p tcp --dport 5555 -j ACCEPT
# service iptables save
sudo firewall-cmd --zone=public --add-port=5555/tcp --permanent
sudo firewall-cmd --reload

# 创建运行环境
cd ~/hch/web_shell_invoker

conda activate web_shell_env

# 运行脚本
nohup python web_shell_invoker.py &
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言