docs: 集成短信宝 SMS Bao 验证码服务
- 新增 docs/smsbao-integration.md(完整集成文档+代码示例) - 更新 PRD:注册/登录使用短信宝发送验证码 - 更新架构设计:技术栈+选型矩阵+配置说明 - 更新 API 规范:新增 /send-code 接口 - 更新 AGENTS.md:短信服务配置说明 短信宝 API: - 发送: GET https://api.smsbao.com/sms?u=&p=&m=&c= - 查询: GET http://api.smsbao.com/query?u=&p= - 签名格式: 【QihuAI】您的验证码是123456
This commit is contained in:
1 parent
d45410abef
commit
a9e141b695
5 files changed
+356
-6
No files matched your search
+10
-2
@@ -68,16 +68,24 @@ AI PaaS 商业平台是一个 **AI 能力的"水电煤"基础设施**。用户
|
||||
### 3.1 注册与登录
|
||||
|
||||
#### 注册方式
|
||||
1. **手机号 + 验证码**(主推)
|
||||
1. **手机号 + 短信验证码**(主推,短信宝 SMS Bao 接口)
|
||||
2. **邮箱 + 密码**(备用)
|
||||
3. **第三方登录**: 微信扫码(通过 Casdoor SSO 对接)
|
||||
|
||||
#### 登录方式
|
||||
1. 手机号 + 验证码
|
||||
1. 手机号 + 短信验证码
|
||||
2. 邮箱 + 密码
|
||||
3. 微信扫码
|
||||
4. API Key(程序化调用,无需登录态)
|
||||
|
||||
#### 短信服务
|
||||
- **服务商**: 短信宝 (SMS Bao)
|
||||
- **接口**: https://api.smsbao.com/sms (GET)
|
||||
- **验证码**: 6 位数字,5 分钟有效
|
||||
- **频率限制**: 同号码 60 秒 1 条,每天 10 条
|
||||
- **签名格式**: 【QihuAI】您的验证码是123456...
|
||||
- **详细集成文档**: docs/smsbao-integration.md
|
||||
|
||||
### 3.2 实名认证(阿里云接口)
|
||||
|
||||
用户需要完成实名认证才能发布能力。对接阿里云实名认证 API:
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
| **实名认证** | 自建 OCR | 阿里云 API | **阿里云** | 国内最权威,接口稳定 |
|
||||
| **AI 服务框架** | Go 自研 | FastAPI | **FastAPI** | AI 生态标准,79k Stars,异步高性能 |
|
||||
| **本地 LLM** | 自建 | Ollama | **Ollama** | 130k Stars,本地部署事实标准 |
|
||||
| **短信服务** | 阿里云短信 | 短信宝 | **短信宝** | 简单 HTTP GET 接口,快速接入 |
|
||||
| **API 网关** | 自研 | APISIX | **APISIX** | 高性能,插件丰富,国内团队 |
|
||||
| **数据库** | MySQL | PostgreSQL | **PostgreSQL** | JSONB + RLS,功能更强 |
|
||||
| **缓存** | Memcached | Redis | **Redis** | Lua 脚本支持,原子计费必备 |
|
||||
@@ -247,6 +248,25 @@ return 'OK:' .. new_balance
|
||||
|
||||
## 6. 开源组件集成方案
|
||||
|
||||
### 6.0 SMS Bao (短信服务)
|
||||
|
||||
```
|
||||
接入方式: HTTP GET 接口(无需 SDK)
|
||||
接口地址: https://api.smsbao.com/sms
|
||||
认证方式: ApiKey 或 MD5 密码
|
||||
配置文件: infra/smsbao/config.yaml
|
||||
smsbao:
|
||||
username: "your_username"
|
||||
api_key: "your_api_key" # 优先使用 ApiKey
|
||||
sign_name: "QihuAI" # 短信签名
|
||||
api_url: "https://api.smsbao.com/sms"
|
||||
query_url: "http://api.smsbao.com/query"
|
||||
code_length: 6 # 验证码长度
|
||||
code_ttl: 300 # 验证码有效期(秒)
|
||||
rate_limit: 60 # 同号码发送间隔(秒)
|
||||
daily_limit: 10 # 同号码每日上限
|
||||
```
|
||||
|
||||
### 6.1 Casdoor (SSO 认证中心)
|
||||
|
||||
```
|
||||
|
||||
+23
-3
@@ -46,9 +46,29 @@
|
||||
|
||||
## 2. 认证接口(Casdoor 代理)
|
||||
|
||||
### POST /api/v1/auth/send-code
|
||||
```yaml
|
||||
description: 发送手机验证码(短信宝 SMS Bao)
|
||||
request:
|
||||
body:
|
||||
phone: string (required)
|
||||
captcha_id: string (required) # 图形验证码ID
|
||||
captcha_code: string (required) # 图形验证码答案
|
||||
response:
|
||||
200:
|
||||
data:
|
||||
message: "验证码已发送"
|
||||
400:
|
||||
code: 40001
|
||||
message: "图形验证码错误"
|
||||
429:
|
||||
code: 42901
|
||||
message: "发送太频繁,请60秒后重试"
|
||||
```
|
||||
|
||||
### POST /api/v1/auth/register
|
||||
```yaml
|
||||
description: 注册(手机号 + 验证码)
|
||||
description: 注册(手机号 + 短信验证码)
|
||||
request:
|
||||
body:
|
||||
phone: string (required)
|
||||
@@ -64,11 +84,11 @@ response:
|
||||
|
||||
### POST /api/v1/auth/login
|
||||
```yaml
|
||||
description: 登录(手机号 + 验证码)
|
||||
description: 登录(手机号 + 短信验证码)
|
||||
request:
|
||||
body:
|
||||
phone: string (required)
|
||||
code: string (required)
|
||||
code: string (required, 6位验证码)
|
||||
response:
|
||||
200:
|
||||
data:
|
||||
|
||||
@@ -22,9 +22,10 @@
|
||||
- **API 网关**: APISIX (Lua, 开源) — 路由/限流/鉴权
|
||||
- **本地 LLM**: Ollama (Go, 开源) — 本地模型推理
|
||||
- **Agent 编排**: LangFlow (Python, 开源) — 可视化工作流
|
||||
- **短信服务**: 短信宝 SMS Bao (HTTP GET) — 验证码发送
|
||||
- **前端**: React 18 + TypeScript + Ant Design Pro + ECharts
|
||||
- **数据库**: PostgreSQL 16
|
||||
- **缓存**: Redis 7 (Lua 原子计费)
|
||||
- **缓存**: Redis 7 (Lua 原子计费 + 验证码存储)
|
||||
- **消息队列**: Kafka 3.x (用量上报)
|
||||
- **实名认证**: 阿里云实名认证 API
|
||||
|
||||
@@ -145,6 +146,15 @@ QihuAI/
|
||||
- 流式: SSE (text/event-stream)
|
||||
- 百度需适配层 (NEW API 内置)
|
||||
|
||||
## 短信服务 (SMS Bao)
|
||||
- 接口: https://api.smsbao.com/sms (GET)
|
||||
- 参数: u(用户名), p(ApiKey或MD5密码), m(手机号), c(内容,URL编码)
|
||||
- 验证码: 6位数字, 5分钟有效, Redis存储
|
||||
- 频率限制: 同号码60秒1条, 每天10条
|
||||
- 签名格式: 【QihuAI】您的验证码是...
|
||||
- 安全: 发送前必须验证图形验证码(防轰炸)
|
||||
- 详细集成: docs/smsbao-integration.md
|
||||
|
||||
## 实名认证
|
||||
- 阿里云实名认证 API
|
||||
- 用户提交: 真实姓名 + 身份证号
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
# 短信宝 (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. **白名单**: 测试阶段在短信宝后台添加测试手机号白名单
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
Reference in new issue
Block a user