动态识别文件夹下flask接口

python 复制代码
@echo off
REM 设置标题
title Python Server Manager

REM 使用完整路径运行Python脚本
"E:\code\my_python_server\micromambavenv\python.exe" "E:\code\my_python_server\main.py"

REM 检查运行结果
if %errorlevel% neq 0 (
    echo.
    echo 程序运行出错,错误代码: %errorlevel%
)

echo.
echo 按任意键关闭窗口...
pause >nul
python 复制代码
# main.py
import subprocess
import sys
import os
import threading
import time
from pathlib import Path
import requests
import json
import re
import ast
import fnmatch
from typing import List, Dict, Any
import concurrent.futures
from functools import partial
import signal

# 添加新的常量
AUTO_PORT_START = 5000  # 自动分配端口起始值
AUTO_PORT_RANGE = 1000  # 自动分配端口范围

class ServiceManager:
    def __init__(self):
        self.processes = {}  # 存储运行中的服务进程
        self.services_config = {}  # 存储所有服务配置
        self.running = True
        
    def scan_single_file(self, file_path, directory):
        """
        扫描单个文件是否包含Flask路由
        """
        try:
            file_name = os.path.basename(file_path)
            # 排除main.py自身
            if file_name == 'main.py':
                return None
                
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
                
            # 检查文件是否包含Flask应用和路由定义
            if ('@app.route' in content or 'Flask(' in content) and 'if __name__ == \'__main__\':' in content:
                # 尝试解析Python代码以确认其有效性
                ast.parse(content)
                return {
                    'file_path': file_path,
                    'file_name': file_name,
                    'relative_path': os.path.relpath(file_path, directory)
                }
        except (SyntaxError, UnicodeDecodeError, Exception):
            # 忽略无法解析或编码错误的文件
            pass
        return None

    def scan_python_files_with_routes(self, directory="E:\\code\\my_python_server"):
        """
        扫描目录下所有包含Flask路由的Python文件(优化版)
        """
        print("正在扫描服务文件...")
        route_files = []
        
        # 收集所有Python文件路径
        python_files = []
        for root, dirs, files in os.walk(directory):
            # 排除一些常见的不需要扫描的目录
            dirs[:] = [d for d in dirs if d not in ['__pycache__', '.git', 'venv', 'env', 'logs']]
            
            for file in files:
                if file.endswith('.py'):
                    file_path = os.path.join(root, file)
                    python_files.append(file_path)
        
        # 使用线程池并行处理文件扫描
        with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
            scan_func = partial(self.scan_single_file, directory=directory)
            results = executor.map(scan_func, python_files)
            route_files = [result for result in results if result is not None]
        
        print(f"扫描完成,共发现 {len(route_files)} 个服务文件")
        return route_files

    def create_auto_servers(self, route_files):
        """
        为找到的路由文件创建服务器配置
        """
        auto_servers = {}
        port_counter = AUTO_PORT_START
        
        for route_file in route_files:
            # 生成服务名称
            file_name = os.path.splitext(route_file['file_name'])[0]
            relative_dir = os.path.dirname(route_file['relative_path'])
            if relative_dir:
                # 清理路径分隔符,生成更友好的服务名称
                dir_name = relative_dir.replace(os.sep, '_').replace('/', '_').replace('\\', '_')
                service_name = f"{dir_name}_{file_name}".title().replace('_', ' ')
            else:
                service_name = f"{file_name.title()} Server"
                
            # 创建服务器配置
            server_config = {
                "name": service_name,
                "command": f"micromambavenv\\python {route_file['relative_path'].replace(os.sep, '/')}",
                "cwd": "E:\\code\\my_python_server",
                "log_file": f"logs/{file_name}.log",
                "port": port_counter,
                "file_path": route_file['file_path']
            }
            
            auto_servers[service_name] = server_config
            port_counter += 1
            
            # 防止超出分配的端口范围
            if port_counter >= AUTO_PORT_START + AUTO_PORT_RANGE:
                print(f"警告: 达到端口分配上限 ({AUTO_PORT_RANGE}个服务)")
                break
                
        return auto_servers

    def select_individual_services(self, all_services):
        """
        允许用户逐个选择要启动的服务
        """
        if not all_services:
            print("没有发现可启动的服务")
            return []
        
        service_list = list(all_services.values())
        print("\n发现以下服务:")
        for i, (name, service) in enumerate(all_services.items(), 1):
            status = "运行中" if name in self.processes else "已停止"
            print(f"{i}. {service['name']} (端口: {service['port']}, 状态: {status})")
        
        print(f"{len(all_services) + 1}. 返回主菜单")
        
        selected_indices = input("\n请输入要操作的服务编号(用逗号分隔,如: 1,3,5): ").strip()
        
        if not selected_indices:
            return []
        
        if selected_indices == str(len(all_services) + 1):
            return []
        
        try:
            indices = [int(x.strip()) for x in selected_indices.split(',')]
            selected_services = []
            for idx in indices:
                if 1 <= idx <= len(service_list):
                    selected_services.append(list(all_services.keys())[idx-1])
                else:
                    print(f"警告: 无效的服务编号 {idx}")
            return selected_services
        except ValueError:
            print("输入格式错误,应为数字并用逗号分隔")
            return []

    def clear_log_file(self, log_path):
        '''清空日志文件'''
        try:
            with open(log_path, 'w', encoding='utf-8') as f:
                f.write('')
            print(f"已清空日志文件: {log_path}")
        except Exception as e:
            print(f"清空日志文件 {log_path} 时出错: {str(e)}")
            return False
        return True

    def tail_file(self, log_path, name):
        '''实时读取日志文件内容并输出到终端'''
        while name in self.processes and self.running:
            try:
                with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
                    f.seek(0, 2)  # 移到文件末尾
                    while name in self.processes and self.running:
                        line = f.readline()
                        if not line:
                            time.sleep(0.1)  # 短暂休眠以降低 CPU 使用
                            continue
                        # 为每个服务端的输出添加前缀标识
                        print(f"[{name}]: {line.strip()}")
            except FileNotFoundError:
                time.sleep(0.1)  # 文件可能尚未创建,等待重试
            except Exception as e:
                print(f"读取日志文件 {log_path} 时出错: {str(e)}")
                time.sleep(1)

    def get_service_routes(self, port):
        """
        获取指定端口服务的路由信息
        """
        try:
            response = requests.get(f"http://localhost:{port}/routes", timeout=3)
            if response.status_code == 200:
                return response.json()
            else:
                return None
        except Exception as e:
            # print(f"无法获取端口 {port} 的路由信息: {str(e)}")
            return None

    def print_service_routes(self, service_name, port):
        """
        打印特定服务的路由信息
        """
        print(f"\n=== {service_name} 路由信息 (端口: {port}) ===")
        routes_info = self.get_service_routes(port)
        if routes_info and 'routes' in routes_info:
            for idx, route in enumerate(routes_info['routes'], 1):
                methods = ', '.join([m for m in route['methods'] if m != 'HEAD'])
                # 只显示第一行描述
                description = route['description'].split('\n')[0] if route['description'] else ''
                
                # 默认显示基本URL
                base_url = f"http://localhost:{port}{route['rule']}"
                
                # 检查路由中是否包含路径参数 <path:...>
                path_params = re.findall(r'<path:(.*?)>', route['rule'])
                
                # 检查是否有查询参数
                has_query_params = False
                if 'GET' in route['methods'] and route['description']:
                    lines = route['description'].split('\n')
                    params = []
                    for line in lines:
                        if 'name:' in line:
                            has_query_params = True
                            # 提取参数名
                            param_name_match = re.search(r'name:\s*(\w+)', line)
                            if param_name_match:
                                param_name = param_name_match.group(1)
                                # 检查是否有默认值
                                default_match = re.search(r'default:\s*([^,\n]+)', line)
                                if default_match:
                                    default_val = default_match.group(1).strip()
                                    params.append(f"{param_name}={default_val}")
                                else:
                                    params.append(f"{param_name}=")
                    
                    # 如果找到查询参数,则构建带参数的URL示例
                    if params:
                        query_string = '&'.join(params)
                        print(f"{idx}. http://localhost:{port}{route['rule']}?{query_string} [{methods}] - {description}")
                    else:
                        # 有参数定义但未解析出具体参数的情况
                        print(f"{idx}. {base_url} [{methods}] - {description}")
                else:
                    # 非GET请求或没有描述的情况
                    print(f"{idx}. {base_url} [{methods}] - {description}")
                
                # 如果有路径参数,提供额外说明
                if path_params:
                    print(f"    注意: 此路由包含路径参数 {', '.join(path_params)},这些参数应直接包含在URL路径中")
        else:
            print(f"  无法获取路由信息或服务尚未启动")
        print("=" * 50)

    def start_service(self, service_name):
        """启动指定服务"""
        if service_name in self.processes:
            print(f"服务 {service_name} 已经在运行中")
            return False
            
        if service_name not in self.services_config:
            print(f"未找到服务 {service_name}")
            return False
            
        service = self.services_config[service_name]
        
        # 为服务添加端口参数
        command = service['command']
        if '--port' not in command:
            command += f" --port {service['port']}"
            
        # 确保日志目录存在
        log_path = Path(service["log_file"])
        log_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 清空日志文件
        if not self.clear_log_file(log_path):
            return False
            
        try:
            print(f"启动 {service['name']} (端口: {service['port']})...")
            # 打开日志文件(追加模式)
            with open(log_path, 'a', encoding='utf-8') as log_file:
                # 启动进程,重定向输出到日志文件
                process = subprocess.Popen(
                    command,
                    cwd=service["cwd"],
                    shell=True,
                    stdout=log_file,
                    stderr=subprocess.STDOUT,  # 将错误输出也重定向到日志
                    text=True,
                    bufsize=1
                )
                
                # 等待一会检查进程是否仍在运行
                try:
                    return_code = process.wait(timeout=5)
                    if return_code != 0:
                        print(f"错误: {service['name']} 启动失败,返回码: {return_code}")
                        print(f"请检查日志文件: {log_path}")
                        return False
                except subprocess.TimeoutExpired:
                    # 如果运行正常,进程仍在运行
                    self.processes[service_name] = process
                    print(f"{service['name']} 启动成功,日志输出到: {log_path}")
                    # 启动线程实时读取日志文件
                    threading.Thread(target=self.tail_file, args=(log_path, service_name), daemon=True).start()
                    # 自动显示路由信息
                    self.print_service_routes(service_name, service['port'])
                    return True
                    
        except Exception as e:
            print(f"启动 {service['name']} 时发生异常: {str(e)}")
            return False

    def stop_service(self, service_name):
        """停止指定服务"""
        if service_name not in self.processes:
            print(f"服务 {service_name} 未在运行")
            return False
            
        try:
            process = self.processes[service_name]
            print(f"正在停止 {service_name}...")
            process.terminate()
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                process.kill()
                process.wait()
                
            del self.processes[service_name]
            print(f"服务 {service_name} 已停止")
            return True
        except Exception as e:
            print(f"停止服务 {service_name} 时出错: {str(e)}")
            return False

    def list_services(self):
        """列出所有服务及其状态"""
        if not self.services_config:
            print("未发现任何服务")
            return
            
        print("\n=== 服务列表 ===")
        for i, (name, service) in enumerate(self.services_config.items(), 1):
            status = "运行中" if name in self.processes else "已停止"
            print(f"{i}. {service['name']} (端口: {service['port']}, 状态: {status})")
        print("=" * 30)

    def execute_get_request(self, port, route_rule, service_name, route_index, route_info):
        """
        执行GET请求,支持参数输入
        """
        try:
            # 处理路径参数 <path:...>
            path_params = re.findall(r'<path:(.*?)>', route_rule)
            modified_route_rule = route_rule
            
            # 如果有路径参数,提示用户输入
            if path_params:
                print(f"注意: 此路由包含路径参数: {', '.join(path_params)}")
                print("这些参数必须作为URL路径的一部分提供")
                
                for param in path_params:
                    value = input(f"请输入路径参数 '{param}' 的值 (可以包含 /): ").strip()
                    if not value:
                        print(f"路径参数 '{param}' 是必需的")
                        return
                    # 替换路径参数占位符
                    modified_route_rule = modified_route_rule.replace(f'<path:{param}>', value)
            
            # 解析查询参数
            params = {}
            if route_info.get('description'):
                lines = route_info['description'].split('\n')
                required_params = []
                optional_params = []
                
                for line in lines:
                    if 'name:' in line:
                        # 提取参数信息
                        param_name_match = re.search(r'name:\s*(\w+)', line)
                        if param_name_match:
                            param_name = param_name_match.group(1)
                            
                            # 检查是否必需
                            required = 'required:' in line and 'required: true' in line
                            
                            # 检查是否有默认值
                            default_match = re.search(r'default:\s*([^,\n]+)', line)
                            default_val = default_match.group(1).strip() if default_match else None
                            
                            if default_val is not None:
                                optional_params.append((param_name, default_val))
                            elif required:
                                required_params.append(param_name)
                            else:
                                optional_params.append((param_name, None))
                
                # 获取用户输入的必需参数
                for param_name in required_params:
                    value = input(f"请输入参数 '{param_name}' 的值: ").strip()
                    if value:
                        params[param_name] = value
                    else:
                        print(f"参数 '{param_name}' 是必需的")
                        return
                
                # 询问可选参数
                for param_name, default_val in optional_params:
                    prompt = f"请输入参数 '{param_name}' 的值"
                    if default_val is not None:
                        prompt += f" (默认: {default_val})"
                    prompt += " (直接回车跳过): "
                    
                    value = input(prompt).strip()
                    if value:
                        params[param_name] = value
                    elif default_val is not None:
                        params[param_name] = default_val
        
            url = f"http://localhost:{port}{modified_route_rule}"
            print(f"正在执行请求: {url}")
            if params:
                print(f"查询参数: {params}")
            
            response = requests.get(url, params=params, timeout=10)
            print(f"\n=== 响应结果 (服务: {service_name}, 路由 #{route_index}) ===")
            print(f"状态码: {response.status_code}")
            print(f"请求URL: {response.url}")
            print(f"响应头: {dict(response.headers)}")
            
            # 尝试解析JSON响应
            try:
                json_data = response.json()
                print("响应内容 (JSON格式):")
                print(json.dumps(json_data, indent=2, ensure_ascii=False))
            except:
                # 如果不是JSON格式,则按文本处理
                print("响应内容 (文本格式):")
                print(response.text)
            print("=" * 50)
            
        except Exception as e:
            print(f"执行请求时出错: {str(e)}")

    def manage_services_menu(self):
        """服务管理菜单"""
        # 只在进入菜单时显示一次服务列表
      
        
        while True:
            # 显示可用命令
            print("\n可用命令:")
            print("start <编号> - 启动服务")
            print("stop <编号> - 停止服务")
            print("routes <编号> - 显示服务路由")
            print("get <编号> <路由序号> - 执行GET请求")
            print("list - 显示服务列表")
            print("exit - 退出程序")
            
            # 获取用户输入
            command = input("\n请输入命令: ").strip().lower()
            
            if command.startswith("start"):
                try:
                    service_index = int(command.split()[1])
                    if 1 <= service_index <= len(self.services_config):
                        service_name = list(self.services_config.keys())[service_index-1]
                        self.start_service(service_name)
                    else:
                        print("无效的服务编号")
                except (ValueError, IndexError):
                    print("请输入有效的命令格式: start <编号>")
                    
            elif command.startswith("stop"):
                try:
                    service_index = int(command.split()[1])
                    if 1 <= service_index <= len(self.services_config):
                        service_name = list(self.services_config.keys())[service_index-1]
                        self.stop_service(service_name)
                    else:
                        print("无效的服务编号")
                except (ValueError, IndexError):
                    print("请输入有效的命令格式: stop <编号>")
                    
            elif command.startswith("routes"):
                try:
                    service_index = int(command.split()[1])
                    if 1 <= service_index <= len(self.services_config):
                        service_name = list(self.services_config.keys())[service_index-1]
                        service = self.services_config[service_name]
                        self.print_service_routes(service_name, service['port'])
                    else:
                        print("无效的服务编号")
                except (ValueError, IndexError):
                    print("请输入有效的命令格式: routes <编号>")
                    
            elif command.startswith("get"):
                try:
                    parts = command.split()
                    if len(parts) < 3:
                        print("请输入有效的命令格式: get <编号> <路由序号>")
                        continue
                        
                    service_index = int(parts[1])
                    route_index = int(parts[2])
                    
                    if 1 <= service_index <= len(self.services_config):
                        service_name = list(self.services_config.keys())[service_index-1]
                        service = self.services_config[service_name]
                        
                        # 获取路由信息
                        routes_info = self.get_service_routes(service['port'])
                        if routes_info and 'routes' in routes_info:
                            if 1 <= route_index <= len(routes_info['routes']):
                                route = routes_info['routes'][route_index-1]
                                if 'GET' in route['methods']:
                                    self.execute_get_request(
                                        service['port'], 
                                        route['rule'], 
                                        service_name, 
                                        route_index,
                                        route
                                    )
                                else:
                                    print("该路由不支持GET方法")
                            else:
                                print("无效的路由序号")
                        else:
                            print("无法获取路由信息")
                    else:
                        print("无效的服务编号")
                except (ValueError, IndexError):
                    print("请输入有效的命令格式: get <编号> <路由序号>")
                    
            elif command == "list":
                self.list_services()
                
            elif command == "exit":
                break
                
            else:
                print("无效命令,请重新输入")

    def initialize_services(self):
        """初始化服务配置"""
        route_files = self.scan_python_files_with_routes()
        self.services_config = self.create_auto_servers(route_files)
        
        if not self.services_config:
            print("未发现任何服务")
            return False
        return True

    def start_initial_services(self):
        """启动初始服务"""
        print("\n请选择要启动的服务:")
        selected = self.select_individual_services(self.services_config)
        
        started_count = 0
        for service_name in selected:
            if self.start_service(service_name):
                started_count += 1
                # 注意:这里不再需要手动调用 print_service_routes,因为 start_service 内部已经自动调用了
                
        print(f"\n成功启动 {started_count} 个服务")
        return started_count > 0

    def cleanup(self):
        """清理所有运行中的服务"""
        print("\n正在停止所有服务...")
        for service_name in list(self.processes.keys()):
            self.stop_service(service_name)
        self.running = False

    def main_menu(self):
        """主菜单"""
        print("===== 服务器启动脚本 =====")
        
        # 初始化服务
        if not self.initialize_services():
            return
            
        # 启动初始服务
        self.start_initial_services()
        
        # 进入管理循环
        try:
            while self.running:
                # 直接进入服务管理菜单,不显示主菜单
                self.manage_services_menu()
        except KeyboardInterrupt:
            print("\n收到中断信号...")
            self.cleanup()

# 全局服务管理器实例
service_manager = ServiceManager()

def signal_handler(sig, frame):
    """处理信号中断"""
    print("\n收到终止信号,正在清理...")
    service_manager.cleanup()
    sys.exit(0)

if __name__ == "__main__":
    # 注册信号处理器
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # 运行主菜单
    service_manager.main_menu()
相关推荐
安当加密15 小时前
CAS汽车固件签名:从“完成签名”到“安全治理”的演进之路
1024程序员节
傻童:CPU15 小时前
C语言需要掌握的基础知识点之矩阵
c语言·1024程序员节
摘星编程15 小时前
技术引领场景革新|合合信息PRCV论坛聚焦多模态文本智能前沿实践
合合信息·1024程序员节·textin·多模态文本·fidok
ink@re15 小时前
消息队列集群——RabbitMQ
分布式·rabbitmq·1024程序员节
cs阿坤dn15 小时前
SQL-Server2019离线部署安装【CentOS7.4】
1024程序员节
Pocker_Spades_A15 小时前
金仓多模数据库平替MongoDB的电子证照国产化实践——从2TB数据迁移到1600+并发支撑
数据库·1024程序员节
彭刷子15 小时前
《商户查询缓存案例》使用案例学习Redis的缓存使用;缓存击穿、穿透、雪崩的原理的解决方式
redis·1024程序员节
郝学胜-神的一滴15 小时前
主成分分析(PCA)在计算机图形学中的深入解析与应用
开发语言·人工智能·算法·机器学习·1024程序员节
云边有个稻草人15 小时前
反爬克星还是效率神器?Browser-Use+cpolar重构Web自动化逻辑
cpolar·1024程序员节