sms_util.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import enum
  2. import os
  3. import json
  4. from typing import Optional
  5. from collections import namedtuple
  6. from functools import lru_cache
  7. from alibabacloud_credentials.client import Client as CredentialClient
  8. from alibabacloud_credentials.models import Config as CredentialConfig
  9. from alibabacloud_tea_openapi import models as open_api_models
  10. from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
  11. from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
  12. from alibabacloud_tea_util import models as util_models
  13. from pydantic import BaseModel, Field, AliasChoices
  14. from pydantic_settings import SettingsConfigDict, BaseSettings
  15. from app.config.path_conf import ENV_DIR
  16. _SmsTemplate = namedtuple("_SmsTemplate", ["template_code", "template_param_fn"])
  17. class SmsTemplateEnum(enum.Enum):
  18. VERIFICATION_CODE = _SmsTemplate(
  19. template_code="SMS_333796424",
  20. template_param_fn=lambda code: f'{{"code": "{code}"}}'
  21. )
  22. class SendSmsRequest(BaseModel):
  23. phone_numbers: str = Field(..., description="支持向不同的手机号码发送短信,手机号码之间以半角逗号(,)分隔。"
  24. "上限为 1000 个手机号码。批量发送相对于单条发送,及时性稍有延迟。"
  25. "验证码类型的短信,建议单条发送。")
  26. sign_name: str = Field(default="湖南钱程似锦技术服务", description="短信签名名称。")
  27. template_code: str = Field(..., description="短信模板 Code。")
  28. template_param: Optional[str] = Field(default=None, description="短信模板变量对应的实际值,请传入JSON 字符串。"
  29. "当您选择的模板内容含有变量时,此参数必填。参数个数应与模板内变量个数一致。")
  30. class AliyunConfig(BaseSettings):
  31. model_config = SettingsConfigDict(
  32. env_file=ENV_DIR / f".env.{os.getenv('ENVIRONMENT')}",
  33. env_file_encoding="utf-8",
  34. extra="ignore",
  35. case_sensitive=True,
  36. )
  37. access_key_id: str = Field(
  38. default=None, validation_alias=AliasChoices("ALIBABA_CLOUD_ACCESS_KEY_ID"), description="必填参数,从环境变量中获取AccessKey ID ")
  39. access_key_secret: str = Field(
  40. default=None, validation_alias=AliasChoices("ALIBABA_CLOUD_ACCESS_KEY_SECRET"), description="必填参数,从环境变量中获取AccessKey Secret")
  41. @staticmethod
  42. @lru_cache(maxsize=1)
  43. def get_credential_config() -> "AliyunConfig":
  44. """"""
  45. return AliyunConfig()
  46. class SmsSender:
  47. @classmethod
  48. def get_client(cls) -> Dysmsapi20170525Client:
  49. credentials_config = CredentialConfig(
  50. type='access_key',
  51. access_key_id=AliyunConfig.get_credential_config().access_key_id,
  52. access_key_secret=AliyunConfig.get_credential_config().access_key_secret,
  53. )
  54. credential = CredentialClient(config=credentials_config)
  55. config = open_api_models.Config(
  56. credential=credential,
  57. endpoint='dysmsapi.aliyuncs.com'
  58. )
  59. return Dysmsapi20170525Client(config)
  60. @classmethod
  61. async def send_sms(cls, sms_request: SendSmsRequest) -> None:
  62. client = SmsSender.get_client()
  63. send_sms_request = dysmsapi_20170525_models.SendSmsRequest(**sms_request.model_dump(exclude_none=True))
  64. runtime = util_models.RuntimeOptions()
  65. try:
  66. resp = await client.send_sms_with_options_async(send_sms_request, runtime)
  67. print(json.dumps(resp, default=str, indent=2))
  68. except Exception as error:
  69. # 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
  70. # 错误 message
  71. print(error.message)
  72. # 诊断地址
  73. print(error.data.get("Recommend"))