# Agent 文档 — rust-backend ## 项目概述 微信小程序天气数据采集后端。用户通过微信认证,上传天气观测数据,并通过 REST API 管理数据。 ## 技术栈 - **语言**: Rust (edition 2024) - **Web 框架**: actix-web 4.11 - **数据库**: PostgreSQL,通过 sqlx 0.8.6 连接 - **认证**: JWT (HS256),使用 jsonwebtoken 9.3 - **HTTP 客户端**: reqwest(用于调用微信 API) - **序列化**: serde + serde_json - **时间处理**: chrono ## 项目结构 ``` src/ ├── main.rs # 入口文件,所有路由处理器,服务器配置 ├── auth.rs # JWT 中间件,令牌生成/验证 ├── db.rs # 数据库操作(通过 sqlx 执行原始 SQL) ├── models.rs # 数据结构(Claims、User、WeatherData 等) └── config.rs # 配置结构体(main.rs 中未使用 — 死代码) migrations/ └── 001_add_payment_fields.sql # 用于添加支付字段的 ALTER TABLE tests/ └── integration_test.rs # 基础测试框架 ``` ## 数据库表结构 ### users 表 ```sql id INTEGER PRIMARY KEY openid VARCHAR UNIQUE(微信用户 ID) name VARCHAR(默认为 openid 前 8 个字符) type INTEGER(硬编码为 2) is_paid BOOLEAN DEFAULT false is_admin BOOLEAN DEFAULT false paid_expires_at TIMESTAMPTZ DEFAULT NULL ``` ### weather_data 表 ```sql id INTEGER PRIMARY KEY user_id INTEGER(外键,关联 users 表) title, date, hour, min, longitude, latitude, ... -- 30+ 个天气测量字段列 ``` ## 认证流程 1. **登录**: `POST /api/login` 传入微信 code → 调用微信 API → UPSERT 用户 → 返回 JWT 2. **JWT 声明**: `{exp, iat, user_id, openid, user_type}`(24 小时过期) 3. **中间件**: `jwt_middleware` 提取 Bearer 令牌,验证后将 Claims 插入请求扩展 4. **处理器访问**: 通过 `claims: web::ReqData` 参数获取 ## API 接口 ### 公开接口(无需认证) - `POST /api/login` — 微信登录 - `GET /weather/details?temp_token=...` — 通过临时令牌访问 - `GET /static/{tail:*}` — 静态文件 ### 受保护接口(需要 JWT) - `POST /api/post-weather-data` — 上传天气数据(带配额检查) - `GET /api/user/profile` — 获取当前用户信息(付费状态、管理员标识) - `GET /weather` — 分页列出用户的天气数据 - `GET /weather/details?id=...` — 获取天气详情(JWT 认证) - `POST /api/generate-temp-token/{resource_id}` — 生成 10 分钟分享令牌 - `DELETE /weather/delete/{id}` — 删除天气记录 ### 管理员接口(需要 JWT + is_admin) - `PUT /api/admin/users/{id}/payment` — 更新用户支付状态 - `GET /api/admin/users/{id}` — 获取用户信息 ## 支付系统 ### 支付状态逻辑 ```rust is_paid_active = is_paid && (paid_expires_at.is_none() || paid_expires_at > Utc::now()) ``` ### 配额限制 - 未付费用户限制为 `FREE_USER_DATA_LIMIT` 条记录(环境变量,默认 20) - 付费用户(活跃状态)无限制 - 在 `insert_weather_data` 执行 INSERT 前进行检查 ### 管理员 API ```json PUT /api/admin/users/{id}/payment { "is_paid": true, "paid_expires_at": "2026-12-31T23:59:59Z" // 可选,null 表示永久 } ``` ## 代码模式 ### 处理器模式 ```rust #[post("/api/endpoint")] async fn handler( pool: web::Data, claims: web::ReqData, body: web::Json, ) -> impl Responder { match db::function(pool.get_ref(), claims.user_id).await { Ok(data) => HttpResponse::Ok().json(serde_json::json!({"success": true, "data": data})), Err(e) => HttpResponse::Ok().json(serde_json::json!({"success": false, "errcode": 500, "errmsg": e})) } } ``` ### 数据库函数模式 ```rust pub async fn function_name(pool: &PgPool, param: i32) -> Result { match sqlx::query_as::<_, Type>("SELECT ...") .bind(param) .fetch_optional(pool) .await { Ok(Some(row)) => Ok(row), Ok(None) => Err("Not found".to_string()), Err(e) => Err(format!("Query failed: {}", e)), } } ``` ### 错误响应格式 ```json {"success": false, "errcode": 403, "errmsg": "Error message"} ``` ## 环境变量 ```env DATABASE_URL=postgres://user:pass@host:5432/dbname WECHAT_APPID=wx... WECHAT_SECRET=... JWT_SECRET=your_secret_key SSL_KEY_PATH=/path/to/key SSL_CERT_PATH=/path/to/cert RUST_LOG=info APP_VERSION=0.2.0 FREE_USER_DATA_LIMIT=20 ``` ## 约束条件 ### 禁止事项 - 在 JWT Claims 中添加 `is_admin`(必须查询数据库) - 信任 JWT 中的 `is_paid` 来做配额决策(必须查询数据库) - 使用 `as any`、`@ts-ignore` 或类型错误抑制 - 修改 config.rs(死代码) - 添加支付网关集成 - 添加审计日志 ### 必须事项 - 管理员接口必须通过数据库查询验证管理员身份 - 已认证处理器使用 `web::ReqData` - 遵循现有的错误响应格式 - 通过数据库查询检查 `is_paid_active`,而非 JWT - SQL 中使用 `desc` 等保留关键字时必须加双引号:`"desc"` ## 常见任务 ### 添加新接口 1. 在 `main.rs` 中创建处理器函数 2. 如需要,在 `db.rs` 中添加数据库函数 3. 在 `create_server_config` 中注册(如果是受保护接口,放在 JWT 作用域内) 4. 如需要,在 `models.rs` 中添加请求/响应结构体 ### 修改数据库 1. 在 `migrations/` 中创建迁移 SQL 2. 在 PostgreSQL 上手动执行迁移 3. 如果表结构变更,更新 `models.rs` 中的 `User` 结构体 4. 更新 `db.rs` 中的数据库函数 ## 构建与运行 ```bash cargo build # 编译 cargo run # 启动服务器 cargo test # 运行测试 cargo clippy # 代码检查 ``` 服务器尝试端口顺序:4433、8443、8080、3000、8000、8888