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 后端 - 改进计划 # 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 代码组织问题 ### 1.1 代码组织问题
@@ -566,19 +623,22 @@ fi
### 13.1 前后端字段命名一致性 ### 13.1 前后端字段命名一致性
**状态**:✅ 已修复 (2026-04-17)
**问题描述** **问题描述**
后端 Rust 使用 `#[serde(rename = "camelCase")]` 序列化 JSON 字段,前端 TypeScript 必须使用相同的 camelCase 命名才能正确解析。 后端 Rust 使用 `#[serde(rename = "camelCase")]` 序列化 JSON 字段,前端 TypeScript 必须使用相同的 camelCase 命名才能正确解析。
**受影响字段** **受影响字段**
| Rust 字段 | JSON 键 | 前端错误写法 | 前端正确写法 | | Rust 字段 | JSON 键 | 前端正确写法 |
|-----------|---------|-------------|-------------| |-----------|---------|-------------|
| `is_favorite` | `isFavorite` | `is_favorite` | `isFavorite` | | `is_favorite` | `isFavorite` | `isFavorite` |
| `inspection_type` | `inspectionType` | `inspection_type` | `inspectionType` | | `inspection_type` | `inspectionType` | `inspectionType` |
| `assignment_number` | `assignmentNumber` | `assignment_number` | `assignmentNumber` | | `assignment_number` | `assignmentNumber` | `assignmentNumber` |
**问题后果** **修复内容**
- 前端使用 snake_case 命名,接口返回的 camelCase 字段会是 `undefined` - 后端 `models.rs`:为所有字段添加 `#[serde(rename = "camelCase")]`
- 详情页面显示 `undefined` 而非正确值 - 前端 TypeScript统一使用 camelCase 接口定义
- 前端 `dataCollector.ts`:字段名从 snake_case 改为 camelCase
**验证方法** **验证方法**
```bash ```bash
@@ -586,9 +646,11 @@ fi
grep -n 'rename = "' src/models.rs grep -n 'rename = "' src/models.rs
``` ```
**经验教训** **命名规范总结**(详见本文档开头「命名规范」章节)
- API 接口字段命名应在前后端团队间统一约定 - 前端 TypeScriptcamelCase
- 或让后端提供 JSON Schema / OpenAPI 文档 - API 请求/响应camelCase
- Rust 结构体snake_case
- 数据库字段snake_case
**参考**[LRN-20260417-015](file:///home/milky/Documents/ASD/.learnings/LEARNINGS.md#LRN-20260417-015) **参考**[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( pub async fn confirm_payment_order(
pool: &PgPool, pool: &PgPool,
order_no: &str, 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 接口设计 ### 12.4 接口设计
**注意**:所有 API 请求/响应均使用 camelCase 命名。
#### 微信支付流程 #### 微信支付流程
```json ```json
// 1. 创建订单 // 1. 创建订单
POST /api/payment/create-order POST /api/payment/create-order
Request: { "package_type": "monthly" | "yearly" | "permanent" } Request: { "packageType": "monthly" | "yearly" | "permanent" }
Response: { Response: {
"success": true, "success": true,
"data": { "data": {
"order_id": "内部订单号", "orderId": "内部订单号",
"prepay_id": "微信预支付ID", "prepayId": "微信预支付ID",
"package": "prepay_id=...", "package": "prepay_id=...",
"timestamp": "...", "timestamp": "...",
"nonce_str": "...", "nonceStr": "...",
"pay_sign": "..." "paySign": "..."
} }
} }
// 2. 支付回调 // 2. 支付回调
POST /api/payment/callback POST /api/payment/callback
Request: { Request: {
"event_type": "TRANSACTION.SUCCESS", "eventType": "TRANSACTION.SUCCESS",
"resource": { "resource": {
"out_trade_no": "内部订单号", "outTradeNo": "内部订单号",
"transaction_id": "微信订单号", "transactionId": "微信订单号",
"trade_state": "SUCCESS" "tradeState": "SUCCESS"
} }
} }
Response: { "code": "SUCCESS" } Response: { "code": "SUCCESS" }
@@ -840,17 +928,17 @@ Request: { "code": "ASD2024Y01A2B3C" }
Response: { Response: {
"success": true, "success": true,
"data": { "data": {
"package_type": "yearly", "packageType": "yearly",
"paid_days": 365, "paidDays": 365,
"expires_at": "2027-04-17T00:00:00Z" "expiresAt": "2027-04-17T00:00:00Z"
} }
} }
// 4. 管理员开通 // 4. 管理员开通
PUT /api/admin/users/{id}/payment PUT /api/admin/users/{id}/payment
Request: { Request: {
"is_paid": true, "isPaid": true,
"paid_expires_at": "2026-12-31T23:59:59Z" "paidExpiresAt": "2026-12-31T23:59:59Z"
} }
``` ```
@@ -937,9 +1025,9 @@ Error:
POST /api/admin/invitation-codes POST /api/admin/invitation-codes
Request: Request:
{ {
"package_type": "yearly", "packageType": "yearly",
"count": 10, "count": 10,
"expires_in_days": 365 "expiresInDays": 365
} }
Response: Response:
@@ -947,8 +1035,8 @@ Response:
"success": true, "success": true,
"data": { "data": {
"codes": [ "codes": [
{"code": "ASD2024Y01A2B3C", "paid_days": 365, "expires_at": "2027-04-17T00:00:00Z"}, {"code": "ASD2024Y01A2B3C", "paidDays": 365, "expiresAt": "2027-04-17T00:00:00Z"},
{"code": "ASD2024Y04D5E6F", "paid_days": 365, "expires_at": "2027-04-17T00:00:00Z"} {"code": "ASD2024Y04D5E6F", "paidDays": 365, "expiresAt": "2027-04-17T00:00:00Z"}
] ]
} }
} }