1
This commit is contained in:
@@ -57,18 +57,82 @@ class DailyFileHandler(logging.FileHandler):
|
||||
self._file_handler = logging.FileHandler(
|
||||
self.baseFilename, mode="a", encoding=self.encoding
|
||||
)
|
||||
self._file_handler.setFormatter(self.formatter)
|
||||
self._current_date = date_str
|
||||
self.stream = self._file_handler.stream
|
||||
super().emit(record)
|
||||
if self._file_handler:
|
||||
self._file_handler.emit(record)
|
||||
else:
|
||||
super().emit(record)
|
||||
|
||||
|
||||
_handler = DailyFileHandler(_log_dir)
|
||||
_handler.setFormatter(logging.Formatter(
|
||||
"[%(asctime)s] %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
||||
))
|
||||
if not logger.handlers:
|
||||
logger.addHandler(_handler)
|
||||
# Add daily file handler
|
||||
_log_handler = DailyFileHandler(_log_dir)
|
||||
_log_formatter = logging.Formatter(
|
||||
"[%(asctime)s] %(levelname)s %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
_log_handler.setFormatter(_log_formatter)
|
||||
logger.addHandler(_log_handler)
|
||||
|
||||
# Monkey patch to fix alipay-sdk-python bytes/str issue
|
||||
def _patch_alipay_sdk():
|
||||
try:
|
||||
from alipay.aop.api.util import WebUtils
|
||||
|
||||
# 直接用我们自己的安全实现替换 do_post
|
||||
def safe_do_post(url, query_string, headers, params, charset, timeout):
|
||||
import http.client
|
||||
import socket
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parse_result = urlparse(url)
|
||||
if parse_result.scheme == 'https':
|
||||
conn = http.client.HTTPSConnection(
|
||||
parse_result.hostname,
|
||||
parse_result.port or 443,
|
||||
timeout=timeout
|
||||
)
|
||||
else:
|
||||
conn = http.client.HTTPConnection(
|
||||
parse_result.hostname,
|
||||
parse_result.port or 80,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
try:
|
||||
body = query_string.encode(charset) if params is None else params
|
||||
conn.request(
|
||||
'POST',
|
||||
parse_result.path + ('?' + parse_result.query if parse_result.query else ''),
|
||||
body,
|
||||
headers
|
||||
)
|
||||
response = conn.getresponse()
|
||||
if response.status == 200:
|
||||
response_body = response.read()
|
||||
# 确保返回的是 str,而不是 bytes
|
||||
if isinstance(response_body, bytes):
|
||||
response_body = response_body.decode(charset)
|
||||
return response_body
|
||||
else:
|
||||
response_body = response.read()
|
||||
if isinstance(response_body, bytes):
|
||||
response_body = response_body.decode(charset)
|
||||
raise Exception(f"invalid http status {response.status}, detail body: {response_body}")
|
||||
except socket.timeout:
|
||||
raise Exception("timeout")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# Apply patch - 直接替换,避免原函数的问题
|
||||
WebUtils.do_post = safe_do_post
|
||||
logger.info("Successfully replaced alipay-sdk-python WebUtils.do_post with safe implementation")
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to patch alipay-sdk-python: {e}")
|
||||
|
||||
# Apply the patch when module is loaded
|
||||
_patch_alipay_sdk()
|
||||
|
||||
# Orders pending payment for longer than this are auto-cancelled
|
||||
ORDER_EXPIRE_MINUTES = 5
|
||||
@@ -140,51 +204,6 @@ def _is_mock_mode(db_configs: dict[str, str]) -> bool:
|
||||
# ---------------------------------------------------------------------------
|
||||
_alipay_client = None
|
||||
_alipay_client_app_id = None
|
||||
_alipay_web_utils_patched = False
|
||||
|
||||
|
||||
def _patch_alipay_web_utils():
|
||||
"""Monkey-patch alipay SDK WebUtils.do_post to fix Python 3 bytes/str TypeError.
|
||||
|
||||
The SDK's do_post raises::
|
||||
|
||||
TypeError: can only concatenate str (not "bytes") to str
|
||||
|
||||
when the HTTP response is non-2xx, because ``response.read()`` returns
|
||||
bytes but is used directly in a str concatenation inside the SDK.
|
||||
"""
|
||||
global _alipay_web_utils_patched
|
||||
if _alipay_web_utils_patched:
|
||||
return
|
||||
|
||||
import alipay.aop.api.util.WebUtils as _web_utils
|
||||
|
||||
_original_do_post = _web_utils.do_post
|
||||
|
||||
def _patched_do_post(url, query_string, headers, params, charset, timeout):
|
||||
try:
|
||||
return _original_do_post(url, query_string, headers, params, charset, timeout)
|
||||
except TypeError as e:
|
||||
err_str = str(e)
|
||||
if "bytes" not in err_str and "str" not in err_str:
|
||||
raise
|
||||
|
||||
# SDK bug: response.read() returned bytes but was used in str concat.
|
||||
# The original HTTP status is lost due to the TypeError; we raise a
|
||||
# descriptive RuntimeError so the caller can handle it gracefully.
|
||||
try:
|
||||
from alipay.aop.api.util.WebUtils import THREAD_LOCAL
|
||||
uuid = THREAD_LOCAL.uuid
|
||||
except Exception:
|
||||
uuid = "???"
|
||||
raise RuntimeError(
|
||||
f"[{uuid}] Alipay HTTP request failed (non-2xx response). "
|
||||
f"The SDK raised a bytes/str TypeError. "
|
||||
f"URL: {url}"
|
||||
) from e
|
||||
|
||||
_web_utils.do_post = _patched_do_post
|
||||
_alipay_web_utils_patched = True
|
||||
|
||||
|
||||
def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway: str = ""):
|
||||
@@ -204,9 +223,6 @@ def _get_alipay_client(app_id: str, private_key: str, public_key: str, gateway:
|
||||
)
|
||||
return None
|
||||
|
||||
# Fix SDK's Python 3 bytes/str bug in WebUtils.do_post (once per process)
|
||||
_patch_alipay_web_utils()
|
||||
|
||||
config = AlipayClientConfig()
|
||||
config.server_url = gateway or "https://openapi.alipay.com/gateway.do"
|
||||
config.app_id = app_id
|
||||
@@ -422,16 +438,13 @@ def _create_alipay_order(order: PaymentOrder, db_configs: dict[str, str]) -> str
|
||||
)
|
||||
return None
|
||||
|
||||
except RuntimeError as e:
|
||||
# The patched WebUtils raises RuntimeError on non-2xx HTTP responses
|
||||
# (the original SDK would have raised a confusing TypeError). This is
|
||||
# expected — the Alipay gateway rejected the request for some reason.
|
||||
logger.warning(
|
||||
f"Alipay precreate HTTP error: order_no={order.order_no}, "
|
||||
f"detail={str(e)}"
|
||||
)
|
||||
return None
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# 处理 SDK 内部的 bytes/str 错误
|
||||
if "TypeError" in str(e) and ("bytes" in str(e) or "str" in str(e)):
|
||||
logger.error(
|
||||
f"Alipay SDK TypeError (bytes/str issue): order_no={order.order_no}, "
|
||||
f"error={str(e)}"
|
||||
)
|
||||
logger.exception(f"Alipay precreate exception: order_no={order.order_no}")
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user