Files
spider/server.py
2026-04-10 10:57:44 +08:00

205 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import re
import platform
import subprocess
import psutil
import winreg
from flask import Flask, jsonify, request
from flask_cors import CORS
app = Flask(__name__)
# <--- 2. 启用全局跨域支持
# 默认允许所有域名跨域访问所有接口。
# 如果想只允许特定前端域名,可以改成 CORS(app, origins=["http://localhost:8080", "http://你的域名.com"])
CORS(app, supports_credentials=True)
# Windows 下分离进程的标志位,确保 exe 独立运行
DETACHED_PROCESS = 0x00000008
def get_cpu_model():
"""
通过读取 Windows 注册表获取准确的 CPU 型号
"""
try:
# 打开注册表路径
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0")
# 读取 ProcessorNameString 的值
cpu_model, _ = winreg.QueryValueEx(key, "ProcessorNameString")
return cpu_model.strip()
except Exception:
return "Unknown CPU"
def is_valid_task_id(task_id):
"""
校验 task_id只允许字母、数字、下划线和连字符防止目录穿越攻击
"""
return re.match(r'^[\w\-]+$', task_id) is not None
@app.route('/api/system_info', methods=['GET'])
def get_system_info():
"""
接口 1获取当前设备(Windows)的负载情况和硬件信息(附带字段说明)
"""
try:
# 获取基础监控数据
cpu_usage = psutil.cpu_percent(interval=0.5)
cpu_cores_physical = psutil.cpu_count(logical=False)
cpu_cores_logical = psutil.cpu_count(logical=True)
cpu_freq = psutil.cpu_freq()
virtual_mem = psutil.virtual_memory()
disk_usage = psutil.disk_usage('C:\\')
# 构建返回数据,为每个返回值添加 desc 注释说明
info = {
# "os": {
# "system": platform.system(), # 操作系统名称 (如 Windows)
# "release": platform.release(), # 操作系统主要版本号 (如 10, 11)
# "version": platform.version(), # 操作系统的详细内部构建版本号
# "machine": platform.machine() # 系统架构 (如 AMD64 代表 64位)
# },
"os": f"{platform.system()} {platform.version()} ({platform.machine()})", # 综合描述操作系统的字符串
"hardware": {
"cpu": f"{get_cpu_model()} ({cpu_cores_physical}P/{cpu_cores_logical}L) {cpu_freq.max if cpu_freq else None}GHz", # CPU 型号和核心数的综合描述字符串
# "cpu_model": get_cpu_model(), # CPU 具体型号名称
# "cpu_cores_physical": cpu_cores_physical, # CPU 物理核心数 (真实核心)
# "cpu_cores_logical": cpu_cores_logical, # CPU 逻辑核心数 (包含超线程)
# "cpu_max_freq_mhz": cpu_freq.max if cpu_freq else None, # CPU 最大设计频率 (单位: MHz)
"ram_total_gb": round(virtual_mem.total / (1024 ** 3), 2), # 物理内存 (RAM) 总容量 (单位: GB)
"disk_c_total_gb": round(disk_usage.total / (1024 ** 3), 2) # 系统盘 (C盘) 总容量 (单位: GB)
},
"load": {
"cpu_usage_percent": cpu_usage, # 当前 CPU 总利用率 (单位: %)
"ram_usage_percent": virtual_mem.percent, # 当前物理内存使用率 (单位: %)
"ram_available_gb": round(virtual_mem.available / (1024 ** 3), 2), # 当前可用的物理内存大小 (单位: GB)
"disk_c_usage_percent": disk_usage.percent, # 系统盘 (C盘) 存储空间使用率 (单位: %)
"disk_c_free_gb": round(disk_usage.free / (1024 ** 3), 2) # 系统盘 (C盘) 剩余可用空间 (单位: GB)
}
}
return jsonify({"code": 200, "status": "success", "data": info}), 200
except Exception as e:
return jsonify({"code": 500, "status": "error", "message": str(e)}), 500
@app.route('/api/run_exe', methods=['POST'])
def run_exe():
"""
接口 2非阻塞地运行一个 exe 程序
"""
data = request.get_json()
if not data or 'exe_path' not in data:
return jsonify({"status": "error", "message": "Missing 'exe_path' in request body."}), 400
exe_path = data['exe_path']
args = data.get('args', [])
if not os.path.exists(exe_path):
return jsonify({"status": "error", "message": f"File not found: {exe_path}"}), 404
try:
command = [exe_path] + args
process = subprocess.Popen(
command,
creationflags=DETACHED_PROCESS,
close_fds=True,
shell=False
)
return jsonify({
"code": 200,
"status": "success",
"message": "Executable started successfully.",
"pid": process.pid
}), 200
except Exception as e:
return jsonify({"code": 500, "status": "error", "message": str(e)}), 500
@app.route('/api/upload', methods=['POST'])
def upload_files():
"""
接口 3上传文件
接收 form-data 格式数据:
- task_id: 任务ID (字符串)
- file: 上传的文件 (可以同时上传多个文件)
"""
# 1. 获取并校验 task_id
task_id = request.form.get('task_id')
if not task_id:
return jsonify({"status": "error", "message": "Missing 'task_id' in form data."}), 400
if not is_valid_task_id(task_id):
return jsonify({"status": "error", "message": "Invalid 'task_id'. Only alphanumeric, dash, and underscore are allowed."}), 400
# 2. 准备目录
# os.path.join 会自动根据当前系统(Windows)处理路径分隔符
data_dir = os.path.join("data", task_id)
lib_dir = "lib/module"
# 自动创建不存在的目录 (exist_ok=True 避免目录已存在时报错)
os.makedirs(data_dir, exist_ok=True)
os.makedirs(lib_dir, exist_ok=True)
saved_files = []
# 3. 遍历并保存文件
# request.files 是一个字典,通过 .getlist('file') 获取所有上传的文件(假设表单键名为 file
# 为了兼容不同的前端传法,我们遍历 request.files 中的所有文件对象
for key in request.files:
for file in request.files.getlist(key):
if file and file.filename:
# 获取文件后缀,转换为小写
ext = os.path.splitext(file.filename)[1].lower()
if ext == '.txt':
# .txt 储存至 data/{task_id}/goods.txt
save_path = os.path.join(data_dir, "goods.txt")
file.save(save_path)
saved_files.append({"original_name": file.filename, "saved_path": save_path})
elif ext == '.py':
# .py 储存至 lib/{task_id}.py
save_path = os.path.join(lib_dir, f"{task_id}.py")
file.save(save_path)
saved_files.append({"original_name": file.filename, "saved_path": save_path})
else:
# 忽略不支持的文件格式
pass
if not saved_files:
return jsonify({"status": "error", "message": "No valid .txt or .py files were uploaded."}), 400
return jsonify({
"status": "success",
"message": "Files uploaded successfully.",
"data": saved_files
}), 200
@app.route('/api/get_status', methods=['GET'])
def get_status():
params = request.args
task_id = params.get('task_id')
progress = 0
status = 0
if os.path.exists(f"data/{task_id}/progress"):
with open(f"data/{task_id}/progress", "r", encoding="utf-8") as f:
progress = f.read().strip()
if os.path.exists(f"data/{task_id}/status"):
with open(f"data/{task_id}/status", "r", encoding="utf-8") as f:
status = f.read().strip()
return jsonify({
"status": "success",
"message": f"Task '{task_id}' is ready.",
"data": {
"task_id": task_id,
"progress": progress,
"status": status
}
}), 200
if __name__ == '__main__':
# 解决 jsonify 返回中文字符时变成 Unicode 编码的问题
app.config['JSON_AS_ASCII'] = False
app.run(host='127.0.0.1', port=5000, debug=True)