@hile/http 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +626 -0
- package/dist/controller.d.ts +3 -3
- package/dist/loader.d.ts +1 -1
- package/package.json +6 -3
- package/skill.json +16 -0
package/README.md
CHANGED
|
@@ -175,7 +175,7 @@ await http.load('./src/controllers', {
|
|
|
175
175
|
配合 `hile` 服务容器管理 HTTP 服务的生命周期:
|
|
176
176
|
|
|
177
177
|
```typescript
|
|
178
|
-
import { defineService } from 'hile'
|
|
178
|
+
import { defineService } from '@hile/core'
|
|
179
179
|
import { Http } from '@hile/http'
|
|
180
180
|
|
|
181
181
|
export const httpService = defineService(async (shutdown) => {
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
# @hile/http
|
|
2
|
+
|
|
3
|
+
`@hile/http` 是基于 Koa + find-my-way 的 HTTP 服务框架。本文档是面向 AI 编码模型和人类开发者的 **代码生成规范**,阅读后应能正确地使用本库编写符合架构规则的 HTTP 服务代码。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 架构总览
|
|
8
|
+
|
|
9
|
+
`@hile/http` 提供三层抽象:
|
|
10
|
+
|
|
11
|
+
- **Http 类** — 服务核心,封装 Koa 实例和路由器,提供中间件注册、路由注册和服务启停
|
|
12
|
+
- **defineController** — 控制器定义函数,将 HTTP 方法 + 中间件 + 处理函数打包为标准控制器对象
|
|
13
|
+
- **Loader** — 路由加载器,支持手动编译绑定和基于文件系统的自动路由加载
|
|
14
|
+
|
|
15
|
+
典型工作流:`定义控制器 → 通过 Loader 编译绑定到 Http → 启动服务`
|
|
16
|
+
|
|
17
|
+
### 依赖关系
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
@hile/http
|
|
21
|
+
├── koa — Web 框架
|
|
22
|
+
├── koa-compose — 中间件组合
|
|
23
|
+
├── find-my-way — 高性能路由匹配
|
|
24
|
+
└── glob — 文件系统路由扫描
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. 类型签名
|
|
30
|
+
|
|
31
|
+
生成代码时,必须严格遵循以下类型:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Context, Middleware } from 'koa'
|
|
35
|
+
import { HTTPMethod } from 'find-my-way'
|
|
36
|
+
|
|
37
|
+
// Http 配置(port 必填,其余可选)
|
|
38
|
+
type HttpProps = {
|
|
39
|
+
port: number
|
|
40
|
+
keys?: string[]
|
|
41
|
+
ignoreDuplicateSlashes?: boolean // 默认 true
|
|
42
|
+
ignoreTrailingSlash?: boolean // 默认 true
|
|
43
|
+
maxParamLength?: number // 默认 +Infinity
|
|
44
|
+
allowUnsafeRegex?: boolean // 默认 true
|
|
45
|
+
caseSensitive?: boolean // 默认 true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 控制器处理函数:接收 Koa Context,返回值自动赋给 ctx.body
|
|
49
|
+
type ControllerFunction = (ctx: Context) => unknown | Promise<unknown>
|
|
50
|
+
|
|
51
|
+
// 控制器注册信息:由 defineController 返回
|
|
52
|
+
interface ControllerRegisterProps {
|
|
53
|
+
id: number
|
|
54
|
+
method: HTTPMethod
|
|
55
|
+
middlewares: Middleware[]
|
|
56
|
+
data: Record<string, any> // 编译后 data.url 会被自动设置
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Loader 编译选项
|
|
60
|
+
interface LoaderCompileOptions {
|
|
61
|
+
defaultSuffix?: string // 默认 '/index',匹配时去除该后缀
|
|
62
|
+
prefix?: string // 路由前缀
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Loader 文件加载选项
|
|
66
|
+
type LoaderFromOptions = {
|
|
67
|
+
suffix?: string // 文件后缀标记,默认 'controller'
|
|
68
|
+
} & LoaderCompileOptions
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 3. 代码生成模板
|
|
74
|
+
|
|
75
|
+
### 3.1 创建 Http 服务(必须遵循的模式)
|
|
76
|
+
|
|
77
|
+
**模板:**
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { Http } from '@hile/http'
|
|
81
|
+
|
|
82
|
+
const http = new Http({
|
|
83
|
+
port: 3000,
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**规则:**
|
|
88
|
+
- 构造参数 `port` **必填**
|
|
89
|
+
- 不需要手动传 `keys`,框架会自动生成随机密钥
|
|
90
|
+
- 路由器默认配置(忽略重复斜杠、忽略尾部斜杠、大小写敏感)通常无需修改
|
|
91
|
+
|
|
92
|
+
### 3.2 注册全局中间件
|
|
93
|
+
|
|
94
|
+
**模板:**
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
http.use(async (ctx, next) => {
|
|
98
|
+
const start = Date.now()
|
|
99
|
+
await next()
|
|
100
|
+
ctx.set('X-Response-Time', `${Date.now() - start}ms`)
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**规则:**
|
|
105
|
+
- `use()` **必须**在 `listen()` 之前调用
|
|
106
|
+
- 中间件签名固定为 `(ctx: Context, next: Next) => Promise<void>`
|
|
107
|
+
- `use()` 返回 `this`,支持链式调用
|
|
108
|
+
- 全局中间件对 **所有路由** 生效
|
|
109
|
+
|
|
110
|
+
### 3.3 手动注册路由
|
|
111
|
+
|
|
112
|
+
**模板:**
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// 注册路由,返回注销函数
|
|
116
|
+
const off = http.get('/api/users', async (ctx) => {
|
|
117
|
+
ctx.body = { users: [] }
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// 需要时可注销路由
|
|
121
|
+
off()
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**规则:**
|
|
125
|
+
- 使用 `http.get()` / `http.post()` / `http.put()` / `http.delete()` / `http.trace()` 快捷方法
|
|
126
|
+
- 或使用 `http.route(method, url, ...middlewares)` 指定任意 HTTP 方法
|
|
127
|
+
- 所有路由注册方法 **都返回注销回调函数**,调用后路由不再匹配
|
|
128
|
+
- 路由支持路径参数:`/users/:id`
|
|
129
|
+
- 路由支持多个中间件,按顺序执行
|
|
130
|
+
|
|
131
|
+
### 3.4 定义控制器(文件路由模式,必须遵循的模式)
|
|
132
|
+
|
|
133
|
+
**模板(简洁写法 — 无额外中间件):**
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { defineController } from '@hile/http'
|
|
137
|
+
|
|
138
|
+
export default defineController('GET', (ctx) => {
|
|
139
|
+
return { message: 'hello' }
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**模板(带中间件):**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { defineController } from '@hile/http'
|
|
147
|
+
|
|
148
|
+
const authMiddleware = async (ctx, next) => {
|
|
149
|
+
if (!ctx.headers.authorization) {
|
|
150
|
+
ctx.throw(401)
|
|
151
|
+
}
|
|
152
|
+
await next()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default defineController('POST', [authMiddleware], (ctx) => {
|
|
156
|
+
return { data: ctx.request.body }
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**规则:**
|
|
161
|
+
- 第一个参数 **必须** 是 HTTP 方法字符串:`'GET'` | `'POST'` | `'PUT'` | `'DELETE'` 等
|
|
162
|
+
- 第二个参数可以是 **控制器函数**(无中间件时)或 **中间件数组**(有中间件时)
|
|
163
|
+
- 有中间件数组时,第三个参数 **必须** 是控制器函数
|
|
164
|
+
- 控制器函数接收 `ctx: Context`,返回值非 `undefined` 时自动赋给 `ctx.body`
|
|
165
|
+
- 返回 `undefined` 时不修改 `ctx.body`(用于流式响应或手动设置)
|
|
166
|
+
- 控制器文件 **必须** `export default` 导出 `defineController` 的返回值
|
|
167
|
+
- 一个文件可以导出单个控制器或控制器数组(用于同一路径注册多个 HTTP 方法)
|
|
168
|
+
|
|
169
|
+
### 3.5 文件系统路由(自动加载)
|
|
170
|
+
|
|
171
|
+
**模板:**
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { Http } from '@hile/http'
|
|
175
|
+
|
|
176
|
+
const http = new Http({ port: 3000 })
|
|
177
|
+
const off = await http.load('./src/controllers', {
|
|
178
|
+
suffix: 'controller', // 匹配 *.controller.{ts,js} 文件
|
|
179
|
+
defaultSuffix: '/index', // index 文件映射到父路径
|
|
180
|
+
prefix: '/api', // 所有路由添加前缀
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**规则:**
|
|
185
|
+
- `load()` 扫描目录下所有匹配 `**/*.{suffix}.{ts,js}` 的文件
|
|
186
|
+
- 文件路径自动转换为路由路径(去除后缀标记和文件扩展名)
|
|
187
|
+
- `load()` 返回 `Promise<() => void>`,**必须** `await`
|
|
188
|
+
- 返回的注销函数调用后移除所有加载的路由
|
|
189
|
+
|
|
190
|
+
### 3.6 文件路由路径转换规则
|
|
191
|
+
|
|
192
|
+
文件系统路径到路由路径的转换遵循以下规则:
|
|
193
|
+
|
|
194
|
+
| 文件路径 | 路由路径 | 说明 |
|
|
195
|
+
|---------|---------|------|
|
|
196
|
+
| `users/index.controller.ts` | `/users` 或 `/` | `index` 被 defaultSuffix 去除 |
|
|
197
|
+
| `users/list.controller.ts` | `/users/list` | 普通路径 |
|
|
198
|
+
| `users/[id].controller.ts` | `/users/:id` | `[param]` 转换为 `:param` |
|
|
199
|
+
| `[category]/[id].controller.ts` | `/:category/:id` | 多参数 |
|
|
200
|
+
| `index.controller.ts` | `/` | 根路径 |
|
|
201
|
+
|
|
202
|
+
**转换流程:**
|
|
203
|
+
1. 去除文件名中的 `.{suffix}.{ts,js}` 后缀
|
|
204
|
+
2. 路径不以 `/` 开头时自动补充
|
|
205
|
+
3. 路径以 `defaultSuffix`(默认 `/index`)结尾时去除该后缀
|
|
206
|
+
4. 去除后为空则重置为 `/`
|
|
207
|
+
5. 添加 `prefix` 前缀(如果指定)
|
|
208
|
+
6. 将 `[param]` 替换为 `:param`
|
|
209
|
+
|
|
210
|
+
### 3.7 启动服务
|
|
211
|
+
|
|
212
|
+
**模板:**
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const close = await http.listen((server) => {
|
|
216
|
+
console.log(`Server running on port ${http.port}`)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// 关闭服务
|
|
220
|
+
close()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**规则:**
|
|
224
|
+
- `listen()` 返回 `Promise<() => void>`,**必须** `await`
|
|
225
|
+
- 可选传入 `onListen` 回调,在服务端口绑定前执行,接收 `Server` 对象
|
|
226
|
+
- 返回的关闭函数调用后停止服务
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 4. 强制规则(生成代码时必须遵守)
|
|
231
|
+
|
|
232
|
+
| # | 规则 | 原因 |
|
|
233
|
+
|---|------|------|
|
|
234
|
+
| 1 | 全局中间件 **必须** 在 `listen()` 之前通过 `use()` 注册 | `listen()` 内部冻结中间件栈 |
|
|
235
|
+
| 2 | 控制器文件 **必须** `export default` 导出 | Loader 通过 `import(path).default` 加载 |
|
|
236
|
+
| 3 | 控制器文件命名 **必须** 以 `{suffix}.ts` 结尾 | 默认为 `*.controller.ts`,Loader 通过文件名后缀匹配 |
|
|
237
|
+
| 4 | `defineController` 的控制器函数参数是 `ctx`(不是 `ctx, next`) | 框架自动包装为中间件,`ctx.body` 由返回值自动设置 |
|
|
238
|
+
| 5 | 控制器函数需要返回响应数据时 **直接 return** | 不要手动 `ctx.body = ...` 然后又 return 值 |
|
|
239
|
+
| 6 | 路由参数使用 `[param]` 方括号语法 | Loader 会自动转换为 find-my-way 的 `:param` 格式 |
|
|
240
|
+
| 7 | `load()` 返回 Promise,**必须** `await` | 内部有异步文件扫描和动态 import |
|
|
241
|
+
| 8 | 同一路径的多个 HTTP 方法控制器 **导出为数组** | `export default [getController, postController]` |
|
|
242
|
+
| 9 | **不要** 在控制器函数中调用 `next()` | 控制器是终端处理器,不是中间件 |
|
|
243
|
+
| 10 | 需要 `next()` 的逻辑 **放在中间件数组中** | `defineController('GET', [myMiddleware], handler)` |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 5. 完整示例:项目结构
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
src/
|
|
251
|
+
├── controllers/
|
|
252
|
+
│ ├── index.controller.ts # GET /api
|
|
253
|
+
│ ├── users/
|
|
254
|
+
│ │ ├── index.controller.ts # GET /api/users
|
|
255
|
+
│ │ └── [id].controller.ts # GET /api/users/:id, PUT /api/users/:id
|
|
256
|
+
│ └── posts/
|
|
257
|
+
│ ├── index.controller.ts # GET /api/posts, POST /api/posts
|
|
258
|
+
│ └── [id].controller.ts # GET /api/posts/:id
|
|
259
|
+
├── middlewares/
|
|
260
|
+
│ ├── logger.ts
|
|
261
|
+
│ └── auth.ts
|
|
262
|
+
└── main.ts
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### middlewares/logger.ts
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { Middleware } from 'koa'
|
|
269
|
+
|
|
270
|
+
export const logger: Middleware = async (ctx, next) => {
|
|
271
|
+
const start = Date.now()
|
|
272
|
+
await next()
|
|
273
|
+
const ms = Date.now() - start
|
|
274
|
+
console.log(`${ctx.method} ${ctx.url} - ${ctx.status} ${ms}ms`)
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### middlewares/auth.ts
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { Middleware } from 'koa'
|
|
282
|
+
|
|
283
|
+
export const auth: Middleware = async (ctx, next) => {
|
|
284
|
+
const token = ctx.headers.authorization
|
|
285
|
+
if (!token) {
|
|
286
|
+
ctx.throw(401, 'Unauthorized')
|
|
287
|
+
}
|
|
288
|
+
await next()
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### controllers/index.controller.ts
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { defineController } from '@hile/http'
|
|
296
|
+
|
|
297
|
+
export default defineController('GET', () => {
|
|
298
|
+
return { status: 'ok', timestamp: Date.now() }
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### controllers/users/index.controller.ts
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { defineController } from '@hile/http'
|
|
306
|
+
|
|
307
|
+
export default defineController('GET', async (ctx) => {
|
|
308
|
+
return { users: [] }
|
|
309
|
+
})
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### controllers/users/[id].controller.ts
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { defineController } from '@hile/http'
|
|
316
|
+
import { auth } from '../../middlewares/auth'
|
|
317
|
+
|
|
318
|
+
const getUser = defineController('GET', async (ctx) => {
|
|
319
|
+
const { id } = ctx.params
|
|
320
|
+
return { id, name: `User ${id}` }
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
const updateUser = defineController('PUT', [auth], async (ctx) => {
|
|
324
|
+
const { id } = ctx.params
|
|
325
|
+
return { id, updated: true }
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
export default [getUser, updateUser]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### controllers/posts/index.controller.ts
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { defineController } from '@hile/http'
|
|
335
|
+
import { auth } from '../../middlewares/auth'
|
|
336
|
+
|
|
337
|
+
const getPosts = defineController('GET', async (ctx) => {
|
|
338
|
+
return { posts: [] }
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const createPost = defineController('POST', [auth], async (ctx) => {
|
|
342
|
+
return { created: true }
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
export default [getPosts, createPost]
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### controllers/posts/[id].controller.ts
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { defineController } from '@hile/http'
|
|
352
|
+
|
|
353
|
+
export default defineController('GET', async (ctx) => {
|
|
354
|
+
const { id } = ctx.params
|
|
355
|
+
return { id, title: `Post ${id}` }
|
|
356
|
+
})
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### main.ts
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { Http } from '@hile/http'
|
|
363
|
+
import { logger } from './middlewares/logger'
|
|
364
|
+
|
|
365
|
+
async function main() {
|
|
366
|
+
const http = new Http({ port: 3000 })
|
|
367
|
+
|
|
368
|
+
http.use(logger)
|
|
369
|
+
|
|
370
|
+
await http.load('./src/controllers', {
|
|
371
|
+
suffix: 'controller',
|
|
372
|
+
prefix: '/api',
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const close = await http.listen(() => {
|
|
376
|
+
console.log(`Server running on http://localhost:${http.port}`)
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
main()
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 生成的路由表
|
|
384
|
+
|
|
385
|
+
| 方法 | 路径 | 来源文件 |
|
|
386
|
+
|------|------|---------|
|
|
387
|
+
| GET | `/api` | `index.controller.ts` |
|
|
388
|
+
| GET | `/api/users` | `users/index.controller.ts` |
|
|
389
|
+
| GET | `/api/users/:id` | `users/[id].controller.ts` |
|
|
390
|
+
| PUT | `/api/users/:id` | `users/[id].controller.ts` |
|
|
391
|
+
| GET | `/api/posts` | `posts/index.controller.ts` |
|
|
392
|
+
| POST | `/api/posts` | `posts/index.controller.ts` |
|
|
393
|
+
| GET | `/api/posts/:id` | `posts/[id].controller.ts` |
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 6. 反模式(生成代码时必须避免)
|
|
398
|
+
|
|
399
|
+
### 6.1 不要在控制器函数中调用 next()
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// ✗ 错误:控制器函数不是中间件,不应调用 next
|
|
403
|
+
export default defineController('GET', async (ctx, next) => {
|
|
404
|
+
await next()
|
|
405
|
+
return { data: 'hello' }
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// ✓ 正确:直接返回数据
|
|
409
|
+
export default defineController('GET', async (ctx) => {
|
|
410
|
+
return { data: 'hello' }
|
|
411
|
+
})
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 6.2 不要同时 return 和手动设置 ctx.body
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// ✗ 错误:重复设置,return 值会覆盖
|
|
418
|
+
export default defineController('GET', async (ctx) => {
|
|
419
|
+
ctx.body = { wrong: true }
|
|
420
|
+
return { right: true }
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// ✓ 正确:只用 return
|
|
424
|
+
export default defineController('GET', async (ctx) => {
|
|
425
|
+
return { right: true }
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
// ✓ 也正确:只用 ctx.body,return undefined
|
|
429
|
+
export default defineController('GET', async (ctx) => {
|
|
430
|
+
ctx.body = fs.createReadStream('file.txt')
|
|
431
|
+
})
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 6.3 不要在 listen() 之后注册全局中间件
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// ✗ 错误:listen 后注册的中间件不会生效
|
|
438
|
+
const close = await http.listen()
|
|
439
|
+
http.use(lateMiddleware)
|
|
440
|
+
|
|
441
|
+
// ✓ 正确:在 listen 之前注册
|
|
442
|
+
http.use(earlyMiddleware)
|
|
443
|
+
const close = await http.listen()
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 6.4 不要忘记 export default
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// ✗ 错误:Loader 无法加载,因为没有 default 导出
|
|
450
|
+
export const myController = defineController('GET', () => 'hello')
|
|
451
|
+
|
|
452
|
+
// ✓ 正确
|
|
453
|
+
export default defineController('GET', () => 'hello')
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### 6.5 不要手动写路径参数为 :param 格式
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// ✗ 错误:文件名使用 :param(文件系统不支持冒号)
|
|
460
|
+
// 文件名: users/:id.controller.ts
|
|
461
|
+
|
|
462
|
+
// ✓ 正确:文件名使用 [param]
|
|
463
|
+
// 文件名: users/[id].controller.ts
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### 6.6 不要把 load() 的 await 遗漏
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
// ✗ 错误:load 是异步的,不 await 会导致路由未注册就启动服务
|
|
470
|
+
http.load('./controllers')
|
|
471
|
+
const close = await http.listen()
|
|
472
|
+
|
|
473
|
+
// ✓ 正确
|
|
474
|
+
await http.load('./controllers')
|
|
475
|
+
const close = await http.listen()
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 7. API 速查
|
|
481
|
+
|
|
482
|
+
### Http 类
|
|
483
|
+
|
|
484
|
+
| 方法 | 签名 | 说明 |
|
|
485
|
+
|------|------|------|
|
|
486
|
+
| `constructor` | `(props: HttpProps)` | 创建 Http 服务实例 |
|
|
487
|
+
| `port` | `number` (getter) | 获取端口号 |
|
|
488
|
+
| `use` | `(middleware: Middleware) => this` | 注册全局中间件,支持链式调用 |
|
|
489
|
+
| `listen` | `(onListen?) => Promise<() => void>` | 启动服务,返回关闭函数 |
|
|
490
|
+
| `get` | `(url, ...middlewares) => () => void` | 注册 GET 路由,返回注销函数 |
|
|
491
|
+
| `post` | `(url, ...middlewares) => () => void` | 注册 POST 路由,返回注销函数 |
|
|
492
|
+
| `put` | `(url, ...middlewares) => () => void` | 注册 PUT 路由,返回注销函数 |
|
|
493
|
+
| `delete` | `(url, ...middlewares) => () => void` | 注册 DELETE 路由,返回注销函数 |
|
|
494
|
+
| `trace` | `(url, ...middlewares) => () => void` | 注册 TRACE 路由,返回注销函数 |
|
|
495
|
+
| `route` | `(method, url, ...middlewares) => () => void` | 注册任意方法路由,返回注销函数 |
|
|
496
|
+
| `load` | `(directory, options?) => Promise<() => void>` | 文件系统路由加载,返回注销函数 |
|
|
497
|
+
|
|
498
|
+
### defineController 函数
|
|
499
|
+
|
|
500
|
+
| 调用形式 | 说明 |
|
|
501
|
+
|---------|------|
|
|
502
|
+
| `defineController(method, fn: ControllerFunction)` | 无中间件,直接传控制器函数 |
|
|
503
|
+
| `defineController(method, middlewares: Middleware[], fn: ControllerFunction)` | 有中间件数组 + 控制器函数 |
|
|
504
|
+
|
|
505
|
+
**返回值 `ControllerRegisterProps`(非泛型):**
|
|
506
|
+
|
|
507
|
+
| 字段 | 类型 | 说明 |
|
|
508
|
+
|------|------|------|
|
|
509
|
+
| `id` | `number` | 唯一 ID |
|
|
510
|
+
| `method` | `HTTPMethod` | HTTP 方法 |
|
|
511
|
+
| `middlewares` | `Middleware[]` | 中间件数组(含控制器包装后的尾部中间件) |
|
|
512
|
+
| `data` | `Record<string, any>` | 编译后 `data.url` 为实际路由路径 |
|
|
513
|
+
|
|
514
|
+
### Loader 类
|
|
515
|
+
|
|
516
|
+
| 方法 | 签名 | 说明 |
|
|
517
|
+
|------|------|------|
|
|
518
|
+
| `compile` | `(path, controllers: ControllerRegisterProps \| ControllerRegisterProps[], options?) => () => void` | 手动编译绑定路由 |
|
|
519
|
+
| `from` | `(directory, options?) => Promise<() => void>` | 文件系统批量加载 |
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 8. 内部机制(供理解,不供直接调用)
|
|
524
|
+
|
|
525
|
+
### Http 内部组成
|
|
526
|
+
|
|
527
|
+
| 组件 | 类型 | 说明 |
|
|
528
|
+
|------|------|------|
|
|
529
|
+
| `koa` | `Koa` | Koa 实例,处理中间件管线 |
|
|
530
|
+
| `router` | `Instance` | find-my-way 包装实例,高性能路由匹配 |
|
|
531
|
+
| `loader` | `Loader` | 路由加载器实例 |
|
|
532
|
+
| `server` | `Server?` | Node.js HTTP Server,listen 后创建 |
|
|
533
|
+
|
|
534
|
+
### 路由匹配流程
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
HTTP 请求到达
|
|
538
|
+
│
|
|
539
|
+
├─ Koa 中间件管线(use 注册的全局中间件按顺序执行)
|
|
540
|
+
│
|
|
541
|
+
└─ router.routes()(最后一个中间件)
|
|
542
|
+
│
|
|
543
|
+
├─ find-my-way.find(method, path)
|
|
544
|
+
│ ├─ 匹配成功 → 设置 ctx.params, ctx.store → 执行路由中间件
|
|
545
|
+
│ └─ 匹配失败 → defaultRoute → 调用 next()(返回 404)
|
|
546
|
+
│
|
|
547
|
+
└─ 路由中间件(koa-compose 组合)
|
|
548
|
+
├─ 用户定义的中间件(defineController 的 middlewares 参数)
|
|
549
|
+
└─ 控制器包装中间件(执行 fn(ctx),返回值赋给 ctx.body)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Loader 文件路径转换流程
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
文件: users/[id].controller.ts
|
|
556
|
+
│
|
|
557
|
+
├─ 去除后缀 → users/[id].
|
|
558
|
+
├─ 去除扩展名和点 → users/[id]
|
|
559
|
+
├─ 补充前导斜杠 → /users/[id]
|
|
560
|
+
├─ 检查 defaultSuffix → 不匹配,保持
|
|
561
|
+
├─ 添加 prefix → /api/users/[id]
|
|
562
|
+
└─ 替换参数 → /api/users/:id
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### 控制器包装机制
|
|
566
|
+
|
|
567
|
+
`defineController` 将控制器函数包装为 Koa 中间件并追加到 `middlewares` 末尾:
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
defineController('GET', [mw1, mw2], fn)
|
|
571
|
+
→ middlewares = [mw1, mw2, async (ctx) => {
|
|
572
|
+
const result = await fn(ctx)
|
|
573
|
+
if (result !== undefined) ctx.body = result
|
|
574
|
+
}]
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
控制器函数 `fn` 的返回值类型为 `unknown | Promise<unknown>`:
|
|
578
|
+
- 返回非 `undefined` 值 → 自动设置 `ctx.body`
|
|
579
|
+
- 返回 `undefined` → 不修改 `ctx.body`(用于流、手动设置等场景)
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## 9. 与 hile(core)集成
|
|
584
|
+
|
|
585
|
+
`@hile/http` 通常通过 `hile` 服务容器进行管理:
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import { defineService, loadService } from '@hile/core'
|
|
589
|
+
import { Http } from '@hile/http'
|
|
590
|
+
|
|
591
|
+
export const httpService = defineService(async (shutdown) => {
|
|
592
|
+
const http = new Http({ port: 3000 })
|
|
593
|
+
|
|
594
|
+
http.use(logger)
|
|
595
|
+
await http.load('./src/controllers', { suffix: 'controller', prefix: '/api' })
|
|
596
|
+
|
|
597
|
+
const close = await http.listen(() => {
|
|
598
|
+
console.log(`Server running on port ${http.port}`)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
shutdown(close)
|
|
602
|
+
|
|
603
|
+
return http
|
|
604
|
+
})
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**规则:**
|
|
608
|
+
- 将 `listen()` 返回的 `close` 函数注册为 `shutdown` 回调
|
|
609
|
+
- 通过 `loadService(httpService)` 在其他地方获取 Http 实例
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## 10. 开发
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
pnpm --filter @hile/http install
|
|
617
|
+
pnpm --filter @hile/http build
|
|
618
|
+
pnpm --filter @hile/http test
|
|
619
|
+
pnpm --filter @hile/http dev
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**技术栈:** TypeScript, Koa, find-my-way, Vitest
|
|
623
|
+
|
|
624
|
+
## License
|
|
625
|
+
|
|
626
|
+
MIT
|
package/dist/controller.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HTTPMethod } from 'find-my-way';
|
|
2
2
|
import { Context, Middleware } from 'koa';
|
|
3
|
-
export type ControllerFunction
|
|
4
|
-
export interface ControllerRegisterProps
|
|
3
|
+
export type ControllerFunction = (ctx: Context) => unknown | Promise<unknown>;
|
|
4
|
+
export interface ControllerRegisterProps {
|
|
5
5
|
id: number;
|
|
6
6
|
method: HTTPMethod;
|
|
7
7
|
middlewares: Middleware[];
|
|
@@ -14,4 +14,4 @@ export interface ControllerRegisterProps<R> {
|
|
|
14
14
|
* @param fn 控制器函数
|
|
15
15
|
* @returns 路由控制器注册信息
|
|
16
16
|
*/
|
|
17
|
-
export declare function defineController
|
|
17
|
+
export declare function defineController(method: HTTPMethod, middlewares: Middleware[] | ControllerFunction, fn?: ControllerFunction): ControllerRegisterProps;
|
package/dist/loader.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare class Loader {
|
|
|
17
17
|
* @param options 选项
|
|
18
18
|
* @returns 注销回调
|
|
19
19
|
*/
|
|
20
|
-
compile
|
|
20
|
+
compile(path: string, controllers: ControllerRegisterProps | ControllerRegisterProps[], options?: LoaderCompileOptions): () => void;
|
|
21
21
|
/**
|
|
22
22
|
* 文件夹批量路由绑定编译
|
|
23
23
|
* @param directory 文件夹路径
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/http",
|
|
3
3
|
"description": "Hile http - HTTP service framework",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"test": "vitest run"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"dist"
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"SKILL.md",
|
|
17
|
+
"skill.json"
|
|
15
18
|
],
|
|
16
19
|
"license": "MIT",
|
|
17
20
|
"publishConfig": {
|
|
@@ -27,5 +30,5 @@
|
|
|
27
30
|
"koa": "^3.1.2",
|
|
28
31
|
"koa-compose": "^4.1.0"
|
|
29
32
|
},
|
|
30
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "315d22b9f3c2a21f40938bbefe422d100fca2cee"
|
|
31
34
|
}
|
package/skill.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hile/http",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Hile http - HTTP service framework",
|
|
5
|
+
"repository": "https://github.com/cevio/hile/tree/main/packages/http",
|
|
6
|
+
"skills": [
|
|
7
|
+
{
|
|
8
|
+
"id": "http",
|
|
9
|
+
"name": "Hile HTTP 服务框架优化指南",
|
|
10
|
+
"description": "提供 Hile 开发者使用 HTTP 服务框架开发 Hile 应用的指南",
|
|
11
|
+
"path": "SKILL.md"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"authors": ["evio"],
|
|
15
|
+
"license": "MIT"
|
|
16
|
+
}
|