1737 lines
79 KiB
Python
1737 lines
79 KiB
Python
"""
|
||
订单状态处理器
|
||
专门处理订单状态更新逻辑,用于更新订单管理中的状态
|
||
"""
|
||
|
||
import re
|
||
import json
|
||
import time
|
||
import uuid
|
||
import threading
|
||
import asyncio
|
||
from loguru import logger
|
||
from typing import Optional, Dict, Any
|
||
|
||
# ==================== 订单状态处理器配置 ====================
|
||
# 订单状态处理器配置
|
||
ORDER_STATUS_HANDLER_CONFIG = {
|
||
'use_pending_queue': True, # 是否使用待处理队列
|
||
'strict_validation': True, # 是否启用严格的状态转换验证
|
||
'log_level': 'info', # 日志级别 (debug/info/warning/error)
|
||
'max_pending_age_hours': 24, # 待处理更新的最大保留时间(小时)
|
||
'enable_status_logging': True, # 是否启用详细的状态变更日志
|
||
'pending_terminal_bind_max_gap_seconds': 90, # 旧终态待处理消息允许绑定到后续订单提取的最大间隔
|
||
'terminal_resolution_search_minutes': 30, # 无订单ID时按匹配键回填旧订单的搜索窗口
|
||
}
|
||
|
||
|
||
class OrderStatusHandler:
|
||
"""订单状态处理器"""
|
||
|
||
# 状态转换规则常量
|
||
# 规则说明:
|
||
# 1. 已付款的订单和已完成的订单不能回退到处理中
|
||
# 2. 已付款的订单和已完成的订单可以设置为已关闭(因为会出现退款)
|
||
# 3. 退款中的订单或者退货中的订单设置为退款中
|
||
# 4. 退款中的订单可以设置为已完成(因为买家可能取消退款)
|
||
# 5. 只有退款完成才设置为已关闭
|
||
VALID_TRANSITIONS = {
|
||
'processing': ['pending_ship', 'partial_success', 'partial_pending_finalize', 'shipped', 'completed', 'cancelled'],
|
||
'pending_ship': ['partial_success', 'partial_pending_finalize', 'shipped', 'completed', 'cancelled', 'refunding'], # 已付款,可以退款
|
||
'partial_success': ['partial_pending_finalize', 'shipped', 'completed', 'cancelled', 'refunding'], # 部分已完成,可继续推进或退款
|
||
'partial_pending_finalize': ['partial_success', 'shipped', 'completed', 'cancelled', 'refunding'], # 部分待收尾,可继续收尾后进入下一状态
|
||
'shipped': ['completed', 'cancelled', 'refunding'], # 已发货,可以退款
|
||
'completed': ['cancelled', 'refunding'], # 已完成,可以退款
|
||
'refunding': ['completed', 'cancelled', 'refund_cancelled'], # 退款中,可以完成(取消退款)、关闭(退款完成)或撤销
|
||
'refund_cancelled': [], # 退款撤销(临时状态,会立即回退到上一次状态)
|
||
'cancelled': [] # 已关闭,不能转换到其他状态
|
||
}
|
||
|
||
def __init__(self):
|
||
"""初始化订单状态处理器"""
|
||
# 加载配置
|
||
self.config = ORDER_STATUS_HANDLER_CONFIG
|
||
|
||
self.status_mapping = {
|
||
'processing': '处理中', # 初始状态/基本信息阶段
|
||
'pending_ship': '待发货', # 已付款,等待发货
|
||
'partial_success': '部分发货', # 多数量发货部分完成
|
||
'partial_pending_finalize': '部分待收尾', # 多数量发货部分消息已发,待补收尾
|
||
'shipped': '已发货', # 发货确认后
|
||
'completed': '已完成', # 交易完成
|
||
'refunding': '退款中', # 退款中/退货中
|
||
'refund_cancelled': '退款撤销', # 退款撤销(临时状态,会回退)
|
||
'cancelled': '已关闭', # 交易关闭
|
||
}
|
||
|
||
# 待处理的订单状态更新队列 {order_id: [update_info, ...]}
|
||
self.pending_updates = {}
|
||
# 待处理的系统消息队列(用于延迟处理){cookie_id: [message_info, ...]}
|
||
self._pending_system_messages = {}
|
||
# 待处理的红色提醒消息队列(用于延迟处理){cookie_id: [message_info, ...]}
|
||
self._pending_red_reminder_messages = {}
|
||
|
||
# 订单状态历史记录 {order_id: [status_history, ...]}
|
||
# 用于退款撤销时回退到上一次状态
|
||
self._order_status_history = {}
|
||
|
||
# 使用threading.RLock保护并发访问
|
||
# 注意:虽然在async环境中asyncio.Lock更理想,但本类的所有方法都是同步的
|
||
# 且被同步代码调用,因此保持使用threading.RLock是合适的
|
||
self._lock = threading.RLock()
|
||
|
||
# 设置日志级别
|
||
log_level = self.config.get('log_level', 'info')
|
||
logger.info(f"订单状态处理器初始化完成,配置: {self.config}")
|
||
|
||
def extract_order_id(self, message: dict) -> Optional[str]:
|
||
"""从消息中提取订单ID"""
|
||
try:
|
||
logger.info(f"🔍 完整消息结构: {message}")
|
||
|
||
for source, candidate_text in self._collect_order_id_candidate_texts(message, root='message'):
|
||
order_id = self._extract_order_id_from_candidate_text(candidate_text, source=source)
|
||
if order_id:
|
||
logger.info(f'🎯 最终提取到订单ID: {order_id} (source={source})')
|
||
return order_id
|
||
|
||
logger.error('❌ 未能从消息中提取到订单ID')
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.error(f"提取订单ID失败: {str(e)}")
|
||
return None
|
||
|
||
def _extract_order_id_from_update_key(self, raw_text: Any) -> Optional[str]:
|
||
normalized_text = str(raw_text or '').strip()
|
||
if not normalized_text:
|
||
return None
|
||
|
||
direct_match_found = False
|
||
direct_match = re.search(r'updateKey["\']?\s*[:=]\s*["\']([^"\']+)', normalized_text)
|
||
if direct_match:
|
||
direct_match_found = True
|
||
normalized_text = direct_match.group(1)
|
||
|
||
colon_parts = [part.strip().strip('"\'') for part in normalized_text.split(':')]
|
||
long_numeric_parts = [part for part in colon_parts if part.isdigit() and len(part) >= 16]
|
||
if long_numeric_parts:
|
||
return long_numeric_parts[0]
|
||
|
||
if direct_match_found:
|
||
generic_matches = re.findall(r'\d{16,}', normalized_text)
|
||
if generic_matches:
|
||
return generic_matches[0]
|
||
return None
|
||
|
||
def _extract_order_id_from_candidate_text(self, raw_text: Any, source: str = '') -> Optional[str]:
|
||
normalized_text = str(raw_text or '').strip()
|
||
if not normalized_text:
|
||
return None
|
||
|
||
patterns = [
|
||
r'orderId(?:=|:|%3[Dd]|\\u003[dD])\s*"?(\d{10,})',
|
||
r'bizOrderId["\']?\s*[:=]\s*"?(\d{10,})',
|
||
r'order[_-]?id["\']?\s*[:=]\s*"?(\d{10,})',
|
||
r'order[_-]?detail\?(?:[^\s#]*?&)?id=(\d{10,})',
|
||
r'order-detail\?(?:[^\s#]*?&)?orderId=(\d{10,})',
|
||
]
|
||
|
||
for pattern in patterns:
|
||
match = re.search(pattern, normalized_text)
|
||
if match:
|
||
return match.group(1)
|
||
|
||
source_lower = source.lower()
|
||
text_lower = normalized_text.lower()
|
||
if (
|
||
'updatekey' in source_lower
|
||
or 'updatekey' in text_lower
|
||
or ('trade_' in text_lower and ':' in normalized_text)
|
||
or ('buyer_confirm' in text_lower and ':' in normalized_text)
|
||
):
|
||
return self._extract_order_id_from_update_key(normalized_text)
|
||
|
||
return None
|
||
|
||
def _collect_order_id_candidate_texts(self, data: Any, root: str = 'message'):
|
||
candidates = []
|
||
seen = set()
|
||
|
||
def add_candidate(source: str, value: Any):
|
||
if value is None:
|
||
return
|
||
normalized_text = str(value).strip()
|
||
if not normalized_text:
|
||
return
|
||
dedupe_key = (source, normalized_text)
|
||
if dedupe_key in seen:
|
||
return
|
||
seen.add(dedupe_key)
|
||
candidates.append((source, normalized_text))
|
||
|
||
if normalized_text[:1] in {'{', '['}:
|
||
try:
|
||
parsed_value = json.loads(normalized_text)
|
||
except Exception:
|
||
return
|
||
walk_value(parsed_value, f'{source}.json')
|
||
|
||
def walk_value(value: Any, source: str):
|
||
if isinstance(value, dict):
|
||
for key, nested_value in value.items():
|
||
nested_source = f'{source}.{key}'
|
||
if isinstance(nested_value, (dict, list)):
|
||
walk_value(nested_value, nested_source)
|
||
else:
|
||
add_candidate(nested_source, nested_value)
|
||
elif isinstance(value, list):
|
||
for index, nested_value in enumerate(value[:20]):
|
||
walk_value(nested_value, f'{source}[{index}]')
|
||
else:
|
||
add_candidate(source, value)
|
||
|
||
walk_value(data, root)
|
||
return candidates
|
||
|
||
def _load_json_dict(self, raw_value: Any) -> Dict[str, Any]:
|
||
if isinstance(raw_value, dict):
|
||
return raw_value
|
||
if not isinstance(raw_value, str) or not raw_value.strip():
|
||
return {}
|
||
try:
|
||
parsed = json.loads(raw_value)
|
||
except Exception:
|
||
return {}
|
||
return parsed if isinstance(parsed, dict) else {}
|
||
|
||
def _get_status_priority(self, status: str) -> int:
|
||
priority_map = {
|
||
'processing': 10,
|
||
'pending_ship': 20,
|
||
'partial_success': 30,
|
||
'partial_pending_finalize': 30,
|
||
'shipped': 40,
|
||
'completed': 50,
|
||
'refunding': 60,
|
||
'refund_cancelled': 65,
|
||
'cancelled': 70,
|
||
}
|
||
return priority_map.get(status, 0)
|
||
|
||
def _extract_system_message_meta(self, message: dict) -> Dict[str, Any]:
|
||
message_1 = message.get('1', {}) if isinstance(message, dict) else {}
|
||
message_10 = message_1.get('10', {}) if isinstance(message_1, dict) else {}
|
||
message_6 = message_1.get('6', {}) if isinstance(message_1, dict) else {}
|
||
message_6_3 = message_6.get('3', {}) if isinstance(message_6, dict) else {}
|
||
payload = self._load_json_dict(message_6_3.get('5', '') if isinstance(message_6_3, dict) else '')
|
||
biz_tag_raw = str(message_10.get('bizTag', '') or '').strip() if isinstance(message_10, dict) else ''
|
||
biz_tag_dict = self._load_json_dict(biz_tag_raw)
|
||
ext_json_dict = self._load_json_dict(message_10.get('extJson', '') if isinstance(message_10, dict) else '')
|
||
|
||
try:
|
||
button_text = str(
|
||
payload.get('dxCard', {})
|
||
.get('item', {})
|
||
.get('main', {})
|
||
.get('exContent', {})
|
||
.get('button', {})
|
||
.get('text', '')
|
||
).strip()
|
||
except Exception:
|
||
button_text = ''
|
||
|
||
try:
|
||
card_title = str(
|
||
payload.get('dxCard', {})
|
||
.get('item', {})
|
||
.get('main', {})
|
||
.get('exContent', {})
|
||
.get('title', '')
|
||
).strip()
|
||
except Exception:
|
||
card_title = ''
|
||
|
||
message_direction = message_1.get('7', 0) if isinstance(message_1, dict) else 0
|
||
content_type = message_6_3.get('4', 0) if isinstance(message_6_3, dict) else 0
|
||
task_name = str(biz_tag_dict.get('taskName') or '').strip()
|
||
is_system_biz = bool(task_name) or 'SECURITY' in biz_tag_raw or 'taskId' in biz_tag_raw
|
||
|
||
return {
|
||
'message_direction': message_direction,
|
||
'content_type': content_type,
|
||
'is_system_message': message_direction == 1 or content_type == 6 or is_system_biz,
|
||
'message_red_reminder': str(message_10.get('redReminder', '') or '').strip() if isinstance(message_10, dict) else '',
|
||
'top_red_reminder': str(message.get('3', {}).get('redReminder', '') or '').strip() if isinstance(message, dict) and isinstance(message.get('3'), dict) else '',
|
||
'reminder_content': str(message_10.get('reminderContent', '') or '').strip() if isinstance(message_10, dict) else '',
|
||
'detail_notice': str(message_10.get('detailNotice', '') or '').strip() if isinstance(message_10, dict) else '',
|
||
'reminder_notice': str(message_10.get('reminderNotice', '') or '').strip() if isinstance(message_10, dict) else '',
|
||
'task_name': task_name,
|
||
'update_key': str(ext_json_dict.get('updateKey') or '').strip(),
|
||
'button_text': button_text,
|
||
'card_title': card_title,
|
||
}
|
||
|
||
def _match_system_status_from_text(self, text: Any) -> Optional[str]:
|
||
normalized = str(text or '').strip()
|
||
if not normalized:
|
||
return None
|
||
|
||
exact_mapping = {
|
||
'[买家确认收货,交易成功]': 'completed',
|
||
'[你已确认收货,交易成功]': 'completed',
|
||
'买家已确认收货,交易成功': 'completed',
|
||
'已确认收货,交易成功': 'completed',
|
||
'快给ta一个评价吧~': 'completed',
|
||
'快给ta一个评价吧~': 'completed',
|
||
'[你已发货]': 'shipped',
|
||
'你已发货': 'shipped',
|
||
'[你已发货,请等待买家确认收货]': 'shipped',
|
||
'[我已付款,等待你发货]': 'pending_ship',
|
||
'[已付款,待发货]': 'pending_ship',
|
||
'[买家已付款]': 'pending_ship',
|
||
'[付款完成]': 'pending_ship',
|
||
'[记得及时发货]': 'pending_ship',
|
||
'等待你发货': 'pending_ship',
|
||
'等待卖家发货': 'pending_ship',
|
||
'去发货': 'pending_ship',
|
||
'[我已拍下,待付款]': 'processing',
|
||
'买家已拍下,待付款': 'processing',
|
||
'等待买家付款': 'processing',
|
||
'[退款成功,钱款已原路退返]': 'cancelled',
|
||
'[你关闭了订单,钱款已原路退返]': 'cancelled',
|
||
'交易关闭': 'cancelled',
|
||
'退款撤销': 'refund_cancelled',
|
||
'等待买家收货': 'shipped',
|
||
'已发货': 'shipped',
|
||
'交易成功': 'completed',
|
||
}
|
||
if normalized in exact_mapping:
|
||
return exact_mapping[normalized]
|
||
|
||
lowered = normalized.lower()
|
||
if '钱款已原路退返' in normalized or '订单关闭' in normalized:
|
||
return 'cancelled'
|
||
if '退款撤销' in normalized:
|
||
return 'refund_cancelled'
|
||
if '退款中' in normalized or '退货退款' in normalized or '退款关闭' in normalized:
|
||
return 'refunding'
|
||
if '买家确认收货' in normalized:
|
||
return 'completed'
|
||
if '快给ta一个评价吧' in normalized:
|
||
return 'completed'
|
||
if '已发货_卖家' in normalized or '等待买家收货' in normalized:
|
||
return 'shipped'
|
||
if '付款完成待发货' in normalized or 'trade_paid_done_seller' in lowered:
|
||
return 'pending_ship'
|
||
if '已拍下_未付款' in normalized or '1_not_pay_seller' in lowered:
|
||
return 'processing'
|
||
return None
|
||
|
||
def _resolve_system_message_status(self, message: dict, send_message: str):
|
||
message_meta = self._extract_system_message_meta(message)
|
||
candidates = []
|
||
|
||
refund_status = self._check_refund_message(message, send_message)
|
||
if refund_status:
|
||
candidates.append({
|
||
'status': refund_status,
|
||
'source': 'refund_card',
|
||
'text': send_message,
|
||
'signal_priority': 90,
|
||
})
|
||
|
||
signal_inputs = [
|
||
('top_red_reminder', message_meta.get('top_red_reminder'), 80),
|
||
('message_red_reminder', message_meta.get('message_red_reminder'), 80),
|
||
('button_text', message_meta.get('button_text'), 80),
|
||
('send_message', send_message, 70),
|
||
('reminder_content', message_meta.get('reminder_content'), 70),
|
||
('card_title', message_meta.get('card_title'), 60),
|
||
('detail_notice', message_meta.get('detail_notice'), 50),
|
||
('task_name', message_meta.get('task_name'), 40),
|
||
('update_key', message_meta.get('update_key'), 30),
|
||
('reminder_notice', message_meta.get('reminder_notice'), 20),
|
||
]
|
||
|
||
seen = set()
|
||
for source_name, raw_text, signal_priority in signal_inputs:
|
||
normalized_text = str(raw_text or '').strip()
|
||
if not normalized_text:
|
||
continue
|
||
|
||
matched_status = self._match_system_status_from_text(normalized_text)
|
||
if not matched_status:
|
||
continue
|
||
|
||
dedupe_key = (matched_status, source_name, normalized_text)
|
||
if dedupe_key in seen:
|
||
continue
|
||
seen.add(dedupe_key)
|
||
|
||
candidates.append({
|
||
'status': matched_status,
|
||
'source': source_name,
|
||
'text': normalized_text,
|
||
'signal_priority': signal_priority,
|
||
})
|
||
|
||
if not candidates:
|
||
return None, message_meta, []
|
||
|
||
candidates.sort(
|
||
key=lambda item: (item.get('signal_priority', 0), self._get_status_priority(item.get('status'))),
|
||
reverse=True,
|
||
)
|
||
return candidates[0].get('status'), message_meta, candidates
|
||
|
||
def _build_message_hash(self, message: Any) -> Optional[int]:
|
||
"""构建待处理消息的匹配哈希。"""
|
||
if message is None:
|
||
return None
|
||
|
||
try:
|
||
return hash(str(sorted(message.items()))) if isinstance(message, dict) else hash(str(message))
|
||
except Exception:
|
||
try:
|
||
return hash(json.dumps(message, ensure_ascii=False, sort_keys=True, default=str))
|
||
except Exception as e:
|
||
logger.warning(f"构建消息哈希失败: {e}")
|
||
return None
|
||
|
||
def _normalize_match_text(self, value: Any) -> Optional[str]:
|
||
if value is None:
|
||
return None
|
||
|
||
normalized = str(value).strip()
|
||
if not normalized:
|
||
return None
|
||
|
||
if normalized.lower() in {'unknown', 'unknown_user', 'none', 'null'}:
|
||
return None
|
||
|
||
return normalized
|
||
|
||
def _normalize_item_match_value(self, value: Any) -> Optional[str]:
|
||
normalized = self._normalize_match_text(value)
|
||
if not normalized:
|
||
return None
|
||
|
||
if normalized.startswith('auto_'):
|
||
return None
|
||
|
||
return normalized
|
||
|
||
def _normalize_pending_match_context(self, message: dict = None, match_context: Dict[str, Any] = None) -> Dict[str, Any]:
|
||
raw_context = dict(match_context or {})
|
||
message_hash = raw_context.get('message_hash')
|
||
if message_hash is None and message is not None:
|
||
message_hash = self._build_message_hash(message)
|
||
|
||
message_timestamp_ms = raw_context.get('message_timestamp_ms')
|
||
if message_timestamp_ms is None and message is not None:
|
||
message_timestamp_ms = self._extract_message_timestamp_ms(message)
|
||
|
||
sid = self._normalize_match_text(raw_context.get('sid'))
|
||
buyer_id = self._normalize_match_text(raw_context.get('buyer_id'))
|
||
item_id = self._normalize_item_match_value(raw_context.get('item_id'))
|
||
|
||
sid_prefix = sid.split('@')[0] if sid and '@' in sid else sid
|
||
if buyer_id and sid_prefix and buyer_id == sid_prefix:
|
||
buyer_id = None
|
||
|
||
return {
|
||
'message_hash': message_hash,
|
||
'message_timestamp_ms': message_timestamp_ms,
|
||
'sid': sid,
|
||
'buyer_id': buyer_id,
|
||
'item_id': item_id,
|
||
'has_strong_match_key': bool(sid and buyer_id and item_id),
|
||
}
|
||
|
||
def _format_pending_match_context(self, match_context: Dict[str, Any]) -> str:
|
||
context = match_context or {}
|
||
return (
|
||
f"hash={context.get('message_hash')}, "
|
||
f"sid={context.get('sid') or '-'}, "
|
||
f"buyer_id={context.get('buyer_id') or '-'}, "
|
||
f"item_id={context.get('item_id') or '-'}"
|
||
)
|
||
|
||
def _pending_message_matches_strong_key(self, pending_msg: Dict[str, Any], match_context: Dict[str, Any]) -> bool:
|
||
if not match_context.get('has_strong_match_key'):
|
||
return False
|
||
|
||
return (
|
||
pending_msg.get('sid') == match_context.get('sid') and
|
||
pending_msg.get('buyer_id') == match_context.get('buyer_id') and
|
||
pending_msg.get('item_id') == match_context.get('item_id')
|
||
)
|
||
|
||
def _is_terminal_pending_status(self, status: Optional[str]) -> bool:
|
||
return status in {'shipped', 'completed', 'cancelled', 'refund_cancelled'}
|
||
|
||
def _pending_message_matches_fields(self, pending_msg: Dict[str, Any], match_context: Dict[str, Any], fields: tuple) -> bool:
|
||
for field in fields:
|
||
expected_value = match_context.get(field)
|
||
if not expected_value or pending_msg.get(field) != expected_value:
|
||
return False
|
||
return True
|
||
|
||
def _is_pending_terminal_message_within_gap(self, pending_msg: Dict[str, Any], current_timestamp_ms: Optional[int]) -> bool:
|
||
if not self._is_terminal_pending_status(pending_msg.get('new_status')):
|
||
return True
|
||
|
||
pending_timestamp_ms = pending_msg.get('message_timestamp_ms')
|
||
if not pending_timestamp_ms or not current_timestamp_ms:
|
||
return False
|
||
|
||
max_gap_seconds = self.config.get('pending_terminal_bind_max_gap_seconds', 90)
|
||
max_gap_ms = int(max_gap_seconds * 1000)
|
||
gap_ms = abs(int(current_timestamp_ms) - int(pending_timestamp_ms))
|
||
return gap_ms <= max_gap_ms
|
||
|
||
def _select_terminal_pending_message_index(self, pending_messages: list, match_context: Dict[str, Any], queue_name: str):
|
||
current_timestamp_ms = match_context.get('message_timestamp_ms')
|
||
if not current_timestamp_ms:
|
||
return None, 'terminal_no_timestamp'
|
||
|
||
fallback_matchers = [
|
||
('terminal_sid_item_buyer_recent', ('sid', 'item_id', 'buyer_id')),
|
||
('terminal_sid_item_recent', ('sid', 'item_id')),
|
||
('terminal_sid_buyer_recent', ('sid', 'buyer_id')),
|
||
('terminal_sid_recent', ('sid',)),
|
||
]
|
||
|
||
for mode, fields in fallback_matchers:
|
||
if any(not match_context.get(field) for field in fields):
|
||
continue
|
||
|
||
candidate_indexes = [
|
||
i for i, msg in enumerate(pending_messages)
|
||
if self._is_terminal_pending_status(msg.get('new_status'))
|
||
and self._pending_message_matches_fields(msg, match_context, fields)
|
||
and self._is_pending_terminal_message_within_gap(msg, current_timestamp_ms)
|
||
]
|
||
|
||
if len(candidate_indexes) == 1:
|
||
return candidate_indexes[0], mode
|
||
|
||
if len(candidate_indexes) > 1:
|
||
logger.warning(
|
||
f"{queue_name} 待处理队列终态近邻回退命中多个候选,拒绝匹配: "
|
||
f"mode={mode}, candidates={len(candidate_indexes)}, "
|
||
f"{self._format_pending_match_context(match_context)}"
|
||
)
|
||
return None, f'ambiguous_{mode}'
|
||
|
||
return None, 'terminal_recent_miss'
|
||
|
||
def _select_pending_message_index(self, pending_messages: list, match_context: Dict[str, Any], queue_name: str):
|
||
if not pending_messages:
|
||
return None, 'empty'
|
||
|
||
ambiguous_reason = None
|
||
|
||
message_hash = match_context.get('message_hash')
|
||
if message_hash is not None:
|
||
hash_candidates = [
|
||
i for i, msg in enumerate(pending_messages)
|
||
if msg.get('message_hash') == message_hash
|
||
]
|
||
if len(hash_candidates) == 1:
|
||
return hash_candidates[0], 'message_hash'
|
||
if len(hash_candidates) > 1:
|
||
strong_candidates = [
|
||
i for i in hash_candidates
|
||
if self._pending_message_matches_strong_key(pending_messages[i], match_context)
|
||
]
|
||
if len(strong_candidates) == 1:
|
||
return strong_candidates[0], 'message_hash+strong_key'
|
||
|
||
logger.warning(
|
||
f"{queue_name} 待处理队列出现多个 message_hash 候选,严格模式拒绝匹配: "
|
||
f"candidates={len(hash_candidates)}, {self._format_pending_match_context(match_context)}"
|
||
)
|
||
ambiguous_reason = 'ambiguous_message_hash'
|
||
|
||
if match_context.get('has_strong_match_key'):
|
||
strong_candidates = [
|
||
i for i, msg in enumerate(pending_messages)
|
||
if self._pending_message_matches_strong_key(msg, match_context)
|
||
]
|
||
if len(strong_candidates) == 1:
|
||
return strong_candidates[0], 'strong_key'
|
||
if len(strong_candidates) > 1:
|
||
logger.warning(
|
||
f"{queue_name} 待处理队列出现多个强关联键候选,严格模式拒绝匹配: "
|
||
f"candidates={len(strong_candidates)}, {self._format_pending_match_context(match_context)}"
|
||
)
|
||
ambiguous_reason = 'ambiguous_strong_key'
|
||
|
||
terminal_index, terminal_mode = self._select_terminal_pending_message_index(
|
||
pending_messages,
|
||
match_context,
|
||
queue_name,
|
||
)
|
||
if terminal_index is not None:
|
||
return terminal_index, terminal_mode
|
||
|
||
if ambiguous_reason:
|
||
return None, ambiguous_reason
|
||
|
||
return None, 'miss'
|
||
|
||
def _extract_message_timestamp_ms(self, message: Any) -> Optional[int]:
|
||
if not isinstance(message, dict):
|
||
return None
|
||
|
||
candidates = []
|
||
nested_message = message.get('1')
|
||
if isinstance(nested_message, dict):
|
||
candidates.append(nested_message.get('5'))
|
||
candidates.append(message.get('5'))
|
||
|
||
for raw_value in candidates:
|
||
if raw_value is None:
|
||
continue
|
||
try:
|
||
timestamp_ms = int(raw_value)
|
||
except (TypeError, ValueError):
|
||
continue
|
||
|
||
if timestamp_ms > 0:
|
||
return timestamp_ms
|
||
|
||
return None
|
||
|
||
def _get_terminal_resolution_candidate_statuses(self) -> list:
|
||
return [
|
||
'processing',
|
||
'pending_ship',
|
||
'partial_success',
|
||
'partial_pending_finalize',
|
||
'shipped',
|
||
'completed',
|
||
'refunding',
|
||
]
|
||
|
||
def _find_recent_orders_for_match_context(self, cookie_id: str, match_context: Dict[str, Any],
|
||
statuses: list, exclude_order_id: str = None) -> list:
|
||
if not match_context.get('has_strong_match_key'):
|
||
return []
|
||
|
||
try:
|
||
from db_manager import db_manager
|
||
return db_manager.find_recent_orders_by_match_context(
|
||
sid=match_context.get('sid'),
|
||
buyer_id=match_context.get('buyer_id'),
|
||
item_id=match_context.get('item_id'),
|
||
cookie_id=cookie_id,
|
||
statuses=statuses,
|
||
exclude_order_id=exclude_order_id,
|
||
minutes=self.config.get('terminal_resolution_search_minutes', 30),
|
||
limit=10
|
||
)
|
||
except Exception as e:
|
||
logger.warning(
|
||
"按匹配键查询最近订单失败: "
|
||
f"cookie_id={cookie_id}, {self._format_pending_match_context(match_context)}, error={e}"
|
||
)
|
||
return []
|
||
|
||
def _try_resolve_cancelled_message_without_order_id(self, cookie_id: str, msg_time: str, new_status: str,
|
||
context_label: str, match_context: Dict[str, Any]) -> bool:
|
||
if new_status != 'cancelled' or not match_context.get('has_strong_match_key'):
|
||
return False
|
||
|
||
candidates = self._find_recent_orders_for_match_context(
|
||
cookie_id=cookie_id,
|
||
match_context=match_context,
|
||
statuses=self._get_terminal_resolution_candidate_statuses()
|
||
)
|
||
|
||
if not candidates:
|
||
return False
|
||
|
||
if len(candidates) > 1:
|
||
logger.warning(
|
||
f"[{msg_time}] 【{cookie_id}】{context_label} 无法直接回填旧订单,命中过多候选: "
|
||
f"count={len(candidates)}, {self._format_pending_match_context(match_context)}"
|
||
)
|
||
return False
|
||
|
||
resolved_order = candidates[0]
|
||
resolved_order_id = resolved_order.get('order_id')
|
||
logger.info(
|
||
f"[{msg_time}] 【{cookie_id}】{context_label} 命中唯一旧订单,直接回填关闭状态: "
|
||
f"order_id={resolved_order_id}, current_status={resolved_order.get('order_status')}"
|
||
)
|
||
|
||
success = self.update_order_status(
|
||
order_id=resolved_order_id,
|
||
new_status=new_status,
|
||
cookie_id=cookie_id,
|
||
context=f"{context_label} - {msg_time} - 按匹配键即时回填"
|
||
)
|
||
if success:
|
||
logger.info(
|
||
f"[{msg_time}] 【{cookie_id}】{context_label} 已直接回填到旧订单: "
|
||
f"order_id={resolved_order_id}"
|
||
)
|
||
else:
|
||
logger.warning(
|
||
f"[{msg_time}] 【{cookie_id}】{context_label} 命中旧订单但回填失败,保留待处理逻辑: "
|
||
f"order_id={resolved_order_id}"
|
||
)
|
||
return success
|
||
|
||
def _should_bind_pending_terminal_message(self, pending_msg: Dict[str, Any], message: dict) -> bool:
|
||
if not self._is_terminal_pending_status(pending_msg.get('new_status')):
|
||
return True
|
||
|
||
pending_timestamp_ms = pending_msg.get('message_timestamp_ms')
|
||
current_timestamp_ms = self._extract_message_timestamp_ms(message)
|
||
if not pending_timestamp_ms or not current_timestamp_ms:
|
||
return True
|
||
|
||
if self._is_pending_terminal_message_within_gap(pending_msg, current_timestamp_ms):
|
||
return True
|
||
|
||
max_gap_seconds = self.config.get('pending_terminal_bind_max_gap_seconds', 90)
|
||
max_gap_ms = int(max_gap_seconds * 1000)
|
||
gap_ms = abs(int(current_timestamp_ms) - int(pending_timestamp_ms))
|
||
|
||
logger.warning(
|
||
"待处理终态消息与当前提取订单时间差过大,拒绝绑定以避免串单: "
|
||
f"status={pending_msg.get('new_status')}, gap_ms={gap_ms}, max_gap_ms={max_gap_ms}"
|
||
)
|
||
return False
|
||
|
||
def _get_terminal_resolution_statuses(self, pending_status: Optional[str]) -> list:
|
||
if pending_status == 'cancelled':
|
||
return ['cancelled']
|
||
if pending_status == 'refund_cancelled':
|
||
return ['refund_cancelled', 'pending_ship', 'partial_success', 'partial_pending_finalize', 'shipped', 'completed']
|
||
if pending_status == 'shipped':
|
||
return ['shipped', 'completed', 'refunding', 'cancelled']
|
||
if pending_status == 'completed':
|
||
return ['completed', 'refunding', 'cancelled']
|
||
return []
|
||
|
||
def _pending_terminal_message_already_resolved(self, order_id: str, cookie_id: str,
|
||
pending_msg: Dict[str, Any],
|
||
match_context: Dict[str, Any]) -> bool:
|
||
pending_status = pending_msg.get('new_status')
|
||
if not self._is_terminal_pending_status(pending_status):
|
||
return False
|
||
|
||
resolution_statuses = self._get_terminal_resolution_statuses(pending_status)
|
||
if not resolution_statuses:
|
||
return False
|
||
|
||
resolved_orders = self._find_recent_orders_for_match_context(
|
||
cookie_id=cookie_id,
|
||
match_context=match_context,
|
||
statuses=resolution_statuses,
|
||
exclude_order_id=order_id
|
||
)
|
||
if not resolved_orders:
|
||
return False
|
||
|
||
logger.info(
|
||
"检测到同匹配键下已有其它终态订单,视为待处理终态消息已消化: "
|
||
f"pending_status={pending_status}, current_order_id={order_id}, "
|
||
f"resolved_order_id={resolved_orders[0].get('order_id')}, "
|
||
f"resolved_status={resolved_orders[0].get('order_status')}"
|
||
)
|
||
return True
|
||
|
||
def _clear_temp_pending_update(self, temp_order_id: Optional[str], log_prefix: str = ""):
|
||
if not temp_order_id:
|
||
return
|
||
|
||
if temp_order_id in self.pending_updates:
|
||
del self.pending_updates[temp_order_id]
|
||
logger.info(f"{log_prefix}清理临时订单ID {temp_order_id} 的待处理更新")
|
||
|
||
def update_order_status(self, order_id: str, new_status: str, cookie_id: str, context: str = "") -> bool:
|
||
"""更新订单状态到数据库
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
new_status: 新状态 (processing/pending_ship/partial_success/partial_pending_finalize/shipped/completed/cancelled)
|
||
cookie_id: Cookie ID
|
||
context: 上下文信息,用于日志记录
|
||
|
||
Returns:
|
||
bool: 更新是否成功
|
||
"""
|
||
logger.info(f"🔄 订单状态处理器.update_order_status开始: order_id={order_id}, new_status={new_status}, cookie_id={cookie_id}, context={context}")
|
||
with self._lock:
|
||
try:
|
||
from db_manager import db_manager
|
||
|
||
# 验证状态值是否有效
|
||
if new_status not in self.status_mapping:
|
||
logger.error(f"❌ 无效的订单状态: {new_status},有效状态: {list(self.status_mapping.keys())}")
|
||
return False
|
||
|
||
logger.info(f"✅ 订单状态验证通过: {new_status}")
|
||
|
||
# 检查订单是否存在于数据库中(带重试机制)
|
||
current_order = None
|
||
max_retries = 3
|
||
for attempt in range(max_retries):
|
||
try:
|
||
logger.info(f"🔍 尝试获取订单信息 (尝试 {attempt + 1}/{max_retries}): {order_id}")
|
||
current_order = db_manager.get_order_by_id(order_id)
|
||
logger.info(f"✅ 订单信息获取成功: {order_id}")
|
||
break
|
||
except Exception as db_e:
|
||
if attempt == max_retries - 1:
|
||
logger.error(f"❌ 获取订单信息失败 (尝试 {attempt + 1}/{max_retries}): {str(db_e)}")
|
||
return False
|
||
else:
|
||
logger.error(f"⚠️ 获取订单信息失败,重试中 (尝试 {attempt + 1}/{max_retries}): {str(db_e)}")
|
||
time.sleep(0.1 * (attempt + 1)) # 递增延迟
|
||
|
||
if not current_order:
|
||
# 订单不存在,根据配置决定是否添加到待处理队列
|
||
logger.info(f"⚠️ 订单 {order_id} 不存在于数据库中")
|
||
if self.config.get('use_pending_queue', True):
|
||
logger.info(f"📝 订单 {order_id} 不存在于数据库中,添加到待处理队列等待主程序拉取订单详情")
|
||
self._add_to_pending_updates(order_id, new_status, cookie_id, context)
|
||
else:
|
||
logger.error(f"❌ 订单 {order_id} 不存在于数据库中且未启用待处理队列,跳过状态更新")
|
||
return False
|
||
|
||
current_status = current_order.get('order_status', 'processing')
|
||
logger.info(f"📊 当前订单状态: {current_status}, 目标状态: {new_status}")
|
||
|
||
# 检查是否是相同的状态更新(避免重复处理)
|
||
if current_status == new_status:
|
||
status_text = self.status_mapping.get(new_status, new_status)
|
||
logger.info(f"⏭️ 订单 {order_id} 状态无变化,跳过重复更新: {status_text}")
|
||
return True # 返回True表示"成功",避免重复日志
|
||
|
||
# 检查状态转换是否合理(根据配置决定是否启用严格验证)
|
||
if self.config.get('strict_validation', True) and not self._is_valid_status_transition(current_status, new_status):
|
||
logger.error(f"❌ 订单 {order_id} 状态转换不合理: {current_status} -> {new_status} (严格验证已启用)")
|
||
logger.error(f"当前状态 '{current_status}' 允许转换到: {self._get_allowed_transitions(current_status)}")
|
||
return False
|
||
|
||
logger.info(f"✅ 状态转换验证通过: {current_status} -> {new_status}")
|
||
|
||
# 处理退款撤销的特殊逻辑
|
||
if new_status == 'refund_cancelled':
|
||
# 从历史记录中获取上一次状态
|
||
previous_status = self._get_previous_status(order_id, current_status=current_status)
|
||
if not previous_status:
|
||
previous_status = db_manager.get_order_pre_refund_status(order_id)
|
||
if previous_status:
|
||
logger.info(f"🔄 退款撤销,回退到上一次状态: {previous_status}")
|
||
new_status = previous_status
|
||
else:
|
||
logger.warning(f"⚠️ 退款撤销但无法获取上一次状态,保持当前状态: {current_status}")
|
||
new_status = current_status
|
||
|
||
pre_refund_status_to_save = ...
|
||
clear_pre_refund_status = False
|
||
if new_status == 'refunding':
|
||
if current_status and current_status not in ['refunding', 'refund_cancelled', 'unknown']:
|
||
pre_refund_status_to_save = current_status
|
||
elif current_status == 'refunding' and new_status != 'refunding':
|
||
clear_pre_refund_status = True
|
||
|
||
# 更新订单状态(带重试机制)
|
||
success = False
|
||
for attempt in range(max_retries):
|
||
try:
|
||
logger.info(f"💾 尝试更新订单状态 (尝试 {attempt + 1}/{max_retries}): {order_id}")
|
||
success = db_manager.insert_or_update_order(
|
||
order_id=order_id,
|
||
order_status=new_status,
|
||
cookie_id=cookie_id,
|
||
pre_refund_status=pre_refund_status_to_save,
|
||
clear_pre_refund_status=clear_pre_refund_status
|
||
)
|
||
logger.info(f"✅ 订单状态更新成功: {order_id}")
|
||
break
|
||
except Exception as db_e:
|
||
if attempt == max_retries - 1:
|
||
logger.error(f"❌ 更新订单状态失败 (尝试 {attempt + 1}/{max_retries}): {str(db_e)}")
|
||
return False
|
||
else:
|
||
logger.error(f"⚠️ 更新订单状态失败,重试中 (尝试 {attempt + 1}/{max_retries}): {str(db_e)}")
|
||
time.sleep(0.1 * (attempt + 1)) # 递增延迟
|
||
|
||
if success:
|
||
# 记录状态历史(用于退款撤销时回退)
|
||
self._record_status_history(order_id, current_status, new_status, context)
|
||
|
||
try:
|
||
from order_event_hub import publish_order_update_event
|
||
publish_order_update_event(order_id, source='order_status_handler')
|
||
except Exception as publish_e:
|
||
logger.warning(f"发布订单状态事件失败: order_id={order_id}, error={publish_e}")
|
||
|
||
status_text = self.status_mapping.get(new_status, new_status)
|
||
if self.config.get('enable_status_logging', True):
|
||
logger.info(f"✅ 订单状态更新成功: {order_id} -> {status_text} ({context})")
|
||
else:
|
||
logger.error(f"❌ 订单状态更新失败: {order_id} -> {new_status} ({context})")
|
||
|
||
return success
|
||
|
||
except Exception as e:
|
||
logger.error(f"更新订单状态时出错: {str(e)}")
|
||
import traceback
|
||
logger.error(f"详细错误信息: {traceback.format_exc()}")
|
||
return False
|
||
|
||
def _is_valid_status_transition(self, current_status: str, new_status: str) -> bool:
|
||
"""检查状态转换是否合理
|
||
|
||
Args:
|
||
current_status: 当前状态
|
||
new_status: 新状态
|
||
|
||
Returns:
|
||
bool: 转换是否合理
|
||
"""
|
||
# 如果当前状态不在规则中,允许转换(兼容性)
|
||
if current_status not in self.VALID_TRANSITIONS:
|
||
return True
|
||
|
||
# 特殊规则:已付款的订单和已完成的订单不能回退到处理中
|
||
if new_status == 'processing' and current_status in ['pending_ship', 'partial_success', 'partial_pending_finalize', 'shipped', 'completed', 'refunding', 'refund_cancelled']:
|
||
logger.warning(f"❌ 状态转换被拒绝:{current_status} -> {new_status} (已付款/已完成的订单不能回退到处理中)")
|
||
return False
|
||
|
||
# 检查新状态是否在允许的转换列表中
|
||
allowed_statuses = self.VALID_TRANSITIONS.get(current_status, [])
|
||
return new_status in allowed_statuses
|
||
|
||
def _get_allowed_transitions(self, current_status: str) -> list:
|
||
"""获取当前状态允许转换到的状态列表
|
||
|
||
Args:
|
||
current_status: 当前状态
|
||
|
||
Returns:
|
||
list: 允许转换到的状态列表
|
||
"""
|
||
if current_status not in self.VALID_TRANSITIONS:
|
||
return ['所有状态'] # 兼容性
|
||
|
||
return self.VALID_TRANSITIONS.get(current_status, [])
|
||
|
||
def _check_refund_message(self, message: dict, send_message: str) -> Optional[str]:
|
||
"""检查退款申请消息,需要同时识别标题和按钮文本
|
||
|
||
Args:
|
||
message: 原始消息数据
|
||
send_message: 消息内容
|
||
|
||
Returns:
|
||
str: 对应的状态,如果不是退款消息则返回None
|
||
"""
|
||
try:
|
||
# 检查消息结构,寻找退款相关的信息
|
||
message_1 = message.get('1', {})
|
||
if not isinstance(message_1, dict):
|
||
return None
|
||
|
||
# 检查消息卡片内容
|
||
message_1_6 = message_1.get('6', {})
|
||
if not isinstance(message_1_6, dict):
|
||
return None
|
||
|
||
# 解析JSON内容
|
||
content_json_str = message_1_6.get('3', {}).get('5', '') if isinstance(message_1_6.get('3', {}), dict) else ''
|
||
if not content_json_str:
|
||
return None
|
||
|
||
try:
|
||
content_data = json.loads(content_json_str)
|
||
|
||
# 检查dynamicOperation中的内容
|
||
dynamic_content = content_data.get('dynamicOperation', {}).get('changeContent', {})
|
||
if not dynamic_content:
|
||
return None
|
||
|
||
dx_card = dynamic_content.get('dxCard', {}).get('item', {}).get('main', {})
|
||
if not dx_card:
|
||
return None
|
||
|
||
ex_content = dx_card.get('exContent', {})
|
||
if not ex_content:
|
||
return None
|
||
|
||
# 获取标题和按钮文本
|
||
title = ex_content.get('title', '')
|
||
button_text = ex_content.get('button', {}).get('text', '')
|
||
|
||
logger.info(f"🔍 检查退款消息 - 标题: '{title}', 按钮: '{button_text}'")
|
||
|
||
# 检查是否是退款申请且已同意
|
||
if title == '我发起了退款申请' and button_text == '已同意':
|
||
logger.info(f"✅ 识别到退款申请已同意消息")
|
||
return 'refunding'
|
||
|
||
# 检查是否是退款撤销(买家主动撤销)
|
||
if title == '我发起了退款申请' and button_text == '已撤销':
|
||
logger.info(f"✅ 识别到退款撤销消息")
|
||
return 'refund_cancelled'
|
||
|
||
# 退款申请被拒绝不需要改变状态,因为没同意
|
||
# if title == '我发起了退款申请' and button_text == '已拒绝':
|
||
# logger.info(f"ℹ️ 识别到退款申请被拒绝消息,不改变订单状态")
|
||
# return None
|
||
|
||
except Exception as parse_e:
|
||
logger.debug(f"解析退款消息JSON失败: {parse_e}")
|
||
return None
|
||
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.debug(f"检查退款消息失败: {e}")
|
||
return None
|
||
|
||
def _record_status_history(self, order_id: str, from_status: str, to_status: str, context: str):
|
||
"""记录订单状态历史
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
from_status: 原状态
|
||
to_status: 新状态
|
||
context: 上下文信息
|
||
"""
|
||
with self._lock:
|
||
if order_id not in self._order_status_history:
|
||
self._order_status_history[order_id] = []
|
||
|
||
# 只记录非临时状态的历史(排除 refund_cancelled)
|
||
if to_status != 'refund_cancelled':
|
||
history_entry = {
|
||
'from_status': from_status,
|
||
'to_status': to_status,
|
||
'context': context,
|
||
'timestamp': time.time()
|
||
}
|
||
self._order_status_history[order_id].append(history_entry)
|
||
|
||
# 限制历史记录数量,只保留最近10条
|
||
if len(self._order_status_history[order_id]) > 10:
|
||
self._order_status_history[order_id] = self._order_status_history[order_id][-10:]
|
||
|
||
logger.debug(f"📝 记录订单状态历史: {order_id} {from_status} -> {to_status}")
|
||
|
||
def _get_previous_status(self, order_id: str, current_status: str = None) -> Optional[str]:
|
||
"""获取订单的上一次状态(用于退款撤销时回退)
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
|
||
Returns:
|
||
str: 上一次状态,如果没有历史记录则返回None
|
||
"""
|
||
with self._lock:
|
||
if order_id not in self._order_status_history or not self._order_status_history[order_id]:
|
||
return None
|
||
|
||
history = self._order_status_history[order_id]
|
||
|
||
if current_status:
|
||
for entry in reversed(history):
|
||
if entry.get('to_status') == current_status:
|
||
previous_status = entry.get('from_status')
|
||
if previous_status and previous_status != 'refund_cancelled':
|
||
return previous_status
|
||
|
||
last_entry = history[-1]
|
||
fallback_status = last_entry.get('from_status') or last_entry.get('to_status')
|
||
if fallback_status == 'refund_cancelled':
|
||
return None
|
||
return fallback_status
|
||
|
||
def _add_to_pending_updates(self, order_id: str, new_status: str, cookie_id: str, context: str):
|
||
"""添加到待处理更新队列
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
new_status: 新状态
|
||
cookie_id: Cookie ID
|
||
context: 上下文信息
|
||
"""
|
||
with self._lock:
|
||
if order_id not in self.pending_updates:
|
||
self.pending_updates[order_id] = []
|
||
|
||
update_info = {
|
||
'new_status': new_status,
|
||
'cookie_id': cookie_id,
|
||
'context': context,
|
||
'timestamp': time.time()
|
||
}
|
||
|
||
self.pending_updates[order_id].append(update_info)
|
||
logger.info(f"订单 {order_id} 状态更新已添加到待处理队列: {new_status} ({context})")
|
||
|
||
def process_pending_updates(self, order_id: str) -> bool:
|
||
"""处理指定订单的待处理更新
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
|
||
Returns:
|
||
bool: 是否有更新被处理
|
||
"""
|
||
with self._lock:
|
||
if order_id not in self.pending_updates:
|
||
return False
|
||
|
||
updates = self.pending_updates.pop(order_id)
|
||
processed_count = 0
|
||
|
||
for update_info in updates:
|
||
try:
|
||
success = self.update_order_status(
|
||
order_id=order_id,
|
||
new_status=update_info['new_status'],
|
||
cookie_id=update_info['cookie_id'],
|
||
context=f"待处理队列: {update_info['context']}"
|
||
)
|
||
|
||
if success:
|
||
processed_count += 1
|
||
logger.info(f"处理待处理更新成功: 订单 {order_id} -> {update_info['new_status']}")
|
||
else:
|
||
logger.error(f"处理待处理更新失败: 订单 {order_id} -> {update_info['new_status']}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理待处理更新时出错: {str(e)}")
|
||
|
||
if processed_count > 0:
|
||
logger.info(f"订单 {order_id} 共处理了 {processed_count} 个待处理状态更新")
|
||
|
||
return processed_count > 0
|
||
|
||
def process_all_pending_updates(self) -> int:
|
||
"""处理所有待处理的更新
|
||
|
||
Returns:
|
||
int: 处理的订单数量
|
||
"""
|
||
with self._lock:
|
||
if not self.pending_updates:
|
||
return 0
|
||
|
||
order_ids = list(self.pending_updates.keys())
|
||
processed_orders = 0
|
||
|
||
for order_id in order_ids:
|
||
if self.process_pending_updates(order_id):
|
||
processed_orders += 1
|
||
|
||
return processed_orders
|
||
|
||
def get_pending_updates_count(self) -> int:
|
||
"""获取待处理更新的数量
|
||
|
||
Returns:
|
||
int: 待处理更新的数量
|
||
"""
|
||
with self._lock:
|
||
return len(self.pending_updates)
|
||
|
||
def clear_old_pending_updates(self, max_age_hours: int = None):
|
||
"""清理过期的待处理更新
|
||
|
||
Args:
|
||
max_age_hours: 最大保留时间(小时),如果为None则使用配置中的默认值
|
||
"""
|
||
# 检查是否启用待处理队列
|
||
if not self.config.get('use_pending_queue', True):
|
||
logger.error("未启用待处理队列,跳过清理操作")
|
||
return
|
||
|
||
if max_age_hours is None:
|
||
max_age_hours = self.config.get('max_pending_age_hours', 24)
|
||
|
||
current_time = time.time()
|
||
max_age_seconds = max_age_hours * 3600
|
||
|
||
with self._lock:
|
||
# 清理 pending_updates
|
||
expired_orders = []
|
||
for order_id, updates in self.pending_updates.items():
|
||
# 过滤掉过期的更新
|
||
valid_updates = [
|
||
update for update in updates
|
||
if current_time - update['timestamp'] < max_age_seconds
|
||
]
|
||
|
||
if not valid_updates:
|
||
expired_orders.append(order_id)
|
||
else:
|
||
self.pending_updates[order_id] = valid_updates
|
||
|
||
# 移除完全过期的订单
|
||
for order_id in expired_orders:
|
||
del self.pending_updates[order_id]
|
||
logger.info(f"清理过期的待处理更新: 订单 {order_id}")
|
||
|
||
if expired_orders:
|
||
logger.info(f"共清理了 {len(expired_orders)} 个过期的待处理订单更新")
|
||
|
||
# 清理 _pending_system_messages
|
||
expired_cookies_system = []
|
||
for cookie_id, messages in self._pending_system_messages.items():
|
||
valid_messages = [
|
||
msg for msg in messages
|
||
if current_time - msg.get('timestamp', 0) < max_age_seconds
|
||
]
|
||
|
||
if not valid_messages:
|
||
expired_cookies_system.append(cookie_id)
|
||
else:
|
||
self._pending_system_messages[cookie_id] = valid_messages
|
||
|
||
for cookie_id in expired_cookies_system:
|
||
del self._pending_system_messages[cookie_id]
|
||
logger.info(f"清理过期的待处理系统消息: 账号 {cookie_id}")
|
||
|
||
# 清理 _pending_red_reminder_messages
|
||
expired_cookies_red = []
|
||
for cookie_id, messages in self._pending_red_reminder_messages.items():
|
||
valid_messages = [
|
||
msg for msg in messages
|
||
if current_time - msg.get('timestamp', 0) < max_age_seconds
|
||
]
|
||
|
||
if not valid_messages:
|
||
expired_cookies_red.append(cookie_id)
|
||
else:
|
||
self._pending_red_reminder_messages[cookie_id] = valid_messages
|
||
|
||
for cookie_id in expired_cookies_red:
|
||
del self._pending_red_reminder_messages[cookie_id]
|
||
logger.info(f"清理过期的待处理红色提醒消息: 账号 {cookie_id}")
|
||
|
||
total_cleared = len(expired_orders) + len(expired_cookies_system) + len(expired_cookies_red)
|
||
if total_cleared > 0:
|
||
logger.info(f"内存清理完成,共清理了 {total_cleared} 个过期项目")
|
||
|
||
def handle_system_message(self, message: dict, send_message: str, cookie_id: str, msg_time: str,
|
||
match_context: Dict[str, Any] = None) -> bool:
|
||
"""处理系统消息并更新订单状态
|
||
|
||
Args:
|
||
message: 原始消息数据
|
||
send_message: 消息内容
|
||
cookie_id: Cookie ID
|
||
msg_time: 消息时间
|
||
|
||
Returns:
|
||
bool: 是否处理了订单状态更新
|
||
"""
|
||
try:
|
||
new_status, message_meta, status_candidates = self._resolve_system_message_status(message, send_message)
|
||
if not new_status:
|
||
return False
|
||
|
||
if not message_meta.get('is_system_message') and not any(
|
||
candidate.get('source') == 'refund_card' for candidate in status_candidates
|
||
):
|
||
return False
|
||
|
||
candidate_summary = ', '.join(
|
||
f"{candidate.get('status')}<{candidate.get('source')}>:{candidate.get('text')}"
|
||
for candidate in status_candidates[:6]
|
||
)
|
||
logger.info(
|
||
f'[{msg_time}] 【{cookie_id}】系统消息状态候选: {candidate_summary or "none"},'
|
||
f'最终采用 {new_status}'
|
||
)
|
||
|
||
# 提取订单ID
|
||
order_id = self.extract_order_id(message)
|
||
if not order_id:
|
||
# 如果无法提取订单ID,根据配置决定是否添加到待处理队列
|
||
if not self.config.get('use_pending_queue', True):
|
||
logger.error(f'[{msg_time}] 【{cookie_id}】{send_message},无法提取订单ID且未启用待处理队列,跳过处理')
|
||
return False
|
||
|
||
pending_match_context = self._normalize_pending_match_context(message=message, match_context=match_context)
|
||
if self._try_resolve_cancelled_message_without_order_id(
|
||
cookie_id=cookie_id,
|
||
msg_time=msg_time,
|
||
new_status=new_status,
|
||
context_label=send_message,
|
||
match_context=pending_match_context
|
||
):
|
||
return True
|
||
|
||
logger.info(f'[{msg_time}] 【{cookie_id}】{send_message},暂时无法提取订单ID,添加到待处理队列')
|
||
|
||
# 创建一个临时的订单ID占位符,用于标识这个待处理的状态更新
|
||
temp_order_id = f"temp_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
|
||
|
||
# 添加到待处理队列,使用特殊标记
|
||
self._add_to_pending_updates(
|
||
order_id=temp_order_id,
|
||
new_status=new_status,
|
||
cookie_id=cookie_id,
|
||
context=f"{send_message} - {msg_time} - 等待订单ID提取"
|
||
)
|
||
|
||
# 添加到待处理的系统消息队列
|
||
if cookie_id not in self._pending_system_messages:
|
||
self._pending_system_messages[cookie_id] = []
|
||
|
||
self._pending_system_messages[cookie_id].append({
|
||
'message': message,
|
||
'send_message': send_message,
|
||
'cookie_id': cookie_id,
|
||
'msg_time': msg_time,
|
||
'new_status': new_status,
|
||
'temp_order_id': temp_order_id,
|
||
'message_hash': pending_match_context.get('message_hash'),
|
||
'sid': pending_match_context.get('sid'),
|
||
'buyer_id': pending_match_context.get('buyer_id'),
|
||
'item_id': pending_match_context.get('item_id'),
|
||
'message_timestamp_ms': self._extract_message_timestamp_ms(message),
|
||
'timestamp': time.time() # 添加时间戳用于清理
|
||
})
|
||
logger.info(
|
||
f"[{msg_time}] 【{cookie_id}】{send_message} 已进入严格待处理队列: "
|
||
f"{self._format_pending_match_context(pending_match_context)}"
|
||
)
|
||
|
||
return True
|
||
|
||
# 获取对应的状态(new_status已经在上面通过_check_refund_message或message_status_mapping确定了)
|
||
|
||
# 检查当前订单状态,避免不合理的状态回退
|
||
from db_manager import db_manager
|
||
current_order = db_manager.get_order_by_id(order_id)
|
||
|
||
# 如果订单存在,检查是否需要忽略这次状态更新
|
||
if current_order and current_order.get('order_status'):
|
||
current_status = current_order.get('order_status')
|
||
current_priority = self._get_status_priority(current_status)
|
||
new_priority = self._get_status_priority(new_status)
|
||
|
||
# 如果新状态的优先级低于当前状态,且不是特殊状态(退款、取消),则忽略
|
||
if new_priority < current_priority and new_status not in ['refunding', 'cancelled', 'refund_cancelled']:
|
||
logger.warning(f'[{msg_time}] 【{cookie_id}】{send_message},订单 {order_id} 当前状态为 {current_status},忽略回退到 {new_status}')
|
||
return True # 返回True表示已处理,但实际上是忽略
|
||
|
||
# 更新订单状态
|
||
success = self.update_order_status(
|
||
order_id=order_id,
|
||
new_status=new_status,
|
||
cookie_id=cookie_id,
|
||
context=f"{send_message} - {msg_time}"
|
||
)
|
||
|
||
if success:
|
||
status_text = self.status_mapping.get(new_status, new_status)
|
||
logger.info(f'[{msg_time}] 【{cookie_id}】{send_message},订单 {order_id} 状态已更新为{status_text}')
|
||
else:
|
||
logger.error(f'[{msg_time}] 【{cookie_id}】{send_message},但订单 {order_id} 状态更新失败')
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f'[{msg_time}] 【{cookie_id}】处理系统消息订单状态更新时出错: {str(e)}')
|
||
return False
|
||
|
||
def handle_red_reminder_message(self, message: dict, red_reminder: str, user_id: str, cookie_id: str, msg_time: str,
|
||
match_context: Dict[str, Any] = None) -> bool:
|
||
"""处理红色提醒消息并更新订单状态
|
||
|
||
Args:
|
||
message: 原始消息数据
|
||
red_reminder: 红色提醒内容
|
||
user_id: 用户ID
|
||
cookie_id: Cookie ID
|
||
msg_time: 消息时间
|
||
|
||
Returns:
|
||
bool: 是否处理了订单状态更新
|
||
"""
|
||
try:
|
||
# 只处理交易关闭的情况
|
||
if red_reminder != '交易关闭':
|
||
return False
|
||
|
||
# 提取订单ID
|
||
order_id = self.extract_order_id(message)
|
||
if not order_id:
|
||
# 如果无法提取订单ID,根据配置决定是否添加到待处理队列
|
||
if not self.config.get('use_pending_queue', True):
|
||
logger.error(f'[{msg_time}] 【{cookie_id}】交易关闭,无法提取订单ID且未启用待处理队列,跳过处理')
|
||
return False
|
||
|
||
pending_match_context = self._normalize_pending_match_context(message=message, match_context=match_context)
|
||
if self._try_resolve_cancelled_message_without_order_id(
|
||
cookie_id=cookie_id,
|
||
msg_time=msg_time,
|
||
new_status='cancelled',
|
||
context_label='交易关闭',
|
||
match_context=pending_match_context
|
||
):
|
||
return True
|
||
|
||
logger.info(f'[{msg_time}] 【{cookie_id}】交易关闭,暂时无法提取订单ID,添加到待处理队列')
|
||
|
||
# 创建一个临时的订单ID占位符,用于标识这个待处理的状态更新
|
||
temp_order_id = f"temp_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
|
||
|
||
# 添加到待处理队列,使用特殊标记
|
||
self._add_to_pending_updates(
|
||
order_id=temp_order_id,
|
||
new_status='cancelled',
|
||
cookie_id=cookie_id,
|
||
context=f"交易关闭 - 用户{user_id} - {msg_time} - 等待订单ID提取"
|
||
)
|
||
|
||
# 添加到待处理的红色提醒消息队列
|
||
if cookie_id not in self._pending_red_reminder_messages:
|
||
self._pending_red_reminder_messages[cookie_id] = []
|
||
|
||
self._pending_red_reminder_messages[cookie_id].append({
|
||
'message': message,
|
||
'red_reminder': red_reminder,
|
||
'user_id': user_id,
|
||
'cookie_id': cookie_id,
|
||
'msg_time': msg_time,
|
||
'new_status': 'cancelled',
|
||
'temp_order_id': temp_order_id,
|
||
'message_hash': pending_match_context.get('message_hash'),
|
||
'sid': pending_match_context.get('sid'),
|
||
'buyer_id': pending_match_context.get('buyer_id'),
|
||
'item_id': pending_match_context.get('item_id'),
|
||
'message_timestamp_ms': self._extract_message_timestamp_ms(message),
|
||
'timestamp': time.time() # 添加时间戳用于清理
|
||
})
|
||
logger.info(
|
||
f"[{msg_time}] 【{cookie_id}】交易关闭已进入严格待处理队列: "
|
||
f"{self._format_pending_match_context(pending_match_context)}"
|
||
)
|
||
|
||
return True
|
||
|
||
# 更新订单状态为已关闭
|
||
success = self.update_order_status(
|
||
order_id=order_id,
|
||
new_status='cancelled',
|
||
cookie_id=cookie_id,
|
||
context=f"交易关闭 - 用户{user_id} - {msg_time}"
|
||
)
|
||
|
||
if success:
|
||
logger.info(f'[{msg_time}] 【{cookie_id}】交易关闭,订单 {order_id} 状态已更新为已关闭')
|
||
else:
|
||
logger.error(f'[{msg_time}] 【{cookie_id}】交易关闭,但订单 {order_id} 状态更新失败')
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f'[{msg_time}] 【{cookie_id}】处理交易关闭订单状态更新时出错: {str(e)}')
|
||
return False
|
||
|
||
def handle_red_reminder_order_status(self, red_reminder: str, message: dict, user_id: str, cookie_id: str, msg_time: str,
|
||
match_context: Dict[str, Any] = None) -> bool:
|
||
"""兼容旧调用入口,统一委托到红色提醒状态处理逻辑。"""
|
||
return self.handle_red_reminder_message(
|
||
message=message,
|
||
red_reminder=red_reminder,
|
||
user_id=user_id,
|
||
cookie_id=cookie_id,
|
||
msg_time=msg_time,
|
||
match_context=match_context
|
||
)
|
||
|
||
def handle_auto_delivery_order_status(self, order_id: str, cookie_id: str, context: str = "自动发货") -> bool:
|
||
"""处理自动发货时的订单状态更新
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
cookie_id: Cookie ID
|
||
context: 上下文信息
|
||
|
||
Returns:
|
||
bool: 更新是否成功
|
||
"""
|
||
return self.update_order_status(
|
||
order_id=order_id,
|
||
new_status='shipped', # 已发货
|
||
cookie_id=cookie_id,
|
||
context=context
|
||
)
|
||
|
||
def handle_order_basic_info_status(self, order_id: str, cookie_id: str, context: str = "基本信息保存") -> bool:
|
||
"""处理订单基本信息保存时的状态设置
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
cookie_id: Cookie ID
|
||
context: 上下文信息
|
||
|
||
Returns:
|
||
bool: 更新是否成功
|
||
"""
|
||
return self.update_order_status(
|
||
order_id=order_id,
|
||
new_status='processing', # 处理中
|
||
cookie_id=cookie_id,
|
||
context=context
|
||
)
|
||
|
||
def handle_order_detail_fetched_status(self, order_id: str, cookie_id: str, context: str = "详情已获取") -> bool:
|
||
"""处理订单详情拉取后的状态设置
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
cookie_id: Cookie ID
|
||
context: 上下文信息
|
||
|
||
Returns:
|
||
bool: 更新是否成功
|
||
"""
|
||
logger.info(f"🔄 订单状态处理器.handle_order_detail_fetched_status开始: order_id={order_id}, cookie_id={cookie_id}, context={context}")
|
||
|
||
# 订单详情获取成功后,不需要改变状态,只是处理待处理队列
|
||
logger.info(f"✅ 订单详情已获取,处理待处理队列: order_id={order_id}")
|
||
return True
|
||
|
||
def on_order_details_fetched(self, order_id: str):
|
||
"""当主程序拉取到订单详情后调用此方法处理待处理的更新
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
"""
|
||
logger.info(f"🔄 订单状态处理器.on_order_details_fetched开始: order_id={order_id}")
|
||
|
||
# 检查是否启用待处理队列
|
||
if not self.config.get('use_pending_queue', True):
|
||
logger.info(f"⏭️ 订单 {order_id} 详情已拉取,但未启用待处理队列,跳过处理")
|
||
return
|
||
|
||
logger.info(f"✅ 待处理队列已启用,检查订单 {order_id} 的待处理更新")
|
||
|
||
with self._lock:
|
||
if order_id in self.pending_updates:
|
||
logger.info(f"📝 检测到订单 {order_id} 详情已拉取,开始处理待处理的状态更新")
|
||
# 注意:process_pending_updates 内部也有锁,这里需要先释放锁避免死锁
|
||
updates = self.pending_updates.pop(order_id)
|
||
logger.info(f"📊 订单 {order_id} 有 {len(updates)} 个待处理更新")
|
||
else:
|
||
logger.info(f"ℹ️ 订单 {order_id} 没有待处理的更新")
|
||
return
|
||
|
||
# 在锁外处理更新,避免死锁
|
||
if 'updates' in locals():
|
||
logger.info(f"🔄 开始处理订单 {order_id} 的 {len(updates)} 个待处理更新")
|
||
self._process_updates_outside_lock(order_id, updates)
|
||
logger.info(f"✅ 订单 {order_id} 的待处理更新处理完成")
|
||
|
||
def _process_updates_outside_lock(self, order_id: str, updates: list):
|
||
"""在锁外处理更新,避免死锁
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
updates: 更新列表
|
||
"""
|
||
processed_count = 0
|
||
|
||
for update_info in updates:
|
||
try:
|
||
success = self.update_order_status(
|
||
order_id=order_id,
|
||
new_status=update_info['new_status'],
|
||
cookie_id=update_info['cookie_id'],
|
||
context=f"待处理队列: {update_info['context']}"
|
||
)
|
||
|
||
if success:
|
||
processed_count += 1
|
||
logger.info(f"处理待处理更新成功: 订单 {order_id} -> {update_info['new_status']}")
|
||
else:
|
||
logger.error(f"处理待处理更新失败: 订单 {order_id} -> {update_info['new_status']}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理待处理更新时出错: {str(e)}")
|
||
|
||
if processed_count > 0:
|
||
logger.info(f"订单 {order_id} 共处理了 {processed_count} 个待处理状态更新")
|
||
|
||
def on_order_id_extracted(self, order_id: str, cookie_id: str, message: dict = None,
|
||
match_context: Dict[str, Any] = None):
|
||
"""当主程序成功提取到订单ID后调用此方法处理待处理的系统消息
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
cookie_id: Cookie ID
|
||
message: 原始消息(可选,用于匹配)
|
||
match_context: 结构化匹配键(message_hash/sid/buyer_id/item_id)
|
||
"""
|
||
logger.info(f"🔄 订单状态处理器.on_order_id_extracted开始: order_id={order_id}, cookie_id={cookie_id}")
|
||
|
||
with self._lock:
|
||
# 检查是否启用待处理队列
|
||
if not self.config.get('use_pending_queue', True):
|
||
logger.info(f"⏭️ 订单 {order_id} ID已提取,但未启用待处理队列,跳过处理")
|
||
return
|
||
|
||
normalized_match_context = self._normalize_pending_match_context(message=message, match_context=match_context)
|
||
logger.info(
|
||
f"🔗 订单 {order_id} 严格关联键: {self._format_pending_match_context(normalized_match_context)}"
|
||
)
|
||
|
||
logger.info(f"✅ 待处理队列已启用,检查账号 {cookie_id} 的待处理系统消息")
|
||
|
||
# 处理待处理的系统消息队列
|
||
if cookie_id in self._pending_system_messages and self._pending_system_messages[cookie_id]:
|
||
logger.info(f"📝 账号 {cookie_id} 有 {len(self._pending_system_messages[cookie_id])} 个待处理的系统消息")
|
||
pending_msg = None
|
||
discarded_msg = None
|
||
matched_index, match_mode = self._select_pending_message_index(
|
||
self._pending_system_messages[cookie_id],
|
||
normalized_match_context,
|
||
'系统消息'
|
||
)
|
||
if matched_index is not None:
|
||
candidate_msg = self._pending_system_messages[cookie_id][matched_index]
|
||
if self._should_bind_pending_terminal_message(candidate_msg, message):
|
||
pending_msg = self._pending_system_messages[cookie_id].pop(matched_index)
|
||
logger.info(
|
||
f"✅ 严格匹配到待处理的系统消息: {pending_msg['send_message']} "
|
||
f"(mode={match_mode}, {self._format_pending_match_context(normalized_match_context)})"
|
||
)
|
||
elif self._pending_terminal_message_already_resolved(
|
||
order_id=order_id,
|
||
cookie_id=cookie_id,
|
||
pending_msg=candidate_msg,
|
||
match_context=normalized_match_context
|
||
):
|
||
discarded_msg = self._pending_system_messages[cookie_id].pop(matched_index)
|
||
logger.info(
|
||
f"🗑️ 丢弃已被旧订单消化的待处理系统消息: {discarded_msg['send_message']} "
|
||
f"(mode={match_mode}, {self._format_pending_match_context(normalized_match_context)})"
|
||
)
|
||
|
||
if pending_msg:
|
||
logger.info(f"🔄 开始处理待处理的系统消息: {pending_msg['send_message']}")
|
||
|
||
# 更新订单状态
|
||
success = self.update_order_status(
|
||
order_id=order_id,
|
||
new_status=pending_msg['new_status'],
|
||
cookie_id=cookie_id,
|
||
context=f"{pending_msg['send_message']} - {pending_msg['msg_time']} - 延迟处理"
|
||
)
|
||
|
||
if success:
|
||
status_text = self.status_mapping.get(pending_msg['new_status'], pending_msg['new_status'])
|
||
logger.info(f'✅ [{pending_msg["msg_time"]}] 【{cookie_id}】{pending_msg["send_message"]},订单 {order_id} 状态已更新为{status_text} (延迟处理)')
|
||
else:
|
||
logger.error(f'❌ [{pending_msg["msg_time"]}] 【{cookie_id}】{pending_msg["send_message"]},但订单 {order_id} 状态更新失败 (延迟处理)')
|
||
|
||
# 清理临时订单ID的待处理更新
|
||
self._clear_temp_pending_update(
|
||
pending_msg.get('temp_order_id'),
|
||
log_prefix="🗑️ "
|
||
)
|
||
|
||
# 如果队列为空,删除该账号的队列
|
||
if not self._pending_system_messages[cookie_id]:
|
||
del self._pending_system_messages[cookie_id]
|
||
logger.info(f"🗑️ 账号 {cookie_id} 的待处理系统消息队列已清空")
|
||
elif discarded_msg:
|
||
self._clear_temp_pending_update(
|
||
discarded_msg.get('temp_order_id'),
|
||
log_prefix="🗑️ "
|
||
)
|
||
if cookie_id in self._pending_system_messages and not self._pending_system_messages[cookie_id]:
|
||
del self._pending_system_messages[cookie_id]
|
||
logger.info(f"🗑️ 账号 {cookie_id} 的待处理系统消息队列已清空")
|
||
else:
|
||
logger.info(
|
||
f"ℹ️ 订单 {order_id} ID已提取,但严格关联未命中待处理系统消息,保留队列等待后续命中: "
|
||
f"mode={match_mode}, {self._format_pending_match_context(normalized_match_context)}"
|
||
)
|
||
else:
|
||
logger.info(f"ℹ️ 账号 {cookie_id} 没有待处理的系统消息")
|
||
|
||
# 处理待处理的红色提醒消息队列
|
||
if cookie_id in self._pending_red_reminder_messages and self._pending_red_reminder_messages[cookie_id]:
|
||
pending_msg = None
|
||
discarded_msg = None
|
||
matched_index, match_mode = self._select_pending_message_index(
|
||
self._pending_red_reminder_messages[cookie_id],
|
||
normalized_match_context,
|
||
'红色提醒'
|
||
)
|
||
if matched_index is not None:
|
||
candidate_msg = self._pending_red_reminder_messages[cookie_id][matched_index]
|
||
if self._should_bind_pending_terminal_message(candidate_msg, message):
|
||
pending_msg = self._pending_red_reminder_messages[cookie_id].pop(matched_index)
|
||
logger.info(
|
||
f"✅ 严格匹配到待处理的红色提醒消息: {pending_msg['red_reminder']} "
|
||
f"(mode={match_mode}, {self._format_pending_match_context(normalized_match_context)})"
|
||
)
|
||
elif self._pending_terminal_message_already_resolved(
|
||
order_id=order_id,
|
||
cookie_id=cookie_id,
|
||
pending_msg=candidate_msg,
|
||
match_context=normalized_match_context
|
||
):
|
||
discarded_msg = self._pending_red_reminder_messages[cookie_id].pop(matched_index)
|
||
logger.info(
|
||
f"🗑️ 丢弃已被旧订单消化的待处理红色提醒消息: {discarded_msg['red_reminder']} "
|
||
f"(mode={match_mode}, {self._format_pending_match_context(normalized_match_context)})"
|
||
)
|
||
|
||
if pending_msg:
|
||
logger.info(f"检测到订单 {order_id} ID已提取,开始处理待处理的红色提醒消息: {pending_msg['red_reminder']}")
|
||
|
||
# 更新订单状态
|
||
success = self.update_order_status(
|
||
order_id=order_id,
|
||
new_status=pending_msg['new_status'],
|
||
cookie_id=cookie_id,
|
||
context=f"{pending_msg['red_reminder']} - 用户{pending_msg['user_id']} - {pending_msg['msg_time']} - 延迟处理"
|
||
)
|
||
|
||
if success:
|
||
status_text = self.status_mapping.get(pending_msg['new_status'], pending_msg['new_status'])
|
||
logger.info(f'[{pending_msg["msg_time"]}] 【{cookie_id}】{pending_msg["red_reminder"]},订单 {order_id} 状态已更新为{status_text} (延迟处理)')
|
||
else:
|
||
logger.error(f'[{pending_msg["msg_time"]}] 【{cookie_id}】{pending_msg["red_reminder"]},但订单 {order_id} 状态更新失败 (延迟处理)')
|
||
|
||
# 清理临时订单ID的待处理更新
|
||
self._clear_temp_pending_update(pending_msg.get('temp_order_id'))
|
||
|
||
# 如果队列为空,删除该账号的队列
|
||
if not self._pending_red_reminder_messages[cookie_id]:
|
||
del self._pending_red_reminder_messages[cookie_id]
|
||
elif discarded_msg:
|
||
self._clear_temp_pending_update(discarded_msg.get('temp_order_id'))
|
||
if cookie_id in self._pending_red_reminder_messages and not self._pending_red_reminder_messages[cookie_id]:
|
||
del self._pending_red_reminder_messages[cookie_id]
|
||
else:
|
||
logger.info(
|
||
f"ℹ️ 订单 {order_id} ID已提取,但严格关联未命中待处理红色提醒消息,保留队列等待后续命中: "
|
||
f"mode={match_mode}, {self._format_pending_match_context(normalized_match_context)}"
|
||
)
|
||
|
||
|
||
# 创建全局实例
|
||
order_status_handler = OrderStatusHandler()
|