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]