Initial commit of akmon project
This commit is contained in:
294
doc_zhipao/uts_akreq_head_request_support.md
Normal file
294
doc_zhipao/uts_akreq_head_request_support.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# UTS AkReq HEAD 请求支持说明
|
||||
|
||||
## 概述
|
||||
|
||||
为了正确支持 Supabase 的 `head: true` 选项,我们对 `ak-req.uts` 进行了增强,使其能够正确处理 HEAD 请求的特殊响应格式。
|
||||
|
||||
## HEAD 请求的特点
|
||||
|
||||
### HTTP 标准
|
||||
- HEAD 请求与 GET 请求相同,但**只返回响应头,不返回响应体**
|
||||
- 适用于只需要元数据而不需要实际内容的场景
|
||||
- 用于检查资源状态、获取缓存信息、计算文件大小等
|
||||
|
||||
### Supabase 中的应用
|
||||
- `{ head: true }` 选项会使用 HEAD 请求
|
||||
- 响应体为空,但 `Content-Range` header 包含 count 信息
|
||||
- 极大减少网络传输量
|
||||
|
||||
## AkReq 的增强实现
|
||||
|
||||
### 修改前的问题
|
||||
```typescript
|
||||
// 原始实现:不区分 HEAD 和 GET
|
||||
success: (res) => {
|
||||
// 总是尝试解析 res.data,即使是 HEAD 请求
|
||||
let data: UTSJSONObject | Array<any> | null;
|
||||
if (typeof res.data == 'string') {
|
||||
// ... 解析逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 修改后的正确处理
|
||||
```typescript
|
||||
success: (res) => {
|
||||
// HEAD 请求特殊处理:没有响应体,只有 headers
|
||||
if (options.method === 'HEAD') {
|
||||
const result = {
|
||||
status: res.statusCode,
|
||||
data: null, // HEAD 请求没有数据
|
||||
headers: res.header as UTSJSONObject,
|
||||
error: null
|
||||
} as AkReqResponse<any>
|
||||
resolve(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他请求正常解析 data
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 完整的数据流
|
||||
|
||||
### 1. 发起 HEAD 请求
|
||||
```typescript
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', userId)
|
||||
.execute()
|
||||
```
|
||||
|
||||
### 2. AkSupa 构建请求
|
||||
```typescript
|
||||
// aksupa.uts
|
||||
let httpMethod = 'GET';
|
||||
if (options != null && options.head == true) {
|
||||
httpMethod = 'HEAD'; // 使用 HEAD 方法
|
||||
}
|
||||
|
||||
let reqOptions: AkReqOptions = {
|
||||
url,
|
||||
method: httpMethod, // 'HEAD'
|
||||
headers: {
|
||||
'Prefer': 'count=exact,return=minimal'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. AkReq 处理响应
|
||||
```typescript
|
||||
// ak-req.uts
|
||||
if (options.method === 'HEAD') {
|
||||
return {
|
||||
status: 200,
|
||||
data: null, // HEAD 请求没有响应体
|
||||
headers: { // 重要信息在 headers 中
|
||||
'content-range': '0-0/42', // count 信息
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
error: null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. AkSupa 解析 count
|
||||
```typescript
|
||||
// aksupa.uts
|
||||
// 从 content-range header 解析 count
|
||||
let contentRange = res.headers?.get('content-range')
|
||||
if (contentRange != null) {
|
||||
const match = /\/(\d+)$/.exec(contentRange);
|
||||
if (match != null) {
|
||||
total = parseInt(match[1] ?? "0"); // 提取 count
|
||||
}
|
||||
}
|
||||
|
||||
// HEAD 模式返回 count
|
||||
if (this._options.head == true) {
|
||||
return {
|
||||
data: null,
|
||||
count: total, // 解析出的 count
|
||||
total,
|
||||
// ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 应用层使用
|
||||
```typescript
|
||||
// dashboard.uvue
|
||||
const result = await supa.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.execute()
|
||||
|
||||
const count = result.total // 42
|
||||
```
|
||||
|
||||
## 网络请求对比
|
||||
|
||||
### GET 请求(大量数据传输)
|
||||
```
|
||||
Request:
|
||||
GET /rest/v1/ak_assignments?select=*&count=exact&teacher_id=eq.123
|
||||
|
||||
Response:
|
||||
HTTP/1.1 200 OK
|
||||
Content-Range: 0-9/42
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{"id": 1, "title": "作业1", "description": "...", ...},
|
||||
{"id": 2, "title": "作业2", "description": "...", ...},
|
||||
// ... 更多数据
|
||||
]
|
||||
```
|
||||
|
||||
### HEAD 请求(最小数据传输)
|
||||
```
|
||||
Request:
|
||||
HEAD /rest/v1/ak_assignments?select=*&count=exact&teacher_id=eq.123
|
||||
|
||||
Response:
|
||||
HTTP/1.1 200 OK
|
||||
Content-Range: 0-0/42
|
||||
Content-Type: application/json
|
||||
|
||||
(无响应体)
|
||||
```
|
||||
|
||||
## 性能优势
|
||||
|
||||
### 传输量对比
|
||||
| 请求类型 | 响应体大小 | 网络传输 | 解析开销 |
|
||||
|---------|-----------|---------|---------|
|
||||
| GET | ~50KB | 大 | 高 |
|
||||
| HEAD | 0KB | 小 | 极低 |
|
||||
|
||||
### 实际场景
|
||||
```typescript
|
||||
// 统计查询场景
|
||||
const stats = await Promise.all([
|
||||
supa.from('assignments').select('*', { count: 'exact', head: true }).execute(),
|
||||
supa.from('students').select('*', { count: 'exact', head: true }).execute(),
|
||||
supa.from('courses').select('*', { count: 'exact', head: true }).execute()
|
||||
])
|
||||
|
||||
// 如果用 GET:可能传输几百KB数据
|
||||
// 使用 HEAD:只传输几KB headers
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### HEAD 请求可能的错误
|
||||
```typescript
|
||||
const safeHeadRequest = async (queryBuilder: any) => {
|
||||
try {
|
||||
const result = await queryBuilder.execute()
|
||||
|
||||
// HEAD 请求成功但没有 count 信息
|
||||
if (result.data === null && result.total === 0) {
|
||||
console.warn('HEAD 请求可能没有正确解析 count')
|
||||
}
|
||||
|
||||
return result.total ?? 0
|
||||
} catch (error) {
|
||||
console.error('HEAD 请求失败:', error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 调试 HEAD 请求
|
||||
```typescript
|
||||
const debugHeadRequest = async () => {
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.execute()
|
||||
|
||||
console.log('HEAD 请求结果:')
|
||||
console.log('- Status:', result.status)
|
||||
console.log('- Data:', result.data) // 应该为 null
|
||||
console.log('- Total:', result.total) // 应该有数值
|
||||
console.log('- Headers:', result.headers) // 包含 content-range
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 统计查询使用 HEAD
|
||||
```typescript
|
||||
// ✅ 推荐:统计查询使用 HEAD
|
||||
const getAssignmentCount = async (teacherId: string) => {
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', teacherId)
|
||||
.execute()
|
||||
|
||||
return result.total ?? 0
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据查询使用 GET
|
||||
```typescript
|
||||
// ✅ 推荐:需要数据时使用 GET
|
||||
const getAssignmentList = async (teacherId: string) => {
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact' }) // 不使用 head
|
||||
.eq('teacher_id', teacherId)
|
||||
.limit(10)
|
||||
.execute()
|
||||
|
||||
return {
|
||||
data: result.data,
|
||||
total: result.total
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 混合场景
|
||||
```typescript
|
||||
// 先获取总数,再按需获取数据
|
||||
const loadDashboard = async (teacherId: string) => {
|
||||
// 1. 快速获取统计(HEAD)
|
||||
const [totalCount, completedCount] = await Promise.all([
|
||||
supa.from('ak_assignments').select('*', { count: 'exact', head: true }).eq('teacher_id', teacherId).execute(),
|
||||
supa.from('ak_assignments').select('*', { count: 'exact', head: true }).eq('teacher_id', teacherId).eq('status', 'completed').execute()
|
||||
])
|
||||
|
||||
// 2. 根据需要获取具体数据(GET)
|
||||
const recentData = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*')
|
||||
.eq('teacher_id', teacherId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(5)
|
||||
.execute()
|
||||
|
||||
return {
|
||||
stats: {
|
||||
total: totalCount.total,
|
||||
completed: completedCount.total
|
||||
},
|
||||
recent: recentData.data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过增强 `ak-req.uts` 对 HEAD 请求的支持:
|
||||
|
||||
1. **正确处理**:HEAD 请求不解析响应体,直接返回 `data: null`
|
||||
2. **性能优化**:大幅减少网络传输量
|
||||
3. **符合标准**:遵循 HTTP HEAD 请求规范
|
||||
4. **无缝集成**:与现有 Supabase count 功能完美配合
|
||||
|
||||
这使得 UTS 应用能够高效地进行数据统计查询,特别适合仪表板、统计页面等场景。
|
||||
Reference in New Issue
Block a user