feat: 统一前后端日志和错误处理
This commit is contained in:
485
AGENTS.md
485
AGENTS.md
@@ -1,66 +1,97 @@
|
||||
# Agent 文档 — rust-backend
|
||||
# AGENTS.md - Rust 后端
|
||||
|
||||
## 项目概述
|
||||
|
||||
微信小程序天气数据采集后端。用户通过微信认证,上传天气观测数据,并通过 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
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| Rust | edition 2024 | 主力语言 |
|
||||
| actix-web | 4.11 | Web 框架 |
|
||||
| sqlx | 0.8.6 | PostgreSQL 连接 |
|
||||
| serde | 1.0 | 序列化 |
|
||||
| jsonwebtoken | 9.3 | JWT 认证 |
|
||||
| reqwest | 0.12 | HTTP 客户端 |
|
||||
| chrono | 0.4 | 时间处理 |
|
||||
|
||||
---
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
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})
|
||||
├── 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
|
||||
├── user.rs # 用户相关
|
||||
├── admin.rs # 管理员功能
|
||||
├── payment.rs # 支付相关
|
||||
├── health.rs # 健康检查 (/health)
|
||||
└── static_files.rs # 静态文件服务
|
||||
|
||||
config/
|
||||
├── default.toml # 默认配置(所有环境的共同默认值)
|
||||
├── development.toml # 开发/测试环境配置
|
||||
└── production.toml # 生产环境配置
|
||||
├── default.toml # 默认配置(所有环境的共同默认值)
|
||||
├── development.toml # 开发/测试环境配置
|
||||
└── production.toml # 生产环境配置
|
||||
|
||||
migrations/
|
||||
└── 001_add_payment_fields.sql # 用于添加支付字段的 ALTER TABLE
|
||||
|
||||
static/
|
||||
└── katex/ # KaTeX 数学公式渲染库
|
||||
|
||||
test_deployment.sh # 部署后测试脚本(自动执行)
|
||||
tests/
|
||||
└── integration_test.rs # 基础测试框架
|
||||
|
||||
deploy.sh # 部署脚本(支持 development/production 参数)
|
||||
.env.example # 环境变量模板
|
||||
migrations/ # 数据库迁移 SQL
|
||||
tests/ # 集成测试
|
||||
static/ # 静态文件(KaTeX 等)
|
||||
deploy.sh # 部署脚本
|
||||
.env.example # 环境变量模板
|
||||
```
|
||||
|
||||
## 服务器连接
|
||||
---
|
||||
|
||||
## 配置管理
|
||||
|
||||
### 配置文件方式(推荐)
|
||||
|
||||
通过 `APP_ENV` 环境变量选择配置文件:
|
||||
|
||||
### SSH 连接
|
||||
```bash
|
||||
ssh root@1panel-server
|
||||
APP_ENV=development cargo run # 使用 config/development.toml
|
||||
APP_ENV=production cargo run # 使用 config/production.toml
|
||||
```
|
||||
|
||||
### 数据库
|
||||
### 直接环境变量方式
|
||||
|
||||
PostgreSQL 通过 Docker 部署:
|
||||
最高优先级,可覆盖配置文件:
|
||||
|
||||
```env
|
||||
APP_DATABASE_URL=postgres://user:pass@host:5432/dbname
|
||||
APP_JWT_SECRET=your_secret_key
|
||||
APP_WECHAT_APPID=wx...
|
||||
```
|
||||
|
||||
### 配置文件优先级
|
||||
|
||||
`环境变量 > production.toml > development.toml > default.toml`
|
||||
|
||||
---
|
||||
|
||||
## 数据库
|
||||
|
||||
### 连接信息
|
||||
|
||||
| 环境 | 连接字符串 |
|
||||
|------|-----------|
|
||||
| 测试 | `postgres://milkydata:password@127.0.0.1:5432/milkydata_dev` |
|
||||
| 生产 | `postgres://milkydata:password@127.0.0.1:5432/milkydata` |
|
||||
|
||||
> ⚠️ Docker PostgreSQL 监听在 `127.0.0.1` 而非 `localhost`
|
||||
|
||||
### 连接数据库
|
||||
|
||||
```bash
|
||||
# 查看容器
|
||||
@@ -68,19 +99,12 @@ docker ps | grep postgres
|
||||
|
||||
# 连接数据库(容器内)
|
||||
docker exec -it postgres_container psql -U postgres -d milkydata_dev
|
||||
|
||||
# 连接字符串
|
||||
# 测试环境
|
||||
postgres://milkydata:password@127.0.0.1:5432/milkydata_dev
|
||||
# 生产环境
|
||||
postgres://milkydata:password@127.0.0.1:5432/milkydata
|
||||
```
|
||||
|
||||
> 注意:Docker PostgreSQL 监听在 `127.0.0.1` 而非 `localhost`
|
||||
### 表结构
|
||||
|
||||
## 数据库表结构
|
||||
#### users 表
|
||||
|
||||
### users 表
|
||||
```sql
|
||||
id INTEGER PRIMARY KEY
|
||||
openid VARCHAR UNIQUE(微信用户 ID)
|
||||
@@ -91,81 +115,58 @@ is_admin BOOLEAN DEFAULT false
|
||||
paid_expires_at TIMESTAMPTZ DEFAULT NULL
|
||||
```
|
||||
|
||||
### weather_data 表
|
||||
#### 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>` 参数获取
|
||||
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` — 获取天气详情(支持 JWT 或 temp_token,详见下文)
|
||||
- `GET /static/{tail:*}` — 静态文件
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `POST /api/login` | 微信登录 |
|
||||
| `GET /weather/details` | 获取天气详情(支持 JWT 或 temp_token) |
|
||||
| `GET /static/{tail:*}` | 静态文件 |
|
||||
|
||||
### 受保护接口(需要 JWT)
|
||||
- `POST /api/post-weather-data` — 上传天气数据(带配额检查)
|
||||
- `GET /api/user/profile` — 获取当前用户信息(付费状态、管理员标识)
|
||||
- `GET /weather` — 分页列出用户的天气数据
|
||||
- `POST /api/generate-temp-token/{resource_id}` — 生成 10 分钟分享令牌
|
||||
- `DELETE /weather/delete/{id}` — 删除天气记录
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `POST /api/post-weather-data` | 上传天气数据(带配额检查) |
|
||||
| `GET /api/user/profile` | 获取当前用户信息 |
|
||||
| `GET /weather` | 分页列出用户的天气数据 |
|
||||
| `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}` — 获取用户信息
|
||||
|
||||
## 支付系统
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `PUT /api/admin/users/{id}/payment` | 更新用户支付状态 |
|
||||
| `GET /api/admin/users/{id}` | 获取用户信息 |
|
||||
|
||||
### 支付模式
|
||||
| 模式 | 来源 | 处理方式 |
|
||||
|------|------|---------|
|
||||
| 微信支付 | `payment_orders` | 收到微信回调后确认,累积计算有效期 |
|
||||
| 邀请码 | `invitation_codes` | 核销后直接激活,累积计算有效期 |
|
||||
| 管理员开通 | 直接 UPDATE | 后台手动设置 |
|
||||
|
||||
### 累积计算逻辑
|
||||
用户多次购买时,有效期会累加而非覆盖:
|
||||
```rust
|
||||
// 新到期时间 = MAX(当前到期, 现在) + 本次套餐天数
|
||||
let base_time = std::cmp::max(current_expires, Utc::now());
|
||||
let new_expires = base_time + days(pkg_days);
|
||||
```
|
||||
|
||||
### 永久会员
|
||||
永久会员的 `expires_at` 设为 `2099-12-31` 而非 NULL。
|
||||
|
||||
### 配额限制
|
||||
- 未付费用户限制为 `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"
|
||||
}
|
||||
```
|
||||
|
||||
### 支付处理器
|
||||
- 文件:`src/handlers/payment.rs`
|
||||
- `mock-confirm` 接口仅用于测试环境
|
||||
- 套餐金额常量定义在 `get_package_info()` 函数中
|
||||
---
|
||||
|
||||
## 代码模式
|
||||
|
||||
### 处理器模式
|
||||
|
||||
```rust
|
||||
#[post("/api/endpoint")]
|
||||
async fn handler(
|
||||
@@ -174,13 +175,21 @@ async fn handler(
|
||||
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}))
|
||||
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 ...")
|
||||
@@ -196,172 +205,224 @@ pub async fn function_name(pool: &PgPool, param: i32) -> Result<Type, String> {
|
||||
```
|
||||
|
||||
### 错误响应格式
|
||||
|
||||
```json
|
||||
{"success": false, "errcode": 403, "errmsg": "Error message"}
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
---
|
||||
|
||||
### 方式一:使用配置文件(推荐)
|
||||
## ⚠️ 常见坑
|
||||
|
||||
通过 `APP_ENV` 环境变量选择配置文件:
|
||||
### 1. config crate 路径问题
|
||||
|
||||
```bash
|
||||
APP_ENV=development cargo run # 使用 config/development.toml
|
||||
APP_ENV=production cargo run # 使用 config/production.toml
|
||||
`File::with_name()` 使用当前工作目录,而非 `CARGO_MANIFEST_DIR`。
|
||||
|
||||
```rust
|
||||
// ❌ 错误
|
||||
let config = Config::builder()
|
||||
.add_source(File::with_name("config"))
|
||||
.build();
|
||||
|
||||
// ✅ 正确:使用 CARGO_MANIFEST_DIR
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let config_path = Path::new(manifest_dir).join("config");
|
||||
```
|
||||
|
||||
**配置文件**:
|
||||
### 2. TOML 配置结构
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `config/default.toml` | 所有环境的共同默认值 |
|
||||
| `config/development.toml` | 开发/测试环境覆盖 |
|
||||
| `config/production.toml` | 生产环境覆盖 |
|
||||
**保持扁平结构,不使用 section headers**。
|
||||
|
||||
**示例 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]
|
||||
# ❌ 错误:section headers 导致合并冲突
|
||||
[development]
|
||||
database_url = "..."
|
||||
|
||||
# ✅ 正确:扁平结构
|
||||
database_url = "..."
|
||||
```
|
||||
|
||||
### 方式二:直接环境变量
|
||||
### 3. serde skip 与查询冲突
|
||||
|
||||
最高优先级,可覆盖配置文件:
|
||||
`skip_deserializing` 会导致 SELECT 时字段缺失错误。
|
||||
|
||||
```env
|
||||
APP_DATABASE_URL=postgres://user:pass@host:5432/dbname
|
||||
APP_JWT_SECRET=your_secret_key
|
||||
APP_WECHAT_APPID=wx...
|
||||
APP_RUST_LOG=info
|
||||
```rust
|
||||
// ❌ 错误:skip_deserializing 影响查询
|
||||
#[serde(rename = "isFavorite", skip_deserializing)]
|
||||
pub is_favorite: bool,
|
||||
|
||||
// ✅ 正确:使用 default
|
||||
#[serde(rename = "isFavorite", default)]
|
||||
pub is_favorite: bool,
|
||||
```
|
||||
|
||||
### systemd service 配置
|
||||
### 4. JWT 中不能添加 is_admin
|
||||
|
||||
测试服务和生产服务通过不同 systemd unit 和工作目录隔离:
|
||||
**禁止在 JWT Claims 中添加 `is_admin`**,必须查询数据库验证。
|
||||
|
||||
| 服务 | systemd unit | 工作目录 | APP_ENV |
|
||||
### 5. 配额决策不能信任 JWT
|
||||
|
||||
必须查询数据库检查 `is_paid_active`,而非信任 JWT 中的声明。
|
||||
|
||||
### 6. SQL 保留关键字
|
||||
|
||||
SQL 中使用 `desc` 等保留关键字时必须加双引号:
|
||||
|
||||
```sql
|
||||
-- ❌ 错误
|
||||
ORDER BY desc
|
||||
|
||||
-- ✅ 正确
|
||||
ORDER BY "desc"
|
||||
```
|
||||
|
||||
### 7. actix-ratelimit 不兼容
|
||||
|
||||
**actix-ratelimit 0.3.1 与 actix-web 4.x 不兼容**。
|
||||
|
||||
备选方案:actix-web-lab、手动 HashMap 实现、Nginx 层限流。
|
||||
|
||||
---
|
||||
|
||||
## 支付系统
|
||||
|
||||
### 支付模式
|
||||
|
||||
| 模式 | 来源 | 处理方式 |
|
||||
|------|------|---------|
|
||||
| 微信支付 | `payment_orders` | 收到微信回调后确认 |
|
||||
| 邀请码 | `invitation_codes` | 核销后直接激活 |
|
||||
| 管理员开通 | 直接 UPDATE | 后台手动设置 |
|
||||
|
||||
### 累积计算逻辑
|
||||
|
||||
用户多次购买时,有效期会累加而非覆盖:
|
||||
|
||||
```rust
|
||||
let base_time = std::cmp::max(current_expires, Utc::now());
|
||||
let new_expires = base_time + days(pkg_days);
|
||||
```
|
||||
|
||||
### 永久会员
|
||||
|
||||
永久会员的 `expires_at` 设为 `2099-12-31` 而非 NULL。
|
||||
|
||||
### 配额限制
|
||||
|
||||
- 未付费用户限制为 `FREE_USER_DATA_LIMIT` 条记录
|
||||
- 付费用户(活跃状态)无限制
|
||||
|
||||
---
|
||||
|
||||
## 部署
|
||||
|
||||
### 服务器信息
|
||||
|
||||
| 环境 | 域名 | 端口 | 远程目录 |
|
||||
|------|------|------|---------|
|
||||
| 测试 | xmclassmate.top/dev | 8080 | /root/rust/rust_backend_dev |
|
||||
| 生产 | xmclassmate.top | 4433 | /root/rust/rust_backend |
|
||||
|
||||
### systemd 服务
|
||||
|
||||
| 环境 | systemd unit | 工作目录 | APP_ENV |
|
||||
|------|-------------|---------|---------|
|
||||
| 测试 | `rust-backend-dev.service` | `/root/rust/rust_backend_dev` | `development` |
|
||||
| 生产 | `rust-backend.service` | `/root/rust/rust_backend` | (使用 .env) |
|
||||
|
||||
## 约束条件
|
||||
> ⚠️ systemd 配置使用 `Environment=` 而非 `EnvironmentFile=`
|
||||
|
||||
### 已知兼容性问题
|
||||
⚠️ **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
|
||||
```bash
|
||||
./deploy.sh development # 部署到测试服务器
|
||||
./deploy.sh production # 部署到生产服务器
|
||||
```
|
||||
|
||||
### 禁止事项
|
||||
- 在 JWT Claims 中添加 `is_admin`(必须查询数据库)
|
||||
- 信任 JWT 中的 `is_paid` 来做配额决策(必须查询数据库)
|
||||
- 使用 `as any`、`@ts-ignore` 或类型错误抑制
|
||||
- 添加审计日志
|
||||
**deploy.sh 功能**:依赖检查 → 编译 → 上传二进制/配置 → 重启服务 → 部署后测试
|
||||
|
||||
### 必须事项
|
||||
- 管理员接口必须通过数据库查询验证管理员身份
|
||||
- 已认证处理器使用 `web::ReqData<Claims>`
|
||||
- 遵循现有的错误响应格式
|
||||
- 通过数据库查询检查 `is_paid_active`,而非 JWT
|
||||
- SQL 中使用 `desc` 等保留关键字时必须加双引号:`"desc"`
|
||||
### 前端部署
|
||||
|
||||
前端部署由微信开发者工具单独完成,详见 `ASD-fronted/AGENTS.md`:
|
||||
|
||||
```typescript
|
||||
// ASD-fronted/miniprogram/config/env.ts
|
||||
const CURRENT_ENV: 'development' | 'production' = 'production'; // 发布前切换
|
||||
```
|
||||
|
||||
切换后通过微信开发者工具上传。
|
||||
|
||||
### 部署后测试
|
||||
|
||||
自动执行 `test_deployment.sh`,测试内容:
|
||||
- 本地后端健康检查
|
||||
- 域名访问检查
|
||||
- 静态文件检查
|
||||
|
||||
---
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- ❌ 在 JWT Claims 中添加 `is_admin`(必须查数据库)
|
||||
- ❌ 信任 JWT 中的 `is_paid` 来做配额决策
|
||||
- ❌ 使用 `as any` 类型错误抑制
|
||||
- ❌ 添加审计日志
|
||||
- ❌ **直接操作服务器文件**(必须通过部署脚本)
|
||||
|
||||
## 必须事项
|
||||
|
||||
- ✅ 管理员接口必须通过数据库查询验证管理员身份
|
||||
- ✅ 已认证处理器使用 `web::ReqData<Claims>`
|
||||
- ✅ 遵循现有的错误响应格式
|
||||
- ✅ 通过数据库查询检查 `is_paid_active`
|
||||
- ✅ SQL 中使用保留关键字时加双引号
|
||||
|
||||
---
|
||||
|
||||
## 常见任务
|
||||
|
||||
### 添加新接口
|
||||
|
||||
1. 在 `main.rs` 中创建处理器函数
|
||||
2. 如需要,在 `db.rs` 中添加数据库函数
|
||||
3. 在 `create_server_config` 中注册(如果是受保护接口,放在 JWT 作用域内)
|
||||
3. 在 `create_server_config` 中注册
|
||||
4. 如需要,在 `models.rs` 中添加请求/响应结构体
|
||||
|
||||
### 路由配置结构
|
||||
|
||||
```rust
|
||||
// create_server_config 函数中的 App 配置顺序很重要
|
||||
App::new()
|
||||
.app_data(web::Data::new(pool)) // 数据源
|
||||
.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 /weather/details(支持 JWT 或 temp_token)
|
||||
.service(login)
|
||||
// 受保护接口(JWT middleware)
|
||||
.service(
|
||||
web::scope("")
|
||||
.wrap(from_fn(jwt_middleware)) // JWT 验证
|
||||
.service(post_weather_data) // POST /api/post-weather-data
|
||||
// ... 其他受保护接口
|
||||
.wrap(from_fn(jwt_middleware))
|
||||
.service(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` 结构体
|
||||
3. 更新 `models.rs` 中的结构体
|
||||
4. 更新 `db.rs` 中的数据库函数
|
||||
|
||||
## 构建与运行
|
||||
---
|
||||
|
||||
## 开发命令
|
||||
|
||||
```bash
|
||||
cargo build # 编译
|
||||
APP_ENV=development cargo run # 开发环境运行
|
||||
APP_ENV=production cargo run # 生产环境运行
|
||||
cargo test # 运行测试
|
||||
APP_ENV=development cargo run # 开发环境运行
|
||||
APP_ENV=production cargo run # 生产环境运行
|
||||
cargo test # 运行测试
|
||||
cargo clippy # 代码检查
|
||||
```
|
||||
|
||||
**部署**:
|
||||
```bash
|
||||
./deploy.sh development # 部署到测试服务器(编译 + 传文件 + 重启服务)
|
||||
./deploy.sh production # 部署到生产服务器
|
||||
|
||||
```
|
||||
|
||||
**deploy.sh 功能说明**:
|
||||
|
||||
| 步骤 | 说明 |
|
||||
|------|------|
|
||||
| 依赖检查 | 验证 `cargo`、`rsync`、`ssh` 命令 |
|
||||
| 编译 | `APP_ENV={环境} cargo build --release` |
|
||||
| 上传二进制 | `rsync target/release/rust-backend → 远程目录` |
|
||||
| 上传配置 | `rsync ./config/ → 远程目录/config/` |
|
||||
| 上传测试脚本 | `rsync test_deployment.sh → 远程目录/` |
|
||||
| 重启服务 | `systemctl restart {服务名}` |
|
||||
| **部署后测试** | `ssh 远程执行 test_deployment.sh` |
|
||||
|
||||
**远程目录与服务对应**:
|
||||
|
||||
| 环境 | 远程目录 | systemd 服务 |
|
||||
|------|---------|-------------|
|
||||
| development | `/root/rust/rust_backend_dev` | `rust-backend-dev.service` |
|
||||
| production | `/root/rust/rust_backend` | `rust-backend.service` |
|
||||
|
||||
> 直接运行 `./deploy.sh` 默认部署 production 环境。
|
||||
|
||||
**test_deployment.sh 测试内容**:
|
||||
|
||||
| 测试项 | 说明 |
|
||||
|--------|------|
|
||||
| 本地后端 | `GET http://127.0.0.1:8080/health` 返回 200 |
|
||||
| 域名访问 | `GET https://xmclassmate.top/dev` 返回 200/301/302 |
|
||||
| 静态文件 | CSS、JS、favicon 等文件返回 200 |
|
||||
|
||||
> 部署后自动执行,测试通过才算部署成功。
|
||||
|
||||
服务器尝试端口顺序:4433、8443、8080、3000、8000、8888
|
||||
|
||||
Reference in New Issue
Block a user