Просмотр исходного кода

新增阿里云短信功能 放进commen里

mengqiankang 2 месяцев назад
Родитель
Сommit
a28f0ea541

+ 14 - 4
alien_gateway/config.py

@@ -1,4 +1,4 @@
-from pydantic_settings import BaseSettings
+from pydantic_settings import BaseSettings, SettingsConfigDict
 from typing import List
 from typing import List
 
 
 class Settings(BaseSettings):
 class Settings(BaseSettings):
@@ -23,12 +23,22 @@ class Settings(BaseSettings):
 
 
     # 下游服务地址
     # 下游服务地址
     STORE_BASE_URL: str = "http://127.0.0.1:8001"  # alien_store 服务地址
     STORE_BASE_URL: str = "http://127.0.0.1:8001"  # alien_store 服务地址
+
+    # 阿里云短信配置
+    ALIYUN_SMS_SIGN_NAME_CONTRACT: str = "爱丽恩严大连商务科技"
+    ALIYUN_SMS_TEMPLATE_CODE_CONTRACT: str = "SMS_501820309"
+    ALIYUN_ACCESS_KEY_ID: str = "LTAI5t77CS9gD7JMkMAjD2vF"
+    ALIYUN_ACCESS_KEY_SECRET: str = "jLYGPpaJuc7NqmRdLvu1ObAS9CJFB8"
+
+
+
     @property
     @property
     def SQLALCHEMY_DATABASE_URI(self) -> str:
     def SQLALCHEMY_DATABASE_URI(self) -> str:
         return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
         return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
 
 
-    class Config:
-        case_sensitive = True
-        env_file = ".env"
+    model_config = SettingsConfigDict(
+        case_sensitive=True,
+        env_file=".env",
+    )
 
 
 settings = Settings()
 settings = Settings()

+ 10 - 20
alien_util/tasks/contract_tasks.py

@@ -7,6 +7,7 @@ from sqlalchemy.orm import Session
 from alien_gateway.config import settings
 from alien_gateway.config import settings
 from alien_store.db.models.contract_store import ContractStore
 from alien_store.db.models.contract_store import ContractStore
 from alien_util.celery_app import celery_app
 from alien_util.celery_app import celery_app
+from common.aliyun_sms_server.sms_client import send_sms
 import logging
 import logging
 
 
 # 配置日志
 # 配置日志
@@ -82,9 +83,8 @@ def check_contract_expiry():
                         f"到期时间: {contract.expiry_time}, "
                         f"到期时间: {contract.expiry_time}, "
                         f"距离到期: {days_until_expiry} 天"
                         f"距离到期: {days_until_expiry} 天"
                     )
                     )
-                    
-                    # 发送提醒短信(暂时不实现,只记录日志)
-                    send_expiry_reminder_sms(contract.contact_phone, contract.merchant_name, days_until_expiry)
+
+                    send_expiry_reminder_sms(contract.contact_phone, contract.merchant_name, settings.ALIYUN_SMS_SIGN_NAME_CONTRACT, settings.ALIYUN_SMS_TEMPLATE_CODE_CONTRACT)
                     
                     
                 except Exception as e:
                 except Exception as e:
                     logger.error(f"处理合同ID {contract.id} 时出错: {e}", exc_info=True)
                     logger.error(f"处理合同ID {contract.id} 时出错: {e}", exc_info=True)
@@ -99,28 +99,18 @@ def check_contract_expiry():
         raise
         raise
 
 
 
 
-def send_expiry_reminder_sms(phone: str, merchant_name: str, days_until_expiry: int):
+def send_expiry_reminder_sms(phone: str, merchant_name: str, sign_name: str, template_code: str):
     """
     """
     发送合同到期提醒短信
     发送合同到期提醒短信
-    
     Args:
     Args:
         phone: 联系电话
         phone: 联系电话
         merchant_name: 商家名称
         merchant_name: 商家名称
         days_until_expiry: 距离到期的天数
         days_until_expiry: 距离到期的天数
     """
     """
-    # TODO: 实现短信发送功能
-    # 这里暂时只记录日志,后续可以接入短信服务商API
-    
-    message = (
-        f"【合同到期提醒】尊敬的{merchant_name},您的合同将在{days_until_expiry}天后到期,"
-        f"请及时续签。如有疑问,请联系客服。"
-    )
-    
-    logger.info(f"准备发送短信到 {phone}: {message}")
-    
-    # 示例:调用短信服务API
-    # sms_service.send(phone, message)
-    
-    # 暂时只记录日志
-    logger.info(f"[模拟] 已发送提醒短信到 {phone}")
+    template_param = {
+        "name": merchant_name,
+    }
+    send_sms(phone, template_param, sign_name, template_code)
+
+send_expiry_reminder_sms("17337039317", "", settings.ALIYUN_SMS_SIGN_NAME_CONTRACT, settings.ALIYUN_SMS_TEMPLATE_CODE_CONTRACT)
 
 

+ 78 - 0
common/aliyun_sms_server/aliyun_sms_server.py

@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+# This file is auto-generated, don't edit it. Thanks.
+import os
+import sys
+import json
+
+from typing import List
+
+from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
+from alibabacloud_credentials.client import Client as CredentialClient
+from alibabacloud_tea_openapi import models as open_api_models
+from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
+from alibabacloud_tea_util import models as util_models
+from alibabacloud_tea_util.client import Client as UtilClient
+
+
+class Sample:
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def create_client() -> Dysmsapi20170525Client:
+        """
+        使用凭据初始化账号 Client
+        @return: Client
+        @throws Exception
+        """
+        # 工程代码建议使用更安全的无 AK 方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378659.html。
+        credential = CredentialClient()
+        config = open_api_models.Config(
+            credential=credential
+        )
+        # Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
+        config.endpoint = f'dysmsapi.aliyuncs.com'
+        return Dysmsapi20170525Client(config)
+
+    @staticmethod
+    def main(
+        args: List[str],
+    ) -> None:
+        client = Sample.create_client()
+        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
+            phone_numbers='your_value',
+            sign_name='your_value'
+        )
+        try:
+            resp = client.send_sms_with_options(send_sms_request, util_models.RuntimeOptions())
+            print(json.dumps(resp, default=str, indent=2))
+        except Exception as error:
+            # 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
+            # 错误 message
+            print(error.message)
+            # 诊断地址
+            print(error.data.get("Recommend"))
+
+    @staticmethod
+    async def main_async(
+        args: List[str],
+    ) -> None:
+        client = Sample.create_client()
+        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
+            phone_numbers='your_value',
+            sign_name='your_value'
+        )
+        try:
+            resp = await client.send_sms_with_options_async(send_sms_request, util_models.RuntimeOptions())
+            print(json.dumps(resp, default=str, indent=2))
+        except Exception as error:
+            # 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
+            # 错误 message
+            print(error.message)
+            # 诊断地址
+            print(error.data.get("Recommend"))
+
+
+if __name__ == '__main__':
+    Sample.main(sys.argv[1:])
+

+ 100 - 0
common/aliyun_sms_server/sms_client.py

@@ -0,0 +1,100 @@
+import json
+import logging
+from datetime import datetime
+from typing import Dict, Optional
+
+from alibabacloud_tea_openapi import models as open_api_models
+from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
+from alibabacloud_tea_util import models as util_models
+
+from alien_gateway.config import settings
+
+logger = logging.getLogger(__name__)
+
+
+def _create_client():
+    access_key_id = getattr(settings, "ALIYUN_ACCESS_KEY_ID", None)
+    access_key_secret = getattr(settings, "ALIYUN_ACCESS_KEY_SECRET", None)
+    if not access_key_id or not access_key_secret:
+        raise ValueError("请设置 ALIYUN_ACCESS_KEY_ID 和 ALIYUN_ACCESS_KEY_SECRET")
+
+    config = open_api_models.Config(
+        access_key_id=access_key_id,
+        access_key_secret=access_key_secret,
+    )
+    config.endpoint = "dysmsapi.aliyuncs.com"
+    from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
+    return Dysmsapi20170525Client(config)
+
+
+def query_send_status(phone: str, biz_id: str, send_date: Optional[str] = None) -> None:
+    if not phone or not biz_id:
+        logger.warning("短信查询失败:phone 或 biz_id 为空")
+        return
+
+    if not send_date:
+        send_date = datetime.now().strftime("%Y%m%d")
+
+    try:
+        client = _create_client()
+        query_request = dysmsapi_20170525_models.QuerySendDetailsRequest(
+            phone_number=phone,
+            biz_id=biz_id,
+            send_date=send_date,
+            page_size=10,
+            current_page=1,
+        )
+        resp = client.query_send_details_with_options(query_request, util_models.RuntimeOptions())
+        # logger.info(
+        #     "短信查询响应 phone=%s code=%s message=%s total_count=%s",
+        #     phone,
+        #     getattr(resp.body, "code", None),
+        #     getattr(resp.body, "message", None),
+        #     getattr(resp.body, "total_count", None),
+        # )
+        # logger.info("短信查询详情 %s", getattr(resp.body, "sms_send_detail_dto_list", None))
+    except Exception as e:
+        logger.error(f"短信查询失败 phone={phone}: {e}", exc_info=True)
+
+
+def send_sms(phone: str, template_param: Dict[str, object], sign_name: str, template_code: str) -> None:
+    if not phone:
+        logger.warning("短信发送失败:联系电话为空")
+        return
+
+    sign_name = sign_name
+    template_code = template_code
+    if not sign_name or not template_code:
+        logger.error("短信配置缺失:请设置 ALIYUN_SMS_SIGN_NAME_CONTRACT/ALIYUN_SMS_TEMPLATE_CODE_CONTRACT")
+        return
+
+    try:
+        client = _create_client()
+        send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
+            phone_numbers=phone,
+            sign_name=sign_name,
+            template_code=template_code,
+            template_param=json.dumps(template_param, ensure_ascii=False),
+        )
+        resp = client.send_sms_with_options(send_sms_request, util_models.RuntimeOptions())
+        logger.info(
+            "商家入驻协议快到期提醒: 短信发送响应 phone=%s code=%s message=%s biz_id=%s request_id=%s",
+            phone,
+            getattr(resp.body, "code", None),
+            getattr(resp.body, "message", None),
+            getattr(resp.body, "biz_id", None),
+            getattr(resp.body, "request_id", None),
+        )
+        if getattr(resp.body, "code", None) != "OK":
+            logger.error(
+                "短信发送失败 phone=%s code=%s message=%s",
+                phone,
+                getattr(resp.body, "code", None),
+                getattr(resp.body, "message", None),
+            )
+        else:
+            biz_id = getattr(resp.body, "biz_id", None)
+            if biz_id:
+                query_send_status(phone, biz_id)
+    except Exception as e:
+        logger.error(f"短信发送失败 phone={phone}: {e}", exc_info=True)

Разница между файлами не показана из-за своего большого размера
+ 965 - 58
poetry.lock


+ 1 - 0
pyproject.toml

@@ -24,6 +24,7 @@ requests = "^2.32.5"
 aiomysql = "^0.3.2"
 aiomysql = "^0.3.2"
 datetime = "^6.0"
 datetime = "^6.0"
 celery = "^5.6.2"
 celery = "^5.6.2"
+alibabacloud-dysmsapi20170525 = "^4.4.0"
 
 
 [build-system]
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 requires = ["poetry-core>=1.0.0"]

Некоторые файлы не были показаны из-за большого количества измененных файлов