Files
asd-backend/IMPROVEMENTS.md
Milky0217 75cdb1d222 docs: 新增邀请码开通会员方案
- IMPROVEMENTS.md 添加方案二:邀请码开通会员
  - 数据库设计(invitation_codes 表)
  - 接口设计(使用邀请码、生成邀请码)
  - 后端/前端实现要点
  - 邀请码生成规则建议
2026-04-17 21:34:03 +08:00

1004 lines
28 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.
# Rust 后端 - 改进计划
## 一、代码质量与架构改进
### 1.1 代码组织问题
- [x] **所有路由处理器都在 main.rs 中**
- 改进:将 handler 函数按功能模块拆分到 `handlers/` 目录
- 目录结构:
- `handlers/auth.rs` - 登录相关
- `handlers/weather.rs` - 天气数据 CRUD
- `handlers/user.rs` - 用户相关
- `handlers/admin.rs` - 管理员功能
- `handlers/health.rs` - 健康检查
- `handlers/static_files.rs` - 静态文件服务
- 完成时间2026-04-15
- 附加:发现并修复静态文件被 JWT middleware 拦截的问题(路由顺序)
- [x] **config.rs 是死代码**
- 现状AGENTS.md 中提到 config.rs 未被使用
- 改进:保留文件,标注为待清理
- 影响:减少代码混淆
- 完成时间2026-04-15
### 1.2 数据库操作优化
- [ ] **重复查询问题**
- 位置:`insert_weather_data` 中多次查询用户信息
- 改进:合并查询或使用缓存
- 影响:减少数据库负载
- [ ] **缺少数据库索引**
- 现状:未见索引定义
- 改进:添加常用查询字段索引
```sql
CREATE INDEX idx_weather_data_user_id ON weather_data(user_id);
CREATE INDEX idx_weather_data_date ON weather_data(date);
CREATE INDEX idx_users_openid ON users(openid);
```
### 1.3 错误处理改进
- [x] **错误响应格式不统一** ✅
- 现状:部分返回 `ErrorResponse`,部分返回 JSON 字符串
- 改进:统一使用 `ErrorResponse` 结构体
- 彰响:前端解析更一致
- 完成时间2026-04-15
- [x] **错误信息暴露过多** ✅
- 现状:部分错误直接返回数据库错误信息
- 改进:区分用户友好错误和开发者错误
- 影响:安全性提升
- 完成时间2026-04-15
- 修改:移除所有 `errmsg: Some(e.to_string())`,错误详情只记录到日志
## 二、安全性改进
### 2.1 认证安全
- [ ] **缺少请求频率限制** ⚠️
- 风险API 可能被滥用或遭受暴力攻击
- 改进:添加 Rate Limiting 中间件
- 配置:
- 登录接口5 次/分钟
- 数据上传30 次/分钟
- 查询接口100 次/分钟
- **状态**actix-ratelimit 0.3.1 与 actix-web 4.x 不兼容Transform trait 问题)
- **备选方案**
1. `actix-web-lab` - 社区维护的中间件库
2. 手动实现 - 使用 `std::collections::HashMap` 记录 IP 请求数
3. Nginx/网关层限流
- [ ] **缺少 CSRF 防护**
- 风险:跨站请求伪造攻击
- 改进:添加 CSRF Token 验证
- 实现:对于状态变更操作验证 Token
- [ ] **JWT Secret 管理**
- 现状:明文存储在环境变量
- 改进:使用密钥管理服务(如 AWS Secrets Manager
- 影响:提高密钥安全性
### 2.2 数据安全
- [ ] **数据库连接字符串明文存储**
- 位置:`.env` 文件
- 改进:使用连接字符串加密或密钥管理
- 影响:防止凭证泄露
- [ ] **缺少敏感数据加密**
- 现状:用户信息明文存储
- 改进:对敏感字段加密存储
- 字段:手机号、姓名等
- [ ] **缺少审计日志**
- 需求:记录关键操作日志
- 实现:添加审计日志表和中间件
- 记录:登录、数据修改、管理员操作
### 2.3 输入验证
- [ ] **后端输入验证不完整**
- 现状:部分依赖前端验证
- 改进:添加完整的请求验证
- 实现:使用 `validator` crate
## 三、性能优化
### 3.1 数据库性能
- [ ] **缺少连接池监控**
- 现状:连接池配置固定
- 改进:添加连接池监控和动态调整
- 指标:连接数、等待时间、超时次数
- [ ] **缺少查询缓存**
- 场景:用户信息、配置数据
- 改进:添加 Redis 缓存层
- 策略:用户信息缓存 5 分钟,配置数据缓存 1 小时
- [ ] **无读写分离支持**
- 需求:高并发场景下的性能优化
- 改进:支持主从数据库配置
- 实现:使用 sqlx 的多数据源支持
### 3.2 API 性能
- [ ] **无响应压缩**
- 现状:响应未压缩
- 改进:添加 gzip 压缩中间件
- 实现:使用 `actix-web` 的压缩功能
- [ ] **缺少 API 缓存**
- 场景:天气数据列表查询
- 改进:添加 HTTP 缓存头
- 实现:`Cache-Control`、`ETag` 等
- [ ] **无 CDN 支持**
- 场景:静态资源分发
- 改进:配置 CDN 加速
- 资源CSS、JS、图片、字体
## 四、功能完整性改进
### 4.1 API 功能扩展
- [ ] **缺少批量操作接口**
- 需求:批量删除、批量查询
- 实现:添加批量操作端点
- 接口:`POST /weather/batch-delete`、`POST /weather/batch-query`
- [ ] **无数据导出格式支持**
- 需求:支持 CSV、Excel 导出
- 实现:添加导出端点
- 接口:`GET /weather/export?format=csv`
- [ ] **缺少搜索和过滤功能**
- 需求:按日期、标题、地点搜索
- 实现:添加搜索参数
- 参数:`?keyword=xxx&startDate=xxx&endDate=xxx`
### 4.2 用户功能
- [ ] **缺少用户活动统计**
- 需求:用户使用情况分析
- 实现:添加统计接口
- 数据:登录次数、数据上传量、最后活跃时间
- [ ] **无用户偏好设置**
- 需求:保存用户偏好(如默认区域类型)
- 实现:添加用户设置表
- 接口:`GET/PUT /api/user/preferences`
## 五、开发体验改进
### 5.1 测试覆盖
- [ ] **仅有一个基础编译测试**
- 现状:`tests/integration_test.rs` 只有 `assert_eq!(2 + 2, 4)`
- 改进:添加完整的测试套件
- 类型:
- 单元测试:工具函数、数据结构
- 集成测试API 端点
- 数据库测试CRUD 操作
- [ ] **缺少 API 测试**
- 需求:验证 API 行为
- 实现:使用 `actix-web` 的测试工具
- 覆盖:正常流程、异常情况、边界条件
### 5.2 文档完善
- [ ] **无 API 文档**
- 需求:接口文档
- 实现:使用 OpenAPI/Swagger
- 工具:`utoipa` crate
- [ ] **缺少部署文档**
- 内容:环境要求、配置说明、部署步骤
- 形式:`DEPLOYMENT.md` 文件
- [ ] **无变更日志**
- 内容:版本更新记录
- 形式:`CHANGELOG.md` 文件
### 5.3 开发工具
- [ ] **缺少代码质量工具**
- 工具:`clippy`、`rustfmt`
- 配置:`.clippy.toml`、`rustfmt.toml`
- 集成CI/CD 流程
- [ ] **无热重载开发**
- 需求:开发时自动重载
- 实现:使用 `cargo-watch`
- 命令:`cargo watch -x run`
## 六、监控与运维
### 6.1 日志系统
- [x] **日志格式不统一** ✅
- 现状:部分使用 `log` 宏,部分使用 `println`
- 改进:统一使用结构化日志
- 实现:使用 `tracing` crate
- 完成时间2026-04-15
- 修改文件Cargo.toml, src/main.rs, src/config.rs
- [x] **无日志聚合** ✅
- 需求:集中式日志管理
- 实现:配置日志收集器(如 ELK Stack
- 格式JSON 格式便于解析
- 状态:✅ 已完成JSON + 文件轮转2026-04-15
- [ ] **缺少性能指标收集** ⚠️
- 需求API 响应时间、错误率等
- 实现:添加 Prometheus 指标
- **状态**actix-web-prom 与当前架构不兼容ServiceFactory Response 类型冲突)
- **备选方案**
1. `actix-web-lab` - 社区维护的中间件库
2. 手动实现 - 在代码中直接使用 `std::sync::atomic` 收集请求计数
3. Nginx 层收集 - 反向代理层已有 access log
### 6.2 告警机制
- [ ] **无异常告警**
- 需求:系统异常时通知
- 实现:集成告警服务
- 渠道:邮件、钉钉、企业微信
- [ ] **缺少资源监控**
- 需求CPU、内存、磁盘监控
- 实现:使用系统监控工具
- 工具Prometheus + Grafana
### 6.3 健康检查
- [x] **缺少健康检查接口** ✅
- 需求:负载均衡器健康检查
- 实现:添加 `/health` 端点
- 检查:数据库连接、服务状态
- 完成时间2026-04-15
## 七、部署改进
### 7.1 部署流程
- [ ] **部署脚本功能简单**
- 现状:仅支持基本的编译、上传、重启
- 改进:添加回滚、备份、验证功能
- 实现:增强 `deploy.sh` 脚本
- [ ] **无蓝绿部署或滚动更新**
- 需求:零停机部署
- 实现:使用 Docker + Kubernetes
- 或者Nginx 负载均衡 + 多实例
### 7.2 容器化
- [ ] **缺少 Docker 支持**
- 需求:容器化部署
- 实现:添加 `Dockerfile` 和 `docker-compose.yml`
- 优势:环境一致性、易于扩展
### 7.3 CI/CD
- [ ] **无自动化流程**
- 需求:自动测试、构建、部署
- 实现:配置 GitHub Actions 或 Gitea Actions
- 流程:代码提交 → 测试 → 构建 → 部署
## 九、Bug 修复记录
### 9.1 前端输入问题
- [x] **数字输入框无法输入小数点** ✅
- 问题:用户在输入框输入 "1." 时,末尾小数点会丢失
- 原因:`bindinput` 时直接用 `parseFloat()` 转换,`parseFloat("1.")` 返回 `1`
- 修复:移除 `bindinput` 时的数值转换,改为仅在 `blur` 时验证和更新
- 修改文件:`miniprogram/pkg-asd/asdmain/asdmain.ts`
- 影响范围:
- `onMeasuredWindSpeedInput` - 实测风速输入
- `onPointWindSpeedInput` - 测点风速输入
- `onPointHeightInput` - 测点高度输入
- `onWindSpeedInput` - 风向风速数组输入
- `onWindDirectionInput` - 风向数组输入
- 完成时间2026-04-15
## 八、技术债务
### 8.1 高优先级
1. [ ] **拆分 main.rs 中的路由处理器**
- 影响:代码可维护性
- 工作量:中等
2. [ ] **添加请求频率限制**
- 影响:安全性
- 工作量:小
3. [ ] **统一错误响应格式**
- 影响:前后端对接
- 工作量:小
4. [ ] **添加数据库索引**
- 影响:性能
- 工作量:小
### 8.2 中优先级
1. [ ] **添加单元测试和集成测试**
- 影响:代码质量
- 工作量:大
2. [ ] **实现 API 文档OpenAPI**
- 影响:开发体验
- 工作量:中等
3. [ ] **添加结构化日志**
- 影响:运维
- 工作量:中等
4. [ ] **优化数据库查询**
- 影响:性能
- 工作量:中等
### 8.3 低优先级
1. [ ] **实现 Redis 缓存**
- 影响:性能
- 工作量:大
2. [ ] **容器化部署**
- 影响:部署流程
- 工作量:中等
3. [ ] **添加监控告警**
- 影响:运维
- 工作量:大
4. [ ] **实现 CI/CD**
- 影响:开发效率
- 工作量:中等
## 十、缺少环境区分机制
### 问题描述
当前后端项目未区分生产环境和开发/测试环境,所有配置混在一个 `.env` 文件中。
**现状分析**
| 配置项 | 当前做法 | 问题 |
|--------|----------|------|
| DATABASE_URL | 硬编码生产数据库地址 | 开发/测试时无法切换到本地或测试数据库 |
| JWT_SECRET | 混在 .env 中 | 开发环境使用弱密钥存在安全隐患 |
| RUST_LOG | 统一设置为 `info` | 开发时需要 `debug` 级别日志 |
| APP_VERSION | 在 .env 和 Cargo.toml 两处定义 | 版本不一致 |
**.env 当前内容**
```bash
DATABASE_URL=postgres://milkydata:***@154.37.213.24:5432/milkydata
WECHAT_APPID="wx5b00eb90621802f7"
WECHAT_SECRET="494efc...9bfd"
JWT_SECRET="your_s..._key"
SSL_KEY_PATH=/etc/ssl/private/private.key
SSL_CERT_PATH=/etc/ssl/certs/full_chain.pem
RUST_LOG=info
APP_VERSION="0.2.0"
FREE_USER_DATA_LIMIT=20
```
### 影响
- 开发时连接生产数据库,有误操作风险
- 测试时无法使用独立的测试数据
- 切换环境需要手动修改配置,容易出错
- 生产配置泄露到代码仓库(.env 通常被 gitignore但部署时容易混淆
### 解决方案
#### 方案一:使用 config crate推荐
**添加依赖**
```toml
# Cargo.toml
[dependencies]
config = "0.14"
serde = { version = "1.0", features = ["derive"] }
```
**目录结构**
```
rust-backend/
├── config/
│ ├── default.toml # 默认配置(开发)
│ ├── development.toml
│ └── production.toml
├── .env # 本地敏感配置(加入 .gitignore
└── .env.example # 配置模板(提交到仓库)
```
**default.toml开发/测试默认)**
```toml
database_url = "postgres://milkydata:password@localhost:5432/milkydata_dev"
wechat_appid = "wx_test_appid"
wechat_secret = "test_secret"
jwt_secret = "dev-only-secret-change-in-production"
ssl_key_path = ""
ssl_cert_path = ""
rust_log = "debug"
app_version = "0.2.3"
environment = "development"
free_user_data_limit = 100
server_host = "0.0.0.0"
server_port = 8080
```
**production.toml生产环境**
```toml
database_url = "postgres://milkydata:***@154.37.213.24:5432/milkydata"
wechat_appid = "wx5b00eb90621802f7"
wechat_secret = "***"
jwt_secret = "***"
ssl_key_path = "/etc/ssl/private/private.key"
ssl_cert_path = "/etc/ssl/certs/full_chain.pem"
rust_log = "info"
app_version = "0.2.3"
environment = "production"
free_user_data_limit = 20
server_host = "0.0.0.0"
server_port = 8080
```
**配置加载逻辑**
```rust
// src/config.rs
use config::{Config, ConfigError, File};
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub database_url: String,
pub wechat_appid: String,
pub wechat_secret: String,
pub jwt_secret: String,
pub ssl_key_path: String,
pub ssl_cert_path: String,
pub rust_log: String,
pub app_version: String,
pub environment: String,
pub free_user_data_limit: i32,
pub server_host: String,
pub server_port: u16,
}
impl AppConfig {
pub fn load() -> Result<Self, ConfigError> {
// 从环境变量读取当前环境,默认为 development
let env = std::env::var("APP_ENV").unwrap_or_else(|_| "development".into());
let config = Config::builder()
// 1. 先加载默认配置
.add_source(File::with_name("config/default"))
// 2. 再加载当前环境配置(覆盖默认值)
.add_source(File::with_name(&format!("config/{}", env)).required(false))
// 3. 最后从环境变量加载(最高优先级)
.add_source(config::Environment::with_prefix("APP"))
.build()?;
config.try_deserialize()
}
}
```
#### 方案二:简化方案(最小改动)
保持现有 `.env` 结构不变,通过 `APP_ENV` 环境变量和 `.env.development` / `.env.production` 文件区分:
**`.env.example`(提交到仓库的配置模板)**
```bash
# 必填配置
DATABASE_URL=postgres://user:pass@host:port/dbname
JWT_SECRET=your-secret-key
WECHAT_APPID=your-wechat-appid
WECHAT_SECRET=your-wechat-secret
# 可选配置(带默认值)
APP_ENV=development
RUST_LOG=info
APP_VERSION=0.2.3
FREE_USER_DATA_LIMIT=20
SSL_KEY_PATH=
SSL_CERT_PATH=
```
**部署脚本增强**
```bash
#!/bin/bash
# deploy.sh
# 接收环境参数
ENV=${1:-production}
# 根据环境加载不同配置
if [ "$ENV" = "development" ]; then
source .env.development
elif [ "$ENV" = "production" ]; then
source .env.production
fi
# 构建和部署...
```
### 环境切换操作指南
| 场景 | 操作方法 |
|------|----------|
| 本地开发 | `APP_ENV=development cargo run`,连接本地数据库 |
| 测试服务器部署 | `APP_ENV=development ./deploy.sh` |
| 生产环境部署 | `APP_ENV=production ./deploy.sh` 或默认 `./deploy.sh` |
| 查看当前环境 | 启动后访问 `/health` 接口或检查日志 |
### 实施步骤
**第一阶段(最小改动)**
1. 创建 `.env.example` 配置模板,移除敏感信息
2. 在 `deploy.sh` 中添加 `APP_ENV` 参数支持
3. 创建 `.env.development` 本地开发配置(可选加入 .gitignore
**第二阶段(推荐)**
1. 添加 `config` crate 依赖
2. 创建 `config/default.toml` 和 `config/production.toml`
3. 重构 `config.rs` 使用 config crate
4. 更新 `deploy.sh` 使用新的配置加载方式
### 相关改进项
- 本改进与"前后端版本统一管理"(改进路线图 P1可合并实施
- 本改进与"拆分 main.rs 路由处理器"(改进路线图 P1有协同效应
### 实施结果
✅ **已完成** (2026-04-17)
**实际实施方案**:采用简化方案 + TOML 配置文件
| 改动项 | 说明 |
|--------|------|
| `config.rs` | 重写为使用 `toml` crate 直接读取配置文件 |
| `config/*.toml` | 创建 `default.toml`、`development.toml`、`production.toml` |
| `Cargo.toml` | 添加 `toml = "0.8"` 依赖,移除 `config = "0.14"` |
| `deploy.sh` | 重写支持 `development`/`production` 环境参数 |
| systemd service | 创建 `rust-backend-dev.service` 测试服务 |
**关键修复**
- TOML 文件去掉 `[development]` 等 section 头config crate 遗留语法)
- `database_url` 使用 `127.0.0.1` 而非 `localhost`Docker PostgreSQL 监听地址)
**测试验证**
- 测试服务运行在端口 8080
- Nginx 代理 `https://xmclassmate.top/dev/api/login` 正常工作
---
## 十三、本次对话经验总结
### 13.1 前后端字段命名一致性
**问题描述**
后端 Rust 使用 `#[serde(rename = "camelCase")]` 序列化 JSON 字段,前端 TypeScript 必须使用相同的 camelCase 命名才能正确解析。
**受影响字段**
| Rust 字段 | JSON 键 | 前端错误写法 | 前端正确写法 |
|-----------|---------|-------------|-------------|
| `is_favorite` | `isFavorite` | `is_favorite` | `isFavorite` |
| `inspection_type` | `inspectionType` | `inspection_type` | `inspectionType` |
| `assignment_number` | `assignmentNumber` | `assignment_number` | `assignmentNumber` |
**问题后果**
- 前端使用 snake_case 命名,接口返回的 camelCase 字段会是 `undefined`
- 详情页面显示 `undefined` 而非正确值
**验证方法**
```bash
# 搜索后端 serde rename 配置
grep -n 'rename = "' src/models.rs
```
**经验教训**
- API 接口字段命名应在前后端团队间统一约定
- 或让后端提供 JSON Schema / OpenAPI 文档
**参考**[LRN-20260417-015](file:///home/milky/Documents/ASD/.learnings/LEARNINGS.md#LRN-20260417-015)
---
### 13.2 serde 配置冲突
**问题描述**
`#[serde(skip_deserializing)]` 用于 POST 请求体解析(避免 id 字段),但在 SELECT 查询时会阻止字段被填充。
**错误配置**
```rust
#[serde(skip_deserializing)] // POST 时跳过,但 SELECT 时也跳过了
pub is_favorite: Option<bool>,
```
**正确配置**
```rust
#[serde(default)] // 缺失字段使用默认值
pub is_favorite: Option<bool>,
```
**经验教训**
- `skip_deserializing` 会导致数据库查询结果无法填充字段
- 对于需要同时支持上传和查询的字段,使用 `default`
**参考**[LRN-20260417-016](file:///home/milky/Documents/ASD/.learnings/LEARNINGS.md#LRN-20260417-016)
---
### 13.3 config crate 路径解析
**问题描述**
`File::with_name("config/default")` 查找文件相对于 `cargo run` 执行目录,而非 `CARGO_MANIFEST_DIR`。
**错误写法**
```rust
config::File::with_name("config/default") // 相对于 cwd
```
**正确写法**
```rust
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.expect("CARGO_MANIFEST_DIR not set");
let config_path = manifest_dir.join("config").join("default.toml");
```
**经验教训**
- 使用 `config` crate 的路径相关函数时注意基准目录
- 直接使用 `std::env::var("CARGO_MANIFEST_DIR")` 更可靠
**参考**[LRN-20260417-001](file:///home/milky/Documents/ASD/.learnings/LEARNINGS.md#LRN-20260417-001)
---
### 13.4 TOML 配置文件结构
**问题描述**
TOML 文件中的 `[development]` 等 section headers 与 `config` crate 的合并逻辑冲突。
**错误写法**
```toml
[development]
database_url = "..."
```
**正确写法**
```toml
database_url = "..."
environment = "development"
```
**经验教训**
- 保持 TOML 文件扁平结构,不使用 section headers
- 简化配置加载逻辑
**参考**[LRN-20260417-002](file:///home/milky/Documents/ASD/.learnings/LEARNINGS.md#LRN-20260417-002)
---
## 十四、付费功能系统
### 当前状态
**已有基础设施:**
- ✅ 数据库字段:`is_paid` (boolean), `paid_expires_at` (timestamp)
- ✅ 配额检查逻辑:`db.rs:15-24` 非付费用户限制 20 条数据
- ✅ 管理员 API`PUT /api/admin/users/{id}/payment` 手动设置付费状态
- ✅ 用户查询 API`GET /api/user/profile` 返回 `is_paid`, `is_paid_active`, `paid_expires_at`
**缺失功能:**
- ❌ 支付下单接口
- ❌ 微信支付回调接口
- ❌ 前端付费 UI
- ❌ 购买记录表
### 12.1 支付下单接口
**需求**:用户点击购买后,后端生成微信支付订单。
**建议实现**
```rust
// POST /api/payment/create-order
// Request
{
"package_type": "monthly" | "yearly" | "permanent"
}
// Response
{
"success": true,
"data": {
"order_id": "wx_order_xxx",
"prepay_id": "wx_prepay_xxx", // 用于调起支付
"pay_sign": "...",
"expire_time": "2026-05-16T12:00:00Z"
}
}
```
**需新增环境变量**
```
WECHAT_PAYMENT_MCHID=商户号
WECHAT_PAYMENT_SECRET=支付密钥
WECHAT_PAYMENT_NOTIFY_URL=https://your-domain.com/api/payment/callback
```
### 12.2 支付回调接口
**需求**:接收微信支付成功回调,激活用户付费状态。
**建议实现**
```rust
// POST /api/payment/callback (微信支付服务器调用)
{
"event_type": "TRANSACTION.SUCCESS",
"resource": {
"order_id": "wx_order_xxx",
"out_trade_no": "internal_order_xxx",
"trade_state": "SUCCESS",
"amount": {
"total": 100, // 金额(分)
"currency": "CNY"
}
}
}
```
**数据库变更**
```sql
-- 新增订单表
CREATE TABLE payment_orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
order_no VARCHAR(64) UNIQUE NOT NULL, -- 内部订单号
wx_order_id VARCHAR(64), -- 微信订单号
package_type VARCHAR(20) NOT NULL, -- monthly/yearly/permanent
amount INTEGER NOT NULL, -- 金额(分)
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending/paid/cancelled/refunded
paid_at TIMESTAMPTZ, -- 支付时间
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ -- 付费到期时间
);
CREATE INDEX idx_payment_orders_user_id ON payment_orders(user_id);
CREATE INDEX idx_payment_orders_order_no ON payment_orders(order_no);
```
### 12.3 查询配额 API
**需求**:前端显示用户已使用的数据条数和上限。
**建议实现**
```rust
// GET /api/user/quota
// Response
{
"success": true,
"data": {
"is_paid": true,
"is_paid_active": true,
"paid_expires_at": "2026-12-31T23:59:59Z",
"data_usage": {
"used": 45,
"limit": 20, // 非付费用户限制
"unlimited": false // 付费用户无限制
}
}
}
```
**需新增**`db::get_user_quota(pool, user_id) -> QuotaInfo`
### 12.4 前端付费 UI
**页面建议**
```
pages/
├── upgrade/
│ ├── upgrade.js # 购买页面
│ ├── upgrade.wxml # 套餐选择 + 支付按钮
│ └── upgrade.json
└── ...
```
**功能**
1. 展示套餐(包月/包年/永久)
2. 调用后端创建订单
3. 调起微信支付
4. 支付成功/失败提示
5. 跳转回首页
**首页配额展示**
- 非付费用户:显示「已用 X/20 条,开通付费解锁无限存储」
- 付费用户:显示「已用 X 条,无限存储」
### 12.5 定价建议
| 套餐 | 价格 | 有效期 | 说明 |
|------|------|--------|------|
| 包月 | ¥9.9 | 30天 | 尝鲜用户 |
| 包年 | ¥59 | 365天 | 主流套餐 |
| 永久 | ¥199 | 永久 | 忠实用户 |
### 12.6 实施优先级
| 阶段 | 内容 | 复杂度 |
|------|------|--------|
| P1 | 后端:订单表 + 支付回调 | 中 |
| P1 | 后端:下单接口 + 微信支付集成 | 高 |
| P2 | 后端:配额查询 API | 低 |
| P2 | 前端:升级页面 + 支付流程 | 中 |
| P3 | 前端:首页配额展示 | 低 |
### 12.7 注意事项
1. **支付安全**:回调接口必须验证微信签名
2. **幂等性**:支付回调需处理重复通知
3. **退款处理**:需实现退款接口和状态更新
4. **试用期**:可考虑新用户首月免费
---
## 方案二:邀请码开通会员
### 方案描述
用户输入邀请码即可开通会员,无需支付。适合不想接入微信支付但需要会员管理的场景。
### 数据库设计
```sql
-- 邀请码表
CREATE TABLE invitation_codes (
id SERIAL PRIMARY KEY,
code VARCHAR(32) UNIQUE NOT NULL, -- 邀请码
package_type VARCHAR(20) NOT NULL, -- 套餐类型monthly/yearly/permanent
used_by INTEGER REFERENCES users(id), -- 使用者
used_at TIMESTAMPTZ, -- 使用时间
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ, -- 邀请码过期时间(可选)
is_used BOOLEAN DEFAULT FALSE
);
CREATE INDEX idx_invitation_codes_code ON invitation_codes(code);
```
### 接口设计
#### 1. 使用邀请码
```json
POST /api/redeem-invitation-code
Request:
{
"code": "ASD2024VIP001"
}
Response:
{
"success": true,
"data": {
"package_type": "yearly",
"expires_at": "2027-04-17T00:00:00Z"
}
}
Error:
{
"success": false,
"errcode": 400,
"errmsg": "邀请码无效或已使用"
}
```
#### 2. 生成邀请码(管理员)
```json
POST /api/admin/invitation-codes
Request:
{
"package_type": "yearly",
"count": 10,
"expires_in_days": 365
}
Response:
{
"success": true,
"data": {
"codes": [
{"code": "ASD2024VIP001", "expires_at": "2027-04-17T00:00:00Z"},
{"code": "ASD2024VIP002", "expires_at": "2027-04-17T00:00:00Z"}
]
}
}
```
### 后端实现要点
```rust
// 1. 使用邀请码
async fn redeem_invitation_code(pool: &PgPool, user_id: i32, code: &str) -> Result<PackageType> {
// 查询邀请码
// 检查是否已使用
// 检查是否过期
// 标记为已使用
// 更新用户付费状态
}
// 2. 生成邀请码
async fn generate_invitation_codes(pool: &PgPool, package_type: &str, count: i32, expires_days: i32) -> Result<Vec<String>> {
// 生成随机码
// 批量插入数据库
}
```
### 前端实现要点
```typescript
// 页面pkg-extra/upgrade/upgrade
// 添加"使用邀请码"入口
async onRedeemCode() {
const code = this.data.invitationCode.trim();
if (!code) {
wx.showToast({ title: '请输入邀请码', icon: 'none' });
return;
}
const res = await request({
url: `${baseUrl}/api/redeem-invitation-code`,
method: 'POST',
data: { code }
});
if (res.success) {
wx.showModal({
title: '开通成功',
content: `恭喜!您已开通${res.data.package_type}会员`,
showCancel: false
});
// 刷新用户状态
this.fetchUserProfile();
}
}
```
### 邀请码生成规则建议
```
格式ASD + 年份 + 类型 + 随机6位
示例:
- ASD2024Y01A2B3C (年度会员)
- ASD2024M01X2Y3Z (月度会员)
- ASD2024P01Q2W3E (永久会员)
```
### 实施优先级
| 阶段 | 内容 | 复杂度 |
|------|------|--------|
| P2 | 后端:邀请码表 + 使用/生成接口 | 中 |
| P2 | 前端:邀请码输入入口 | 低 |
---
## 十一、检查清单
### 代码提交前检查
- [ ] 通过 `cargo clippy` 检查
- [ ] 通过 `cargo fmt` 格式化
- [ ] 单元测试通过
- [ ] 无硬编码的敏感信息
- [ ] 配置文件不包含实际密钥
### 部署前检查
- [ ] 数据库迁移脚本准备
- [ ] 环境变量配置检查
- [ ] 备份当前版本
- [ ] 健康检查接口正常
- [ ] 确认目标环境的配置正确
### 安全检查
- [ ] 输入验证完整
- [ ] 错误信息不暴露敏感数据
- [ ] 认证和授权正确
- [ ] 日志不记录敏感信息
- [ ] 生产环境使用强密钥
---
**最后更新**2026-04-17
**维护者**milky