13f7c1326a
- RESTful API: POST /heartbeat, POST /checkpoints, GET /status, GET /summaries - State-change-only checkpoint model with extensible StateType enum - PostgreSQL backend with sqlx, auto-migration on startup - pg_cron scheduled aggregation (state_summaries) and offline detection - Heartbeat-based liveness with 60s timeout auto-offline - LEAD() window function for state duration calculation - JSONB content field for extensible checkpoint metadata BREAKING CHANGE: Complete rewrite from Hello World to full API service.
143 lines
4.6 KiB
Markdown
143 lines
4.6 KiB
Markdown
# ck-rs — Checkpoint 用户状态追踪服务
|
||
|
||
基于 [Axum](https://github.com/tokio-rs/axum) 的 RESTful API 服务,用于记录用户在不同状态下的持续时间。客户端定时上报检查点(checkpoint),服务端自动计算各状态累计时长。类比手机屏幕使用时间统计。
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
# 启动服务(默认监听 127.0.0.1:3000,可通过 .env 中 LISTEN_ADDR 修改)
|
||
cargo run
|
||
```
|
||
|
||
## API 端点
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| `GET` | `/health` | 服务健康检查 |
|
||
| `POST` | `/users/{user_id}/checkpoints` | 上报当前状态(心跳) |
|
||
| `GET` | `/users/{user_id}/checkpoints` | 查询检查点历史(`?from=&to=&limit=`) |
|
||
| `GET` | `/users/{user_id}/checkpoints/{id}` | 查询单个检查点 |
|
||
| `GET` | `/users/{user_id}/status` | 当前状态 + 各状态累计时长 |
|
||
|
||
### 状态类型
|
||
|
||
| 内置状态 | 说明 |
|
||
|----------|------|
|
||
| `Online` | 在线 |
|
||
| `Offline` | 离线 |
|
||
| `Idle` | 空闲 |
|
||
| `Working` | 工作中 |
|
||
| `Sleeping` | 睡眠 |
|
||
| `"任意字符串"` | 自定义状态,如 `Gaming`、`Meeting`、`Driving` 等 |
|
||
|
||
### 请求示例
|
||
|
||
```bash
|
||
BASE=http://localhost:3000
|
||
|
||
# 健康检查
|
||
curl $BASE/health
|
||
|
||
# 上报状态(自动记录服务端当前时间)
|
||
curl -X POST $BASE/users/alice/checkpoints \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"state":"Working"}'
|
||
|
||
# 上报状态 + 附带元数据(设备、坐标等)
|
||
curl -X POST $BASE/users/alice/checkpoints \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"state":"Idle","content":{"device":"MacBook","battery":85}}'
|
||
|
||
# 上报自定义状态
|
||
curl -X POST $BASE/users/alice/checkpoints \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"state":"Gaming"}'
|
||
|
||
# 等待几秒后切换状态(产生时长数据)
|
||
sleep 3
|
||
curl -X POST $BASE/users/alice/checkpoints \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"state":"Offline"}'
|
||
|
||
# 查询检查点历史
|
||
curl $BASE/users/alice/checkpoints
|
||
|
||
# 按时间范围查询
|
||
curl "$BASE/users/alice/checkpoints?from=1717161600&to=1717248000&limit=10"
|
||
|
||
# 查询单个检查点
|
||
curl $BASE/users/alice/checkpoints/1
|
||
|
||
# 查询状态汇总(当前状态 + 各状态累计时长)
|
||
curl $BASE/users/alice/status
|
||
# → {"user_id":"alice","current_state":"Offline","since":1717248000,
|
||
# "durations":[{"state":"Working","duration_secs":3}]}
|
||
```
|
||
|
||
## 环境变量
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `LISTEN_ADDR` | `127.0.0.1:3000` | 监听地址 |
|
||
| `DATABASE_URL` | (无) | 数据库连接字符串(接入真实 DB 时设置) |
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
src/
|
||
├── main.rs # 入口:加载配置 → 初始化 DB → 构建路由 → 启动
|
||
├── config.rs # 配置层(环境变量 + 默认值)
|
||
├── state.rs # AppState(全局共享状态,持有 DB)
|
||
├── error.rs # 统一错误类型 AppError(实现 IntoResponse)
|
||
├── router.rs # 路由组装
|
||
├── models/
|
||
│ ├── mod.rs
|
||
│ └── checkpoint.rs # StateType / Checkpoint / UserStatusResponse 等
|
||
├── handlers/
|
||
│ ├── mod.rs
|
||
│ ├── health.rs # GET /health
|
||
│ └── checkpoints.rs # POST/GET /users/{id}/checkpoints
|
||
└── db/
|
||
├── mod.rs # Db trait(数据库抽象接口)
|
||
└── memory.rs # MemoryDb(内存模拟,开发期使用)
|
||
```
|
||
|
||
## 核心设计
|
||
|
||
- **状态可自由扩充**:`StateType` 内置 5 种状态 + `Custom(String)` 变体,传入任意字符串自动作为新状态
|
||
- **content 可扩展**:每个检查点可附带 `Option<serde_json::Value>` 元数据
|
||
- **时长自动计算**:相邻检查点的时间差归属于前一个状态,`/status` 返回各状态累计秒数
|
||
- **timestamp 可选**:请求可带时间戳,不传则服务端取当前时间
|
||
|
||
## 接入真实数据库
|
||
|
||
当前使用内存模拟存储(`MemoryDb`),切换为 PostgreSQL 仅需 3 步:
|
||
|
||
### 1. 添加依赖
|
||
|
||
取消 `Cargo.toml` 中的注释:
|
||
|
||
```toml
|
||
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
|
||
```
|
||
|
||
### 2. 实现 Db trait
|
||
|
||
新建 `src/db/postgres.rs`,对 `PgPool` 实现 `Db` trait。
|
||
|
||
### 3. 修改入口
|
||
|
||
`src/main.rs` 中将 `MemoryDb::new()` 替换为 `PgPool::connect(...).await`。
|
||
|
||
## 技术栈
|
||
|
||
- [Axum 0.8](https://crates.io/crates/axum) — Web 框架
|
||
- [Tokio](https://crates.io/crates/tokio) — 异步运行时
|
||
- [Serde](https://crates.io/crates/serde) — 序列化 / 反序列化
|
||
- [Chrono](https://crates.io/crates/chrono) — 日期时间处理
|
||
- [Dotenvy](https://crates.io/crates/dotenvy) — .env 文件加载
|
||
|
||
## License
|
||
|
||
MIT
|