This commit is contained in:
2026-06-11 10:56:10 +08:00
parent 0ed33b8b8d
commit 1746480e79
+81 -68
View File
@@ -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