docs: 添加命名规范章节并更新付费系统接口
- 新增命名规范章节 - 前后端 JSON 通信统一使用 camelCase - Rust 结构体和数据库使用 snake_case - 添加 serde rename 示例 - 更新 12.4 接口设计为 camelCase - 更新 12.3 累积计算逻辑代码示例 - 更新 13.1 标记为已完成
This commit is contained in:
148
IMPROVEMENTS.md
148
IMPROVEMENTS.md
@@ -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_case,JSON 序列化时需要转换:
|
||||
- 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 文档
|
||||
**命名规范总结**(详见本文档开头「命名规范」章节):
|
||||
- 前端 TypeScript:camelCase
|
||||
- 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"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user