370 lines
10 KiB
Markdown
370 lines
10 KiB
Markdown
# SMS Monitor
|
||
|
||
一个基于 Android + Node.js 的个人短信监控系统。Android App 读取手机短信并上传至服务器,Web Dashboard 提供网页端查看与管理。
|
||
|
||
## 项目架构
|
||
|
||
```
|
||
┌─────────────────┐ HTTP / JSON ┌─────────────────┐ SQL ┌──────────┐
|
||
│ Android App │ ────────────────────→ │ Node.js Server │ ──────────→ │ SQLite │
|
||
│ (Flutter) │ │ (Express) │ │ │
|
||
└─────────────────┘ └────────┬────────┘ └──────────┘
|
||
│
|
||
│ REST API
|
||
▼
|
||
┌─────────────────┐
|
||
│ Web Dashboard │
|
||
│ (React + Vite) │
|
||
└─────────────────┘
|
||
```
|
||
|
||
- **App 端**:Flutter 编写,仅支持 Android。读取系统短信数据库 + 实时监听新短信,支持自动/手动上传。
|
||
- **服务端**:Node.js + Express 提供 REST API,JWT 鉴权,SQLite 存储。
|
||
- **Web 端**:React + Vite SPA,Material You 风格,登录后查看/筛选/删除短信。
|
||
|
||
## 技术栈
|
||
|
||
| 层级 | 技术 | 说明 |
|
||
|------|------|------|
|
||
| Android App | Flutter 3.2+ | `dynamic_color` Material You 主题 |
|
||
| 短信读取 | `sms_maintained` | 查询系统短信数据库 + 接收新短信广播 |
|
||
| 权限管理 | `permission_handler` | Android SMS 权限 |
|
||
| 后端框架 | Express 4.x | TypeScript 编写 |
|
||
| 数据库 | better-sqlite3 | WAL 模式,同步 API |
|
||
| 鉴权 | JWT (jsonwebtoken) | 30 天有效期 |
|
||
| 密码加密 | bcryptjs | 10 轮哈希 |
|
||
| 前端框架 | React 18 + Vite 6 | TypeScript |
|
||
| HTTP 客户端 | fetch (内置) | 无额外依赖 |
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
transfer_sms/
|
||
├── server/
|
||
│ ├── package.json
|
||
│ ├── tsconfig.json
|
||
│ └── src/
|
||
│ ├── index.ts # Express 入口,挂载路由
|
||
│ ├── db.ts # SQLite 初始化 + 自动建表
|
||
│ ├── types.ts # TypeScript 类型定义
|
||
│ ├── middleware/
|
||
│ │ └── auth.ts # JWT Bearer Token 鉴权中间件
|
||
│ └── routes/
|
||
│ ├── auth.ts # POST /api/auth/register, /api/auth/login
|
||
│ ├── sms.ts # POST /api/sms/upload, GET /api/sms, DELETE /api/sms/:id
|
||
│ └── devices.ts # POST /api/devices/register
|
||
├── web/
|
||
│ ├── package.json
|
||
│ ├── vite.config.ts # 开发代理 /api → localhost:3000
|
||
│ └── src/
|
||
│ ├── main.tsx # 入口
|
||
│ ├── App.tsx # 路由:未登录 → Login,已登录 → SmsList
|
||
│ ├── api.ts # 封装 fetch 调用
|
||
│ ├── index.css # Material You CSS 变量 + 基础样式
|
||
│ └── pages/
|
||
│ ├── Login.tsx / .css # 登录/注册页
|
||
│ └── SmsList.tsx / .css # 短信列表 + 详情面板 + 筛选
|
||
└── app/
|
||
├── pubspec.yaml
|
||
└── lib/
|
||
├── main.dart # 入口,DynamicColorBuilder 主题
|
||
├── models/
|
||
│ └── sms_message.dart # SmsMessage 数据类 + JSON 序列化
|
||
├── services/
|
||
│ ├── api_service.dart # HTTP 客户端
|
||
│ ├── sms_reader_service.dart # 查询/监听系统短信
|
||
│ └── settings_service.dart # SharedPreferences 读写
|
||
└── screens/
|
||
├── login_screen.dart # 服务器地址 + 用户名密码登录
|
||
├── home_screen.dart # NavigationBar Tab 容器
|
||
├── sms_list_screen.dart # 短信列表 + 选择上传 + 自动模式
|
||
└── settings_screen.dart # 服务器信息 + 上传模式 + 登出
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 1. 启动服务端
|
||
|
||
```bash
|
||
cd server
|
||
npm install
|
||
npm run dev
|
||
# 服务运行在 http://localhost:3000
|
||
```
|
||
|
||
### 2. 启动 Web Dashboard
|
||
|
||
```bash
|
||
cd web
|
||
npm install
|
||
npm run dev
|
||
# 访问 http://localhost:5173
|
||
```
|
||
|
||
Vite 开发服务器已配置 API 代理,`/api/*` 请求会自动转发到 `localhost:3000`。
|
||
|
||
### 3. 运行 Flutter App
|
||
|
||
```bash
|
||
cd app
|
||
flutter pub get
|
||
flutter run
|
||
```
|
||
|
||
需要 Android 设备或模拟器,且需要安装 Flutter SDK。
|
||
|
||
## API 文档
|
||
|
||
所有 API 均返回 JSON。需要鉴权的接口在 Header 中携带 `Authorization: Bearer <token>`。
|
||
|
||
### 认证
|
||
|
||
#### POST /api/auth/register
|
||
|
||
注册新用户,成功后直接返回 JWT token。
|
||
|
||
```
|
||
Request:
|
||
{
|
||
"username": "alice", // 3-50 字符
|
||
"password": "123456" // 6-100 字符
|
||
}
|
||
|
||
Response 201:
|
||
{
|
||
"token": "eyJhbG...",
|
||
"userId": 1
|
||
}
|
||
|
||
Error 409: { "error": "Username already taken" }
|
||
Error 400: { "error": [...] } // 参数校验失败
|
||
```
|
||
|
||
#### POST /api/auth/login
|
||
|
||
登录已有用户。
|
||
|
||
```
|
||
Request:
|
||
{
|
||
"username": "alice",
|
||
"password": "123456"
|
||
}
|
||
|
||
Response 200:
|
||
{
|
||
"token": "eyJhbG...",
|
||
"userId": 1
|
||
}
|
||
|
||
Error 401: { "error": "Invalid credentials" }
|
||
```
|
||
|
||
### 短信
|
||
|
||
#### POST /api/sms/upload
|
||
|
||
批量上传短信,单次不超过 5MB。
|
||
|
||
```
|
||
Headers: Authorization: Bearer <token>
|
||
|
||
Request:
|
||
[
|
||
{
|
||
"phone_number": "+8613800138000",
|
||
"contact_name": "张三", // 可选
|
||
"content": "你好,明天见面聊",
|
||
"type": "received", // "received" | "sent"
|
||
"sms_date": "2026-04-28T10:30:00.000Z" // ISO 8601
|
||
}
|
||
]
|
||
|
||
Response 200:
|
||
{
|
||
"uploaded": 1 // 成功插入条数
|
||
}
|
||
```
|
||
|
||
#### GET /api/sms
|
||
|
||
分页查询当前用户的短信。
|
||
|
||
```
|
||
Headers: Authorization: Bearer <token>
|
||
|
||
Query:
|
||
page - 页码,默认 1
|
||
limit - 每页条数,默认 20,最大 100
|
||
phone - 按手机号模糊搜索(可选)
|
||
type - 按类型筛选,"received" | "sent"(可选)
|
||
|
||
Response 200:
|
||
{
|
||
"messages": [ ... ],
|
||
"total": 42,
|
||
"page": 1,
|
||
"limit": 20,
|
||
"totalPages": 3
|
||
}
|
||
```
|
||
|
||
#### GET /api/sms/:id
|
||
|
||
查看单条短信详情。
|
||
|
||
```
|
||
Response 200:
|
||
{
|
||
"id": 1,
|
||
"user_id": 1,
|
||
"phone_number": "+8613800138000",
|
||
"contact_name": "张三",
|
||
"content": "你好,明天见面聊",
|
||
"type": "received",
|
||
"sms_date": "2026-04-28T10:30:00.000Z",
|
||
"created_at": "2026-04-28 12:00:00"
|
||
}
|
||
|
||
Error 404: { "error": "Not found" }
|
||
```
|
||
|
||
#### DELETE /api/sms/:id
|
||
|
||
删除一条短信(仅限自己的)。
|
||
|
||
```
|
||
Response 200: { "deleted": true }
|
||
|
||
Error 404: { "error": "Not found" }
|
||
```
|
||
|
||
### 设备
|
||
|
||
#### POST /api/devices/register
|
||
|
||
注册设备,重复注册同名设备会更新同步时间。
|
||
|
||
```
|
||
Request:
|
||
{
|
||
"device_name": "Pixel 8 Pro"
|
||
}
|
||
|
||
Response 201: { "deviceId": 1 }
|
||
```
|
||
|
||
## 数据库
|
||
|
||
使用 SQLite,数据库文件自动创建在 `server/data.db`。
|
||
|
||
```sql
|
||
CREATE TABLE users (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
username TEXT UNIQUE NOT NULL,
|
||
password_hash TEXT NOT NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE TABLE sms_messages (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||
phone_number TEXT NOT NULL,
|
||
contact_name TEXT,
|
||
content TEXT NOT NULL,
|
||
type TEXT NOT NULL CHECK(type IN ('received', 'sent')),
|
||
sms_date DATETIME NOT NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
CREATE INDEX idx_sms_user ON sms_messages(user_id, sms_date);
|
||
|
||
CREATE TABLE devices (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||
device_name TEXT NOT NULL,
|
||
last_sync_at DATETIME
|
||
);
|
||
```
|
||
|
||
## App 功能说明
|
||
|
||
### 登录
|
||
- 首次使用需填写服务器地址、用户名和密码
|
||
- 支持注册新用户或登录已有账号
|
||
- 配置自动保存至 SharedPreferences
|
||
|
||
### 短信列表
|
||
- 读取手机所有短信(收件箱 + 已发送)
|
||
- 每条短信显示联系人、内容摘要、类型箭头图标
|
||
- 多选模式:点击短信或圆圈图标选中,选中的显示对号
|
||
- 点击云上传按钮上传已选中的短信
|
||
- 点击右下角悬浮按钮一键上传全部短信
|
||
|
||
### 上传模式
|
||
- **手动模式**(默认):用户选择短信后点击上传
|
||
- **自动模式**:开启后实时监听新短信,自动上传到服务器
|
||
- 自动模式下新的入站短信会立即出现在列表中
|
||
|
||
### 设置
|
||
- 查看当前服务器地址和 Token 状态
|
||
- 切换自动/手动上传模式
|
||
- 登出按钮清除 Token 并返回登录页
|
||
|
||
## Web Dashboard 功能说明
|
||
|
||
### 登录页
|
||
- 用户名 + 密码登录或注册
|
||
- Material You 风格卡片布局
|
||
|
||
### 短信列表页
|
||
- 表格展示所有上传的短信
|
||
- 按手机号码模糊搜索
|
||
- 按类型(发送/接收)下拉筛选
|
||
- 分页浏览,支持 Prev/Next
|
||
- 点击行查看详情面板(右侧)
|
||
- 每条短信可单独删除
|
||
|
||
## 安全说明
|
||
|
||
- 密码经 bcrypt(10轮)哈希存储,不保存明文
|
||
- JWT token 有效期 30 天
|
||
- 所有短信接口均需认证,用户只能访问自己的数据
|
||
- 生产环境请修改 `JWT_SECRET` 环境变量
|
||
|
||
```bash
|
||
JWT_SECRET=your-secret-key npm run dev
|
||
```
|
||
|
||
## 环境变量
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `PORT` | 3000 | 服务端口 |
|
||
| `JWT_SECRET` | 内置默认值 | JWT 签名密钥,生产环境必须修改 |
|
||
|
||
## 验证测试
|
||
|
||
```bash
|
||
# 注册用户
|
||
curl -s -X POST http://localhost:3000/api/auth/register \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"username":"test","password":"123456"}'
|
||
|
||
# 登录获取 token
|
||
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"username":"test","password":"123456"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||
|
||
# 上传短信
|
||
curl -s -X POST http://localhost:3000/api/sms/upload \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-d '[{"phone_number":"+8613800138000","contact_name":"张三","content":"你好","type":"received","sms_date":"2026-04-28T10:30:00.000Z"}]'
|
||
|
||
# 查询短信列表
|
||
curl -s http://localhost:3000/api/sms -H "Authorization: Bearer $TOKEN"
|
||
|
||
# 删除
|
||
curl -s -X DELETE http://localhost:3000/api/sms/1 -H "Authorization: Bearer $TOKEN"
|
||
```
|