window下Nginx+php不支持并发,导致curl请求卡死

本文描述了在本地配置多个域名时,使用PHP的curl请求出现卡死的情况及原因,详细解释了在Windows环境下配置nginx+php环境中不支持并发的问题,并提供了通过配置不同端口来解决该问题的方法。

1、问题描述:

在本地配置多个域名时,使用php的curl请求出现卡死情况。具体为:一个域名发起请求刚刚超时后,另外一个域名接收到响应,出现卡死情况。curl超时时间为:curl_setopt($http, CURLOPT_TIMEOUT, 10);

发送请求的域名:[2019-08-01 15:24:21] getUserId
接收请求的域名:[2019-08-01 15:24:31] lumen.INFO:[api接口getUserId]

由上可以看出,刚刚好一个请求结束,在处理其他请求。

2、具体原因:在window环境下配置的nginx+php环境时,由于不支持并发,也就是,当本地配置了多个域名,并且同时指向你本地服务请求的时候,就不支持了。

3、解决方案:

我这里有两个服务,所以分别配置成不同的端口号如,然后保存重启 nginx
fastcgi_pass = 127.0.0.1:9000
fastcgi_pass  = 127.0.0.1:9001

你的php目录/php-cgi.exe -b 127.0.0.1:9000 -c 你的php目录/php.ini
你的php目录/php-cgi.exe -b 127.0.0.1:9001 -c 你的php目录/php.ini

这样就完美解决了。

#!/bin/env python # 必须写在第一行 # -*- coding: utf-8 -*- ################################################# # LOAD_MODE__ import os import re import sys from PyQt5.QtCore import Qt, QRegExp, QEvent, QThread, QTimer, QObject, pyqtSignal from PyQt5.QtWidgets import QWidget, QApplication, QMessageBox, QDialog # from ui import param from PyQt5.QtGui import QRegExpValidator from messageBox import messageBox from collections import defaultdict from py39COM import Gateway, InCAM from py39Tools import TableWidget from ICO import ICO from ICNET import ICNET from EqHelper import EqHelper # from fastmcp import Api, Resource # import tkinter as tk import requests import threading from Ui_interface import Ui_Dialog as inerfaceui from Ui_job_input import Ui_ChildWindow as job_input_ui from Ui_get_layer import Ui_ChildWindow2 as get_layer_ui from Ui_draw_circle import Ui_ChildWindow3 as draw_circle_ui import subprocess import psutil import time import netifaces import json from pypinyin import lazy_pinyin from fastapi import FastAPI import uvicorn # from img import apprcc_rc # from pprint import pprint # from sublib import # print('apprcc_rc.rcc_version : %s' % apprcc_rc.rcc_version) #第一步:在终端运行python interface.py #第二步:点击按钮确认GUI功能 #第三步:保持 interface.py 运行状态,在另一个终端中测试:curl http://127.0.0.1:8000,会返回:{"message": "Hello, World!"}类似信息 ##测试调用curl -X POST http://127.0.0.1:8000/api/open_incam,返回json信息。对其他接口同样进行测试,如:curl -X POST http://127.0.0.1:8000/api/open_job # #第四步:运行该代码,在浏览器输入:http://127.0.0.1:8000/docs # MODE_END__ def get_pinyin(chinese): if not chinese: return '' str = chinese new_str = str[0:] # print(new_str) new_str1 =''.join(lazy_pinyin(new_str)) # print(new_str1) return new_str1 def GetUserInfo(mode=None): ''' 从指定URL获取主机信息(JSON格式) 返回值:成功返回JSON对象,失败返回None ''' getHtml = 'http://sheet.scc.com/gethosts/format/json' resp = requests.get(getHtml, timeout=60) if resp.status_code == 200: json_obj = json.loads(resp.content.decode()) return json_obj else: return None def get_local_ip(): """ 获取本机 IP 地址,优先返回 10.x.x.x,其次 192.168.x.x """ ip_list = [] for interface in netifaces.interfaces(): addrs = netifaces.ifaddresses(interface) if netifaces.AF_INET in addrs: for addr_info in addrs[netifaces.AF_INET]: ip = addr_info['addr'] if ip.startswith('10.'): return ip # 立即返回第一个10.x地址 ip_list.append(ip) #找到的是192开头的ip地址则转换成10开头的ip if ip.startswith('192.168.200.'): parts = ip.split('.') new_ip = f'10.61.26.{parts[3]}' return new_ip ip_list.append(ip) # 如果没有10.x地址,返回第一个192.x地址 for ip in ip_list: if ip.startswith('192.'): return ip # 都没有则返回第一个找到的IP return ip_list[0] if ip_list else "127.0.0.1" def get_username(): try: local_ip = get_local_ip() # print(f"Local IP address: {local_ip}") user_info = GetUserInfo(local_ip) if not user_info or not isinstance(user_info, list): print("用户信息为空或格式错误。") return None res= [x for x in user_info if x['ip_address'] == local_ip] if len(res) == 0: return None username = res[0]['user'] # print(f"准备调用 SubCenterGetUserInfo,传入 username: {username}") englishname = get_pinyin(username) return englishname except Exception as e: print(f"获取用户信息失败: {e}") return None #获取当前打开的窗口的进程 def get_window_processes(): try: # 使用wmctl获取窗口列表 output = subprocess.check_output(['wmctrl', '-lp']).decode('utf-8') window_pids = [] for line in output.splitlines(): parts = line.split() if len(parts) >= 3: window_pids.append(int(parts[2])) return list(set(window_pids)) # 去重 except FileNotFoundError: print("请先安装wmctrl: sudo apt install wmctrl") return [] # 判断incam是否在运行 def is_process_running(): """ 判断InCAMPro进程是否正在运行 返回: bool: True表示进程正在运行,False表示未运行 """ window_pids = get_window_processes() if not window_pids: print("无法获取窗口进程信息") return False for proc in psutil.process_iter(['pid', 'name']): if proc.info['pid'] in window_pids and proc.info['name'].lower() == 'incampro': return True return False # 获取incam的pid def get_incampro_pid(): """ 获取InCAMPro进程的PID 返回: int/None: 返回进程PID(如果找到),否则返回None """ window_pids = get_window_processes() if not window_pids: print("无法获取窗口进程信息") return None for proc in psutil.process_iter(['pid', 'name']): if proc.info['pid'] in window_pids and proc.info['name'].lower() == 'incampro': return proc.info['pid'] # 返回找到的PID return None # 没有找到则返回None # FUNCTION__ class Child_window(QDialog): """ 料号输入子窗口 """ def __init__(self, parent=None): super().__init__(parent) self.child_ui1 = job_input_ui() self.child_ui1.setupUi(self) # 添加确认按钮信号连接 self.child_ui1.confirm_btn.clicked.connect(self.confirm_input) self.material_number = None # 存储输入的料号 def get_material_number(self): return self.child_ui1.input_job.text().strip() def confirm_input(self): """验证输入并关闭窗口""" if self.get_material_number(): self.accept() # 关闭对话框并返回QDialog.Accepted else: QMessageBox.warning(self, "错误", "请输入有效料号") class ChildWindow2(QDialog): """层名选择对话框""" def __init__(self, layer_list, parent=None): super().__init__(parent) self.child_ui2 = get_layer_ui() self.child_ui2.setupUi(self, layer_list) # 初始化下拉框 self.child_ui2.get_layer.addItems(layer_list) if layer_list: self.child_ui2.get_layer.setCurrentIndex(0) # 连接信号 self.child_ui2.btn_ok.clicked.connect(self.accept) self.child_ui2.btn_ok.clicked.connect(self.store_selection) self.selected_layer = None def store_selection(self): """存储选择的层名""" self.selected_layer = self.child_ui2.get_layer.currentText() def get_selected_layer(self): """获取选择的层名""" return self.selected_layer class ChildWindow3(QDialog): """ 画圆子窗口 """ def __init__(self, parent=None): super().__init__(parent) self.child_ui3 = draw_circle_ui() self.child_ui3.setupUi(self) # 添加确认按钮信号连接 self.child_ui3.sure_btn.clicked.connect(self.accept) self.value = None def get_circle_data(self): """获取输入的圆参数""" try: x = float(self.child_ui3.x_input.text()) y = float(self.child_ui3.y_input.text()) sym = self.child_ui3.sym_input.text() # 保持为字符串 if not sym: # 检查是否为空 return None return (x, y, sym) except ValueError: return None class ComTestQT(QWidget): """ PYQT界面实现对应按钮操作incam动作 """ def __init__(self): # def __init__(self, pid=None, JOB=None, STEP=None): super(ComTestQT, self).__init__() self.main_ui = inerfaceui() self.main_ui.setupUi(self) self.setWindowFlags(Qt.WindowStaysOnTopHint) self.JOB = os.environ.get('JOB', None) # print(f"JOB: {self.JOB}") # print("111111111111111111111111111111") self.STEP = os.environ.get('STEP', None) # --启动pycharm.sh时,里面有export INCAM_DEBUG=yes设置此环境变量 INCAM_DEBUG = os.getenv('INCAM_DEBUG', None) # 接口定义 # if INCAM_DEBUG == 'yes': # 通过genesis gateway命令连结pid进行会话,不用在genesis环境下运行,直接用gateway的方式,可在pycharm环境下直接debug self.incam = Gateway() # 方法genesis_connect通过查询log-genesis文件获取的料号名 # self.JOB = self.incam.job_name # self.STEP = self.incam.step_name # self.pid = self.incam.pid # else: # self.incam = InCAM() # self.pid = os.getpid() self.ico = ICO(incam=self.incam) # self.icNet = ICNET(incam=self.incam) # # self.eqHelper = EqHelper(self.incam, self.JOB, self.STEP) # print(f"没有debug:{self.JOB}") # self.jobName = self.ico.SimplifyJobName(jobName=self.JOB) # self.dbSite = self.ico.GetDBSite(JOB=self.JOB) # self.SITE = self.ico.GetSite(JOB=self.JOB) # layerMatrix = self.ico.GetLayerMatrix() # self.incam.COM("get_user_group") # self.uGroup = self.incam.COMANS # self.incam.COM('get_user_name') # self.userName = self.incam.COMANS # self.incam.gateway_connect() self.child_window = None self.child_window2 = None self.child_window3 = None self.slot_func() # 全局引用自己,供 FastAPI 使用 global qt_app_instance qt_app_instance = self def slot_func(self): """ 统一定义槽函数 :return: """ self.main_ui.open_incam.clicked.connect(self.start_incam) self.main_ui.open_job.clicked.connect(self.show_child_window) self.main_ui.get_layername.clicked.connect(self.show_layer_dialog) self.main_ui.draw_circle.clicked.connect(self.show_draw_circle_dialog) # 连接信号到实际方法 api_signals.open_incam_signal.connect(self.start_incam) api_signals.open_job_signal.connect(self.show_child_window) api_signals.get_layername_signal.connect(self.show_layer_dialog) api_signals.draw_circle_signal.connect(self.show_draw_circle_dialog) ########运行incam程序###### def start_incam(self): """ 尝试通过终端命令启动 incam 或 pro """ if is_process_running(): print("已有InCAMPro进程正在运行,无法再次启动") # messageBox("Incam 已在运行") return else: try: self.file_path = "/home/genesis/.incam/login" self.file_path_enc = "/home/genesis/.incam/login.enc" account = get_username()#"songwenhua"#self.inputNewAccount.text().strip() password = "scc123456"#self.inputNewPassword.text().strip() if os.path.exists(self.file_path_enc): os.remove(self.file_path_enc) with open(self.file_path, "w", encoding="utf-8") as f: f.writelines('\n'.join((account, password))) home = os.path.expanduser("~") # 获取家目录路径 # subprocess.Popen(['gnome-terminal', '--working-directory', home, '--', 'bash', '-ic', 'incam; exec bash']) subprocess.Popen(['gnome-terminal', '--working-directory', home, '--', 'bash', '-ic', 'pro; exec bash']) print("Incam 已作为后台进程启动") # while not is_process_running(): time.sleep(10) if os.path.exists(self.file_path): os.remove(self.file_path) except FileNotFoundError: print("'gnome-terminal' 未找到,请安装它:sudo apt install gnome-terminal") except Exception as e: print("启动 incam 时发生异常:", str(e)) def show_child_window(self): """显示子窗口并等待输入""" # 重置子窗口状态 if self.child_window is None: self.child_window = Child_window(self) self.child_window.child_ui1.input_job.clear() if self.child_window.exec_() == QDialog.Accepted: short_name = self.child_window.get_material_number() self.open_JOB(short_name) def ensure_incam_ready(self): """确保 incam 和 ico 都已准备就绪""" # if self.incam is None: self.incam = Gateway() self.ico = ICO(incam=self.incam) # else: # pid = get_incampro_pid() # print("22222222222222222222222") if not hasattr(self.incam, 'pid'): print("44444") # if pid is None: if not self.incam.pid: print("pid is None") return False # if self.ico is None: # self.ico = ICO(incam=self.incam) print("kkkkkkkkk") return True def open_JOB(self, short_name=None): """ 根据料号简称打开对应的全称JOB 参数: material_number (str): 要打开的料号,如果为None则使用当前JOB 返回: bool: True表示打开成功,False表示失败 """ try: # 1. 检查InCAMPro进程 pid = get_incampro_pid() if pid is None: QMessageBox.warning(self, "警告", "请先启动InCAMPro") return False # 初始化Gateway连接 # 等待 Gateway 真正准备好 print(f"pid11: {pid}") if not self.ensure_incam_ready(): print(f"pid: {pid}") QMessageBox.warning(self, "警告", "Gateway连接失败") return False # ---------------------------------------------- # print("111111111111111111111111111111111111111") # print(f"pid: {pid}") # # if not self.incam.pid: # # self.incam.connect(pid) # print("self.incam.pid:", self.incam.pid) # print("nnnnn: ",hasattr(self.incam, 'pid')) # # self.incam.pid = get_incampro_pid() # if not hasattr(self.incam, 'pid') or not self.incam.pid: # QMessageBox.warning(self, "警告", "Gateway连接失败") # return False # 获取JOB列表并验证料号 job_list = self.ico.GetJOBlist() if not job_list: QMessageBox.warning(self, "警告", "没有可用的JOB列表") return False # 查找匹配的全称料号 full_name = None for JOB in job_list: simplified = self.ico.SimplifyJobName(jobName=JOB) if simplified == short_name: full_name = JOB break if not full_name: QMessageBox.warning(self, "错误", f"未找到匹配全称的料号: {short_name}") return False # 打开全称料号 self.incam.COM(f'open_job,job={full_name},open_win=yes') self.JOB = full_name if self.ico.isJobOpen(full_name): QMessageBox.information(self, "成功", f"已打开料号: {full_name}") # return True else: QMessageBox.warning(self, "错误", f"打开料号失败: {full_name}") # return False except Exception as e: QMessageBox.critical(self, "异常", f"处理料号时出错: {str(e)}") # return False def show_layer_dialog(self): """显示层名选择对话框""" try: # 验证前置条件 # jobName=self.JOB print(f"JOB value before check: {self.JOB}")#已经得到了准确的job名称 if not self.check_preconditions(): return # 获取层名列表 # print(f"JOB v: {self.JOB}") layer_list = self.ico.GetLayerList() if not layer_list: QMessageBox.warning(self, "警告", "没有可用的Layer列表") return # 创建并显示对话框 dialog2 = ChildWindow2(layer_list, self) if dialog2.exec_() == QDialog.Accepted: selected_layer = dialog2.get_selected_layer() self.handle_selected_layer(selected_layer) except Exception as e: QMessageBox.critical(self, "错误", f"操作失败: {str(e)}") def check_preconditions(self): """检查必要前提条件""" if get_incampro_pid() is None: QMessageBox.warning(self, "警告", "请先启动InCAMPro") return False # 尝试获取当前激活的JOB try: if self.incam.job_name: self.JOB = self.incam.job_name # print(f"从incam获取的JOB: {self.JOB}") QMessageBox.information(self, "成功", f"从incam获取的JOB: {self.JOB}") except Exception as e: # print(f"获取incam job_name失败: {str(e)}") QMessageBox.warning(f"获取incam job_name失败: {str(e)}") # 检查JOB是否有效 if not self.JOB: QMessageBox.warning(self, "警告", "请打开JOB后再操作") return False # 尝试获取layer列表验证JOB有效性 try: self.ico.JOB= self.JOB layer_list = self.ico.GetLayerList() # print(f"获取到的层列表: {layer_list}") if not layer_list: QMessageBox.warning(self, "警告", "获取到的层列表为空") return False return True except Exception as e: QMessageBox.warning(self, "警告", f"获取层列表失败: {str(e)}") return False def handle_selected_layer(self, layer_name): """处理选择的层名""" print(f"选择的层名: {layer_name}") self.LAYER = layer_name QMessageBox.information(self, "成功", f"已选择层名: {layer_name}") def show_draw_circle_dialog(self): """显示画圆参数对话框""" if not self.check_preconditions(): return if not hasattr(self, 'LAYER') or not self.LAYER: QMessageBox.warning(self, "警告", "请先选择层") return dialog3 = ChildWindow3(self) if dialog3.exec_() == QDialog.Accepted: params = dialog3.get_circle_data() # print(f"获取的参数: {params}") if params: self.draw_circle_on_layer(*params) else: QMessageBox.warning(self, "警告", "请输入有效的数字参数") def draw_circle_on_layer(self, center_x, center_y, symbol): """在指定层上画圆""" try: self.ico.ClearAll() layer_name=self.LAYER self.ico.DispWork(layer=layer_name) self.ico.AddPad( x = center_x, y = center_y, sym = symbol ) self.ico.ClearLayer() QMessageBox.information(self, "成功", f"已在层 {self.LAYER} 上绘制圆形") except Exception as e: QMessageBox.critical(self, "错误", f"画圆时出错: {str(e)}") ########## FastAPI 部分 ########### class ApiSignalEmitter(QObject): open_incam_signal = pyqtSignal() open_job_signal = pyqtSignal() get_layername_signal = pyqtSignal() draw_circle_signal = pyqtSignal() api_signals = ApiSignalEmitter() # app = FastAPI(title="InCAM Remote Control API", description="通过HTTP控制InCAM自动化任务") app = FastAPI(title="InCAM 控制 API", version="1.0") @app.post("/api/open_incam") async def api_open_incam(): # qt_app_instance.start_incam() # 不再直接调用 myapp.open_job() # 而是发送信号给主线程处理 api_signals.open_incam_signal.emit() return {"status": "success", "action": "open_incam", "message": "InCAM已启动"} @app.post("/api/open_job") async def api_open_job(): # qt_app_instance.show_child_window() api_signals.open_job_signal.emit() return {"status": "success", "action": "open_job", "message": "Job窗口已打开"} @app.post("/api/get_layername") async def api_get_layername(): # qt_app_instance.show_layer_dialog() api_signals.get_layername_signal.emit() return {"status": "success", "action": "get_layername", "message": "层名获取成功"} @app.post("/api/draw_circle") async def api_draw_circle(): # qt_app_instance.show_draw_circle_dialog() api_signals.draw_circle_signal.emit() return {"status": "success", "action": "draw_circle", "message": "圆圈绘制完成"} @app.get("/") async def root(): return { "message": "InCAM 控制 API 运行中", "endpoints": [ "/api/open_incam", "/api/open_job", "/api/get_layername", "/api/draw_circle" ], "method": "POST" } # @app.get("/") # async def root(): # if qt_app_instance is None: # return {"status": "error", "message": "Qt app not initialized"} # return { # "status": "running", # "service": "InCAM Remote Control API", # "api_docs": "/docs", # "available_endpoints": [ # "/api/open_incam", # "/api/open_job", # "/api/get_layername", # "/api/draw_circle" # ] # } def run_fastapi(): """在后台线程运行 FastAPI""" uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info") # def run_fastapi(): # """在后台线程运行 FastAPI""" # config = uvicorn.Config( # app=app, # host="127.0.0.1", # port=8000, # log_level="info", # lifespan="on" # 显式开启 lifespan 支持 # ) # server = uvicorn.Server(config) # server.run() qt_app_instance = None if __name__ == '__main__': qapp = QApplication(sys.argv) myapp = ComTestQT() myapp.show() print("PyQt5 GUI 已显示,将在1秒后启动 FastAPI...") # 在子线程中启动 FastAPI QTimer.singleShot( 1000, lambda: threading.Thread(target=run_fastapi, daemon=True).start() ) sys.exit(qapp.exec_()) # EXIT__ 以上是play.py的全部代码,并没有内联Uvicorn 的启动逻辑。
最新发布
10-29
非常感谢你提供完整的 `interface.py`(你称为 `play.py`)代码。现在我们可以**完全理解你的系统架构**: --- ## 🔍 当前架构分析 你正在运行一个 **PyQt5 GUI 应用**,它同时集成了: - 通过 `Gateway()` 连接 InCAMPro 的 COM 接口 - 使用 `FastAPI + Uvicorn` 在子线程中启动了一个 HTTP API 服务 - 所有 FastAPI 路由通过 **Qt 信号机制**与主线程的 GUI 操作通信(避免跨线程异常) ### ✅ 启动方式 ```bash python interface.py ``` ### ✅ 功能流程 1. GUI 显示 2. 1秒后,在后台线程调用 `uvicorn.run(...)` 启动 FastAPI 3. 用户可通过浏览器访问 `http://127.0.0.1:8000/docs` 测试接口 4. API 触发 Qt 信号 → 主线程执行 GUI 自动化操作(如打开料号、画圆等) --- ## 🚨 当前问题:这不是标准 Web 服务部署 你现在是将 FastAPI 嵌入到 PyQt 应用中,以多线程方式运行。这在开发阶段可以工作,但: > ❌ **不能直接用于生产环境** > > - Uvicorn 运行在 Python 子线程中,不稳定 > - 没有进程守护(崩溃即终止) > - Nginx 无法有效反向代理(除非暴露端口) > - 权限控制缺失 --- # ✅ 正确解决方案:重构为「分离式架构」 我们应将 **GUI 控制逻辑** 和 **Web API 服务** 分离,并确保整个系统能以 `incam` 用户身份稳定运行。 --- ## ✅ 最佳实践方案:双进程 + 信号通信(推荐) 我们将结构拆分为两个独立模块: | 模块 | 文件 | 说明 | |------|------|------| | **GUI 控制器** | `gui_controller.py` | 包含 `ComTestQT` 类,负责操作 InCAM | | **FastAPI 服务** | `api_server.py` | 提供 REST API,转发请求给 GUI 进程 | 并通过 **共享内存 / 文件锁 / Redis / ZeroMQ / DBus** 实现进程间通信(IPC)。这里使用最轻量的 **Unix Domain Socket 或文件标记法** 示例。 但由于复杂度高,我们先采用更简单的方案: --- ## ✅ 推荐方案:保留单进程结构,但用 Gunicorn 替代内联 Uvicorn ### ✅ 目标: - 保持现有功能完整 - 将 `uvicorn.run()` 替换为由 **Gunicorn 管理的 ASGI 应用** - 仍允许 PyQt GUI 弹出 - 部署为 systemd 服务,运行用户改为 `incam` - 使用 Nginx 反向代理 --- ## ✅ 第一步:修改入口结构(让 FastAPI 成为标准 ASGI App) 你需要把当前 `if __name__ == '__main__':` 中的内容拆解出来。 ### ✅ 修改后的主文件结构(仍叫 `interface.py`) ```python #!/bin/env python # -*- coding: utf-8 -*- ################################################# # LOAD_MODE__ import os import sys from PyQt5.QtCore import Qt, QThread, QTimer, QObject, pyqtSignal from PyQt5.QtWidgets import QWidget, QApplication, QMessageBox, QDialog from PyQt5.QtGui import QRegExpValidator from collections import defaultdict from py39COM import Gateway, InCAM from py39Tools import TableWidget from ICO import ICO from ICNET import ICNET from EqHelper import EqHelper import requests import threading import subprocess import psutil import time import netifaces import json from pypinyin import lazy_pinyin from fastapi import FastAPI import uvicorn # ... 其他导入不变 ... # === FastAPI 应用对象必须定义在顶层 === app = FastAPI(title="InCAM 控制 API", version="1.0") # 全局变量存储 Qt 主窗口实例 qt_app_instance = None qapp = None # 全局 QApplication class ApiSignalEmitter(QObject): open_incam_signal = pyqtSignal() open_job_signal = pyqtSignal() get_layername_signal = pyqtSignal() draw_circle_signal = pyqtSignal() api_signals = ApiSignalEmitter() # ============================== # GUI Classes (Child_window, ComTestQT) # ============================== class ComTestQT(QWidget): def __init__(self): super(ComTestQT, self).__init__() # ... 原始初始化逻辑 ... self.incam = Gateway() self.ico = ICO(incam=self.incam) self.child_window = None self.child_window2 = None self.child_window3 = None self.slot_func() global qt_app_instance qt_app_instance = self def slot_func(self): api_signals.open_incam_signal.connect(self.start_incam) api_signals.open_job_signal.connect(self.show_child_window) api_signals.get_layername_signal.connect(self.show_layer_dialog) api_signals.draw_circle_signal.connect(self.show_draw_circle_dialog) # --- 各个功能函数保持不变 --- def start_incam(self): # ...原内容... pass def show_child_window(self): # ...原内容... pass def show_layer_dialog(self): # ...原内容... pass def show_draw_circle_dialog(self): # ...原内容... pass # ============================== # FastAPI Routes # ============================== @app.post("/api/open_incam") async def api_open_incam(): if qt_app_instance: api_signals.open_incam_signal.emit() return {"status": "success", "action": "open_incam"} return {"status": "error", "message": "Qt app not ready"} @app.post("/api/open_job") async def api_open_job(): if qt_app_instance: api_signals.open_job_signal.emit() return {"status": "success", "action": "open_job"} return {"status": "error", "message": "Qt app not ready"} @app.post("/api/get_layername") async def api_get_layername(): api_signals.get_layername_signal.emit() return {"status": "success", "action": "get_layername"} @app.post("/api/draw_circle") async def api_draw_circle(): api_signals.draw_circle_signal.emit() return {"status": "success", "action": "draw_circle"} @app.get("/") async def root(): return { "message": "InCAM 控制 API 运行中", "endpoints": ["/api/open_incam", "/api/open_job", "/api/get_layername", "/api/draw_circle"], "method": "POST" } # ============================== # 启动函数(供 Gunicorn 调用) # ============================== def run_gui_and_api(): """ 这是 Gunicorn 调用的入口函数 它会创建 QApplication 和 GUI 实例,并启动 Uvicorn 在后台线程 """ global qapp, qt_app_instance if QApplication.instance() is None: qapp = QApplication(sys.argv) # 创建 GUI 实例(不显示) if qt_app_instance is None: qt_app_instance = ComTestQT() qt_app_instance.hide() # 可选择隐藏窗口 print("GUI 已创建,即将在后台启动 FastAPI...") # 启动 FastAPI 在子线程 thread = threading.Thread(target=lambda: uvicorn.run(app, host="127.0.0.1", port=8000), daemon=True) thread.start() # 进入 Qt 事件循环(关键!否则信号不会触发) qapp.exec_() # ============================== # 本地调试入口 # ============================== if __name__ == '__main__': run_gui_and_api() ``` --- ## ✅ 第二步:使用 Gunicorn 启动这个应用 你现在可以让 Gunicorn 调用 `run_gui_and_api` 函数来运行整个程序。 ### 1. 安装依赖 ```bash pip install gunicorn uvicorn[standard] PyQT5 ``` ### 2. 启动命令(测试) ```bash gunicorn -b 127.0.0.1:8000 -w 1 -k sync interface:run_gui_and_api --timeout 300 --max-requests 1000 ``` > ⚠️ 注意: > - `-w 1`:只能使用 1 个工作进程(因为 GUI 是单实例) > - `-k sync`:使用同步 worker(Uvicorn 不需要,因为我们自己启了线程) > - `--timeout` 加大防止长时间阻塞被杀掉 > - `interface:run_gui_and_api`:模块名:可调用函数 --- ## ✅ 第三步:配置 systemd 服务(以 incam 用户运行) ```bash sudo nano /etc/systemd/system/incam-control.service ``` ```ini [Unit] Description=InCAM Control Panel with FastAPI After=network.target [Service] User=incam Group=incam WorkingDirectory=/home/incam/project ExecStart=/home/incam/.venv/bin/gunicorn -b 127.0.0.1:8000 -w 1 -k sync interface:run_gui_and_api --timeout 300 --max-requests 1000 Restart=always Environment=DISPLAY=:0 Environment=XAUTHORITY=/home/incam/.Xauthority Environment=PYTHONPATH=/home/incam/project [Install] WantedBy=multi-user.target ``` > 💡 如果你在无头服务器上运行且不需要 GUI 显示,请考虑移除 GUI 部分或使用虚拟显示(Xvfb) 启用服务: ```bash sudo systemctl enable incam-control.service sudo systemctl start incam-control.service sudo systemctl status incam-control.service ``` --- ## ✅ 第四步:配置 Nginx 反向代理(并切换用户为 `incam`) ### 1. 修改 Nginx 用户 ```bash sudo nano /etc/nginx/nginx.conf ``` ```nginx user incam; ``` ### 2. 创建站点配置 ```bash sudo nano /etc/nginx/sites-available/incam-api ``` ```nginx server { listen 80; server_name _; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; } } ``` 启用: ```bash sudo ln -s /etc/nginx/sites-available/incam-api /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl restart nginx ``` --- ## ✅ 访问方式 现在你可以通过以下方式访问: - 浏览器打开:`http://127.0.0.1/docs` → 查看 API 文档 - 外部访问:`http://your-ip/docs` - 测试接口: ```bash curl -X POST http://127.0.0.1/api/open_incam ``` --- ## ✅ 补充建议 | 问题 | 建议 | |------|------| | GUI 是否必须弹窗? | 若仅需自动化操作,可考虑剥离 GUI,只保留 `Gateway()` 调用 | | 是否支持并发? | 不支持,因 InCAM 是单实例软件,所有操作必须串行 | | 如何保证稳定性? | 添加心跳检测、重启机制、日志监控 | | 如何安全? | 添加 Basic Auth 或 JWT 认证 | --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值