diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md index 8ff00a4..797de9e 100644 --- a/IMPROVEMENTS.md +++ b/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>, + #[serde(rename = "expiresAt")] + pub expires_at: Option>, +} +``` + **累积计算示例**: | 操作 | 原到期时间 | 购买套餐 | 新到期时间 | @@ -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"} ] } }