# 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` 元数据 - **时长自动计算**:相邻检查点的时间差归属于前一个状态,`/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