# 短信宝 (SMS Bao) 接口集成文档 > 版本: 1.0 | 日期: 2026-06-27 > 官网: https://www.smsbao.com | API 文档: https://www.smsbao.com/openapi --- ## 1. 接口概述 短信宝提供简洁的 HTTP GET 接口,用于发送国内短信验证码。 ### 1.1 接口地址 | 功能 | 地址 | 方法 | |------|------|------| | 发送短信 | `https://api.smsbao.com/sms` | GET | | 查询余额 | `http://api.smsbao.com/query` | GET | ### 1.2 请求参数(发送短信) | 参数 | 必填 | 说明 | 示例 | |------|------|------|------| | `u` | 是 | 短信宝注册用户名 | `tom` | | `p` | 是 | 密码 MD5 值(32位)**或** ApiKey(推荐) | `9b11127a9701975c734b8aee81ee3526` | | `g` | 否 | 产品 ID(专用通道时填,不填用通用短信) | `123456` | | `m` | 是 | 手机号(群发逗号分隔,≤99个) | `15205201314` | | `c` | 是 | 短信内容(UTF-8 URL 编码,须含签名) | `%E3%80%90...%E3%80%91` | ### 1.3 返回值 | 返回值 | 含义 | |--------|------| | `0` | 发送成功 | | 其他 | 错误代码(见下方错误码表) | ### 1.4 查询余额参数 | 参数 | 必填 | 说明 | |------|------|------| | `u` | 是 | 用户名 | | `p` | 是 | 密码 MD5 或 ApiKey | 返回: 数字字符串,如 `"523"` 表示剩余 523 条短信。 --- ## 2. 错误码 | 错误码 | 含义 | |--------|------| | `0` | 成功 | | `40` | 手机号格式错误 | | `41` | 手机号为空 | | `42` | 内容为空 | | `43` | 用户名或密码错误 | | `44` | 账户余额不足 | | `45` | 签名格式错误(必须含【签名】) | | `46` | 内容包含敏感词 | | `47` | 频率超限(同号码4条/天) | | `50` | 账号被封禁 | --- ## 3. 短信内容规范 ### 3.1 签名格式 ``` 【QihuAI】您的验证码是123456,5分钟内有效。若非本人操作请忽略。 ``` - 签名在 `【】` 中间,3-10 个字符 - 签名可为公司简称、网站简称、注册商标名称 - 签名需在短信宝后台报备审核 ### 3.2 测试阶段注意事项 - 测试时也需发送正规内容(验证码、通知等) - 不要发送无意义内容(如"测试"、"你好") - 同号码每天限收 4 条(可在后台白名单取消限制) - 群发短信会进入人工审核 --- ## 4. FastAPI 集成代码 ### 4.1 配置 (.env) ```env # 短信宝配置 SMSBAO_USERNAME=your_username SMSBAO_API_KEY=your_api_key_here SMSBAO_PRODUCT_ID= # 可选,专用通道产品ID SMSBAO_SIGN_NAME=QihuAI # 短信签名 SMSBAO_API_URL=https://api.smsbao.com/sms SMSBAO_QUERY_URL=http://api.smsbao.com/query ``` ### 4.2 核心服务代码 ```python # services/gateway/smsbao.py import hashlib from urllib.parse import quote from pydantic import BaseModel import httpx from core.config import settings class SMSBaoClient: """短信宝短信发送客户端""" def __init__(self): self.api_url = settings.SMSBAO_API_URL self.query_url = settings.SMSBAO_QUERY_URL self.username = settings.SMSBAO_USERNAME self.api_key = settings.SMSBAO_API_KEY self.product_id = settings.SMSBAO_PRODUCT_ID self.sign_name = settings.SMSBAO_SIGN_NAME def _get_password(self) -> str: """获取认证密码(优先使用 ApiKey,否则 MD5 密码)""" if self.api_key: return self.api_key # 如果没有 ApiKey,对密码做 MD5 return hashlib.md5(settings.SMSBAO_PASSWORD.encode()).hexdigest() async def send_sms(self, phone: str, content: str) -> dict: """ 发送短信 :param phone: 手机号 :param content: 短信内容(不含签名,会自动添加) :return: {"success": bool, "message": str} """ # 自动添加签名 full_content = f"【{self.sign_name}】{content}" encoded_content = quote(full_content, encoding="utf-8") params = { "u": self.username, "p": self._get_password(), "m": phone, "c": encoded_content, } if self.product_id: params["g"] = self.product_id async with httpx.AsyncClient(timeout=10.0) as client: resp = await client.get(self.api_url, params=params) result = resp.text.strip() if result == "0": return {"success": True, "message": "发送成功"} else: return {"success": False, "message": f"发送失败,错误码: {result}"} async def send_verification_code(self, phone: str, code: str) -> dict: """发送验证码短信""" content = f"您的验证码是{code},5分钟内有效。若非本人操作请忽略。" return await self.send_sms(phone, content) async def query_balance(self) -> dict: """查询余额""" params = { "u": self.username, "p": self._get_password(), } async with httpx.AsyncClient(timeout=10.0) as client: resp = await client.get(self.query_url, params=params) result = resp.text.strip() try: balance = int(result) return {"success": True, "balance": balance} except ValueError: return {"success": False, "message": f"查询失败: {result}"} # 单例 smsbao_client = SMSBaoClient() ``` ### 4.3 验证码服务 ```python # services/gateway/verification.py import random import string from datetime import datetime, timedelta from redis import asyncio as redis redis_client = redis.from_url("redis://localhost:6379/0") VERIFICATION_CODE_TTL = 300 # 5 分钟 VERIFICATION_CODE_LENGTH = 6 RATE_LIMIT_PER_PHONE = 1 # 每分钟最多 1 条 async def generate_and_send_code(phone: str) -> dict: """ 生成验证码并发送 1. 检查频率限制 2. 生成 6 位随机验证码 3. 存入 Redis(5分钟过期) 4. 调用短信宝发送 """ # 频率限制:同号码 60 秒内不能重复发送 rate_key = f"sms:rate:{phone}" if await redis_client.exists(rate_key): return {"success": False, "message": "发送太频繁,请60秒后重试"} # 生成验证码 code = "".join(random.choices(string.digits, k=VERIFICATION_CODE_LENGTH)) # 存入 Redis code_key = f"sms:code:{phone}" await redis_client.setex(code_key, VERIFICATION_CODE_TTL, code) await redis_client.setex(rate_key, 60, "1") # 发送 from smsbao import smsbao_client result = await smsbao_client.send_verification_code(phone, code) if result["success"]: return {"success": True, "message": "验证码已发送"} else: return {"success": False, "message": result["message"]} async def verify_code(phone: str, code: str) -> bool: """验证验证码是否正确""" code_key = f"sms:code:{phone}" stored_code = await redis_client.get(code_key) if stored_code and stored_code.decode() == code: await redis_client.delete(code_key) # 验证通过后删除 return True return False ``` ### 4.4 API 路由 ```python # services/gateway/routes/auth.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel from verification import generate_and_send_code, verify_code router = APIRouter(prefix="/api/v1/auth", tags=["认证"]) class SendCodeRequest(BaseModel): phone: str class VerifyCodeRequest(BaseModel): phone: str code: str @router.post("/send-code") async def send_verification_code(req: SendCodeRequest): """发送手机验证码""" result = await generate_and_send_code(req.phone) if not result["success"]: raise HTTPException(status_code=400, detail=result["message"]) return {"code": 0, "message": "验证码已发送", "data": None} @router.post("/verify-code") async def verify_phone_code(req: VerifyCodeRequest): """验证手机验证码""" valid = await verify_code(req.phone, req.code) if not valid: raise HTTPException(status_code=400, detail="验证码错误或已过期") return {"code": 0, "message": "验证成功", "data": {"verified": True}} ``` --- ## 5. 安全注意事项 1. **图形验证码**: 发送短信前必须验证图形验证码(防短信轰炸) 2. **频率限制**: 同号码 60 秒内只能发 1 条 3. **每日限制**: 同号码每天最多 10 条(可在短信宝后台调整) 4. **密码安全**: ApiKey / MD5 密码存储在 .env,不提交到 Git 5. **HTTPS**: 使用安全接口 `https://api.smsbao.com/sms` 6. **白名单**: 测试阶段在短信宝后台添加测试手机号白名单 --- *文档结束*