资源请求工具 — API 参考
本文档是 @jiess/http 核心类的详细参考,涵盖 Http 入口、Factory 工厂、Request 请求执行器、Cache 缓存引擎,以及完整的配置与类型说明。
快速上手请参阅 介绍&上手。
架构概览
Http ← 对外入口,管理全局配置与 API 分组
└─ create() → Factory ← 工厂类,链式注入参数 → 拦截器 → 发起请求
└─ toRequest() → Request ← 执行请求(fetch / XHR),支持重试
└─ send() → Cache ← 缓存引擎,并发去重分发Http 类
构造函数
new Http(config?: TypeConfig)TypeConfig 完整定义如下(继承自 UnObject,允许任意额外属性——除下列显式字段外,还可自由扩展自定义配置项):
type TypeConfig = UnObject & {
name?: string // 模块名称
timeout?: number // 超时时间(毫秒)
useFetch?: boolean // 是否使用 fetch,默认 true
useCache?: boolean // 是否启用缓存,默认 true
headers?: StrObject // 全局请求头(键值均为 string)
baseURL?: string // 全局基础 URL(通过 UnObject 扩展支持)
maxBodySize?: number // 请求体大小限制(字节),默认 10MB
debug?: boolean // 调试模式,默认 false
httpCacheParam?: {
waitTime: number // 缓存有效期(毫秒)
} & UnObject // 允许扩展额外缓存配置
retry?: {
count?: number // 最大重试次数,默认 0
delay?: number // 重试延迟(毫秒),默认 1000
backoff?: 'linear' | 'exponential' // 退避策略,默认 'linear'
onRetry?: (error: any, count: number) => void // 重试回调
}
interceptors?: {
request?: Function // 请求拦截器
responseSuccess?: Function // 响应成功拦截器
responseError?: Function // 响应错误拦截器
}
customFetch?: Function // 自定义 fetch 实现
httpBefore?: Function // 请求前钩子
onInitXHR?: Function // XHR 初始化钩子
}create(groupName, param)
创建一组 API 接口,返回的对象中包含所有定义的方法及元信息。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
groupName | string | 分组名称,用于生成唯一标识 |
param | TypeCreate | 键为接口名,值为数组 [method, url, baseData?, interceptors?] |
返回值属性:
| 属性 | 类型 | 说明 |
|---|---|---|
$moduleName | string | 模块唯一标识(UUID) |
$groupName | string | 分组名称 |
$moduleGroup | string | 模块+分组组合标识 |
$groupKeys | string[] | 该组所有接口的唯一标识数组 |
[接口名] | Function | 异步函数,(arg1?, arg2?, info?) => Promise<Response> |
示例:
const api = http.create('user', {
getUser: [
'GET',
'/users/:id',
{ responseType: 'json' }, // 基础数据
{ responseError: (e) => {...} } // 接口级拦截器
],
createUser: ['POST', '/users']
})
// 调用
const user = await api.getUser(1)
// api.$moduleGroup → "uuid_user"
// api.$groupKeys → ["uuid_user_getUser", "uuid_user_createUser"]getRequestHeaders(baseData, info)
合并基础数据中的 headers 与调用时传入的 headers。
const headers = http.getRequestHeaders(
{ headers: { Authorization: 'Bearer xxx' } },
{ headers: { 'X-Request-ID': 'req-1' } }
)
// → { Authorization: 'Bearer xxx', 'X-Request-ID': 'req-1' }getRequestData(method, arg1, arg2)
根据 HTTP 方法分配参数语义:
| 方法 | arg1 语义 | arg2 语义 |
|---|---|---|
| GET / DELETE / HEAD | query 查询参数 | body 请求体 |
| POST / PUT / PATCH | body 请求体 | query 查询参数 |
uploadProgress(file, callback) 静态方法
监听文件读取流进度,适用于上传场景。
Http.uploadProgress(file, (progress, uploadedSize) => {
console.log(`进度: ${Math.round(progress)}%, 已上传: ${uploadedSize} 字节`)
})Factory 类
Factory 是 Http 与 Request 之间的桥梁,负责将全局配置、接口定义、调用参数链式组合后发起请求。
构造函数
new Factory(config: TypeConfig, url: string, method: string, httpKey: string)链式方法
addRequestData(data)
注入请求参数,如 responseType、query、body 等。可多次调用,同名属性后覆盖前。
.addRequestData({ responseType: 'blob' })
.addRequestData({ body: { name: '张三' } })addHeaderData(headers)
注入额外的请求头,合并到已有 headers 中。
.addHeaderData({ Authorization: 'Bearer xxx' })addInterceptors(interceptors)
注入接口级拦截器对象。
.addInterceptors({
request: (config) => { ... },
responseError: (error) => { ... }
})toRequest()
终结方法,执行完整的请求流程(含重试),返回 Promise<Response>。
重试机制
由 config.retry 配置控制的重试流程:
请求失败 → 检查 retry.count → 计算延迟 → onRetry 回调 → 重发请求
↓
达到最大次数 → responseError 拦截器 → 抛出// 指数退避:第1次等1000ms → 第2次等2000ms → 第3次等4000ms
const http = new Http({
retry: {
count: 3,
delay: 1000,
backoff: 'exponential',
onRetry: (error, attempt) => {
console.warn(`重试第 ${attempt} 次:`, error.message)
}
}
})拦截器执行顺序
1. 全局 request 拦截器
2. 接口级 request 拦截器
3. [实际网络请求]
4. 全局 responseSuccess / responseError 拦截器
5. 接口级 responseSuccess / responseError 拦截器const http = new Http({
interceptors: {
request: (config) => {
// ① 全局:添加认证 token
config.headers['Authorization'] = `Bearer ${getToken()}`
return config
},
responseError: (error) => {
// ④ 全局:统一错误上报
reportError(error)
return error
}
}
})
const api = http.create('user', {
save: [
'POST', '/users', {},
{
request: (config) => {
// ② 接口级:添加时间戳
config.headers['X-Timestamp'] = Date.now().toString()
return config
},
responseError: (error) => {
// ⑤ 接口级:自定义降级
return { fallback: true }
}
}
]
})请求方法详解
GET 请求
用于获取资源,参数放在 URL 查询字符串中,支持缓存。
const api = http.create('user', {
getUsers: ['GET', '/users'], // 获取列表
getUser: ['GET', '/users/:id'], // 获取详情(路径参数)
search: ['GET', '/users/search'], // 条件搜索
})
// 无参数
await api.getUsers()
// 对象参数 → 自动转为 query string
await api.getUsers({ page: 1, limit: 10, status: 'active' })
// → GET /users?page=1&limit=10&status=active
// 数字/字符串参数 → 替换路径 :id
await api.getUser(1)
// → GET /users/1
// 混合参数:arg1 = query,arg2 = body(GET 很少传 body)
await api.search({ keyword: '张三' }, { ext: 'value' })
// → GET /users/search?keyword=张三,body = { ext: 'value' }POST 请求
用于创建资源,参数作为请求体发送,不支持缓存。
const api = http.create('user', {
createUser: ['POST', '/users'],
batchCreate: ['POST', '/users/batch'],
})
// 对象 → JSON 请求体
await api.createUser({ name: '张三', age: 25, email: 'zhangsan@test.com' })
// → POST /users
// Content-Type: application/json
// Body: {"name":"张三","age":25,"email":"zhangsan@test.com"}
// arg1 = body,arg2 = query
await api.createUser({ name: '张三' }, { timestamp: Date.now() })
// → POST /users?timestamp=1700000000
// Body: {"name":"张三"}
// FormData 上传
const formData = new FormData()
formData.append('name', '张三')
formData.append('avatar', file)
await api.createUser(formData)
// → type 未指定,不设 content-type,由浏览器自动加 boundaryPUT 请求
用于完整替换资源,参数作为请求体,不支持缓存。
const api = http.create('user', {
updateUser: ['PUT', '/users/:id'],
replaceConfig: ['PUT', '/config/full'],
})
// 路径参数 + 请求体
await api.updateUser(1, {
name: '李四',
age: 26,
email: 'lisi@test.com'
})
// → PUT /users/1
// Body: {"name":"李四","age":26,"email":"lisi@test.com"}
// 全量替换
await api.replaceConfig({ theme: 'dark', lang: 'zh-CN' })PATCH 请求
用于部分修改资源,只传需要修改的字段,不支持缓存。
const api = http.create('user', {
patchUser: ['PATCH', '/users/:id'],
})
// 只传要更新的字段
await api.patchUser(1, { age: 27 })
// → PATCH /users/1
// Body: {"age":27}
// 可以带查询参数
await api.patchUser(1, { status: 'active' }, { version: 2 })
// → PATCH /users/1?version=2
// Body: {"status":"active"}DELETE 请求
用于删除资源,参数优先作为查询参数,不支持缓存。
const api = http.create('user', {
deleteUser: ['DELETE', '/users/:id'],
batchDelete: ['DELETE', '/users'],
})
// 单个删除(路径参数)
await api.deleteUser(1)
// → DELETE /users/1
// 批量删除(query string)
await api.batchDelete({ ids: '1,2,3' })
// → DELETE /users?ids=1,2,3
// 带请求体删除(少见)
await api.batchDelete({ ids: '1,2,3' }, { force: true })
// → DELETE /users?ids=1,2,3
// Body: {"force":true}传参对照总表
| 方法 | 用途 | arg1 语义 | arg2 语义 | 是否缓存 | accept |
|---|---|---|---|---|---|
GET | 获取资源 | query | body | ✅ | ['GET','/api',{...}] |
POST | 创建资源 | body | query | ❌ | ['POST','/api',{...}] |
PUT | 完整替换 | body | query | ❌ | ['PUT','/api/:id',{...}] |
PATCH | 部分修改 | body | query | ❌ | ['PATCH','/api/:id',{...}] |
DELETE | 删除资源 | query | body | ❌ | ['DELETE','/api/:id',{...}] |
Request 类
实际执行 HTTP 请求的底层类,负责构建 URL、headers、body,并调用 fetch 或 XHR。
构造函数
new Request(param: TypeRequestParam, config?: UnObject)TypeRequestParam 继承自 TypeConfig(意味着它包含 TypeConfig 的全部字段,如 timeout/baseURL/headers/retry 等,并在其基础上新增以下请求专用字段):
type TypeRequestParam = {
url: string // 完整 URL(已拼装 baseURL)
httpKey: string // 接口唯一标识
mainURL: string // 原始 URL(不含 baseURL)
method: string // HTTP 方法
body?: unknown // 请求体
query?: UnObject // 查询参数(追加到 URL)
responseType?: string // 响应解析类型(json/text/blob/arrayBuffer...)
type?: string // Content-Type 来源类型(json/form/text/html/自定义 MIME)
useCache?: boolean // 是否使用缓存(来自 TypeConfig)
headers?: StrObject // 请求头(来自 TypeConfig)
// ... 以及 TypeConfig 的全部字段(timeout/baseURL/retry/interceptors 等)
} & TypeConfig内部流程
constructor()
├── initFullUrl() → 拼接 query 参数到 URL
├── initHeaders() → headers 标准化(转小写)→ sanitizeContentType()
├── initBody() → 根据 type 序列化 body → checkBodySize()
└── send() → Cache 缓存判断 → request()
├── initResult() → httpBefore 钩子
└── fetch / XHR → 解析响应(json/blob/text/arrayBuffer)initBody 的 content-type 推断
type 参数 | 行为 | Content-Type |
|---|---|---|
'json' | JSON.stringify(body) | application/json |
'form' | 序列化为 URL 编码 | application/x-www-form-urlencoded |
'text' | 原样保留 | text/plain |
'html' | 原样保留 | text/html |
| 自定义字符串 | 校验合法性后使用 | 合法则原值,非法则 application/octet-stream |
| 未指定 | 不设置 content-type(如文件上传) | — |
checkBodySize
Constructor 中自动执行,检查请求体大小:
// 默认限制 10MB
const { maxBodySize = 10 * 1024 * 1024 } = this.config超限抛出 Error('请求体过大 (size > maxBodySize bytes)')。
responseType 响应类型详解
responseType 决定了 send() 如何解析服务端返回的数据,影响 responseText 的最终形态:
| responseType | 解析方式 | responseText 类型 | 适用场景 |
|---|---|---|---|
'json' | response.json() | object / array | RESTful API 返回 JSON |
'text'(默认) | response.text() | string | 纯文本、HTML、XML |
'blob' | response.blob() | Blob | 下载文件、图片预览 |
'arraybuffer' | response.arrayBuffer() | ArrayBuffer | 二进制数据、音频处理 |
'form' | → Content-Type 设为表单 | string | URL 编码表单提交 |
const api = http.create('file', {
// JSON 数据
getData: ['GET', '/api/data', { responseType: 'json' }],
// 纯文本
getText: ['GET', '/api/text', { responseType: 'text' }],
// 下载图片
getImage: ['GET', '/api/image', { responseType: 'blob' }],
// 二进制文件
getBinary: ['GET', '/api/binary', { responseType: 'arraybuffer' }],
})
// JSON → responseText 是解析后的对象
const { responseText: data } = await api.getData()
console.log(data.users)
// Blob → 可用 createObjectURL 预览
const { responseText: blob } = await api.getImage()
imgEl.src = URL.createObjectURL(blob)
// ArrayBuffer → 可传给 AudioContext 等 API
const { responseText: buffer } = await api.getBinary()
audioContext.decodeAudioData(buffer)注意:
responseType仅影响响应解析方式,不影响请求 Content-Type。如需自定义请求 Content-Type,请在基础数据中设置headers或使用type参数。
Cache 类
缓存引擎,核心功能为并发请求去重分发:相同请求只发一次网络调用,结果分发给所有等待者。
缓存状态机
| 状态码 | 含义 | 行为 |
|---|---|---|
1000 | 未开始 | 等待第一个请求完成,回调入队 |
2000 | 已完成(成功) | 直接 resolve 缓存值 |
3000 | 已完成(失败) | 直接 reject 并移除缓存项 |
缓存键生成
// 缓存键格式
`${httpKey}::${url}::${sortedQuery}::${sortedHeaders}::${bodyHash}`httpKey:接口唯一标识(模块_分组_接口名)url:完整请求 URLsortedQuery:排序后的查询参数key=value串sortedHeaders:排序 + 小写处理后的请求头bodyHash:请求体的 DJB2 32位哈希(36进制)
哈希算法
使用 DJB2 算法:
hash(0) = 5381
hash(i) = ((hash(i-1) << 5) + hash(i-1)) + charCode(i)
hash_final = hash >>> 0 (转无符号 32 位)调试模式(process.env.JIESS_HTTP_DEBUG === 'true')下会打印:
[Cache.hashBody] hash=abc123 len=256 elapsed=1ms body={"name":"张三",...关键行为
- 同一
httpKey+ URL + query + headers + body 完全相同的并发请求,只发一次 - 所有等待回调共享同一结果(成功或失败)
- 缓存项在
waitTime后自动清除;waitTime 为 0 则立即清除 - 以下场景不缓存:POSt/PUT/PATCH/DELETE 方法、自定义
type参数、显式设置useCache: false
安全机制
Content-Type 校验
参考独立工具模块 [validators.ts](file:///d:/WORKSPACE/JIESS/jiess-2024-6-16/JIESS/TS-LIB/jiess-http/src/utils/validators.ts),提供以下导出:
| 导出 | 类型 | 说明 |
|---|---|---|
CONTENT_TYPE_REGEX | RegExp | /^[a-zA-Z][a-zA-Z0-9\-+.]*\/[a-zA-Z][a-zA-Z0-9\-+.]*$/ |
DEFAULT_CONTENT_TYPE | string | 'application/octet-stream' |
isValidContentType(ct) | (string) => boolean | 校验 MIME 类型合法性 |
sanitizeContentType(ct) | (string?) => string | 清洗非法值,回退默认 |
import { isValidContentType, sanitizeContentType } from '@jiess/http/src/utils/validators'
isValidContentType('application/json') // true
isValidContentType('<script>alert(1)</script>') // false
isValidContentType('text/plain\r\nX-Injected:malicious') // false
sanitizeContentType('application/json') // 'application/json'
sanitizeContentType('<script>alert(1)</script>') // 'application/octet-stream'
sanitizeContentType(undefined) // 'application/octet-stream'Request 类的 initHeaders() 和 initBody() 中已自动调用 sanitizeContentType 和 isValidContentType,无需手动处理。
URL 编码
路径参数中的字符串和非纯数字参数会通过 encodeURIComponent() 编码,防止 XSS 注入:
// 源码中 buildUrl 对 g1/g2 调用 encodeURIComponent
api.getUser('<img src=x>') // URL → /users/%3Cimg%20src%3Dx%3E请求体大小限制
默认 10MB,可通过 maxBodySize 配置调整(单位:字节)。超出时在构造函数阶段即抛出 Error('请求体过大 (size > maxBodySize bytes)')。
const http = new Http({ maxBodySize: 2 * 1024 * 1024 }) // 限制 2MBmaxBodySize 极端值行为表
| 配置值 | 行为 | 说明 |
|---|---|---|
未设置 / undefined / null | 默认 10MB | != null 判断为假,回退默认值 |
1024 | 限制 1024 字节 | 正常生效 |
0 | 零限制,任何非空 body 都拦截 | 0 != null 为真,严格生效 |
负数 (如 -1) | 所有 body 都会超限 | -1 != null 为真,任何 bodySize > -1 均成立 |
Infinity | 永不拦截 | 合法值,任何有限 body 均小于它 |
⚠️ 注意:
maxBodySize: 0与maxBodySize: null是不同的语义:
0→ 零容忍,所有请求体都被拒绝(包括{ a: 1 }这种最小 JSON)null/ 不传 → 回退为默认 10MB如果你需要禁用请求体大小检查,请设置
maxBodySize: Infinity而不是0。
与 falsy body 的联动
修复后 body = 0、body = false 等 falsy 值会被正常序列化(如 JSON.stringify(0) → "0"),因此也会计入请求体大小检查。例如:
// maxBodySize: 1 → "0" 序列化后 2 字节 → 拦截抛错
new Http({ maxBodySize: 1 }).create('test', { post: ['POST', '/api'] }).post(0)maxBodySize: 0 实战场景
场景一:只读 API 网关 — 后端仅提供 GET 查询,不应接受任何请求体。
const readOnlyApi = new Http({
baseURL: 'https://query-api.example.com',
maxBodySize: 0, // 零容忍:任何 POST/PUT/PATCH 带 body 立即拦截
})
const api = readOnlyApi.create('readonly', {
search: ['GET', '/search'],
detail: ['GET', '/detail/:id'],
})
// GET 正常(无 body,不触发检查)
await api.search({ q: 'hello' }) // ✅ 通过
// 误用的 POST 在构造函数阶段即抛错
await api.search.post({ q: 'hello' })
// ❌ Error: 请求体过大 (21 > 0 bytes)场景二:安全沙箱环境 — 审计模式,记录所有尝试提交的请求但禁止实际执行。
const auditSandbox = new Http({
baseURL: 'https://internal.example.com',
maxBodySize: 0,
interceptors: {
request: (config) => {
// 请求体大小检查在拦截器之前执行,所以此处不会被触发
console.warn('[审计] 拦截到一次非法写请求', config)
return config
},
},
})
const api = auditSandbox.create('sandbox', {
create: ['POST', '/resource'],
update: ['PUT', '/resource/:id'],
})
try {
await api.create({ name: 'test' }) // ❌ 被 maxBodySize:0 拦截
} catch (err) {
// 安全审计日志已在构造函数中产生
reportSecurityEvent(err)
}场景三:逐接口精细控制 — 全局宽松,特定敏感接口零容忍。
const mixedApi = new Http({ baseURL: 'https://api.example.com' })
// 全局不设 maxBodySize → 默认 10MB
// 普通接口允许 body
const normalApi = mixedApi.create('normal', {
submit: ['POST', '/submit'],
})
// 敏感接口:通过内联配置覆盖,禁止任何请求体
const sensitiveApi = mixedApi.create('sensitive', {
delete: ['DELETE', '/admin/delete', {
maxBodySize: 0, // 仅此接口生效
}],
})
await normalApi.submit({ data: 'ok' }) // ✅ 10MB 内放行
await sensitiveApi.delete({ id: 1 }) // ❌ 0 byte 限制立即拦截maxBodySize 配置最佳实践与常见陷阱
最佳实践
1. 始终使用显式数值,避免依赖默认值语义
// ❌ 不推荐:依赖 10MB 默认值,当库升级后默认值可能变化
const http = new Http({ baseURL: 'https://api.example.com' })
// ✅ 推荐:显式声明限制值,可读且不依赖默认值
const http = new Http({
baseURL: 'https://api.example.com',
maxBodySize: 10 * 1024 * 1024, // 10MB
})2. 根据接口粒度分层设置
// ✅ 全局宽松 + 敏感接口收紧
const http = new Http({
baseURL: 'https://api.example.com',
maxBodySize: 10 * 1024 * 1024, // 全局 10MB
})
const generalApi = http.create('general', {
upload: ['POST', '/upload'],
})
const adminApi = http.create('admin', {
bulkDelete: ['DELETE', '/admin/delete', {
maxBodySize: 1 * 1024 * 1024, // 单接口限制 1MB
}],
})3. 用 Infinity 禁用检查,而不是很大的数字
// ❌ 不推荐:魔法数字,语义模糊
const http = new Http({ maxBodySize: 999999999 })
// ✅ 推荐:语义明确,永不拦截
const http = new Http({ maxBodySize: Infinity })4. 在沙箱/审计场景使用 maxBodySize: 0
// ✅ 审计模式:捕获所有写操作但不执行
const auditHttp = new Http({
maxBodySize: 0,
interceptors: {
request: (config) => {
auditLogger.log('ATTEMPTED_WRITE', config)
return config // 不会执行到这里,maxBodySize:0 已经抛出
}
}
})常见陷阱
陷阱 1:maxBodySize: 0 ≠ "不限制"
这是最常见的误解。语义对照表:
| 你想要的 | 正确的配置 | 错误配置(陷阱) |
|---|---|---|
| 不限制大小 | maxBodySize: Infinity | maxBodySize: 0 ❌ |
| 回退默认 10MB | 不设置 / undefined / null | maxBodySize: 0 ❌ |
| 禁止所有请求体 | maxBodySize: 0 ✅ | — |
// ❌ 开发者误以为 0 表示"不限制"
const http = new Http({ maxBodySize: 0 })
await http.create('api', { submit: ['POST', '/submit'] }).submit({ data: 'hello' })
// 💥 Error: 请求体过大 (17 > 0 bytes) — 所有 body 都被拒绝!
// ✅ 正确做法
const http = new Http({ maxBodySize: Infinity })陷阱 2:falsy body 值也会被计入检查
修复后 body = 0、body = false 会被正确序列化为 JSON 并参与大小检查,而非被静默丢弃。
const http = new Http({ maxBodySize: 5 })
// body = 0 → JSON.stringify(0) → "0"(2 字节)→ 2 ≤ 5 → ✅ 放行
// body = false → JSON.stringify(false) → "false"(6 字节)→ 6 > 5 → ❌ 拦截!
// ❌ 之前的行为:body = false 被静默丢弃为 null,检查被跳过
// ✅ 现在的行为:body = false 被序列化为 "false",参与大小检查陷阱 3:拦截器捕获不到 maxBodySize 错误
maxBodySize 检查发生在 Request 构造函数中(见 [Request/index.ts](file:///d:/WORKSPACE/JIESS/jiess-2024-6-16/JIESS/TS-LIB/jiess-http/src/Request/index.ts#L87-L95)),早于拦截器 request 钩子和网络请求。错误会被 toRequest() 的 catch 捕获并通过 responseError 拦截器处理。
const http = new Http({
maxBodySize: 0,
interceptors: {
request: (config) => {
// ⚠️ 当 maxBodySize:0 时,这段代码永远不会执行
// 因为 Request 构造函数阶段就已经抛出了
console.log('请求拦截器', config)
return config
},
responseError: (error) => {
// ✅ 但这里可以捕获到 maxBodySize 导致的错误
console.error('捕获到大小限制错误', error.message)
return Promise.reject(error)
}
}
})陷阱 4:GET 请求不受影响,但同名路由的 POST 会被拦截
const http = new Http({ maxBodySize: 0 })
const api = http.create('readonly', {
search: ['GET', '/search'], // ✅ GET 无 body,不受影响
report: ['POST', '/report'], // ❌ POST 携带 body 会被立即拦截
})
await api.search({ q: 'hello' }) // ✅ 正常执行
await api.report({ data: 'x' }) // ❌ 抛出:请求体过大动态配置的架构展望
当前 maxBodySize 是静态配置:在 Http 实例化时写入 TypeConfig,流经 Factory,最终在 Request.checkBodySize() 中以单一取值点读取:
Http.config → Factory.config → Request.checkBodySize()
└── const maxBodySize = this.config.maxBodySize != null
? this.config.maxBodySize
: 10 * 1024 * 1024优点:简单、可预测、不会发生配置丢失。 局限:无法在运行时根据上下文(用户角色、环境变量、请求路径)动态调整。
若要支持动态配置,需改动三处
| 层级 | 文件 | 改动内容 |
|---|---|---|
| 类型层 | [index.ts](file:///d:/WORKSPACE/JIESS/jiess-2024-6-16/JIESS/TS-LIB/jiess-http/src/index.ts#L28) | maxBodySize 类型扩展为 number | (() => number) | (() => Promise<number>) |
| 请求层 | [Request/index.ts](file:///d:/WORKSPACE/JIESS/jiess-2024-6-16/JIESS/TS-LIB/jiess-http/src/Request/index.ts#L87-L95) | checkBodySize() 改为异步,先 resolve 函数再取值 |
| 调度层 | [Factory/index.ts](file:///d:/WORKSPACE/JIESS/jiess-2024-6-16/JIESS/TS-LIB/jiess-http/src/Factory/index.ts#L52-L106) | toRequest() 中 await req.checkBodySize() 而非构造时同步调用 |
推荐方案:函数式配置 + 环境变量优先
改动量最小的方案是让 maxBodySize 同时支持函数和环境变量覆盖:
// 类型定义变更(minimal impact)
export type TypeConfig = UnObject & {
maxBodySize?: number | (() => number) | (() => Promise<number>)
// ... 其他字段不变
}
// checkBodySize() 重构
async checkBodySize(bodyStr: unknown) {
const ENV_OVERRIDE = process.env.JIESS_HTTP_MAX_BODY_SIZE
let maxBodySize: number
if (ENV_OVERRIDE !== undefined) {
maxBodySize = parseInt(ENV_OVERRIDE, 10)
} else if (typeof this.config.maxBodySize === 'function') {
maxBodySize = await (this.config.maxBodySize as Function)()
} else {
maxBodySize = this.config.maxBodySize != null
? (this.config.maxBodySize as number)
: 10 * 1024 * 1024
}
if (bodyStr) {
const bodySize = JSON.stringify(bodyStr).length
if (bodySize > maxBodySize) {
throw new Error(`请求体过大 (${bodySize} > ${maxBodySize} bytes)`)
}
}
}使用示例:
// 场景 A:环境变量覆盖(CI/CD 统一管控)
// export JIESS_HTTP_MAX_BODY_SIZE=1048576
const http = new Http({ baseURL: 'https://api.example.com' })
// 实际生效 1MB,忽略代码中的默认 10MB
// 场景 B:函数动态读取(按用户角色)
const http = new Http({
maxBodySize: () => {
return currentUser.isAdmin()
? 50 * 1024 * 1024 // 管理员 50MB
: 1 * 1024 * 1024 // 普通用户 1MB
}
})
// 场景 C:异步函数(从远程配置中心拉取)
const http = new Http({
maxBodySize: async () => {
const remoteConfig = await fetchConfigFromCenter()
return remoteConfig.uploadLimit
}
})⚠️ 注意事项:如果采用异步方案,
checkBodySize()和initBody()都需要变为异步方法,连锁影响Request构造函数及下游调用链。建议先在Factory.toRequest()中显式await req.checkBodySize(),逐步迁移而非一次性全改。
错误类型
@jiess/http 提供了丰富的错误类体系:
import {
HttpError, // 基础错误类(code, message, config, response, timestamp)
TimeoutError, // 408 请求超时
RequestBodyTooLargeError, // 413 请求体过大
NetworkError, // 网络错误
AbortError, // 499 请求取消
InvalidParameterError, // 400 无效参数
AuthenticationError, // 401 认证失败
AuthorizationError, // 403 权限不足
NotFoundError, // 404 资源未找到
ServerError // 500 服务器错误
} from '@jiess/http'每个错误类都继承自 HttpError,具有 code、message、config、response、timestamp 属性及 toJSON() 方法。
使用示例:
import { AuthenticationError, NotFoundError } from '@jiess/http'
const http = new Http({
interceptors: {
responseError: (error) => {
if (error instanceof AuthenticationError) {
redirectToLogin()
} else if (error instanceof NotFoundError) {
show404Page()
}
return Promise.reject(error)
}
}
})接口调用方式对比
| 方式 | 代码量 | 适用场景 |
|---|---|---|
直接使用 http.create() | 最少 | 常规 CRUD 接口 |
| 链式 Factory | 中等 | 需要精细控制参数的场景 |
| 直接 new Request | 最多 | 完全自定义的底层请求 |
// 方式一:http.create(推荐)
const api = http.create('user', { getUser: ['GET', '/users/:id'] })
await api.getUser(1)
// 方式二:Factory 链式(高级)
const factory = new Factory(config, '/users/1', 'GET', 'myKey')
await factory.addHeaderData({ Authorization: 'Bearer xxx' }).toRequest()
// 方式三:Request 底层(完全控制)
const request = new Request({
url: 'https://api.example.com/users/1',
method: 'GET',
httpKey: 'myKey',
mainURL: '/users/1'
})
await request.send()