端到端加密的实时消息推送系统,Android 客户端接收推送消息,Rust 后端提供服务,Web 管理面板用于发送消息。
push_test/
├── app/ # 移动端 + Web 面板
│ ├── src/ # React Native Android 应用
│ │ ├── App.tsx # 主应用组件
│ │ └── components/ # UI 组件(PushAppBar、MessageList 等)
│ ├── core/ # Rust 核心库(JNI 绑定)
│ ├── panel/ # SolidJS Web 管理面板
│ └── android/ # Android 原生工程
├── server/ # Rust Axum 后端服务
│ └── src/
│ ├── main.rs # 服务入口
│ ├── routes.rs # HTTP API(推送、同步)
│ ├── channel_routes.rs # 频道管理
│ └── db.rs # SQLite 数据库
├── scripts/ # 构建/运行脚本
└── output/ # 构建产物
| 模块 | 技术 |
|---|---|
| Android 客户端 | React Native 0.76 + TypeScript + Material Design 3 |
| 原生核心库 | Rust(JNI 绑定,UniFFI) |
| Web 管理面板 | SolidJS + Vite + TypeScript |
| 后端服务 | Rust + Axum + SQLite + WebSocket |
| 消息加密 | AES-256-GCM + HMAC-SHA256 签名 |
| 构建系统 | pnpm + Turborepo + Cargo |
- Node.js >= 18
- pnpm >= 9
- Rust toolchain(stable)
- Android SDK(用于构建移动端)
- Java 21(Android 编译)
pnpm install# 1. 构建管理面板静态文件
pnpm run build:panel
# 2. 启动后端(面板已内嵌在二进制中)
cargo run服务默认监听 http://0.0.0.0:3000,管理面板直接访问该地址即可。
首次启动会自动创建 SQLite 数据库文件并执行表迁移。
如需配置监听地址或登录凭据,复制
config.example.toml为config.toml并修改。
# 后端热重载
cargo watch -x run
# 面板独立开发(Vite HMR,API 代理到 3000)
pnpm run dev:panel# Debug 构建
pnpm run build:debug
# Release 构建
pnpm run build构建产物输出到 output/ 目录。
# Debug 模式(启动服务 + 安装应用)
pnpm run local
# Release 模式
pnpm run local:release- 端到端加密 — 消息在发送端使用 AES-256-GCM 加密,仅持有正确密钥的客户端可解密
- 多频道支持 — 支持多频道消息隔离,不同频道使用不同密钥
- 持久化存储 — 消息存储在本地 SQLite,支持离线查看
- 实时推送 — 基于 WebSocket 的长连接,消息到达即时通知
- Web 管理面板 — 内嵌于后端的 SolidJS 管理面板,JWT 鉴权
服务启动时读取 config.toml(可通过 CONFIG_PATH 环境变量指定路径):
[server]
bind_address = "0.0.0.0:3000"
[auth]
jwt_secret = "change-me-to-a-random-secret"
username = "admin"
password = "admin123"| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 频道名称,全局唯一,仅允许字母数字及 - _,最长 80 字符 |
key |
string | 频道密钥。非空时频道为私有,消息加密下发且客户端需签名;为空时频道为公开 |
私有频道签名机制: 客户端调用需鉴权的端点时,携带 ts(时间戳)、nonce(随机数)、signature(HMAC-SHA256 签名)。签名原文为 channel:subject:ts:nonce,密钥为频道 key 原始值。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 消息 UUID |
channel |
string | 所属频道 |
title |
string | 通知标题 |
content |
string | 消息正文 |
extras |
object | 自定义扩展数据(JSON) |
delivery_status |
string | 投递状态:queued 或 online_sent:N |
created_at |
string | ISO 8601 创建时间 |
消息加密信封 EncryptedEnvelope:
| 字段 | 类型 | 说明 |
|---|---|---|
version |
int | 协议版本,当前为 1 |
channel |
string | 频道 |
algorithm |
string | 加密算法:AES-256-GCM+SHA256 或 none |
encrypted |
bool | 是否加密 |
data |
string | 密文(Base64,含 12 字节 nonce 前缀)或明文 |
POST /api/devices/register
鉴权:私有频道需签名
请求:
{
"deviceId": "android-uuid",
"channel": "default",
"auth": {
"ts": "1703001234",
"nonce": "random-hex",
"signature": "hmac-sha256-hex"
}
}响应:
{ "id": "device-uuid" }GET /api/channels/:channel/stream?ts=&nonce=&signature=
鉴权:私有频道需签名(query 参数)
升级为 WebSocket 长连接,服务端以 EncryptedEnvelope JSON 文本帧推送消息。
- 私有频道:帧内容为加密信封(
encrypted: true),数据经 AES-256-GCM 加密后 Base64 编码,客户端需用频道 key 解密 - 公开频道:帧内容为明文信封(
encrypted: false),data直接为消息 JSON
客户端无上行消息(仅维持连接)。
POST /api/push
鉴权:无(公开接口)
请求:
{
"channel": "default",
"title": "新通知",
"content": "消息内容",
"extras": {}
}响应:
{
"id": "message-uuid",
"accepted": true,
"online_deliveries": 3
}channel 默认为 "default",extras 默认为 {}。推送会同时写入数据库并广播给当前在线客户端。
GET /api/messages
鉴权:无(公开接口)
响应: 最近 200 条消息,按创建时间倒序。
[
{
"id": "msg-uuid",
"channel": "default",
"title": "标题",
"content": "正文",
"extras": "{}",
"delivery_status": "online_sent:3",
"created_at": "2024-12-20T10:30:00Z"
}
]DELETE /api/messages
鉴权:无(公开接口)
请求:
{ "ids": ["msg-uuid-1", "msg-uuid-2"] }响应:
{ "deleted": 2 }单次最多删除 500 条。
GET /api/messages/sync?channel=&deviceId=&ts=&nonce=&signature=&after=
鉴权:私有频道需签名
客户端离线后拉取历史消息,返回 EncryptedEnvelope[]。
| 参数 | 说明 |
|---|---|
channel |
频道名 |
deviceId |
设备 ID |
ts / nonce / signature |
HMAC 签名参数 |
after |
可选,ISO 8601 时间戳,只拉该时间后的消息(升序) |
不带 after 时返回最近 200 条(升序排列)。
GET /api/channels
鉴权:JWT(管理面板)
响应:
[
{
"id": "channel-uuid",
"name": "default",
"key": "my-secret-key",
"created_at": "2024-12-20T10:00:00Z",
"updated_at": "2024-12-20T10:00:00Z"
}
]POST /api/channels
鉴权:JWT(管理面板)
请求:
{
"name": "my-channel",
"key": "my-secret-key"
}响应: 返回完整的 ChannelItem。key 留空即为公开频道。频道已存在时更新密钥。
DELETE /api/channels/:channel
鉴权:JWT(管理面板)
级联操作:删除频道的所有消息,将该频道的客户端设备移至 default 频道,断开 WebSocket 连接。
响应:
{
"deleted_channel": true,
"deleted_messages": 42
}POST /api/admin/login
Content-Type: application/x-www-form-urlencoded
鉴权:无
请求:
username=admin&password=admin123
响应:
{ "token": "eyJhbGciOiJIUzI1NiJ9..." }Token 有效期 24 小时。管理面板 API 需携带 Authorization: Bearer <token> 请求头。
| 端点 | 鉴权方式 |
|---|---|
POST /api/push |
无 |
GET /api/messages |
无 |
DELETE /api/messages |
无 |
POST /api/devices/register |
私有频道:HMAC 签名 |
GET /api/channels/:channel/stream |
私有频道:HMAC 签名 |
GET /api/messages/sync |
私有频道:HMAC 签名 |
GET /api/channels |
JWT(管理面板) |
POST /api/channels |
JWT(管理面板) |
DELETE /api/channels/:channel |
JWT(管理面板) |
POST /api/admin/login |
无 |