Files
asd-backend/AGENTS.md
Milky0217 913a235e5c docs: 更新 AGENTS.md 和 IMPROVEMENTS.md,记录已知兼容性问题
AGENTS.md:
- 添加已知兼容性问题章节
  - actix-ratelimit 0.3.1 与 actix-web 4.x 不兼容
  - actix-web-prom 与当前架构不兼容
- 添加路由配置结构说明(App 配置顺序、middleware 应用方式)
- 添加路由优先级说明

IMPROVEMENTS.md:
- 2.1 缺少请求频率限制:标记 ⚠️,记录兼容性问题及备选方案
- 6.1 缺少性能指标收集:标记 ⚠️,记录兼容性问题及备选方案
- 修正无日志聚合的 checkbox 状态
2026-04-15 11:29:20 +08:00

231 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<Claims>` 参数获取
## 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<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<RequestBody>,
) -> 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<Type, String> {
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
```
## 约束条件
### 已知兼容性问题
⚠️ **actix-ratelimit 0.3.1 与 actix-web 4.x 不兼容**
- 错误:`Transform<ResourceService>` trait bound 不满足
- 原因:该 crate 基于 actix-web 3.x 设计
- 备选actix-web-lab、手动 HashMap 实现、Nginx 层限流
⚠️ **actix-web-prom 与当前架构不兼容**
- 错误:`ServiceFactory Response` 类型冲突expected `ServiceResponse`, found `ServiceResponse<EitherBody<..., ...>>`
- 原因middleware 改变了 App 返回类型,与 `impl Trait` 返回类型冲突
- 备选actix-web-lab、手动 atomic 计数器、Nginx access log
### 禁止事项
- 在 JWT Claims 中添加 `is_admin`(必须查询数据库)
- 信任 JWT 中的 `is_paid` 来做配额决策(必须查询数据库)
- 使用 `as any``@ts-ignore` 或类型错误抑制
- 修改 config.rs死代码
- 添加支付网关集成
- 添加审计日志
### 必须事项
- 管理员接口必须通过数据库查询验证管理员身份
- 已认证处理器使用 `web::ReqData<Claims>`
- 遵循现有的错误响应格式
- 通过数据库查询检查 `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 # 编译
cargo run # 启动服务器
cargo test # 运行测试
cargo clippy # 代码检查
```
服务器尝试端口顺序4433、8443、8080、3000、8000、8888