docs: 添加命名规范章节并更新付费系统接口

- 新增命名规范章节
  - 前后端 JSON 通信统一使用 camelCase
  - Rust 结构体和数据库使用 snake_case
  - 添加 serde rename 示例
- 更新 12.4 接口设计为 camelCase
- 更新 12.3 累积计算逻辑代码示例
- 更新 13.1 标记为已完成
This commit is contained in:
2026-04-17 22:53:06 +08:00
parent e02644d6d7
commit b6c022d472

View File

@@ -1,5 +1,62 @@
# Rust 后端 - 改进计划
## 命名规范
### 核心原则
**前后端通过 JSON 通信,字段命名必须统一。**
| 层级 | 命名风格 | 示例 | 说明 |
|------|---------|------|------|
| 前端 TypeScript | camelCase | `isFavorite` | 前端内部使用 |
| API 请求/响应 | camelCase | `isFavorite` | 前后端交互 |
| Rust 结构体 | snake_case | `is_favorite` | Rust 内部 |
| 数据库 | snake_case | `is_favorite` | 数据库字段 |
### 后端 Rust serde 配置
Rust 后端必须使用 `#[serde(rename = "camelCase")]` 确保序列化时使用 camelCase
```rust
#[derive(Serialize, Deserialize)]
pub struct WeatherData {
#[serde(rename = "id")]
pub id: i32,
#[serde(rename = "isFavorite")]
pub is_favorite: bool,
#[serde(rename = "inspectionType")]
pub inspection_type: String,
}
```
### 前端 TypeScript 接口定义
```typescript
interface WeatherData {
id: number;
isFavorite: boolean; // ✅ camelCase
inspectionType: string; // ✅ camelCase
}
```
### 数据库迁移注意
如果字段名使用 snake_caseJSON 序列化时需要转换:
- Rust → JSON`is_favorite``isFavorite`serde 自动处理)
- JSON → Rust`isFavorite``is_favorite`serde 自动处理)
### 已统一字段2026-04-17
| 数据库字段 | JSON 键 | 说明 |
|-----------|---------|------|
| is_favorite | isFavorite | 是否收藏 |
| inspection_type | inspectionType | 检测类型 |
| assignment_number | assignmentNumber | 任务编号 |
| paid_expires_at | paidExpiresAt | 付费到期时间 |
| is_paid_active | isPaidActive | 付费是否活跃 |
---
## 一、代码质量与架构改进
### 1.1 代码组织问题
@@ -566,19 +623,22 @@ fi
### 13.1 前后端字段命名一致性
**状态**:✅ 已修复 (2026-04-17)
**问题描述**
后端 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` |
| Rust 字段 | JSON 键 | 前端正确写法 |
|-----------|---------|-------------|
| `is_favorite` | `isFavorite` | `isFavorite` |
| `inspection_type` | `inspectionType` | `inspectionType` |
| `assignment_number` | `assignmentNumber` | `assignmentNumber` |
**问题后果**
- 前端使用 snake_case 命名,接口返回的 camelCase 字段会是 `undefined`
- 详情页面显示 `undefined` 而非正确值
**修复内容**
- 后端 `models.rs`:为所有字段添加 `#[serde(rename = "camelCase")]`
- 前端 TypeScript统一使用 camelCase 接口定义
- 前端 `dataCollector.ts`:字段名从 snake_case 改为 camelCase
**验证方法**
```bash
@@ -586,9 +646,11 @@ fi
grep -n 'rename = "' src/models.rs
```
**经验教训**
- API 接口字段命名应在前后端团队间统一约定
- 或让后端提供 JSON Schema / OpenAPI 文档
**命名规范总结**(详见本文档开头「命名规范」章节)
- 前端 TypeScriptcamelCase
- API 请求/响应camelCase
- Rust 结构体snake_case
- 数据库字段snake_case
**参考**[LRN-20260417-015](file:///home/milky/Documents/ASD/.learnings/LEARNINGS.md#LRN-20260417-015)
@@ -759,7 +821,7 @@ fn calculate_new_expires(
}
}
/// 确认订单支付
/// 确认订单支付Rust 内部使用 snake_case
pub async fn confirm_payment_order(
pool: &PgPool,
order_no: &str,
@@ -794,6 +856,30 @@ pub async fn confirm_payment_order(
}
```
**Rust 结构体示例**snake_case 内部字段 + serde rename
```rust
#[derive(Serialize, Deserialize)]
pub struct PaymentOrder {
#[serde(rename = "id")]
pub id: i32,
#[serde(rename = "userId")]
pub user_id: i32,
#[serde(rename = "orderId")]
pub order_no: String,
#[serde(rename = "packageType")]
pub package_type: String,
#[serde(rename = "amount")]
pub amount: i32,
#[serde(rename = "status")]
pub status: String,
#[serde(rename = "paidAt")]
pub paid_at: Option<DateTime<Utc>>,
#[serde(rename = "expiresAt")]
pub expires_at: Option<DateTime<Utc>>,
}
```
**累积计算示例**
| 操作 | 原到期时间 | 购买套餐 | 新到期时间 |
@@ -804,32 +890,34 @@ pub async fn confirm_payment_order(
### 12.4 接口设计
**注意**:所有 API 请求/响应均使用 camelCase 命名。
#### 微信支付流程
```json
// 1. 创建订单
POST /api/payment/create-order
Request: { "package_type": "monthly" | "yearly" | "permanent" }
Request: { "packageType": "monthly" | "yearly" | "permanent" }
Response: {
"success": true,
"data": {
"order_id": "内部订单号",
"prepay_id": "微信预支付ID",
"orderId": "内部订单号",
"prepayId": "微信预支付ID",
"package": "prepay_id=...",
"timestamp": "...",
"nonce_str": "...",
"pay_sign": "..."
"nonceStr": "...",
"paySign": "..."
}
}
// 2. 支付回调
POST /api/payment/callback
Request: {
"event_type": "TRANSACTION.SUCCESS",
"eventType": "TRANSACTION.SUCCESS",
"resource": {
"out_trade_no": "内部订单号",
"transaction_id": "微信订单号",
"trade_state": "SUCCESS"
"outTradeNo": "内部订单号",
"transactionId": "微信订单号",
"tradeState": "SUCCESS"
}
}
Response: { "code": "SUCCESS" }
@@ -840,17 +928,17 @@ Request: { "code": "ASD2024Y01A2B3C" }
Response: {
"success": true,
"data": {
"package_type": "yearly",
"paid_days": 365,
"expires_at": "2027-04-17T00:00:00Z"
"packageType": "yearly",
"paidDays": 365,
"expiresAt": "2027-04-17T00:00:00Z"
}
}
// 4. 管理员开通
PUT /api/admin/users/{id}/payment
Request: {
"is_paid": true,
"paid_expires_at": "2026-12-31T23:59:59Z"
"isPaid": true,
"paidExpiresAt": "2026-12-31T23:59:59Z"
}
```
@@ -937,9 +1025,9 @@ Error:
POST /api/admin/invitation-codes
Request:
{
"package_type": "yearly",
"packageType": "yearly",
"count": 10,
"expires_in_days": 365
"expiresInDays": 365
}
Response:
@@ -947,8 +1035,8 @@ Response:
"success": true,
"data": {
"codes": [
{"code": "ASD2024Y01A2B3C", "paid_days": 365, "expires_at": "2027-04-17T00:00:00Z"},
{"code": "ASD2024Y04D5E6F", "paid_days": 365, "expires_at": "2027-04-17T00:00:00Z"}
{"code": "ASD2024Y01A2B3C", "paidDays": 365, "expiresAt": "2027-04-17T00:00:00Z"},
{"code": "ASD2024Y04D5E6F", "paidDays": 365, "expiresAt": "2027-04-17T00:00:00Z"}
]
}
}