# 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 # 配置加载(支持多环境:config/*.toml) └── handlers/ # 路由处理器模块 ├── mod.rs # 模块导出 ├── auth.rs # 登录相关 (login) ├── weather.rs # 天气数据 CRUD (post_weather_data, get_weather, delete_weather) ├── user.rs # 用户相关 (get_user_profile) ├── admin.rs # 管理员功能 (update_user_payment, get_user_by_admin) ├── health.rs # 健康检查 (/health) └── static_files.rs # 静态文件服务 (/static/{path}) config/ ├── default.toml # 默认配置(所有环境的共同默认值) ├── development.toml # 开发/测试环境配置 └── production.toml # 生产环境配置 migrations/ └── 001_add_payment_fields.sql # 用于添加支付字段的 ALTER TABLE static/ └── katex/ # KaTeX 数学公式渲染库 tests/ └── integration_test.rs # 基础测试框架 deploy.sh # 部署脚本(支持 development/production 参数) .env.example # 环境变量模板 ``` ## 服务器连接 ### SSH 连接 ```bash ssh root@1panel-server ``` ### 数据库 PostgreSQL 通过 Docker 部署: ```bash # 查看容器 docker ps | grep postgres # 连接数据库(容器内) docker exec -it postgres_container psql -U postgres -d milkydata # 连接字符串 postgres://milkydata:password@127.0.0.1:5432/milkydata ``` > 注意:Docker PostgreSQL 监听在 `127.0.0.1` 而非 `localhost` ## 数据库表结构 ### 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 表示永久 } ``` ### 支付处理器 - 文件:`src/handlers/payment.rs` - `mock-confirm` 接口仅用于测试环境,未来替换为真实微信支付回调时只需修改此函数 - 套餐金额常量定义在 `payment.rs` 的 `get_package_info()` 函数中 ## 代码模式 ### 处理器模式 ```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"} ``` ## 环境变量 ### 方式一:使用配置文件(推荐) 通过 `APP_ENV` 环境变量选择配置文件: ```bash APP_ENV=development cargo run # 使用 config/development.toml APP_ENV=production cargo run # 使用 config/production.toml ``` **配置文件**: | 文件 | 用途 | |------|------| | `config/default.toml` | 所有环境的共同默认值 | | `config/development.toml` | 开发/测试环境覆盖 | | `config/production.toml` | 生产环境覆盖 | **示例 production.toml**: ```toml database_url = "postgres://user:pass@host:5432/dbname" wechat_appid = "wx..." jwt_secret = "your_secret_key" rust_log = "info" free_user_data_limit = 20 server_host = "0.0.0.0" server_ports = [4433, 8443, 8080, 3000, 8000, 8888] ``` ### 方式二:直接环境变量 最高优先级,可覆盖配置文件: ```env APP_DATABASE_URL=postgres://user:pass@host:5432/dbname APP_JWT_SECRET=your_secret_key APP_WECHAT_APPID=wx... APP_RUST_LOG=info ``` ### systemd service 配置 测试服务和生产服务通过不同 systemd unit 和工作目录隔离: | 服务 | systemd unit | 工作目录 | APP_ENV | |------|-------------|---------|---------| | 测试 | `rust-backend-dev.service` | `/root/rust/rust_backend_dev` | `development` | | 生产 | `rust-backend.service` | `/root/rust/rust_backend` | (使用 .env) | ## 约束条件 ### 已知兼容性问题 ⚠️ **actix-ratelimit 0.3.1 与 actix-web 4.x 不兼容** - 错误:`Transform` trait bound 不满足 - 原因:该 crate 基于 actix-web 3.x 设计 - 备选:actix-web-lab、手动 HashMap 实现、Nginx 层限流 ⚠️ **actix-web-prom 与当前架构不兼容** - 错误:`ServiceFactory Response` 类型冲突(expected `ServiceResponse`, found `ServiceResponse>`) - 原因:middleware 改变了 App 返回类型,与 `impl Trait` 返回类型冲突 - 备选:actix-web-lab、手动 atomic 计数器、Nginx access log ### 禁止事项 - 在 JWT Claims 中添加 `is_admin`(必须查询数据库) - 信任 JWT 中的 `is_paid` 来做配额决策(必须查询数据库) - 使用 `as any`、`@ts-ignore` 或类型错误抑制 - 添加审计日志 ### 必须事项 - 管理员接口必须通过数据库查询验证管理员身份 - 已认证处理器使用 `web::ReqData` - 遵循现有的错误响应格式 - 通过数据库查询检查 `is_paid_active`,而非 JWT - SQL 中使用 `desc` 等保留关键字时必须加双引号:`"desc"` ## 常见任务 ### 添加新接口 1. 在 `main.rs` 中创建处理器函数 2. 如需要,在 `db.rs` 中添加数据库函数 3. 在 `create_server_config` 中注册(如果是受保护接口,放在 JWT 作用域内) 4. 如需要,在 `models.rs` 中添加请求/响应结构体 ### 路由配置结构 ```rust // create_server_config 函数中的 App 配置顺序很重要 App::new() .app_data(web::Data::new(pool)) // 数据源 .app_data(web::Data::new(http_client)) .app_data(web::Data::new(app_state)) // 公开接口(无 middleware) .service(login) // POST /api/login .service(get_weather_details) // GET /api/weather/details // 受保护接口(JWT middleware) .service( web::scope("") .wrap(from_fn(jwt_middleware)) // JWT 验证 .service(post_weather_data) // POST /api/post-weather-data // ... 其他受保护接口 ) // 静态文件和健康检查放最后 .service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files))) .service(health_check) ``` ### 路由优先级 - `web::scope("/")` 内的路由路径不带前缀(handlers 已有 `#[post("/api/...")]` 等属性) - 公开接口和受保护接口分开,便于添加不同的 middleware ### 修改数据库 1. 在 `migrations/` 中创建迁移 SQL 2. 在 PostgreSQL 上手动执行迁移 3. 如果表结构变更,更新 `models.rs` 中的 `User` 结构体 4. 更新 `db.rs` 中的数据库函数 ## 构建与运行 ```bash cargo build # 编译 APP_ENV=development cargo run # 开发环境运行 APP_ENV=production cargo run # 生产环境运行 cargo test # 运行测试 cargo clippy # 代码检查 ``` **部署**: ```bash ./deploy.sh development # 部署到测试服务器 ./deploy.sh production # 部署到生产服务器 ``` 服务器尝试端口顺序:4433、8443、8080、3000、8000、8888