- 添加 SSH 连接命令:ssh root@1panel-server - 说明 PostgreSQL 通过 Docker 部署 - 添加 docker ps 和 docker exec 连接命令 - 注明连接字符串和 127.0.0.1 而非 localhost
10 KiB
10 KiB
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 连接
ssh root@1panel-server
数据库
PostgreSQL 通过 Docker 部署:
# 查看容器
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 表
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 表
id INTEGER PRIMARY KEY
user_id INTEGER(外键,关联 users 表)
title, date, hour, min, longitude, latitude, ...
-- 30+ 个天气测量字段列
认证流程
- 登录:
POST /api/login传入微信 code → 调用微信 API → UPSERT 用户 → 返回 JWT - JWT 声明:
{exp, iat, user_id, openid, user_type}(24 小时过期) - 中间件:
jwt_middleware提取 Bearer 令牌,验证后将 Claims 插入请求扩展 - 处理器访问: 通过
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}— 获取用户信息
支付系统
支付状态逻辑
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
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()函数中
代码模式
处理器模式
#[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}))
}
}
数据库函数模式
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)),
}
}
错误响应格式
{"success": false, "errcode": 403, "errmsg": "Error message"}
环境变量
方式一:使用配置文件(推荐)
通过 APP_ENV 环境变量选择配置文件:
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:
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]
方式二:直接环境变量
最高优先级,可覆盖配置文件:
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<ResourceService>trait bound 不满足 - 原因:该 crate 基于 actix-web 3.x 设计
- 备选:actix-web-lab、手动 HashMap 实现、Nginx 层限流
⚠️ actix-web-prom 与当前架构不兼容
- 错误:
ServiceFactory Response类型冲突(expectedServiceResponse, foundServiceResponse<EitherBody<..., ...>>) - 原因:middleware 改变了 App 返回类型,与
impl Trait返回类型冲突 - 备选:actix-web-lab、手动 atomic 计数器、Nginx access log
禁止事项
- 在 JWT Claims 中添加
is_admin(必须查询数据库) - 信任 JWT 中的
is_paid来做配额决策(必须查询数据库) - 使用
as any、@ts-ignore或类型错误抑制 - 添加审计日志
必须事项
- 管理员接口必须通过数据库查询验证管理员身份
- 已认证处理器使用
web::ReqData<Claims> - 遵循现有的错误响应格式
- 通过数据库查询检查
is_paid_active,而非 JWT - SQL 中使用
desc等保留关键字时必须加双引号:"desc"
常见任务
添加新接口
- 在
main.rs中创建处理器函数 - 如需要,在
db.rs中添加数据库函数 - 在
create_server_config中注册(如果是受保护接口,放在 JWT 作用域内) - 如需要,在
models.rs中添加请求/响应结构体
路由配置结构
// 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
修改数据库
- 在
migrations/中创建迁移 SQL - 在 PostgreSQL 上手动执行迁移
- 如果表结构变更,更新
models.rs中的User结构体 - 更新
db.rs中的数据库函数
构建与运行
cargo build # 编译
APP_ENV=development cargo run # 开发环境运行
APP_ENV=production cargo run # 生产环境运行
cargo test # 运行测试
cargo clippy # 代码检查
部署:
./deploy.sh development # 部署到测试服务器
./deploy.sh production # 部署到生产服务器
服务器尝试端口顺序:4433、8443、8080、3000、8000、8888