# 短信转发接收端 - 开发文档 ## 目录 - [项目概述](#项目概述) - [实现逻辑](#实现逻辑) - [使用指南](#使用指南) - [部署指南](#部署指南) - [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 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. 配置 HTTPS(Let'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