Files
asd-backend/AGENTS.md
2026-07-13 18:30:38 +08:00

833 lines
26 KiB
Markdown
Raw Permalink 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.
# AGENTS.md - Rust 后端
## 项目概述
微信小程序天气数据采集后端。用户通过微信认证,上传天气观测数据,并通过 REST API 管理数据。
---
## 技术栈
| 技术 | 版本 | 说明 |
|------|------|------|
| Rust | edition 2024 | 主力语言Rust 1.85+ 稳定) |
| 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
├── error.rs # 错误处理
├── rate_limiter.rs # 滑动窗口 Rate Limiter5次/分钟/IP
└── handlers/ # 路由处理器模块
├── mod.rs # 模块导出
├── meta.rs # 根路径状态页 (/), 健康检查
├── auth.rs # 登录相关 (login, refresh-token, mock-login)
├── weather.rs # 天气数据 CRUD
├── user.rs # 用户相关
├── admin.rs # 管理员功能
├── payment.rs # 支付相关(支付宝网页支付)
├── favorites.rs # 收藏功能
├── health.rs # 健康检查 (/health)
└── static_files.rs # 静态文件服务
```
config/
├── default.toml # 默认配置(所有环境的共同默认值)
├── development.toml # 开发/测试环境配置
└── production.toml # 生产环境配置
migrations/ # 数据库迁移 SQL
tests/ # 集成测试
static/ # 静态文件
│ ├── css/
│ │ ├── report.css # PDF 报表样式
│ │ └── style.css # 通用样式
│ ├── js/
│ │ ├── afterbody.js # PDF 生成模块html2pdf
│ │ └── ...
│ └── katex/ # KaTeX 数学公式
deploy.sh # 部署脚本
set-paid-user.sh # 设置用户付费状态
expire-paid-user.sh # 使用户付费时间过期
.env.example # 环境变量模板
```
---
## 服务状态页
### 根路径 `/`
访问根路径返回服务状态 HTML 页面,显示:
- 服务名称
- 数据库连接状态
- **版本号**(从 `Cargo.toml` 编译时嵌入)
### 健康检查 `/health`
返回 JSON 格式健康状态:
```json
{
"status": "ok",
"database": "connected",
"version": "0.3.0"
}
```
---
## PDF 报表生成
### 功能位置
`static/js/afterbody.js` - 使用 `html2pdf` 库生成 PDF 报表
### 依赖
- `html2pdf` - HTML 转 PDF 库
- `jspdf` - PDF 生成库
- `html2canvas` - HTML 转图片库
### 静态文件依赖
| 文件 | 用途 |
|------|------|
| `/static/css/style.css` | PDF 报表样式 |
| `/static/js/afterbody.js` | PDF 生成逻辑 |
### 生成流程
1. 前端调用后端获取天气数据
2. 后端返回 `weatherData` JSON
3. 前端加载 `afterbody.js`,将数据注入 `window.weatherData`
4. `afterbody.js` 构建 HTML 并调用 `html2pdf` 生成 PDF
### 注意事项
- ⚠️ 生成 PDF 前需确保 `/static/css/report.css` 存在
- 建议在 PDF 生成前检测静态文件可用性
---
## 配置管理
### 配置文件方式(推荐)
通过 `APP_ENV` 环境变量选择配置文件:
```bash
APP_ENV=development cargo run # 使用 config/development.toml
APP_ENV=production cargo run # 使用 config/production.toml
```
### 直接环境变量方式
最高优先级,可覆盖配置文件:
```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`
### 版本管理
**版本号统一在 `Cargo.toml` 中管理**
```toml
[package]
name = "rust-backend"
version = "0.3.0" # 唯一版本定义
```
**不再使用 `app_version` 字段**(已移除)。
### 版本控制
本项目使用 **jj (Jujutsu)** 替代 git 进行版本控制。**提交变更时务必使用 `jj` 而非原生 git。**
| 操作 | 命令 | 说明 |
|------|------|------|
| 查看状态 | `jj status` | 当前工作目录变更 |
| 提交 | `jj commit -m "msg"` | 创建新变更 |
| 推送 | `jj git push` | 推送到远程 |
| 拉取 | `jj git fetch` | 从远程拉取 |
| 查看日志 | `jj log` | 变更历史 |
git remote`gitea-server:milky/asd-backend.git`
---
## 数据库
### 连接信息
| 环境 | 连接字符串 |
|------|-----------|
| 测试 | `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
# 查看容器
docker ps | grep postgres
# 连接数据库(容器内)
docker exec -it postgres_container psql -U postgres -d milkydata_dev
```
### 表结构
#### 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
hasspotcheckwindspeed BOOLEAN DEFAULT false
spotcheckcount INTEGER DEFAULT 5 0-10
winddirection JSON[] 10 + spotcheckcount
windspeed JSON[]
-- 其他 30+ 个天气测量字段列
```
#### refresh_tokens 表
```sql
id SERIAL PRIMARY KEY
user_id INTEGER users
token VARCHARRefresh Token
expires_at TIMESTAMPTZ
created_at TIMESTAMPTZ
```
#### payment_orders 表
```sql
id SERIAL PRIMARY KEY
order_no VARCHAR UNIQUEASD{timestamp}{random}
user_id INTEGER users
package_type VARCHARmonthly/yearly/permanent
amount INTEGER
status VARCHARpending/paid/cancelled/expired
paid_at TIMESTAMPTZ
created_at TIMESTAMPTZ
```
---
## 认证流程
1. **登录**`POST /api/login` 传入微信 code → 调用微信 API → UPSERT 用户 → 返回双 Token
2. **双 Token 机制**
- `token`: access_token24小时用于 API 认证
- `refresh_token`: 7天有效期用于续期 access_token
3. **JWT 声明**`{exp, iat, user_id, openid, user_type}`
4. **中间件**`jwt_middleware` 提取 Bearer 令牌,验证后将 Claims 插入请求扩展
5. **处理器访问**:通过 `claims: web::ReqData<Claims>` 参数获取
6. **Token 刷新**`POST /api/refresh-token` 用 refresh_token 换取新的 access_token 和 refresh_token
### 公开接口
| 接口 | 说明 |
|------|------|
| `POST /api/login` | 微信登录,返回双 Token |
| `POST /api/refresh-token` | 刷新 access_token |
---
## API 接口
### 公开接口
| 接口 | 说明 |
|------|------|
| `GET /` | 服务状态页(显示版本、数据库连接状态) |
| `GET /health` | 健康检查 |
| `POST /api/login` | 微信登录,返回双 Token |
| `GET /api/mock-login` | Mock 登录(沙箱测试用,受 `MOCK_LOGIN_ENABLED` 环境变量控制) |
| `POST /api/refresh-token` | 刷新 access_token |
| `GET /weather/details` | 获取天气详情(支持 JWT 或 temp_token |
| `GET /static/{tail:*}` | 静态文件 |
### 受保护接口(需要 JWT
| 接口 | 说明 |
|------|------|
| `POST /api/post-weather-data` | 上传天气数据(带配额检查) |
| `GET /api/user/profile` | 获取当前用户信息 |
| `PUT /api/user/profile` | 保存用户信息 |
| `GET /weather` | 分页列出用户的天气数据 |
| `POST /api/generate-temp-token/{resource_id}` | 生成 10 分钟分享令牌 |
| `DELETE /weather/delete/{id}` | 删除天气记录 |
| `GET /api/favorites` | 获取收藏列表 |
| `POST /api/favorites/{id}` | 添加收藏 |
| `DELETE /api/favorites/{id}` | 删除收藏 |
| `GET /api/user/quota` | 获取用户配额 |
### 管理员接口(需要 JWT + is_admin
| 接口 | 说明 |
|------|------|
| `PUT /api/admin/users/{id}/payment` | 更新用户支付状态 |
| `GET /api/admin/users/{id}` | 获取用户信息 |
### 登录码接口(网页授权)
| 接口 | 说明 |
|------|------|
| `POST /api/web-login/auto-confirm` | 小程序一键确认:微信 code → openid → 创建用户 → JWT → 返回 payment_url |
| `POST /api/web-login/code` | 小程序用微信 code 换取 display_code旧方案 |
| `POST /api/web-login/confirm` | 小程序确认登录(需 JWT + code旧方案 |
| `GET /payment/generate-code` | 网页端生成登录码(已废弃,不建议使用) |
| `GET /payment/login-status` | 网页端轮询登录状态 |
### 支付接口
| 接口 | 说明 |
|------|------|
| `GET /payment` | 套餐选择页(无需认证,外部浏览器访问) |
| `GET /payment/page` | 支付页面(需 JWT支持 URL 参数 `jwt` 或 Cookie |
| `POST /api/payment/create-order` | 创建订单(需 JWT |
| `GET /payment/pay` | 唤起支付宝支付(需 JWT + order_no |
| `POST /payment/notify` | 支付宝异步回调通知(需 RSA 签名验证,无认证) |
| `GET /payment/success` | 支付成功页(需 order_no |
| `POST /api/payment/mock-confirm` | 模拟支付确认(沙箱测试用) |
| `POST /api/payment/sync-order` | 根据订单号强制同步会员状态(幂等) |
| `GET /api/payment/orders` | 获取当前用户的订单记录 |
### 支付宝配置(可选)
不配置则使用模拟支付;配置后启用真实支付宝支付:
```env
# .env 或 config/*.toml
ALIPAY_APP_ID=your_alipay_app_id
ALIPAY_PRIVATE_KEY=your_private_key_content
ALIPAY_ALIPAY_PUBLIC_KEY=alipay_public_key_content
ALIPAY_GATEWAY=https://openapi-sandbox.dl.alipaydev.com/gateway.do # 沙箱
ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do # 正式
```
> 正式环境必须使用 HTTPS服务已支持 TLS
---
## 代码模式
### 处理器模式
```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"}
```
---
## ⚠️ 常见坑
### 1. config crate 路径问题
`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 配置结构
**保持扁平结构,不使用 section headers**
```toml
# ❌ 错误section headers 导致合并冲突
[development]
database_url = "..."
# ✅ 正确:扁平结构
database_url = "..."
```
### 3. serde skip 与查询冲突
`skip_deserializing` 会导致 SELECT 时字段缺失错误。
```rust
// ❌ 错误skip_deserializing 影响查询
#[serde(rename = "isFavorite", skip_deserializing)]
pub is_favorite: bool,
// ✅ 正确:使用 default
#[serde(rename = "isFavorite", default)]
pub is_favorite: bool,
```
### 4. JWT 中不能添加 is_admin
**禁止在 JWT Claims 中添加 `is_admin`**,必须查询数据库验证。
### 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 层限流。
---
## 支付系统
### 支付流程(登录码授权模式)
小程序内无法直接接入支付宝支付,采用外部浏览器中转方案:
```
小程序(mine/升级页) → outter页面 → 外部浏览器 → /payment → 支付宝
```
1. 用户在小程序 mine 页面点击升级入口(未付费用户)
2. 小程序调用 `POST /api/web-login/auto-confirm`(微信 code → openid → 创建用户 → JWT → payment_url
3. 小程序跳转 `outter` 页面URL 指向 `/payment?jwt=xxx`
4. outter 页面提示用户在外部浏览器打开
5. 用户在手机浏览器打开 `/payment?jwt=xxx`
6. JWT 在 URL 参数中,网页自动通过 JWT 登录,获取用户信息和付费状态
7. 未付费用户显示套餐选择页(包月/包年),已付费用户显示会员信息
8. 用户选择套餐,点击「去支付」→ 跳转到 `/payment/page?package=xxx&jwt=xxx`
9. 后端生成订单,渲染支付宝支付表单(表单自动提交到沙箱/正式环境)
10. 支付宝沙箱/正式环境展示支付页面
11. 支付完成后,支付宝异步通知 `/payment/notify`
12. 跳转成功页 `/payment/success?order_no=xxx`(同步确认订单,幂等)
### 登录码相关接口
| 接口 | 说明 |
|------|------|
| `POST /api/web-login/code` | 小程序用微信 code 换取 display_code |
| `POST /api/web-login/confirm` | 小程序确认登录(需 JWT + code |
| `GET /payment/generate-code` | 网页端生成登录码(已废弃,不建议使用) |
| `GET /payment/login-status` | 网页端轮询登录状态(返回 token 表示已确认) |
### Mock 登录(沙箱测试)
沙箱环境下无法获取真实微信 code使用 Mock 登录获取测试 JWT
```bash
# 获取 mock JWT需服务器设置 MOCK_LOGIN_ENABLED=true
curl http://127.0.0.1:8080/api/mock-login
# 返回格式
{"success":true,"token":"eyJ0eX...","refresh_token":"MTAxOj...aW9u","user_id":101}
```
Mock 登录支持 `user_id` 参数指定已有用户,或自动创建新用户。环境变量 `MOCK_LOGIN_ENABLED=false` 时返回 403。
### Mock 支付(沙箱测试)
`.env` 中未配置 `ALIPAY_*` 环境变量时,自动启用 Mock 支付模式:
**流程**
```
用户访问 /payment/page → 显示 Mock 支付页面 → 点击"确认模拟支付" → 调用 /api/payment/mock-confirm → 激活会员
```
**特点**
- 无需真实支付宝配置
- Mock 页面显示订单号和套餐信息
- 点击后通过 `POST /api/payment/mock-confirm` 激活会员
- 有效期按套餐天数累加计算
**启用真实支付**
`.env` 中配置 `ALIPAY_APP_ID``ALIPAY_PRIVATE_KEY``ALIPAY_ALIPAY_PUBLIC_KEY``ALIPAY_GATEWAY`,重启服务后自动禁用 Mock 模式。
### 累积计算逻辑
用户多次购买时,有效期会累加而非覆盖:
```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` 条记录
- 付费用户(活跃状态)无限制
---
## 部署
### 服务器信息
| 环境 | 域名 | 端口 | 远程目录 |
|------|------|------|---------|
| 测试 | dev.xmclassmate.top | 8080 | /root/rust/rust_backend_dev |
| 生产(蓝) | xmclassmate.top | 4433 | /root/rust/rust_backend_blue |
| 生产(绿) | xmclassmate.top | 4434 | /root/rust/rust_backend_green |
**服务器**`47.109.203.92` (Aliyun, Debian 13)
**SSH 用户**`deploy`(有 sudo 权限)
**SSH 别名**`aliyun-server``~/.ssh/config` 中配置)
**nginx**:原生 nginx非 Docker/OpenResty配置位于 `/etc/nginx/sites-available/`
**PostgreSQL**Docker 容器 `postgres:17.6-alpine`host 网络模式)
### systemd 服务
| 环境 | systemd unit | 工作目录 | SERVER_PORT |
|------|-------------|---------|-------------|
| 测试 | `rust-backend-dev.service` | `/root/rust/rust_backend_dev` | 8080 |
| 生产蓝 | `rust-backend-blue.service` | `/root/rust/rust_backend_blue` | 4433 |
| 生产绿 | `rust-backend-green.service` | `/root/rust/rust_backend_green` | 4434 |
> 生产采用蓝绿部署nginx 通过 `proxy_pass` 将流量路由到活跃环境(默认 :4433 = 蓝色)。
> `deploy.sh` 自动检测活跃环境并部署到空闲环境,部署成功后自动切换 nginx 代理。
> ⚠️ systemd 配置使用 `Environment=` 直接加载环境变量(非 `EnvironmentFile`)。
> 注意:`EnvironmentFile` 不支持多行值,多行 PEM 密钥需 base64 编码或单行格式
### 部署命令
```bash
./deploy.sh development # 部署到测试服务器
./deploy.sh production # 生产蓝绿部署(自动检测目标)
./deploy.sh production --target blue # 指定部署到蓝色
./deploy.sh production --target green # 指定部署到绿色
```
**deploy.sh 选项:**
| 选项 | 说明 |
|------|------|
| `--dry-run` | 预览模式,不执行实际操作 |
| `--yes, -y` | 跳过确认提示 |
| `--skip-tests` | 跳过部署后测试 |
| `--rollback` | 回滚到上一个备份版本 |
| `--backup-list` | 列出可用备份 |
| `--target blue\|green` | 蓝绿部署目标 |
| `--remote-host IP` | 部署目标服务器(默认 `aliyun-server` |
| `--init-env` | 初始化远程 .env生成随机 JWT_SECRET + 推送模板到服务器) |
| `--help, -h` | 显示帮助信息 |
**示例:**
```bash
./deploy.sh production --dry-run # 预览生产部署
./deploy.sh production --yes # 无需确认直接部署
./deploy.sh development --skip-tests # 跳过测试
./deploy.sh production --init-env # 初始化生产 .env
./deploy.sh production --rollback # 回滚到上一个版本
./deploy.sh production --backup-list # 列出可用备份
```
**deploy.sh 功能**:依赖检查 → 编译 → 备份旧版本 → 清理旧备份保留5个不同版本→ 上传二进制/配置/迁移/脚本 → 重启服务 → **数据库备份****执行迁移** → 部署后测试 → 记录日志 → Webhook 通知
**二进制备份**
- 命名格式:`{project}.backup.{timestamp}.{gitHash}.{md5前8位}`
- 示例:`rust-backend.backup.20260419_204600.a91948b.7f3e2d1c`
- 自动保留 5 个**不同版本**的备份(相同版本只保留最早的)
- 清理时按 MD5 去重,防止同一版本的多个备份占用空间
**数据库备份**
- 位置:`${REMOTE_DIR}/backups/${DB_NAME}_*.dump`
- 保留数量1个
- 格式:`pg_dump -Fc`(自定义格式,可压缩)
**数据库迁移**
- 位置:`${REMOTE_DIR}/migrations/*.sql`
- 自动检测:检查表是否存在,跳过已执行的迁移
- 迁移文件名格式:`{序号}_{描述}.sql`
**自动回滚**:部署后测试失败时,自动回滚到上一个正常版本并重启服务。
- 回滚标记保存在 `${REMOTE_DIR}/.last_deployed`
- 测试失败后自动执行,无需手动干预
- Webhook 通知会发送失败回滚消息
**手动回滚(支持选择版本):**
```bash
./deploy.sh production --rollback # 回滚时可以选择版本
./deploy.sh production --backup-list # 列出所有可用备份
```
回滚时会列出所有备份(按版本分组),用户可以选择回滚到哪个版本。
```
**Webhook 通知(可选):**
```bash
WEBHOOK_URL="https://example.com/webhook" ./deploy.sh production
```
**部署日志位置**`/root/rust/rust_backend/deploy.log`
### 部署规范
> **重要**:所有部署操作**必须使用 `deploy.sh`**,禁止手动操作服务器文件。
**禁止事项:**
- ❌ 直接 SSH 到服务器手动上传文件
- ❌ 直接 `scp``rsync` 二进制文件到服务器
- ❌ 直接 `systemctl restart` 服务而不通过脚本
- ❌ 直接修改服务器上的配置文件
**deploy.sh 已包含的安全保障:**
- ✅ 部署前自动备份旧版本
- ✅ 部署前自动备份数据库
- ✅ 自动检查依赖
- ✅ 自动测试
- ✅ 部署日志记录
### 首次部署准备
首次在服务器上部署时,需要手动创建 systemd 服务文件和环境配置:
**1. 创建 systemd 服务文件:**
开发环境服务:`/etc/systemd/system/rust-backend-dev.service`
生产环境服务:`/etc/systemd/system/rust-backend.service`
参考本地文件:`rust-backend.service`(生产)、`rust-backend-dev.service`(开发)
**2. 初始化 .env 环境配置(推荐使用 deploy.sh**
```bash
# 生产环境 — 自动生成随机 JWT_SECRET推送 .env 模板到服务器
./deploy.sh production --init-env
# 开发环境
./deploy.sh development --init-env
```
`--init-env` 会:
- 生成 256 位随机 `JWT_SECRET``openssl rand -base64 32`
- 推送 `.env` 文件到服务器 `${REMOTE_DIR}/.env`
- 设置文件权限 `600`(仅 root 可读)
- 如文件已存在,先备份再覆盖
执行后仍需 SSH 到服务器填写以下手动变量:
```bash
ssh root@1panel-server
vim /root/rust/rust_backend/.env
# 必须手动填写:
DATABASE_URL=postgres://user:pass@host:5432/dbname
WECHAT_APPID=wx...
WECHAT_SECRET=xxx
```
> **不建议**手动创建 `.env`——因为 `deploy.sh --init-env` 会自动生成强随机 `JWT_SECRET`,手动创建容易忘记生成或使用弱密钥。
### JWT_SECRET 管理
| 生命周期 | 操作 | 说明 |
|----------|------|------|
| **生成** | `openssl rand -base64 32` | 首次 `deploy.sh --init-env` 自动完成 |
| **存储** | 服务器 `.env`,权限 600 | 不进入 Git不进入 TOML 配置 |
| **验证** | `deploy.sh check_env_file` | 部署前自动检查是否缺失 |
| **轮换** | 手动替换 → 重启服务 | 旧 token 失效,所有用户需重新登录 |
**原则:**
- Dev 和 Prod 使用**不同**的 JWT_SECRET
- JWT_SECRET **永不出现在** `config/*.toml` 或任何 Git 管理的文件中
- 服务器 `.env` 文件**不通过 `deploy.sh` 同步**(不会上传到服务器),仅手动或通过 `--init-env` 创建
**3. 启用服务:**
```bash
systemctl daemon-reload
systemctl enable rust-backend-dev.service # 开发环境
systemctl enable rust-backend.service # 生产环境
```
### 前端部署
前端部署由微信开发者工具单独完成,详见 `ASD-fronted/AGENTS.md`
```typescript
// ASD-fronted/miniprogram/config/env.ts
const CURRENT_ENV: 'development' | 'production' = 'production'; // 发布前切换
```
切换后通过微信开发者工具上传。
### 部署后测试
部署后验证是否成功,**直接运行** `test_deployment.sh`
```bash
# 设置测试域名
export TEST_DOMAIN="https://dev.xmclassmate.top" # 开发环境
export TEST_DOMAIN="https://xmclassmate.top" # 生产环境
# 运行测试
./test_deployment.sh
```
**注意**`deploy.sh` 会自动执行此测试,但也可手动单独运行验证。
测试内容:
- 本地后端健康检查
- API 端点检查
- 静态文件检查
---
## 禁止事项
- ❌ 在 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` 中注册
4. 如需要,在 `models.rs` 中添加请求/响应结构体
### 路由配置结构
```rust
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)
// 受保护接口JWT middleware
.service(
web::scope("")
.wrap(from_fn(jwt_middleware))
.service(post_weather_data)
)
// 静态文件和健康检查放最后
.service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files)))
.service(health_check)
```
### 修改数据库
1.`migrations/` 中创建迁移 SQL
2. 在 PostgreSQL 上手动执行迁移
3. 更新 `models.rs` 中的结构体
4. 更新 `db.rs` 中的数据库函数
---
## 开发命令
```bash
cargo build # 编译
APP_ENV=development cargo run # 开发环境运行
APP_ENV=production cargo run # 生产环境运行
cargo test # 运行测试
cargo clippy # 代码检查
```