Files
smsweb/DEVELOPMENT.md
OpenClaw Agent 4e5e93660d Initial commit: SMS Receiver Web Service
Features:
- Receive SMS from TranspondSms Android APP
- HMAC-SHA256 signature verification (optional)
- SQLite database storage
- Web UI with login authentication
- Multiple API tokens support
- Timezone conversion (Asia/Shanghai)
- Search, filter, and statistics
- Auto refresh and session management

Tech Stack:
- Flask 3.0
- SQLite database
- HTML5/CSS3 responsive design
2026-02-06 23:23:49 +00:00

962 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 短信转发接收端 - 开发文档
## 目录
- [项目概述](#项目概述)
- [实现逻辑](#实现逻辑)
- [使用指南](#使用指南)
- [部署指南](#部署指南)
- [API 文档](#api-文档)
- [配置说明](#配置说明)
- [常见问题](#常见问题)
- [开发规范](#开发规范)
---
## 项目概述
### 技术架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Android APP │ │ Flask Web │ │ SQLite DB │
│ TranspondSms │────────▶│ Server │────────▶│ sms_receiver │
│ │ POST │ │ Store │ .db │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Web UI │
│ (登录认证) │
└─────────────────┘
```
### 核心功能
1. **短信接收**:接收 TranspondSms Android APP 转发的短信
2. **签名验证**HMAC-SHA256 签名验证,防止伪造请求
3. **数据存储**SQLite 数据库存储短信和日志
4. **Web 管理**:登录验证 + 短信列表、详情、日志、统计
5. **时区转换**UTC 存储时区转换显示
6. **Token 配置**:支持多设备、多 Token 配置
---
## 实现逻辑
### 1. 整体数据流
```
Android APP 接收短信
├─ 解析短信内容支持多PDU
├─ 提取发送方、内容、时间戳
├─ 可选生成签名HMAC-SHA256
POST 请求发送到 /api/receive
├─ 解析 multipart/form-data
├─ 验证必填参数from, content
├─ 可选:验证签名
│ ├─ 检查时间戳是否过期
│ ├─ 生成期望的签名
│ └─ 比较签名是否匹配
├─ 保存到数据库
│ ├─ 存储为 UTC 时间
│ ├─ 关联 Token 和 Secret
│ └─ 记录接收日志
返回成功响应
```
### 2. 签名验证逻辑
TranspondSms 的签名规则:
```python
# 1. 拼接待签名字符串
string_to_sign = timestamp + "\n" + secret
# 2. HMAC-SHA256 计算
hmac_code = hmac.new(
secret.encode('utf-8'),
string_to_sign.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
# 3. Base64 编码
sign_bytes = base64.b64encode(hmac_code)
# 4. URL 编码
sign = urllib.parse.quote(sign_bytes.decode())
```
**防重放攻击**
- 检查时间戳是否在允许范围内默认1小时
- 超出范围的请求拒绝
### 3. 时区转换逻辑
```
数据库存储UTC
├─ created_at: 2024-02-06 14:30:00 (UTC)
└─ timestamp: 1707223800000 (毫秒时间戳)
读取时转换
├─ UTC 时间 + 时区偏移8小时
└─ datetime.fromtimestamp(timestamp / 1000)
显示(本地时间)
└─ created_at: 2024-02-06 22:30:00 (Asia/Shanghai)
```
### 4. 登录验证流程
```
用户访问页面
检查 session['logged_in']
├─ 已登录 ──▶ 更新 last_activity ──▶ 允许访问
└─ 未登录 ──▶ 跳转到 /login
提交表单
├─ 验证用户名和密码
├─ 成功:
│ ├─ session['logged_in'] = True
│ ├─ session['username'] = username
│ ├─ session['login_time'] = now
│ └─ 跳转到原页面或首页
└─ 失败:显示错误消息
```
**会话超时检查**
```python
last_activity = session.get('last_activity')
if now - last_activity > SESSION_LIFETIME:
# 清空会话,重定向到登录页
session.clear()
return redirect('/login')
```
### 5. Token 匹配逻辑
```python
# 接收请求时
token = request.form.get('token') # 从参数获取
# 在配置中查找对应的 secret
for token_config in API_TOKENS:
if token_config['token'] == token and token_config['enabled']:
secret = token_config['secret']
break
# 使用找到的 secret 进行签名验证
if secret and SIGN_VERIFY:
verify_sign(secret, sign, timestamp)
```
### 6. 数据库设计
#### sms_messages 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER | 主键,自增 |
| from_number | TEXT | 发送方手机号 |
| content | TEXT | 短信内容 |
| timestamp | INTEGER | 原始时间戳(毫秒) |
| device_info | TEXT | 设备信息(可选) |
| sim_info | TEXT | SIM 卡信息(可选) |
| sign_verified | INTEGER | 是否通过签名验证0/1 |
| ip_address | TEXT | 来源 IP 地址 |
| created_at | TIMESTAMP | 创建时间UTC |
#### receive_logs 表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER | 主键,自增 |
| from_number | TEXT | 发送方手机号 |
| content | TEXT | 短信内容 |
| timestamp | INTEGER | 时间戳 |
| sign | TEXT | 签名 |
| sign_valid | INTEGER | 签名是否有效0/1/null |
| ip_address | TEXT | IP 地址 |
| status | TEXT | 处理状态success/error |
| error_message | TEXT | 错误消息 |
| created_at | TIMESTAMP | 创建时间UTC**
### 分层架构
```
┌─────────────────────────────────────────┐
│ Flask Application Layer │
│ (app.py - 路由、业务逻辑、会话管理) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Database Layer │
│ (database.py - 数据库操作、时区转换) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ SQLite Database │
│ (sms_receiver.db - 数据持久化) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Template Layer │
│ (templates/ - HTML、CSS、JS) │
└─────────────────────────────────────────┘
```
### 核心模块职责
| 模块 | 职责 |
|------|------|
| `app.py` | Flask 主应用,路由注册,业务逻辑 |
| `config.py` | 配置加载,从 config.json 读取配置 |
| `database.py` | 数据库模型CRUD 操作,时区转换 |
| `sign_verify.py` | 签名生成和验证 |
| `templates/` | HTML 模板,前端展示 |
---
## 使用指南
### 前置要求
- Python 3.7+
- Flask 3.0+
- SQLite3
### 安装依赖
```bash
pip install -r requirements.txt
```
### 配置文件
创建 `config.json`
```json
{
"server": {
"host": "0.0.0.0",
"port": 9518,
"debug": false
},
"security": {
"enabled": true,
"username": "admin",
"password": "YourStrongPassword123",
"session_lifetime": 3600,
"secret_key": "RandomSecretKeyHere",
"sign_verify": true,
"sign_max_age": 3600000
},
"sms": {
"max_messages": 10000,
"auto_cleanup": true,
"cleanup_days": 30
},
"database": {
"path": "sms_receiver.db"
},
"timezone": "Asia/Shanghai",
"api_tokens": [
{
"name": "我的手机",
"token": "my_phone_token",
"secret": "my_phone_secret",
"enabled": true
}
]
}
```
### 启动服务
```bash
python3 app.py
```
服务启动后访问http://你的IP:9518
### 配置 TranspondSms APP
1. 下载并安装 TranspondSms APP
2. 打开 APP进入"发送方"页面
3. 添加"网页通知"
4. 填写配置:
```
Token (URL): http://你的服务器IP:9518/api/receive?token=my_phone_token
Secret: my_phone_secret
```
5. 点击"测试"按钮,验证是否成功
6. 配置转发规则(如"转发全部"
### 使用 Web 界面
#### 登录
- 访问 http://你的IP:9518
- 输入用户名和密码
- 登录成功后进入短信列表
#### 查看短信
- **短信列表**:主页显示所有收到短信
- **搜索**:支持按号码或内容搜索
- **筛选**:按发送方号码快捷筛选
- **详情**:点击短信查看完整内容和元数据
#### 查看日志
- 访问"接收日志"页面
- 查看每次请求的处理结果
- 包括签名验证状态、IP 地址、错误信息
#### 统计信息
- 访问"统计信息"页面
- 查看短信总数、今日、本周
- 签名验证比例
- 发送方号码排行榜
- 清理旧数据
---
## 部署指南
### 开发环境部署
```bash
# 克隆项目
git clone <your-repo-url>
cd smsweb
# 创建虚拟环境(推荐)
python3 -m venv venv
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
# 配置 config.json
cp config.example.json config.json
vim config.json
# 启动服务
python3 app.py
```
### 生产环境部署(推荐方案)
#### 1. 使用 Gunicorn + Nginx
**安装 Gunicorn**
```bash
pip install gunicorn
```
**创建 systemd 服务**
```bash
sudo vim /etc/systemd/system/sms-receiver.service
```
内容:
```ini
[Unit]
Description=SMS Receiver Service
After=network.target
[Service]
Type=notify
User=www-data
WorkingDirectory=/var/www/sms-receiver
Environment="PATH=/var/www/sms-receiver/venv/bin"
ExecStart=/var/www/sms-receiver/venv/bin/gunicorn \
-w 4 -b 127.0.0.1:9518 \
--timeout 120 \
--access-logfile /var/log/sms-receiver/access.log \
--error-logfile /var/log/sms-receiver/error.log \
app:app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
**启动服务**
```bash
sudo systemctl daemon-reload
sudo systemctl enable sms-receiver
sudo systemctl start sms-receiver
sudo systemctl status sms-receiver
```
**配置 Nginx 反向代理**
```bash
sudo vim /etc/nginx/sites-available/sms-receiver
```
内容:
```nginx
upstream sms_receiver {
server 127.0.0.1:9518;
}
server {
listen 80;
server_name sms.example.com;
access_log /var/log/nginx/sms-receiver-access.log;
error_log /var/log/nginx/sms-receiver-error.log;
location / {
proxy_pass http://sms_receiver;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
}
# 接收 API 不需要登录,但对客户端透明
location /api/receive {
proxy_pass http://sms_receiver/api/receive;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
**启用站点**
```bash
sudo ln -s /etc/nginx/sites-available/sms-receiver /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
#### 2. 配置 HTTPSLet's Encrypt
```bash
sudo certbot --nginx -d sms.example.com
```
#### 3. 防火墙配置
```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
#### 4. 日志轮转
```bash
sudo vim /etc/logrotate.d/sms-receiver
```
内容:
```
/var/log/sms-receiver/*.log {
daily
rotate 14
compress
missingok
notifempty
create 0640 www-data www-data
sharedscripts
postrotate
systemctl reload sms-receiver >/dev/null 2>&1 || true
endscript
}
```
### Docker 部署
**创建 Dockerfile**
```dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 9518
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:9518", "app:app"]
```
**创建 docker-compose.yml**
```yaml
version: '3.8'
services:
sms-receiver:
build: .
ports:
- "9518:9518"
volumes:
- ./sms_receiver.db:/app/sms_receiver.db
- ./config.json:/app/config.json
- ./logs:/app/logs
environment:
- FLASK_ENV=production
restart: unless-stopped
```
**启动**
```bash
docker-compose up -d
```
### 安全加固
1. **修改默认密码**:首次部署后立即修改登录密码
2. **使用强密码**至少16位包含大小写字母、数字、特殊字符
3. **启用 HTTPS**:使用 Let's Encrypt 免费证书
4. **限制访问**:配置防火墙,只开放必要端口
5. **启用签名验证**:设置 Token 的 secret
6. **定期更新**:定期更新 Python 和 Flask 版本
### 监控和维护
**查看日志**
```bash
# 应用日志
tail -f /var/log/sms-receiver/error.log
# Nginx 日志
tail -f /var/log/nginx/sms-receiver-error.log
```
**备份数据库**
```bash
#!/bin/bash
BACKUP_DIR="/backup/sms-receiver"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# 备份数据库
cp /var/www/sms-receiver/sms_receiver.db $BACKUP_DIR/sms_receiver_$DATE.db
# 备份配置
cp /var/www/sms-receiver/config.json $BACKUP_DIR/config_$DATE.json
# 删除30天前的备份
find $BACKUP_DIR -name "*.db" -mtime +30 -delete
find $BACKUP_DIR -name "*.json" -mtime +30 -delete
```
---
## API 文档
### POST /api/receive
接收短信接口。
**请求方式**POST multipart/form-data
**URL 参数**
- `token` (可选): API Token用于匹配对应的 secret
**表单参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| from | string | 是 | 发送方手机号 |
| content | string | 是 | 短信内容 |
| timestamp | string | 否 | 时间戳(毫秒),用于签名验证 |
| sign | string | 否 | 签名HMAC-SHA256 + Base64 + URL Encode |
| device | string | 否 | 设备信息 |
| sim | string | 否 | SIM 卡信息 |
**请求示例**
```bash
curl -X POST http://your-server:9518/api/receive?token=my_token \
-F "from=10086" \
-F "content=验证码: 123456" \
-F "timestamp=1707223800000" \
-F "sign=xxx"
```
**响应示例**
成功:
```json
{
"success": true,
"message_id": 123,
"message": "短信已接收"
}
```
失败:
```json
{
"error": "缺少必填参数"
}
```
**HTTP 状态码**
- 200: 成功
- 400: 参数错误
- 403: 签名验证失败
- 500: 服务器错误
### GET /api/messages
获取短信列表(需要登录)。
**URL 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | int | 否 | 页码默认1 |
| limit | int | 否 | 每页数量默认20 |
| from | string | 否 | 按发送方号码筛选 |
| search | string | 否 | 搜索内容或号码 |
**响应示例**
```json
{
"success": true,
"data": [
{
"id": 1,
"from_number": "10086",
"content": "验证码: 123456",
"timestamp": 1707223800000,
"local_timestamp": "2024-02-06 22:30:00",
"created_at": "2024-02-06 14:30:00",
"sign_verified": true
}
],
"total": 1,
"page": 1,
"limit": 20
}
```
### GET /api/statistics
获取统计信息(需要登录)。
**响应示例**
```json
{
"success": true,
"data": {
"total": 100,
"today": 10,
"week": 50,
"verified": 80,
"unverified": 20
}
}
```
---
## 配置说明
### 完整配置示例
```json
{
"app": {
"name": "短信转发接收端",
"version": "1.0.0"
},
"server": {
"host": "0.0.0.0",
"port": 9518,
"debug": false
},
"security": {
"enabled": true,
"username": "admin",
"password": "YourStrongPassword123",
"session_lifetime": 3600,
"secret_key": "RandomSecretKeyChangeMe",
"sign_verify": true,
"sign_max_age": 3600000
},
"sms": {
"max_messages": 10000,
"auto_cleanup": true,
"cleanup_days": 30
},
"database": {
"path": "sms_receiver.db"
},
"timezone": "Asia/Shanghai",
"api_tokens": [
{
"name": "主手机",
"token": "main_phone_token",
"secret": "main_phone_secret_key",
"enabled": true
},
{
"name": "备用机",
"token": "backup_phone_token",
"secret": "backup_phone_secret_key",
"enabled": true
},
{
"name": "测试设备",
"token": "test_token",
"secret": "",
"enabled": false
}
]
}
```
### 配置项详解
#### server - 服务器配置
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| host | string | 0.0.0.0 | 监听地址 |
| port | int | 9518 | 监听端口 |
| debug | bool | false | 调试模式 |
#### security - 安全配置
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| enabled | bool | true | 是否启用登录验证 |
| username | string | admin | 登录用户名 |
| password | string | admin123 | 登录密码 |
| session_lifetime | int | 3600 | 会话有效期(秒) |
| secret_key | string | - | Flask 会话密钥 |
| sign_verify | bool | true | 是否验证签名 |
| sign_max_age | int | 3600000 | 签名最大有效时间(毫秒) |
#### sms - 短信配置
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| max_messages | int | 10000 | 最多保留短信数 |
| auto_cleanup | bool | true | 是否自动清理老数据 |
| cleanup_days | int | 30 | 清理多少天前的数据 |
#### api_tokens - API Token 配置
| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 配置名称(可选) |
| token | string | Token 值,用于匹配 secret |
| secret | string | 密钥,用于签名验证(可选) |
| enabled | bool | 是否启用此 Token |
---
## 常见问题
### Q1: 如何禁用登录验证?
`config.json` 中设置:
```json
{
"security": {
"enabled": false
}
}
```
### Q2:签名验证失败怎么办?
检查以下几点:
1. **时间戳是否正确**确保客户端时间准确误差不超过1小时
2. **Secret 是否匹配**:确保客户端和服务器端的 secret 完全一致
3. **签名生成算法**使用正确的算法HMAC-SHA256
**调试签名**
```python
# 生成签名
import time, hmac, hashlib, base64, urllib.parse
timestamp = str(int(time.time() * 1000))
secret = "your_secret"
string_to_sign = f"{timestamp}\n{secret}"
sign = urllib.parse.quote(base64.b64encode(
hmac.new(secret.encode(), string_to_sign.encode(), hashlib.sha256).digest()
).decode())
print(f"Timestamp: {timestamp}")
print(f"Sign: {sign}")
```
### Q3: 如何配置多个设备?
`api_tokens` 中添加多个配置:
```json
{
"api_tokens": [
{
"name": "设备A",
"token": "device_a",
"secret": "secret_a",
"enabled": true
},
{
"name": "设备B",
"token": "device_b",
"secret": "secret_b",
"enabled": true
}
]
}
```
在 APP 中配置不同的设备使用不同的 Token。
### Q4: 会话总是过期?
调整 `session_lifetime`
```json
{
"security": {
"session_lifetime": 86400 // 24小时
}
}
```
### Q5: 如何备份数据?
直接复制数据库文件:
```bash
cp sms_receiver.db sms_receiver.db.backup
```
或者使用 SQLite 导出:
```bash
sqlite3 sms_receiver.db ".dump" > backup.sql
```
### Q6: 如何清理所有数据?
删除数据库文件,重启服务会自动重建:
```bash
rm sms_receiver.db
python3 app.py
```
### Q7: 时间显示不正确?
检查时区配置:
```json
{
"timezone": "Asia/Shanghai"
}
```
可用时区列表https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
---
## 开发规范
### 代码风格
- 遵循 PEP 8 Python 代码规范
- 使用有意义的变量和函数名
- 添加必要的类型注解
### Git 提交规范
```
feat: 添加新功能
fix: 修复 bug
docs: 更新文档
style: 代码格式化
refactor: 重构
test: 添加测试
chore: 构建/工具链
```
### 测试建议
```python
# 测试签名生成
python3 sign_verify.py
# 测试 API
curl -X POST http://localhost:9518/api/receive \
-F "from=10086" \
-F "content=test"
```
---
## 许可证
MIT License
## 联系方式
- 项目地址https://gitea.king.nyc.mn/openclaw/smsweb
- 问题反馈:提交 Issue