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 后端 - 改进计划
|
# 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 代码组织问题
|
### 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 接口字段命名应在前后端团队间统一约定
|
- 前端 TypeScript:camelCase
|
||||||
- 或让后端提供 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"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user