Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View 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 应用能够高效地进行数据统计查询,特别适合仪表板、统计页面等场景。