布衣人家 發表於 2025-11-13 11:53:00

在基于FastAPI的Python开发框架后端,增加阿里云短信和邮件发送通知处理

<p>在一些业务系统中,整合短信和邮件通知是一种常见的处理方式,之前我在多篇随笔中介绍过基于.NET的整合处理,本篇随笔介绍基于FastAPI的Python开发框架后端,增加阿里云短信和邮件发送通知处理。</p>
<h3>1、短信通知处理的介绍</h3>
<p>之前我在多篇随笔中介绍过基于.NET的《SqlSugar开发框架》中整合过短信接入的内容:《<span style="font-size: 12px">使用阿里云的短信服务发送短信</span>》、《基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理》、《循序渐进VUE+Element 前端应用开发(32)--- 手机短信动态码登陆处理》。</p>
<p>短信通知,一般用于系统的登录,或者对重要数据变更的身份确认,各个平台都相关的短信接口,不过好像华为服务器已经不提供短信接入了,阿里云还可以,或者也可以找一些其他的短信服务商,基本上都会提供相应的接口或者SDK,对接还是很方便的。</p>
<p>本篇随笔基于阿里云的短信接口进行对接短信通知,也主要就是解决BS端 或者H5端的身份登录及密码修改、重置等重要处理的通知。</p>
<p>对于Python开发来说,基于阿里云的短信处理,可以使用它的&nbsp;alibabacloud_dysmsapi20170525 SDK包来进行对接,虽然这个包时间上比较老,好像也没有看到更新的SDK了。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202511/8867-20251113111817633-1978375735.png" alt="image" loading="lazy"></p>
<p>使用pip 命令安装了该SDK即可。</p>
<div class="cnblogs_code">
<pre>pip <span style="color: rgba(0, 0, 255, 1)">install</span> alibabacloud_dysmsapi20170525</pre>
</div>
<p>我们使用阿里云API发送短信,一般需要提供下面的身份信息。</p>
<p>使用阿里云的短信服务,需要注册登录自己的阿里云控制台,然后进入AccessKeys的处理界面</p>
<p><img src="https://img2018.cnblogs.com/blog/8867/201904/8867-20190410105810429-492056133.png" alt=""></p>
<p>这里我们获取到AccessKey ID 和Access Key Secret两个关键信息,需要用在数据签名的里面的。</p>
<p>发送接口还需要提供下面的的一些信息,包括必要的手机号码,签名,服务器端模板代码,以及短信码等必要信息。</p>
<p>签名,一般为我们短信提示的公司名称,如【广州爱奇迪】这样的字样。</p>
<p><img src="https://img2018.cnblogs.com/blog/8867/201904/8867-20190410110210242-1598268385.png" alt="" class="medium-zoom-image"></p>
<p>服务器端模板代码,阿里云默认提供了一些基础模板,我们可以从中选择。</p>
<p><img src="https://img2018.cnblogs.com/blog/8867/201904/8867-20190410112024695-1167915169.png" alt="" class="medium-zoom-image"></p>
<p>如下具体接口请求需要提供的JSON数据。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phone_numbers</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">13800138000</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sign_name</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">YourSignName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">template_code</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SMS_123456789</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">template_param</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">code</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123456</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p>短信真实接收到的效果如下。</p>
<p><img src="https://img2018.cnblogs.com/blog/8867/201904/8867-20190410113014515-1725476367.png" alt="" class="medium-zoom-image"></p>
<p>&nbsp;</p>
<h3>2、在基于FastAPI的Python开发框架后端整合接口发送短信</h3>
<p>上面了解了短信的处理大致的内容,我们就需要整合它进行短信的发送了。</p>
<p>首先我们为了方便,需要在项目的配置文件.env 中增加配置文件,方便统一使用。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202511/8867-20251113113046388-1382017109.png" alt="image" loading="lazy"></p>
<p>&nbsp;为了能够在多个地方使用,我们对短信发送的处理进行简单的封装一下,如下所示的辅助类,初始化的时候,提供相应的配置的参数即可。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">from</span> alibabacloud_dysmsapi20170525.client <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> Client as Dysmsapi20170525Client
</span><span style="color: rgba(0, 0, 255, 1)">from</span> alibabacloud_tea_openapi <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> models as open_api_models
</span><span style="color: rgba(0, 0, 255, 1)">from</span> alibabacloud_dysmsapi20170525 <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> models as dysmsapi_20170525_models
</span><span style="color: rgba(0, 0, 255, 1)">from</span> alibabacloud_tea_util <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> models as util_models
</span><span style="color: rgba(0, 0, 255, 1)">from</span> alibabacloud_tea_util.client <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> Client as UtilClient

</span><span style="color: rgba(0, 0, 255, 1)">from</span> typing <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> List, Dict, Any


</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> SMSHelper:
    </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 阿里云短信服务工具类 </span><span style="color: rgba(128, 0, 0, 1)">"""</span>

    <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self, access_key_id: str, access_key_secret: str,
               sign_name: str, template_code: str, endpoint : str </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">dysmsapi.aliyuncs.com</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)"> ):
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      初始化阿里云短信服务客户端
      
      :param access_key_id: 阿里云访问密钥 ID
      :param access_key_secret: 阿里云访问密钥 Secret
      :param endpoint: 阿里云 API 网关地址
      :param sign_name: 短信签名
      :param template_code: 短信模板代码
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span>
      
      <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span> access_key_id <span style="color: rgba(0, 0, 255, 1)">or</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> access_key_secret:
            </span><span style="color: rgba(0, 0, 255, 1)">raise</span> ValueError(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">没有设置阿里云访问凭据,请设置环境变量或在代码中设置</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
      
      config </span>=<span style="color: rgba(0, 0, 0, 1)"> open_api_models.Config(
            access_key_id</span>=<span style="color: rgba(0, 0, 0, 1)">access_key_id,
            access_key_secret</span>=<span style="color: rgba(0, 0, 0, 1)">access_key_secret,
            endpoint</span>=<span style="color: rgba(0, 0, 0, 1)">endpoint)
      
      self.client </span>=<span style="color: rgba(0, 0, 0, 1)"> Dysmsapi20170525Client(config)
      self.sign_name </span>=<span style="color: rgba(0, 0, 0, 1)"> sign_name
      self.template_code </span>= template_code</pre>
</div>
<p>初始化后,就可以调用参数进行发送短信了</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">def</span> send_sms(self, phone_numbers: str, template_param: Dict = None) -&gt;<span style="color: rgba(0, 0, 0, 1)"> Dict:
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      使用阿里云 SMS 服务发送短信
      
      Args:
            phone_numbers: 短信接收号码
            template_param: 短信模板参数,字典形式
      
      Returns:
            短信发送结果,字典形式
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span>
      
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 创建 request 对象</span>
      send_sms_request =<span style="color: rgba(0, 0, 0, 1)"> dysmsapi_20170525_models.SendSmsRequest(
            phone_numbers</span>=<span style="color: rgba(0, 0, 0, 1)">phone_numbers,
            sign_name</span>=<span style="color: rgba(0, 0, 0, 1)"> self.sign_name,
            template_code</span>=<span style="color: rgba(0, 0, 0, 1)">self.template_code,
            template_param</span>=str(template_param) <span style="color: rgba(0, 0, 255, 1)">if</span> template_param <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> None
      )
      
      response </span>=<span style="color: rgba(0, 0, 0, 1)"> {}
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 发送短信</span>
            result =<span style="color: rgba(0, 0, 0, 1)"> self.client.send_sms_with_options(send_sms_request, util_models.RuntimeOptions())
            </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 转换结果为字典形式</span>
            response =<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">success</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: True,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">message</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SMS 发送成功</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">request_id</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: result.body.request_id,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">code</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: result.body.code,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">message</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: result.body.message,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">biz_id</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: result.body.biz_id
            }
      </span><span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)"> Exception as error:
            response </span>=<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">success</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: False,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">message</span><span style="color: rgba(128, 0, 0, 1)">'</span>: str(error.message) <span style="color: rgba(0, 0, 255, 1)">if</span> hasattr(error, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">message</span><span style="color: rgba(128, 0, 0, 1)">'</span>) <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> str(error),
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">recommend</span><span style="color: rgba(128, 0, 0, 1)">'</span>: error.data.get(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Recommend</span><span style="color: rgba(128, 0, 0, 1)">"</span>) <span style="color: rgba(0, 0, 255, 1)">if</span> hasattr(error, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">data</span><span style="color: rgba(128, 0, 0, 1)">'</span>) <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> None
            }
      
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> response</pre>
</div>
<p>完成了上面的简单封装,就可以再API的控制器端进行短信处理了。</p>
<p>如我们在登录login的EndPoint(类似C#的控制器类)中需要先初始化短信的服务辅助类。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202511/8867-20251113113642840-161364699.png" alt="image" loading="lazy"></p>
<p>上面也介绍过,短信主要就是解决BS端 或者H5端的身份登录及密码修改、重置等重要处理的通知。</p>
<p>我们在以手机号码和短信号码登录的时候,需要先发送短信,然后短信会在服务端通过Redis的缓存驻留几分钟,这几分钟内容,通过手机和验证码即可登录,登录后验证码失效,如果超时验证码也失效。</p>
<p>通过手机号码发送短信的过程如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@router.post(
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/send-login-smscode</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    summary</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">发送登录验证码</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    response_model</span>=AjaxResponse,
)
async </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> send_login_smscode(
    input: Annotated,
    request: Request,
    db: AsyncSession </span>=<span style="color: rgba(0, 0, 0, 1)"> Depends(get_db),
):
    ip </span>=<span style="color: rgba(0, 0, 0, 1)"> await get_ip(request)

    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 校验手机号码是否合法</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span> input.phonenumber.isdigit() <span style="color: rgba(0, 0, 255, 1)">or</span> len(input.phonenumber) != 11<span style="color: rgba(0, 0, 0, 1)">:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">手机号码格式不正确</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)

    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 校验用户是否存在</span>
    user = await user_crud.get_by_column(db, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">mobilephone</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, input.phonenumber)
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> user:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">用户不存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)

    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 生成6位数字验证码</span>
    code = RandomUtil.random_digit_string(6<span style="color: rgba(0, 0, 0, 1)">)

    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 发送短信验证码</span>
    res =<span style="color: rgba(0, 0, 0, 1)"> sms_helper.send_sms(
      phone_numbers</span>=<span style="color: rgba(0, 0, 0, 1)">input.phonenumber,
      template_param</span>={<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">code</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: code},
    )

    success </span>= res.get(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">success</span><span style="color: rgba(128, 0, 0, 1)">"</span>, False) ==<span style="color: rgba(0, 0, 0, 1)"> True
    message </span>= res.get(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">message</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> success:
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">以手机号码作为键存储验证码缓存</span>
      cache_key =<span style="color: rgba(0, 0, 0, 1)"> input.phonenumber.strip()
      cache_item </span>=<span style="color: rgba(0, 0, 0, 1)"> SmsLoginCodeCacheItem(
            phonenumber</span>=<span style="color: rgba(0, 0, 0, 1)">input.phonenumber.strip(),
            code</span>=<span style="color: rgba(0, 0, 0, 1)">code,
      ).model_dump()

      redis_helper </span>= RedisHelper(client =<span style="color: rgba(0, 0, 0, 1)">redis_client)
      await redis_helper.set(cache_key, cache_item, </span>60 * settings.SMS_EXPIRED_MINUTES)<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 默认5分钟过期</span>

    <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 短信验证码发送结果</span>
    result = CommonResult(success=success, errormessage=<span style="color: rgba(0, 0, 0, 1)">message)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> AjaxResponse(result) </pre>
</div>
<p>上面短信发送后,号码机验证码会驻留在Redis的缓存中一段时间,那么此时如果使用手机验证码进行登录即可匹配到。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@router.post(
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/authenticate-byphone</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    summary</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">手机短信验证码登录授权处理</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
    response_model</span>=<span style="color: rgba(0, 0, 0, 1)">AjaxResponse,
)
async </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> authenticate_by_phone(
    input: Annotated,
    request: Request,
    db: AsyncSession </span>=<span style="color: rgba(0, 0, 0, 1)"> Depends(get_db),
):
    ip </span>=<span style="color: rgba(0, 0, 0, 1)"> await get_ip(request)
    auth_result </span>= AuthenticateResultDto(ip=ip, success=<span style="color: rgba(0, 0, 0, 1)">False)
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 从缓存中获取验证码</span>
    redis_helper = RedisHelper(client=<span style="color: rgba(0, 0, 0, 1)">redis_client)
    cache_key </span>=<span style="color: rgba(0, 0, 0, 1)"> input.phonenumber.strip()
   
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span> cache_key.isdigit() <span style="color: rgba(0, 0, 255, 1)">or</span> len(cache_key) != 11<span style="color: rgba(0, 0, 0, 1)">:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">手机号码格式不正确</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
   
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 校验验证码是否正确</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span> input.smscode.isdigit() <span style="color: rgba(0, 0, 255, 1)">or</span> len(input.smscode) != 6<span style="color: rgba(0, 0, 0, 1)">:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">验证码格式不正确</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)

    cache_item </span>=<span style="color: rgba(0, 0, 0, 1)"> await redis_helper.get(cache_key)
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> cache_item:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">验证码已过期或不存在,请重新获取</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
   
    cache_item </span>= SmsLoginCodeCacheItem(**<span style="color: rgba(0, 0, 0, 1)">cache_item)
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> cache_item.code !=<span style="color: rgba(0, 0, 0, 1)"> input.smscode:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">验证码不正确,请重新输入</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
   
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 校验用户是否存在</span>
    user = await user_crud.get_by_column(db, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">mobilephone</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, input.phonenumber)
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> user:
      </span><span style="color: rgba(0, 0, 255, 1)">raise</span> CustomExceptionBase(detail=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">用户不存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
   
    </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 验证码正确,继续登录流程</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span> cache_item <span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> user:
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">获取用户角色类型</span>
      auth_result.roletype =<span style="color: rgba(0, 0, 0, 1)"> await role_crud.get_user_roletype(
            db, user.id
      )</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 超级管理员、系统管理员、其他</span>

      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 根据用户身份生成tokenresult</span>
      auth_result.expires = int((datetime.utcnow() + timedelta(seconds=<span style="color: rgba(0, 0, 0, 1)">settings.TOKEN_EXPIRE_SECONDS)).timestamp())
      auth_result.userid </span>=<span style="color: rgba(0, 0, 0, 1)"> user.id
      auth_result.name </span>=<span style="color: rgba(0, 0, 0, 1)"> user.name
      auth_result.success </span>=<span style="color: rgba(0, 0, 0, 1)"> True
      auth_result.accesstoken </span>= await generate_token(vars(user), role_type=<span style="color: rgba(0, 0, 0, 1)">auth_result.roletype)

      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">移除缓存短信键值</span>
<span style="color: rgba(0, 0, 0, 1)">      await redis_helper.delete(cache_key)
    </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">:
      auth_result.error </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">登录失败,无法生成令牌</span><span style="color: rgba(128, 0, 0, 1)">"</span>

    <span style="color: rgba(0, 0, 255, 1)">return</span> AjaxResponse(auth_result)</pre>
</div>
<p>其他的重置密码,修改密码,修改重要信息等通知的处理也是类似的处理过程,不在赘述。</p>
<p>&nbsp;</p>
<h3>3、在基于FastAPI的Python开发框架中整合邮件发送</h3>
<p>我们通过pip命令安装fastapi_mail组件进行邮件发送的处理。</p>
<div class="cnblogs_code">
<pre>pip installfastapi_mail</pre>
</div>
<p>邮件发送,一般也是基于模板文件的方式,通过对模板文件的变量进行变化,实现内容的发送过程。</p>
<p>我们的模板路径如下所示,里面有类似下面的几个模板文件,如果需要更多场合的右键,可以进行不同的模板编写即可。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">   templates/</span><span style="color: rgba(0, 128, 0, 1)">
#</span><span style="color: rgba(0, 128, 0, 1)">   email/</span><span style="color: rgba(0, 128, 0, 1)">
#</span><span style="color: rgba(0, 128, 0, 1)">       welcome.html</span><span style="color: rgba(0, 128, 0, 1)">
#</span><span style="color: rgba(0, 128, 0, 1)">       reset_password.html</span></pre>
</div>
<p>增加一个EmailHelper.py的辅助类简单处理下,方便后续的邮件发送处理。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">from</span> typing <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> List, Optional, Dict
</span><span style="color: rgba(0, 0, 255, 1)">from</span> fastapi.templating <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> Jinja2Templates
</span><span style="color: rgba(0, 0, 255, 1)">from</span> fastapi_mail <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> FastMail, MessageSchema, ConnectionConfig, MessageType
</span><span style="color: rgba(0, 0, 255, 1)">from</span> fastapi <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> BackgroundTasks
</span><span style="color: rgba(0, 0, 255, 1)">from</span> jinja2 <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> Environment, FileSystemLoader, select_autoescape

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> EmailHelper:
    </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 邮件发送辅助类,基于 fastapi-mail </span><span style="color: rgba(128, 0, 0, 1)">"""</span>

    <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(
      self,
      username: str,
      password: str,
      mail_from: str,
      server: str </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">smtp.example.com</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
      port: int </span>= 587<span style="color: rgba(0, 0, 0, 1)">,
      use_tls: bool </span>=<span style="color: rgba(0, 0, 0, 1)"> True,
      use_ssl: bool </span>=<span style="color: rgba(0, 0, 0, 1)"> False,
      template_folder: str </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">app/templates/email</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
      enable_template_cache: bool </span>= True,   <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 开关:是否启用模板缓存</span>
<span style="color: rgba(0, 0, 0, 1)">    ):
      self.conf </span>=<span style="color: rgba(0, 0, 0, 1)"> ConnectionConfig(
            MAIL_USERNAME</span>=<span style="color: rgba(0, 0, 0, 1)">username,
            MAIL_PASSWORD</span>=<span style="color: rgba(0, 0, 0, 1)">password,
            MAIL_FROM</span>=<span style="color: rgba(0, 0, 0, 1)">mail_from,
            MAIL_PORT</span>=<span style="color: rgba(0, 0, 0, 1)">port,
            MAIL_SERVER</span>=<span style="color: rgba(0, 0, 0, 1)">server,
            MAIL_SSL_TLS</span>=<span style="color: rgba(0, 0, 0, 1)">use_ssl,
            MAIL_STARTTLS</span>=True <span style="color: rgba(0, 0, 255, 1)">if</span> use_tls <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> False,
            USE_CREDENTIALS</span>=<span style="color: rgba(0, 0, 0, 1)">True,
      )
      self.fm </span>=<span style="color: rgba(0, 0, 0, 1)"> FastMail(self.conf)
      </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 模板目录</span>
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> self.templates = Jinja2Templates(directory=template_folder)</span>
      
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 增加 模板缓存 / 热更新开关,这样在 开发环境下可以实时修改模板生效,在 生产环境下则启用缓存提升性能。</span>
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> Jinja2 环境</span>
      self.env =<span style="color: rgba(0, 0, 0, 1)"> Environment(
            loader</span>=<span style="color: rgba(0, 0, 0, 1)">FileSystemLoader(template_folder),
            autoescape</span>=select_autoescape([<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">html</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">xml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]),
            cache_size</span>=50 <span style="color: rgba(0, 0, 255, 1)">if</span> enable_template_cache <span style="color: rgba(0, 0, 255, 1)">else</span> 0,<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 0 = 每次重新加载模板</span>
<span style="color: rgba(0, 0, 0, 1)">      )
    async </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> send_email(
      self,
      subject: str,
      recipients: List,
      body: str,
      subtype: MessageType </span>=<span style="color: rgba(0, 0, 0, 1)"> MessageType.plain,
      background_tasks: Optional </span>=<span style="color: rgba(0, 0, 0, 1)"> None,
      attachments: Optional] </span>=<span style="color: rgba(0, 0, 0, 1)"> None,
    ):
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      发送邮件(支持同步调用和后台任务)

      :param subject: 邮件主题
      :param recipients: 收件人列表
      :param body: 邮件正文
      :param subtype: 内容类型(plain 或 html)
      :param background_tasks: FastAPI BackgroundTasks(可选)
      :param attachments: 附件路径列表(可选)
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(0, 0, 0, 1)">
      message </span>=<span style="color: rgba(0, 0, 0, 1)"> MessageSchema(
            subject</span>=<span style="color: rgba(0, 0, 0, 1)">subject,
            recipients</span>=<span style="color: rgba(0, 0, 0, 1)">recipients,
            body</span>=<span style="color: rgba(0, 0, 0, 1)">body,
            subtype</span>=<span style="color: rgba(0, 0, 0, 1)">subtype,
            attachments</span>=attachments <span style="color: rgba(0, 0, 255, 1)">or</span> []   <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 如果是 None,自动变成 []</span>
<span style="color: rgba(0, 0, 0, 1)">      )

      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> background_tasks:
            background_tasks.add_task(self.fm.send_message, message)
      </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">:
            await self.fm.send_message(message)

    async </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> send_template_email(
      self,
      subject: str,
      recipients: List,
      template_name: str,
      context: Dict,
      background_tasks: Optional </span>=<span style="color: rgba(0, 0, 0, 1)"> None,
      attachments: Optional] </span>=<span style="color: rgba(0, 0, 0, 1)"> None,
    ):
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">
      发送基于 Jinja2 模板的邮件
      :param subject: 邮件主题
      :param recipients: 收件人列表
      :param template_name: 模板文件名 (如 welcome.html)
      :param context: 模板变量
      </span><span style="color: rgba(128, 0, 0, 1)">"""</span>
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 渲染模板</span>
      <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> template = self.templates.get_template(template_name)</span>
<span style="color: rgba(0, 0, 0, 1)">      
      template </span>=<span style="color: rgba(0, 0, 0, 1)"> self.env.get_template(template_name)
      body </span>= template.render(**<span style="color: rgba(0, 0, 0, 1)">context)

      await self.send_email(
            subject</span>=<span style="color: rgba(0, 0, 0, 1)">subject,
            recipients</span>=<span style="color: rgba(0, 0, 0, 1)">recipients,
            body</span>=<span style="color: rgba(0, 0, 0, 1)">body,
            subtype</span>=<span style="color: rgba(0, 0, 0, 1)">MessageType.html,
            background_tasks</span>=<span style="color: rgba(0, 0, 0, 1)">background_tasks,
            attachments</span>=attachments <span style="color: rgba(0, 0, 255, 1)">or</span> []   <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 如果是 None,自动变成 []</span>
      )</pre>
</div>
<p>我们在一个独立的EndPoint的类中提供邮件发送的处理API。</p>
<p>初始化接口如下所示</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202511/8867-20251113114948608-186272654.png" alt="image" loading="lazy"></p>
<p>两个利用邮件模板发送邮件的例子如下所示。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202511/8867-20251113115236925-1467434832.png" alt="image" loading="lazy"></p>
<p>&nbsp;</p>
<p>以上就是基于FastAPI的Python开发框架后端,增加阿里云短信和邮件发送通知处理的相关实现过程,希望对你有所帮助。</p>

</div>
<div id="MySignature" role="contentinfo">
    <div style="border-right-color: #cccccc; border-right-width: 1px; border-right-style: solid; padding-right: 5px; border-top-color: #cccccc; border-top-width: 1px; border-top-style: solid; padding-left: 4px; font-size: 13px; padding-bottom: 4px; border-left-color: #cccccc; border-left-width: 1px; border-left-style: solid; width: 98%; padding-top: 4px; border-bottom-color: #cccccc; border-bottom-width: 1px; border-bottom-style: solid; background-color: #eeeeee;">
    <img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" alt>
    <span style="color: #000000"><span class="Apple-tab-span" style="white-space: pre"></span>
   专注于代码生成工具、.Net/Python 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架、Python开发框架等框架产品。
   <br>  转载请注明出处:撰写人:伍华聪  http://www.iqidi.com <br>    </span></div><br><br>
来源:https://www.cnblogs.com/wuhuacong/p/19217500
頁: [1]
查看完整版本: 在基于FastAPI的Python开发框架后端,增加阿里云短信和邮件发送通知处理