123 lines
3.8 KiB
Python
123 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import logging
|
|
import time
|
|
|
|
from .format import AliyunSmsFormatter, mask_phone
|
|
from .types import OutboundSmsResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
MAX_SMS_CHARS_UNICODE = 70
|
|
RESERVED_SIGN_CHARS = 8
|
|
|
|
|
|
class AliyunSmsOutbound:
|
|
def __init__(self, gateway):
|
|
self._gateway = gateway
|
|
self._formatter = AliyunSmsFormatter()
|
|
|
|
async def send_text(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
template_type: str | None = None,
|
|
) -> OutboundSmsResult:
|
|
account = self._gateway.account
|
|
|
|
segments = self._formatter.adapt_sms_content(content, max_segments=account.max_segments)
|
|
|
|
if not segments:
|
|
return OutboundSmsResult(
|
|
success=False,
|
|
message="内容为空",
|
|
code="EMPTY_CONTENT",
|
|
)
|
|
|
|
template_code = account.get_template_code(template_type)
|
|
if not template_code:
|
|
return OutboundSmsResult(
|
|
success=False,
|
|
message=f"未配置模板 CODE: template_type={template_type or account.default_template}",
|
|
code="NO_TEMPLATE_CODE",
|
|
)
|
|
|
|
results = []
|
|
|
|
for segment in segments:
|
|
template_param = {
|
|
"content": segment,
|
|
}
|
|
|
|
result = await self._gateway.client.send_sms(
|
|
phone_numbers=target_id,
|
|
sign_name=account.sign_name,
|
|
template_code=template_code,
|
|
template_param=template_param,
|
|
out_id=_generate_out_id(target_id, content),
|
|
)
|
|
results.append(result)
|
|
|
|
if not result.success:
|
|
logger.error(
|
|
"短信发送失败: phone=%s code=%s msg=%s",
|
|
mask_phone(target_id, account.phone_mask_enabled),
|
|
result.code,
|
|
result.message,
|
|
)
|
|
return result
|
|
|
|
final = results[-1] if results else results[0]
|
|
logger.info(
|
|
"短信发送成功: phone=%s biz_id=%s segments=%d",
|
|
mask_phone(target_id, account.phone_mask_enabled),
|
|
final.biz_id,
|
|
len(results),
|
|
)
|
|
return final
|
|
|
|
async def send_batch(
|
|
self,
|
|
phone_numbers: list[str],
|
|
contents: list[str],
|
|
template_type: str | None = None,
|
|
) -> list[OutboundSmsResult]:
|
|
if not phone_numbers:
|
|
return []
|
|
|
|
account = self._gateway.account
|
|
template_code = account.get_template_code(template_type)
|
|
if not template_code:
|
|
return [
|
|
OutboundSmsResult(
|
|
success=False,
|
|
message=f"未配置模板 CODE: {template_type or account.default_template}",
|
|
code="NO_TEMPLATE_CODE",
|
|
)
|
|
for _ in phone_numbers
|
|
]
|
|
|
|
formatted_contents = []
|
|
for content in contents:
|
|
segments = self._formatter.adapt_sms_content(content, max_segments=1)
|
|
formatted_contents.append(segments[0] if segments else "")
|
|
|
|
sign_names = [account.sign_name] * len(phone_numbers)
|
|
template_params = [{"content": c} for c in formatted_contents]
|
|
|
|
return await self._gateway.client.batch_send_sms(
|
|
phone_numbers=phone_numbers,
|
|
sign_names=sign_names,
|
|
template_code=template_code,
|
|
template_params=template_params,
|
|
)
|
|
|
|
def chunker(self, text: str, max_chars: int = MAX_SMS_CHARS_UNICODE - RESERVED_SIGN_CHARS) -> list[str]:
|
|
return self._formatter.chunker(text, max_chars)
|
|
|
|
|
|
def _generate_out_id(target_id: str, content: str) -> str:
|
|
raw = f"{target_id}:{content}:{int(time.time() / 60)}"
|
|
return hashlib.sha256(raw.encode()).hexdigest()[:16]
|