后台管理页面导航栏修改为侧边

This commit is contained in:
rjb
2025-09-07 22:33:16 +08:00
parent 177079221e
commit 78aab17511
14 changed files with 2969 additions and 15 deletions

34
scripts/flask-app.service Normal file
View File

@@ -0,0 +1,34 @@
[Unit]
Description=Flask Prompt Master Application
After=network.target
Wants=network.target
[Service]
Type=forking
User=renjianbo
Group=renjianbo
WorkingDirectory=/home/renjianbo/aitsc
Environment=PATH=/home/renjianbo/miniconda3/envs/myenv/bin
ExecStart=/home/renjianbo/aitsc/scripts/port_manager.sh start
ExecStop=/home/renjianbo/aitsc/scripts/port_manager.sh stop
ExecReload=/home/renjianbo/aitsc/scripts/port_manager.sh restart
PIDFile=/home/renjianbo/aitsc/logs/gunicorn.pid
Restart=always
RestartSec=10
StartLimitInterval=60
StartLimitBurst=3
# 安全设置
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/home/renjianbo/aitsc/logs /home/renjianbo/aitsc/data
# 日志设置
StandardOutput=journal
StandardError=journal
SyslogIdentifier=flask-app
[Install]
WantedBy=multi-user.target

328
scripts/port_manager.sh Executable file
View File

@@ -0,0 +1,328 @@
#!/bin/bash
# 端口管理脚本
# 用于管理Flask应用的端口占用问题
# 配置
PORT=5002
APP_NAME="flask_prompt_master"
PID_FILE="logs/gunicorn.pid"
LOG_DIR="logs"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# 检查端口是否被占用
check_port() {
log_info "检查端口 $PORT 占用情况..."
if ss -tlnp | grep -q ":$PORT "; then
log_warn "端口 $PORT 已被占用"
ss -tlnp | grep ":$PORT "
return 1
else
log_info "端口 $PORT 未被占用"
return 0
fi
}
# 获取占用端口的进程
get_port_processes() {
log_info "获取占用端口 $PORT 的进程信息..."
local processes=$(ss -tlnp | grep ":$PORT " | awk '{print $7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | sort -u)
if [ -n "$processes" ]; then
log_warn "占用端口 $PORT 的进程:"
for pid in $processes; do
if ps -p $pid > /dev/null 2>&1; then
ps -p $pid -o pid,ppid,cmd --no-headers
fi
done
return 1
else
log_info "没有进程占用端口 $PORT"
return 0
fi
}
# 清理端口占用
clean_port() {
log_info "清理端口 $PORT 占用..."
local processes=$(ss -tlnp | grep ":$PORT " | awk '{print $7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | sort -u)
if [ -n "$processes" ]; then
log_warn "发现占用端口 $PORT 的进程,正在清理..."
for pid in $processes; do
if ps -p $pid > /dev/null 2>&1; then
local cmd=$(ps -p $pid -o cmd --no-headers)
log_info "终止进程 $pid: $cmd"
kill -TERM $pid
# 等待进程终止
local count=0
while ps -p $pid > /dev/null 2>&1 && [ $count -lt 10 ]; do
sleep 1
count=$((count + 1))
done
# 强制终止
if ps -p $pid > /dev/null 2>&1; then
log_warn "强制终止进程 $pid"
kill -KILL $pid
fi
fi
done
# 验证清理结果
sleep 2
if check_port; then
log_info "端口 $PORT 清理成功"
return 0
else
log_error "端口 $PORT 清理失败"
return 1
fi
else
log_info "端口 $PORT 未被占用,无需清理"
return 0
fi
}
# 检查Gunicorn进程
check_gunicorn() {
log_info "检查Gunicorn进程状态..."
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p $pid > /dev/null 2>&1; then
log_info "Gunicorn主进程 $pid 正在运行"
local workers=$(ps aux | grep "gunicorn.*run_dev:app" | grep -v grep | wc -l)
log_info "工作进程数量: $workers"
return 0
else
log_warn "PID文件存在但进程不存在: $pid"
return 1
fi
else
log_warn "PID文件不存在: $PID_FILE"
return 1
fi
}
# 启动应用
start_app() {
log_info "启动Flask应用..."
# 切换到项目目录
cd "$PROJECT_DIR" || {
log_error "无法切换到项目目录: $PROJECT_DIR"
return 1
}
# 激活conda环境
if command -v conda > /dev/null 2>&1; then
log_info "激活conda环境..."
eval "$(/home/renjianbo/miniconda3/bin/conda shell.bash hook)" && conda activate myenv
fi
# 检查端口
if ! check_port; then
log_warn "端口被占用,尝试清理..."
if ! clean_port; then
log_error "无法清理端口占用,启动失败"
return 1
fi
fi
# 启动Gunicorn
log_info "启动Gunicorn服务..."
nohup gunicorn -c gunicorn.conf.py run_dev:app > "$LOG_DIR/gunicorn_startup.log" 2>&1 &
# 等待启动
sleep 5
# 验证启动
if check_gunicorn; then
log_info "应用启动成功"
return 0
else
log_error "应用启动失败"
return 1
fi
}
# 停止应用
stop_app() {
log_info "停止Flask应用..."
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p $pid > /dev/null 2>&1; then
log_info "停止Gunicorn主进程 $pid"
kill -TERM $pid
# 等待进程终止
local count=0
while ps -p $pid > /dev/null 2>&1 && [ $count -lt 10 ]; do
sleep 1
count=$((count + 1))
done
# 强制终止
if ps -p $pid > /dev/null 2>&1; then
log_warn "强制终止进程 $pid"
kill -KILL $pid
fi
fi
fi
# 清理所有相关进程
local gunicorn_pids=$(ps aux | grep "gunicorn.*run_dev:app" | grep -v grep | awk '{print $2}')
if [ -n "$gunicorn_pids" ]; then
log_info "清理Gunicorn工作进程..."
for pid in $gunicorn_pids; do
kill -TERM $pid 2>/dev/null
done
fi
log_info "应用停止完成"
}
# 重启应用
restart_app() {
log_info "重启Flask应用..."
stop_app
sleep 2
start_app
}
# 状态检查
status() {
log_info "=== Flask应用状态检查 ==="
echo "1. 端口占用检查:"
if check_port; then
echo " ✅ 端口 $PORT 可用"
else
echo " ❌ 端口 $PORT 被占用"
get_port_processes
fi
echo ""
echo "2. Gunicorn进程检查:"
if check_gunicorn; then
echo " ✅ Gunicorn正在运行"
else
echo " ❌ Gunicorn未运行"
fi
echo ""
echo "3. 服务响应检查:"
if curl -s http://localhost:$PORT/ > /dev/null 2>&1; then
echo " ✅ 服务正常响应"
else
echo " ❌ 服务无响应"
fi
}
# 监控模式
monitor() {
log_info "启动端口监控模式..."
while true; do
if ! check_gunicorn; then
log_warn "检测到Gunicorn进程异常尝试重启..."
restart_app
fi
if ! curl -s http://localhost:$PORT/ > /dev/null 2>&1; then
log_warn "检测到服务无响应,尝试重启..."
restart_app
fi
sleep 30
done
}
# 帮助信息
show_help() {
echo "端口管理脚本使用方法:"
echo ""
echo " $0 check - 检查端口占用情况"
echo " $0 clean - 清理端口占用"
echo " $0 start - 启动应用"
echo " $0 stop - 停止应用"
echo " $0 restart - 重启应用"
echo " $0 status - 查看应用状态"
echo " $0 monitor - 启动监控模式"
echo " $0 help - 显示帮助信息"
echo ""
echo "示例:"
echo " $0 status # 检查当前状态"
echo " $0 restart # 重启应用"
echo " $0 monitor # 后台监控"
}
# 主函数
main() {
case "${1:-help}" in
check)
check_port
;;
clean)
clean_port
;;
start)
start_app
;;
stop)
stop_app
;;
restart)
restart_app
;;
status)
status
;;
monitor)
monitor
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
}
# 执行主函数
main "$@"

399
scripts/port_monitor.py Executable file
View File

@@ -0,0 +1,399 @@
#!/home/renjianbo/miniconda3/envs/myenv/bin/python
# -*- coding: utf-8 -*-
"""
端口监控脚本
用于监控Flask应用的端口占用和服务状态
"""
import os
import sys
import time
import json
import subprocess
import logging
from datetime import datetime
from pathlib import Path
# 配置
PORT = 5002
APP_NAME = "flask_prompt_master"
PID_FILE = "logs/gunicorn.pid"
LOG_FILE = "logs/port_monitor.log"
CONFIG_FILE = "logs/monitor_config.json"
ALERT_FILE = "logs/port_alerts.log"
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class PortMonitor:
def __init__(self):
self.project_dir = Path(__file__).parent.parent
self.pid_file = self.project_dir / PID_FILE
self.config_file = self.project_dir / CONFIG_FILE
self.alert_file = self.project_dir / ALERT_FILE
# 确保日志目录存在
self.project_dir.mkdir(exist_ok=True)
(self.project_dir / "logs").mkdir(exist_ok=True)
# 加载配置
self.config = self.load_config()
def load_config(self):
"""加载监控配置"""
default_config = {
"check_interval": 30, # 检查间隔(秒)
"max_restart_attempts": 3, # 最大重启尝试次数
"alert_threshold": 2, # 告警阈值
"port_timeout": 5, # 端口检查超时时间
"enable_alerts": True, # 启用告警
"auto_restart": True, # 自动重启
}
if self.config_file.exists():
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
default_config.update(config)
except Exception as e:
logger.error("加载配置文件失败: {}".format(e))
return default_config
def save_config(self):
"""保存监控配置"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error("保存配置文件失败: {}".format(e))
def run_command(self, cmd, timeout=10):
"""执行命令"""
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
timeout=timeout
)
return result.returncode == 0, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return False, "", "命令执行超时"
except Exception as e:
return False, "", str(e)
def check_port_usage(self):
"""检查端口占用情况"""
cmd = f"ss -tlnp | grep ':{PORT} '"
success, stdout, stderr = self.run_command(cmd)
if success and stdout.strip():
# 解析进程信息
processes = []
for line in stdout.strip().split('\n'):
if line:
parts = line.split()
if len(parts) >= 7:
pid_info = parts[6]
if 'pid=' in pid_info:
pid = pid_info.split('pid=')[1].split(',')[0]
processes.append(pid)
return True, processes
else:
return False, []
def check_gunicorn_process(self):
"""检查Gunicorn进程状态"""
if not self.pid_file.exists():
return False, "PID文件不存在"
try:
with open(self.pid_file, 'r') as f:
pid = f.read().strip()
if not pid:
return False, "PID文件为空"
# 检查进程是否存在
cmd = f"ps -p {pid}"
success, stdout, stderr = self.run_command(cmd)
if success and stdout.strip():
# 检查工作进程数量
cmd = "ps aux | grep 'gunicorn.*run_dev:app' | grep -v grep | wc -l"
success, stdout, stderr = self.run_command(cmd)
if success:
worker_count = int(stdout.strip())
return True, f"主进程 {pid}, 工作进程 {worker_count}"
return False, f"进程 {pid} 不存在"
except Exception as e:
return False, "检查进程失败: {}".format(e)
def check_service_response(self):
"""检查服务响应"""
cmd = f"curl -s -o /dev/null -w '%{{http_code}}' http://localhost:{PORT}/"
success, stdout, stderr = self.run_command(cmd, timeout=self.config['port_timeout'])
if success and stdout.strip() == '200':
return True, "服务正常响应"
else:
return False, "服务无响应 (HTTP: {})".format(stdout.strip())
def get_system_info(self):
"""获取系统信息"""
info = {}
# CPU使用率
cmd = "top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1"
success, stdout, stderr = self.run_command(cmd)
if success:
info['cpu_usage'] = float(stdout.strip())
# 内存使用率
cmd = "free | grep Mem | awk '{printf \"%.2f\", $3/$2 * 100.0}'"
success, stdout, stderr = self.run_command(cmd)
if success:
info['memory_usage'] = float(stdout.strip())
# 磁盘使用率
cmd = "df / | tail -1 | awk '{print $5}' | sed 's/%//'"
success, stdout, stderr = self.run_command(cmd)
if success:
info['disk_usage'] = float(stdout.strip())
return info
def log_alert(self, message, level="WARNING"):
"""记录告警信息"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
alert_msg = f"[{timestamp}] [{level}] {message}"
logger.warning(alert_msg)
if self.config['enable_alerts']:
try:
with open(self.alert_file, 'a', encoding='utf-8') as f:
f.write(alert_msg + '\n')
except Exception as e:
logger.error("写入告警文件失败: {}".format(e))
def restart_service(self):
"""重启服务"""
logger.info("尝试重启服务...")
# 停止服务
cmd = f"cd {self.project_dir} && ./scripts/port_manager.sh stop"
success, stdout, stderr = self.run_command(cmd)
if not success:
logger.error("停止服务失败: {}".format(stderr))
return False
time.sleep(2)
# 启动服务
cmd = f"cd {self.project_dir} && ./scripts/port_manager.sh start"
success, stdout, stderr = self.run_command(cmd)
if not success:
logger.error("启动服务失败: {}".format(stderr))
return False
logger.info("服务重启成功")
return True
def generate_report(self):
"""生成监控报告"""
report = {
"timestamp": datetime.now().isoformat(),
"port_usage": {},
"gunicorn_status": {},
"service_response": {},
"system_info": {},
"recommendations": []
}
# 检查端口占用
port_occupied, processes = self.check_port_usage()
report["port_usage"] = {
"occupied": port_occupied,
"processes": processes
}
# 检查Gunicorn进程
gunicorn_running, status = self.check_gunicorn_process()
report["gunicorn_status"] = {
"running": gunicorn_running,
"status": status
}
# 检查服务响应
service_ok, response = self.check_service_response()
report["service_response"] = {
"ok": service_ok,
"response": response
}
# 获取系统信息
report["system_info"] = self.get_system_info()
# 生成建议
if not port_occupied:
report["recommendations"].append("端口未被占用,可能需要启动服务")
if not gunicorn_running:
report["recommendations"].append("Gunicorn进程未运行需要重启服务")
if not service_ok:
report["recommendations"].append("服务无响应,需要检查服务状态")
# 检查系统资源
sys_info = report["system_info"]
if sys_info.get('cpu_usage', 0) > 80:
report["recommendations"].append("CPU使用率过高建议优化或扩容")
if sys_info.get('memory_usage', 0) > 90:
report["recommendations"].append("内存使用率过高,建议增加内存或优化应用")
if sys_info.get('disk_usage', 0) > 85:
report["recommendations"].append("磁盘使用率过高,建议清理日志或扩容")
return report
def monitor_loop(self):
"""监控主循环"""
logger.info("开始端口监控...")
restart_count = 0
last_restart_time = 0
while True:
try:
# 生成报告
report = self.generate_report()
# 检查是否需要重启
need_restart = False
if not report["gunicorn_status"]["running"]:
logger.warning("Gunicorn进程未运行")
need_restart = True
if not report["service_response"]["ok"]:
logger.warning("服务无响应")
need_restart = True
# 执行重启
if need_restart and self.config['auto_restart']:
current_time = time.time()
# 检查重启限制
if (current_time - last_restart_time > 300 and # 5分钟内不重复重启
restart_count < self.config['max_restart_attempts']):
self.log_alert("检测到服务异常,尝试自动重启")
if self.restart_service():
restart_count += 1
last_restart_time = current_time
else:
self.log_alert("自动重启失败", "ERROR")
else:
self.log_alert("达到重启限制,跳过自动重启", "ERROR")
# 记录状态
status_msg = f"端口占用: {report['port_usage']['occupied']}, " \
f"Gunicorn: {report['gunicorn_status']['running']}, " \
f"服务响应: {report['service_response']['ok']}"
logger.info(status_msg)
# 等待下次检查
time.sleep(self.config['check_interval'])
except KeyboardInterrupt:
logger.info("监控被用户中断")
break
except Exception as e:
logger.error(f"监控过程中发生错误: {e}")
time.sleep(self.config['check_interval'])
def show_status(self):
"""显示当前状态"""
report = self.generate_report()
print("=== Flask应用监控状态 ===")
print(f"检查时间: {report['timestamp']}")
print()
print("1. 端口占用检查:")
if report['port_usage']['occupied']:
print(f" ✅ 端口 {PORT} 被占用")
print(f" 进程: {', '.join(report['port_usage']['processes'])}")
else:
print(f" ❌ 端口 {PORT} 未被占用")
print()
print("2. Gunicorn进程检查:")
if report['gunicorn_status']['running']:
print(f"{report['gunicorn_status']['status']}")
else:
print(f"{report['gunicorn_status']['status']}")
print()
print("3. 服务响应检查:")
if report['service_response']['ok']:
print(f"{report['service_response']['response']}")
else:
print(f"{report['service_response']['response']}")
print()
print("4. 系统资源:")
sys_info = report['system_info']
print(f" CPU使用率: {sys_info.get('cpu_usage', 'N/A')}%")
print(f" 内存使用率: {sys_info.get('memory_usage', 'N/A')}%")
print(f" 磁盘使用率: {sys_info.get('disk_usage', 'N/A')}%")
if report['recommendations']:
print()
print("5. 建议:")
for i, rec in enumerate(report['recommendations'], 1):
print(f" {i}. {rec}")
def main():
monitor = PortMonitor()
if len(sys.argv) > 1:
command = sys.argv[1]
if command == "status":
monitor.show_status()
elif command == "report":
report = monitor.generate_report()
print(json.dumps(report, indent=2, ensure_ascii=False))
elif command == "restart":
monitor.restart_service()
elif command == "monitor":
monitor.monitor_loop()
elif command == "config":
print(json.dumps(monitor.config, indent=2, ensure_ascii=False))
else:
print("用法: python port_monitor.py [status|report|restart|monitor|config]")
else:
monitor.monitor_loop()
if __name__ == "__main__":
main()