在基于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开发来说,基于阿里云的短信处理,可以使用它的 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> </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> 为了能够在多个地方使用,我们对短信发送的处理进行简单的封装一下,如下所示的辅助类,初始化的时候,提供相应的配置的参数即可。</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) -><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> </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> </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]