mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2025-10-22 18:33:11 +00:00
600 lines
11 KiB
Markdown
600 lines
11 KiB
Markdown
# 前端迁移指南
|
||
|
||
## 概述
|
||
|
||
本文档描述了后端中间件系统的重构,以及前端需要如何适配这些变化。核心变化是统一了设备信息获取和权限验证流程。
|
||
|
||
---
|
||
|
||
## 核心变化
|
||
|
||
### 1. 统一的设备中间件系统
|
||
|
||
后端现在使用统一的中间件处理所有与设备UUID相关的操作:
|
||
|
||
- **`deviceMiddleware`**: 自动获取或创建设备,设备不存在时自动创建
|
||
- **`requireWriteAuth`**: 验证写权限,检查设备密码
|
||
- **`tokenAuth`**: Token认证,用于应用访问
|
||
|
||
### 2. 设备自动创建
|
||
|
||
**重要变化**: 当使用一个新的UUID访问API时,后端会自动创建该设备,无需手动调用创建设备接口。
|
||
|
||
### 3. 权限模型
|
||
|
||
- **读操作**: 永远不需要密码
|
||
- **写操作**: 如果设备设置了密码则需要验证,否则直接允许
|
||
|
||
---
|
||
|
||
|
||
## 场景1: 基于UUID的直接访问
|
||
|
||
适用于:用户直接操作设备数据(设备配置、设备管理等)
|
||
|
||
### 读操作(无需密码)
|
||
|
||
**请求方式**: `GET /device/:deviceUuid/*`
|
||
|
||
**特点**:
|
||
- 设备不存在时自动创建
|
||
- 无需提供密码
|
||
- 任何知道UUID的人都可以读取
|
||
|
||
**请求示例**:
|
||
```http
|
||
GET /device/550e8400-e29b-41d4-a716-446655440000/info
|
||
Headers:
|
||
x-site-key: your-site-key
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"id": 1,
|
||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||
"name": null,
|
||
"password": null,
|
||
"passwordHint": null,
|
||
"accountId": null,
|
||
"createdAt": "2025-01-30T10:00:00.000Z",
|
||
"updatedAt": "2025-01-30T10:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
### 写操作(需要密码验证)
|
||
|
||
**请求方式**: `POST|PUT|DELETE /device/:deviceUuid/*`
|
||
|
||
**特点**:
|
||
- 设备不存在时自动创建
|
||
- 如果设备设置了密码,必须提供正确密码
|
||
- 如果设备没有密码,直接允许写入
|
||
|
||
#### 密码提供方式
|
||
|
||
**方式1: 通过请求体(推荐)**
|
||
|
||
```http
|
||
POST /device/550e8400-e29b-41d4-a716-446655440000/config
|
||
Headers:
|
||
Content-Type: application/json
|
||
x-site-key: your-site-key
|
||
Body:
|
||
{
|
||
"password": "device-password",
|
||
"data": {
|
||
"theme": "dark",
|
||
"language": "zh-CN"
|
||
}
|
||
}
|
||
```
|
||
|
||
**方式2: 通过查询参数**
|
||
|
||
```http
|
||
POST /device/550e8400-e29b-41d4-a716-446655440000/config?password=device-password
|
||
Headers:
|
||
Content-Type: application/json
|
||
x-site-key: your-site-key
|
||
Body:
|
||
{
|
||
"data": {
|
||
"theme": "dark",
|
||
"language": "zh-CN"
|
||
}
|
||
}
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"message": "数据已更新",
|
||
"updatedAt": "2025-01-30T10:05:00.000Z"
|
||
}
|
||
```
|
||
|
||
**错误响应 - 需要密码** (401):
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "此操作需要密码",
|
||
"passwordHint": "您的生日(8位数字)"
|
||
}
|
||
```
|
||
|
||
**错误响应 - 密码错误** (401):
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "密码错误"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 场景2: 基于Token的应用访问
|
||
|
||
适用于:应用访问KV存储数据
|
||
|
||
### 步骤1: 获取Token
|
||
|
||
**请求方式**: `POST /apps/:appId/authorize`
|
||
|
||
**请求示例**:
|
||
```http
|
||
POST /apps/1/authorize
|
||
Headers:
|
||
Content-Type: application/json
|
||
x-site-key: your-site-key
|
||
Body:
|
||
{
|
||
"deviceUuid": "550e8400-e29b-41d4-a716-446655440000",
|
||
"password": "device-password",
|
||
"note": "我的应用授权"
|
||
}
|
||
```
|
||
|
||
**说明**:
|
||
- `deviceUuid`: 必填,设备UUID
|
||
- `password`: 如果设备有密码则必填
|
||
- `note`: 可选,授权备注
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"token": "clxxx123456789abcdefg",
|
||
"appId": 1,
|
||
"appName": "我的应用",
|
||
"deviceUuid": "550e8400-e29b-41d4-a716-446655440000",
|
||
"deviceName": null,
|
||
"note": "我的应用授权",
|
||
"authorizedAt": "2025-01-30T10:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**错误响应 - 需要密码** (401):
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "此操作需要密码",
|
||
"passwordHint": "您的生日(8位数字)"
|
||
}
|
||
```
|
||
|
||
### 步骤2: 使用Token访问KV存储
|
||
|
||
#### Token提供方式
|
||
|
||
**方式1: Authorization Header(推荐)**
|
||
```http
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
```
|
||
|
||
**方式2: Query参数**
|
||
```http
|
||
?token=clxxx123456789abcdefg
|
||
```
|
||
|
||
**方式3: Request Body**
|
||
```json
|
||
{
|
||
"token": "clxxx123456789abcdefg",
|
||
...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### KV API端点
|
||
|
||
| 方法 | 端点 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/kv` | 列出所有键(含元数据) |
|
||
| GET | `/kv/_keys` | 列出所有键名(仅键名) |
|
||
| GET | `/kv/:key` | 获取键值 |
|
||
| GET | `/kv/:key/metadata` | 获取键元数据 |
|
||
| POST | `/kv/:key` | 创建/更新键值 |
|
||
| POST | `/kv/_batchimport` | 批量导入 |
|
||
| DELETE | `/kv/:key` | 删除键值 |
|
||
|
||
---
|
||
|
||
### GET /kv - 列出所有键(含元数据)
|
||
|
||
**请求示例**:
|
||
```http
|
||
GET /kv?sortBy=key&sortDir=asc&limit=10&skip=0
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
x-site-key: your-site-key
|
||
```
|
||
|
||
**查询参数**:
|
||
- `sortBy`: 排序字段(key/createdAt/updatedAt),默认 key
|
||
- `sortDir`: 排序方向(asc/desc),默认 asc
|
||
- `limit`: 每页数量,默认 100
|
||
- `skip`: 跳过数量,默认 0
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"deviceId": 1,
|
||
"key": "config",
|
||
"metadata": {
|
||
"creatorIp": "192.168.1.1",
|
||
"createdAt": "2025-01-30T10:00:00.000Z",
|
||
"updatedAt": "2025-01-30T10:00:00.000Z"
|
||
}
|
||
},
|
||
{
|
||
"deviceId": 1,
|
||
"key": "user.name",
|
||
"metadata": {
|
||
"creatorIp": "192.168.1.1",
|
||
"createdAt": "2025-01-30T10:01:00.000Z",
|
||
"updatedAt": "2025-01-30T10:01:00.000Z"
|
||
}
|
||
}
|
||
],
|
||
"total_rows": 25,
|
||
"load_more": "/kv?sortBy=key&sortDir=asc&limit=10&skip=10"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /kv/_keys - 列出所有键名
|
||
|
||
**请求示例**:
|
||
```http
|
||
GET /kv/_keys?limit=50&skip=0
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
x-site-key: your-site-key
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"keys": ["config", "user.name", "user.theme", "app.settings"],
|
||
"total_rows": 4,
|
||
"current_page": {
|
||
"limit": 50,
|
||
"skip": 0,
|
||
"count": 4
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /kv/:key - 获取键值
|
||
|
||
**请求示例**:
|
||
```http
|
||
GET /kv/config
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
x-site-key: your-site-key
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"theme": "dark",
|
||
"language": "zh-CN",
|
||
"fontSize": 14
|
||
}
|
||
```
|
||
|
||
**错误响应 - 键不存在** (404):
|
||
```json
|
||
{
|
||
"statusCode": 404,
|
||
"message": "未找到键名为 'config' 的记录"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /kv/:key/metadata - 获取键元数据
|
||
|
||
**请求示例**:
|
||
```http
|
||
GET /kv/config/metadata
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
x-site-key: your-site-key
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"deviceId": 1,
|
||
"key": "config",
|
||
"metadata": {
|
||
"creatorIp": "192.168.1.1",
|
||
"createdAt": "2025-01-30T10:00:00.000Z",
|
||
"updatedAt": "2025-01-30T10:05:00.000Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST /kv/:key - 创建/更新键值
|
||
|
||
**请求示例**:
|
||
```http
|
||
POST /kv/config
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
Content-Type: application/json
|
||
x-site-key: your-site-key
|
||
Body:
|
||
{
|
||
"theme": "dark",
|
||
"language": "zh-CN",
|
||
"fontSize": 14
|
||
}
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"deviceId": 1,
|
||
"key": "config",
|
||
"created": false,
|
||
"updatedAt": "2025-01-30T10:10:00.000Z"
|
||
}
|
||
```
|
||
|
||
**说明**:
|
||
- `created`: true表示新建,false表示更新
|
||
|
||
**错误响应 - 空值** (400):
|
||
```json
|
||
{
|
||
"statusCode": 400,
|
||
"message": "请提供有效的JSON值"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST /kv/_batchimport - 批量导入
|
||
|
||
**请求示例**:
|
||
```http
|
||
POST /kv/_batchimport
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
Content-Type: application/json
|
||
x-site-key: your-site-key
|
||
Body:
|
||
{
|
||
"config": {
|
||
"theme": "dark",
|
||
"language": "zh-CN"
|
||
},
|
||
"user.name": {
|
||
"firstName": "John",
|
||
"lastName": "Doe"
|
||
},
|
||
"app.settings": {
|
||
"notifications": true
|
||
}
|
||
}
|
||
```
|
||
|
||
**成功响应** (200):
|
||
```json
|
||
{
|
||
"deviceId": 1,
|
||
"total": 3,
|
||
"successful": 3,
|
||
"failed": 0,
|
||
"results": [
|
||
{
|
||
"key": "config",
|
||
"created": false
|
||
},
|
||
{
|
||
"key": "user.name",
|
||
"created": true
|
||
},
|
||
{
|
||
"key": "app.settings",
|
||
"created": true
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**部分失败响应** (200):
|
||
```json
|
||
{
|
||
"deviceId": 1,
|
||
"total": 3,
|
||
"successful": 2,
|
||
"failed": 1,
|
||
"results": [
|
||
{
|
||
"key": "config",
|
||
"created": false
|
||
},
|
||
{
|
||
"key": "user.name",
|
||
"created": true
|
||
}
|
||
],
|
||
"errors": [
|
||
{
|
||
"key": "app.settings",
|
||
"error": "Invalid value"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### DELETE /kv/:key - 删除键值
|
||
|
||
**请求示例**:
|
||
```http
|
||
DELETE /kv/config
|
||
Headers:
|
||
Authorization: Bearer clxxx123456789abcdefg
|
||
x-site-key: your-site-key
|
||
```
|
||
|
||
**成功响应** (204):
|
||
```
|
||
无响应体
|
||
```
|
||
|
||
**错误响应 - 键不存在** (404):
|
||
```json
|
||
{
|
||
"statusCode": 404,
|
||
"message": "未找到键名为 'config' 的记录"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 错误码参考
|
||
|
||
| 状态码 | 说明 | 场景 |
|
||
|--------|------|------|
|
||
| 200 | 成功 | 操作成功 |
|
||
| 204 | 成功(无内容) | 删除成功 |
|
||
| 400 | 请求错误 | 参数缺失或格式错误 |
|
||
| 401 | 未授权 | 需要密码、密码错误、Token无效 |
|
||
| 403 | 禁止访问 | 权限不足 |
|
||
| 404 | 未找到 | 资源不存在 |
|
||
| 500 | 服务器错误 | 服务器内部错误 |
|
||
|
||
---
|
||
|
||
## 401错误详解
|
||
|
||
### 需要密码
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "此操作需要密码",
|
||
"passwordHint": "您的生日(8位数字)"
|
||
}
|
||
```
|
||
|
||
**处理方式**: 提示用户输入密码,使用 `passwordHint` 作为提示信息
|
||
|
||
### 密码错误
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "密码错误"
|
||
}
|
||
```
|
||
|
||
**处理方式**: 提示用户密码错误,允许重试
|
||
|
||
### Token无效
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "未提供身份验证令牌"
|
||
}
|
||
```
|
||
|
||
或
|
||
|
||
```json
|
||
{
|
||
"statusCode": 401,
|
||
"message": "无效的身份验证令牌"
|
||
}
|
||
```
|
||
|
||
**处理方式**: 清除本地Token,引导用户重新授权
|
||
|
||
---
|
||
|
||
## 迁移检查清单
|
||
|
||
### Phase 1: 基础适配
|
||
- [ ] 移除手动创建设备的逻辑(设备会自动创建)
|
||
- [ ] 更新密码提供方式(从header改为body/query)
|
||
- [ ] 实现统一的错误处理
|
||
- [ ] 更新API端点路径
|
||
|
||
### Phase 2: Token集成
|
||
- [ ] 实现应用授权流程(POST /apps/:appId/authorize)
|
||
- [ ] 集成Token到KV操作
|
||
- [ ] 实现Token存储和管理(localStorage)
|
||
- [ ] 处理Token过期/无效场景
|
||
|
||
### Phase 3: 优化
|
||
- [ ] 封装统一的API客户端
|
||
- [ ] 实现请求重试机制
|
||
- [ ] 添加Loading状态管理
|
||
- [ ] 优化错误提示用户体验
|
||
|
||
### Phase 4: 测试
|
||
- [ ] 测试设备自动创建
|
||
- [ ] 测试密码验证流程(需要密码、密码错误、密码正确)
|
||
- [ ] 测试Token授权流程
|
||
- [ ] 测试各种错误场景(404、401、400等)
|
||
|
||
---
|
||
|
||
## 关键注意事项
|
||
|
||
### 1. 设备自动创建
|
||
- ✅ 无需手动创建设备,首次访问自动创建
|
||
- ✅ 简化前端流程,减少API调用
|
||
- ⚠️ 确保UUID使用正确的格式(建议使用uuidv4)
|
||
|
||
### 2. 密码处理
|
||
- ✅ 读操作永远不需要密码
|
||
- ✅ 写操作只在设备设置了密码时才需要
|
||
- ⚠️ 密码通过body或query提供,不要放在header中
|
||
- ⚠️ 注意区分"需要密码"和"密码错误"两种情况
|
||
|
||
### 3. Token管理
|
||
- ✅ Token一次获取,可重复使用
|
||
- ✅ Token与设备和应用绑定
|
||
- ⚠️ Token需要安全存储(localStorage/sessionStorage)
|
||
- ⚠️ Token失效时需要重新授权
|
||
|
||
### 4. Header要求
|
||
- 所有请求必须携带 `x-site-key` header
|
||
- Token认证使用 `Authorization: Bearer <token>` header(推荐)
|
||
|
||
--- |