# 阿里云短信服务部署指南 > 注意:这份文档是早期短信接入草稿,里面包含“修改 config.py / 替换 sms.py”等旧步骤。当前项目已经内置 `SMS_MODE=local/aliyun` 和阿里云短信发送逻辑,正式部署请以 `docs/正式部署操作手册.md` 的“短信验证码配置”章节为准。 ## 前置条件 - 阿里云账号(已实名认证) - 已备案的域名(短信签名需要) - 服务器(已部署后端服务) ## 第一步:开通短信服务 1. 登录阿里云控制台:https://console.aliyun.com 2. 搜索「短信服务」或访问:https://dysms.console.aliyun.com 3. 点击「免费开通」 4. 阅读并同意服务协议 5. 点击「立即开通」 ## 第二步:申请短信签名 短信签名是短信开头的标识,如【培训结业系统】。 1. 进入短信服务控制台 2. 左侧菜单选择「国内消息」→「签名管理」 3. 点击「添加签名」 4. 填写信息: - **签名名称**:培训结业证书系统(或其他名称,2-12个字符) - **适用场景**:选择「自用-验证码」 - **签名来源**:选择「企事业单位的全称或简称」 - **上传证明**:营业执照照片 - **申请说明**:用于学员查询培训结业证书时的短信验证码 5. 点击「提交」 6. 等待审核(通常1-2小时) ## 第三步:申请短信模板 短信模板是短信内容的格式。 1. 左侧菜单选择「国内消息」→「模板管理」 2. 点击「添加模板」 3. 填写信息: - **模板名称**:验证码模板 - **模板类型**:选择「验证码」 - **模板内容**:`您的验证码是${code},5分钟内有效,请勿泄露给他人。` - **申请说明**:用于学员查询培训结业证书时的身份验证 4. 点击「提交」 5. 等待审核(通常1-2小时) **注意**:模板变量格式必须是 `${变量名}`,不能是其他格式。 ## 第四步:获取 AccessKey AccessKey 用于后端调用阿里云 API。 1. 点击右上角头像 →「AccessKey管理」 2. 或直接访问:https://ram.console.aliyun.com/manage/ak 3. 点击「创建AccessKey」 4. 进行身份验证(短信验证码或邮箱验证) 5. 创建成功后,保存: - **AccessKey ID** - **AccessKey Secret** **重要**:AccessKey Secret 只显示一次,请立即保存到安全的地方。 **安全建议**: - 不要将 AccessKey 写在代码里 - 使用 RAM 子账号的 AccessKey,并只授予短信服务权限 - 定期轮换 AccessKey ## 第五步:配置环境变量 在服务器上配置环境变量,编辑 `.env` 文件或系统环境变量: ```bash # 阿里云短信服务配置 ALIYUN_ACCESS_KEY_ID=你的AccessKeyID ALIYUN_ACCESS_KEY_SECRET=你的AccessKeySecret ALIYUN_SMS_SIGN_NAME=你的短信签名 ALIYUN_SMS_TEMPLATE_CODE=你的模板CODE ``` **示例**: ```bash ALIYUN_ACCESS_KEY_ID=LTAI5tQkB3VRg8xR ALIYUN_ACCESS_KEY_SECRET=your_secret_key_here ALIYUN_SMS_SIGN_NAME=培训结业系统 ALIYUN_SMS_TEMPLATE_CODE=SMS_123456789 ``` ## 第六步:安装依赖 ```bash cd backend pip install alibabacloud-dysmsapi20170525==3.0.0 ``` 或在 `requirements.txt` 中添加: ``` alibabacloud-dysmsapi20170525==3.0.0 ``` 然后执行: ```bash pip install -r requirements.txt ``` ## 第七步:配置后端代码 编辑 `backend/app/core/config.py`,添加短信配置: ```python import os class Settings: # ... 其他配置 ... # 阿里云短信配置 ALIYUN_ACCESS_KEY_ID: str = os.getenv("ALIYUN_ACCESS_KEY_ID", "") ALIYUN_ACCESS_KEY_SECRET: str = os.getenv("ALIYUN_ACCESS_KEY_SECRET", "") ALIYUN_SMS_SIGN_NAME: str = os.getenv("ALIYUN_SMS_SIGN_NAME", "") ALIYUN_SMS_TEMPLATE_CODE: str = os.getenv("ALIYUN_SMS_TEMPLATE_CODE", "") settings = Settings() ``` ## 第八步:修改短信发送服务 编辑 `backend/app/services/sms.py`,替换为真实的短信发送逻辑: ```python import logging import random import secrets import string import time from alibabacloud_dysmsapi20170525.client import Client from alibabacloud_dysmsapi20170525 import models as dysmsapi_models from alibabacloud_tea_openapi import models as open_api_models from app.core.config import settings logger = logging.getLogger(__name__) SMS_CODE_TTL_SECONDS = 300 # 5分钟有效期 _sms_codes: dict[str, tuple[str, float]] = {} def _get_sms_client() -> Client: """获取阿里云短信客户端""" config = open_api_models.Config( access_key_id=settings.ALIYUN_ACCESS_KEY_ID, access_key_secret=settings.ALIYUN_ACCESS_KEY_SECRET, ) config.endpoint = "dysmsapi.aliyuncs.com" return Client(config) def send_sms(phone: str, code: str) -> bool: """发送短信验证码""" try: client = _get_sms_client() request = dysmsapi_models.SendSmsRequest( phone_numbers=phone, sign_name=settings.ALIYUN_SMS_SIGN_NAME, template_code=settings.ALIYUN_SMS_TEMPLATE_CODE, template_param=f'{{"code":"{code}"}}', ) response = client.send_sms(request) if response.body.code == "OK": logger.info(f"[短信发送成功] 手机号: {phone}") return True else: logger.error(f"[短信发送失败] 手机号: {phone}, 错误: {response.body.message}") return False except Exception as e: logger.error(f"[短信发送异常] 手机号: {phone}, 异常: {str(e)}") return False def generate_sms_code(phone: str) -> str: """生成短信验证码""" clean_expired_codes() code = "".join(random.choices(string.digits, k=6)) sms_id = secrets.token_urlsafe(16) _sms_codes[sms_id] = (code, time.time() + SMS_CODE_TTL_SECONDS) # 发送短信 send_sms(phone, code) # 开发环境下记录日志 logger.info(f"[短信验证码] 手机号: {phone}, 验证码: {code}, 有效期: {SMS_CODE_TTL_SECONDS}秒") return sms_id def verify_sms_code(sms_id: str, code: str) -> bool: """验证短信验证码""" clean_expired_codes() record = _sms_codes.pop(sms_id, None) if not record: return False expected, expires_at = record if expires_at < time.time(): return False return expected == code.strip() def clean_expired_codes() -> None: """清理过期的验证码""" now = time.time() expired = [sms_id for sms_id, (_, expires_at) in _sms_codes.items() if expires_at < now] for sms_id in expired: _sms_codes.pop(sms_id, None) ``` ## 第九步:测试验证 ### 1. 检查配置 ```bash # 查看环境变量 echo $ALIYUN_ACCESS_KEY_ID echo $ALIYUN_SMS_SIGN_NAME echo $ALIYUN_SMS_TEMPLATE_CODE ``` ### 2. 重启服务 ```bash # 重启后端服务 systemctl restart backend # 或 pm2 restart backend ``` ### 3. 查看日志 ```bash # 查看日志 tail -f /var/log/backend/app.log ``` ### 4. 测试发送 访问公开查询页面,输入手机号,点击「获取验证码」,查看: - 是否收到短信 - 日志是否有发送记录 ## 常见问题排查 ### 1. 签名或模板审核不通过 **原因**: - 签名与备案域名不一致 - 模板内容不符合规范 - 证明材料不清晰 **解决**: - 确保签名与备案域名一致 - 模板变量格式使用 `${变量名}` - 上传清晰的营业执照照片 ### 2. 发送失败,返回错误码 **常见错误码**: - `isv.BUSINESS_LIMIT_CONTROL`:发送频率超限 - `isv.INVALID_PARAMETERS`:参数错误 - `isv.SMS_SIGNATURE_ILLEGAL`:签名不合法 - `isv.SMS_TEMPLATE_ILLEGAL`:模板不合法 **解决**: - 检查签名和模板是否审核通过 - 检查手机号格式是否正确 - 检查模板变量格式是否正确 ### 3. 发送成功但收不到短信 **可能原因**: - 手机号被运营商拦截 - 短信内容触发风控 - 手机号在黑名单中 **解决**: - 换个手机号测试 - 检查短信内容是否合规 - 联系阿里云客服 ### 4. AccessKey 权限不足 **错误信息**: ``` Code: InvalidAccessKeyId.NotFound ``` **解决**: - 检查 AccessKey ID 是否正确 - 检查 AccessKey 是否已启用 - 检查 RAM 子账号权限 ## 费用说明 - **短信费用**:0.045元/条(国内) - **免费额度**:新用户有100条免费额度 - **计费方式**:按条计费,发送成功才计费 ## 安全建议 1. **使用 RAM 子账号**: - 创建专门的 RAM 子账号 - 只授予「短信服务」权限 - 不使用主账号的 AccessKey 2. **限制发送频率**: - 同一手机号:1条/分钟,5条/小时 - 同一IP:5条/分钟 3. **监控告警**: - 设置短信发送量告警 - 监控异常发送行为 4. **定期轮换 AccessKey**: - 建议每3个月轮换一次 - 轮换时先创建新 Key,再删除旧 Key ## 相关文档 - [阿里云短信服务官方文档](https://help.aliyun.com/product/44286.html) - [短信服务API参考](https://help.aliyun.com/document_detail/101414.html) - [短信服务错误码](https://help.aliyun.com/document_detail/101345.html)