--- url: 'https://vafast.dev/middleware/api-client.md' --- # API Client 中间件 用于 [Vafast](https://github.com/vafastjs/vafast) 的中间件,提供现代化、类型安全的 API 客户端。 ## ✨ 特性 * 🚀 **专为 Vafast 设计**: 完全兼容 Vafast 框架架构 * 🔒 **类型安全**: 完整的 TypeScript 类型支持 * 🎯 **智能路由**: 自动推断路由类型和方法 * 🔄 **自动重试**: 内置指数退避重试机制 * 📡 **WebSocket 支持**: 完整的 WebSocket 客户端 * 🧩 **中间件系统**: 灵活的请求/响应处理 * 🎛️ **拦截器**: 强大的请求/响应拦截能力 * 📁 **文件上传**: 支持文件和 FormData 上传 * 💾 **缓存系统**: 智能的响应缓存机制 * 📊 **监控统计**: 详细的请求统计和性能监控 ## 安装 通过以下命令安装: ```bash bun add @vafast/api-client ``` ## 基本用法 ### 基础客户端 ```typescript import { VafastApiClient } from '@vafast/api-client' // 创建客户端 const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 10000, retries: 3 }) // 发送请求 const response = await client.get('/users', { page: 1, limit: 10 }) if (response.error) { console.error('Error:', response.error) } else { console.log('Users:', response.data) } ``` ### 类型安全客户端 ```typescript import { createTypedClient } from '@vafast/api-client' import type { Server } from 'vafast' // 从 Vafast 服务器创建类型安全客户端 const typedClient = createTypedClient(server, { baseURL: 'https://api.example.com' }) // 现在有完整的类型检查 const users = await typedClient.get('/users', { page: 1, limit: 10 }) const user = await typedClient.post('/users', { name: 'John', email: 'john@example.com' }) ``` ### WebSocket 客户端 ```typescript import { createWebSocketClient } from '@vafast/api-client' const wsClient = createWebSocketClient('wss://ws.example.com', { autoReconnect: true, maxReconnectAttempts: 5 }) await wsClient.connect() wsClient.on('message', (data) => { console.log('Received:', data) }) wsClient.send({ type: 'chat', message: 'Hello!' }) ``` ## 配置选项 ### ApiClientConfig ```typescript interface ApiClientConfig { baseURL?: string // 基础 URL defaultHeaders?: Record // 默认请求头 timeout?: number // 请求超时时间(毫秒) retries?: number // 重试次数 retryDelay?: number // 重试延迟(毫秒) validateStatus?: (status: number) => boolean // 状态码验证函数 } ``` ## API 参考 ### VafastApiClient 主要的 API 客户端类。 #### 构造函数 ```typescript new VafastApiClient(config?: ApiClientConfig) ``` #### 方法 * `get(path, query?, config?)` - GET 请求 * `post(path, body?, config?)` - POST 请求 * `put(path, body?, config?)` - PUT 请求 * `delete(path, config?)` - DELETE 请求 * `patch(path, body?, config?)` - PATCH 请求 * `head(path, config?)` - HEAD 请求 * `options(path, config?)` - OPTIONS 请求 ### 中间件系统 ```typescript client.addMiddleware({ name: 'logging', onRequest: async (request, config) => { console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`) return request }, onResponse: async (response, config) => { console.log(`Response: ${response.status}`) return response }, onError: async (error, config) => { console.error('Error:', error.message) } }) ``` ### 拦截器系统 ```typescript client.addInterceptor({ request: async (config) => { // 添加认证头 config.headers = { ...config.headers, 'Authorization': 'Bearer token' } return config }, response: async (response) => { // 处理响应 return response }, error: async (error) => { // 处理错误 return error } }) ``` ### WebSocket 客户端 ```typescript const wsClient = createWebSocketClient(url, options) // 连接 await wsClient.connect() // 监听事件 wsClient.on('message', (data) => console.log(data)) wsClient.on('open', () => console.log('Connected')) wsClient.on('close', () => console.log('Disconnected')) // 发送数据 wsClient.send({ type: 'chat', message: 'Hello' }) // 断开连接 wsClient.disconnect() ``` ## 高级用法 ### 文件上传 ```typescript // 单个文件 const response = await client.post('/upload', { file: fileInput.files[0], description: 'User avatar' }) // 多个文件 const response = await client.post('/upload', { files: [file1, file2, file3], category: 'images' }) // 混合数据 const response = await client.post('/upload', { file: fileInput.files[0], metadata: { name: 'avatar.jpg', size: fileInput.files[0].size, type: fileInput.files[0].type } }) ``` ### 路径参数 ```typescript // 使用工具函数替换路径参数 import { replacePathParams } from '@vafast/api-client' const path = '/users/:id/posts/:postId' const params = { id: '123', postId: '456' } const resolvedPath = replacePathParams(path, params) // 结果: '/users/123/posts/456' const response = await client.get(resolvedPath) ``` ### 查询参数构建 ```typescript import { buildQueryString } from '@vafast/api-client' const query = { page: 1, limit: 10, search: 'john' } const queryString = buildQueryString(query) // 结果: '?page=1&limit=10&search=john' const response = await client.get(`/users${queryString}`) ``` ### 缓存配置 ```typescript client.setCacheConfig({ enabled: true, ttl: 300000, // 5分钟 maxSize: 100, strategy: 'memory' }) ``` ### 重试配置 ```typescript client.setRetryConfig({ enabled: true, maxRetries: 5, retryDelay: 1000, backoffMultiplier: 2, retryableStatuses: [408, 429, 500, 502, 503, 504] }) ``` ## 完整示例 ```typescript import { VafastApiClient, createTypedClient, createWebSocketClient } from '@vafast/api-client' // 创建基础客户端 const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 10000, retries: 3 }) // 添加认证中间件 client.addMiddleware({ name: 'auth', onRequest: async (request, config) => { const token = localStorage.getItem('auth_token') if (token) { request.headers.set('Authorization', `Bearer ${token}`) } return request } }) // 添加日志拦截器 client.addInterceptor({ request: async (config) => { console.log(`[${new Date().toISOString()}] ${config.method} ${config.url}`) return config }, response: async (response) => { console.log(`Response: ${response.status} ${response.statusText}`) return response } }) // 使用客户端 async function fetchUsers() { const response = await client.get('/users', { page: 1, limit: 10 }) if (response.error) { console.error('Failed to fetch users:', response.error) return [] } return response.data } async function createUser(userData: { name: string; email: string }) { const response = await client.post('/users', userData) if (response.error) { throw new Error(`Failed to create user: ${response.error.message}`) } return response.data } // WebSocket 客户端 const wsClient = createWebSocketClient('wss://ws.example.com', { autoReconnect: true, maxReconnectAttempts: 5 }) wsClient.on('message', (data) => { console.log('WebSocket message:', data) }) // 连接并发送消息 await wsClient.connect() wsClient.send({ type: 'join', room: 'general' }) ``` ## 测试 ```bash bun test ``` ## 相关链接 * [GitHub 仓库](https://github.com/vafastjs/vafast-api-client) * [问题反馈](https://github.com/vafastjs/vafast-api-client/issues) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/api.md' --- # API 参考 本文档提供了 Vafast 框架的完整 API 参考。所有类型定义和接口都基于 TypeScript,确保类型安全。 ## 核心类 ### Server `Server` 是 Vafast 的核心类,用于创建 HTTP 服务器。 ```typescript import { Server } from 'vafast' const server = new Server(routes) export default { fetch: server.fetch } ``` #### 构造函数 ```typescript new Server(routes: Route[], options?: ServerOptions) ``` **参数:** * `routes`: 路由配置数组 * `options`: 可选的服务器配置 #### 方法 ##### `fetch(request: Request): Promise` 处理 HTTP 请求并返回响应。 ```typescript const server = new Server(routes) const response = await server.fetch(new Request('http://localhost:3000/api/users')) ``` ### ComponentServer `ComponentServer` 用于创建支持组件路由的服务器。 ```typescript import { ComponentServer } from 'vafast' const server = new ComponentServer(routes) export default { fetch: server.fetch } ``` #### 构造函数 ```typescript new ComponentServer(routes: ComponentRoute[], options?: ServerOptions) ``` **参数:** * `routes`: 组件路由配置数组 * `options`: 可选的服务器配置 ## 类型定义 ### Route 基本路由配置接口。 ```typescript interface Route { method: HTTPMethod path: string handler: RouteHandler middleware?: Middleware[] body?: any query?: any params?: any headers?: any cookies?: any docs?: RouteDocs timeout?: number maxBodySize?: string [key: string]: any } ``` **属性:** * `method`: HTTP 方法(GET、POST、PUT、DELETE、PATCH、OPTIONS、HEAD) * `path`: 路由路径 * `handler`: 路由处理函数 * `middleware`: 中间件数组 * `body`: 请求体验证配置 * `query`: 查询参数验证配置 * `params`: 路径参数验证配置 * `headers`: 请求头验证配置 * `cookies`: Cookie 验证配置 * `docs`: API 文档配置 * `timeout`: 请求超时时间 * `maxBodySize`: 最大请求体大小 ### ComponentRoute 组件路由配置接口。 ```typescript interface ComponentRoute { path: string component: () => Promise middleware?: Middleware[] children?: (ComponentRoute | NestedComponentRoute)[] } ``` **属性:** * `path`: 路由路径 * `component`: 组件导入函数 * `middleware`: 中间件数组 * `children`: 子路由配置 ### NestedRoute 嵌套路由配置接口。 ```typescript interface NestedRoute { path: string middleware?: Middleware[] children?: (Route | NestedRoute)[] } ``` **属性:** * `path`: 路由路径 * `middleware`: 中间件数组 * `children`: 子路由配置 ### Middleware 中间件函数类型。 ```typescript type Middleware = ( req: Request, next: () => Promise ) => Promise ``` **参数:** * `req`: HTTP 请求对象 * `next`: 下一个中间件或路由处理函数 **返回值:** HTTP 响应对象 ### RouteHandler 路由处理函数类型。 ```typescript type RouteHandler = ( req: Request, params?: Record ) => Response | Promise ``` **参数:** * `req`: HTTP 请求对象 * `params`: 路径参数(可选) **返回值:** HTTP 响应对象或 Promise ### HTTPMethod 支持的 HTTP 方法类型。 ```typescript type HTTPMethod = | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD' ``` ### RouteDocs API 文档配置接口。 ```typescript interface RouteDocs { description?: string tags?: string[] security?: any[] responses?: Record } ``` **属性:** * `description`: 路由描述 * `tags`: 标签数组 * `security`: 安全配置 * `responses`: 响应配置 ## 服务器配置 ### ServerOptions 服务器配置选项。 ```typescript interface ServerOptions { port?: number host?: string cors?: CorsOptions compression?: boolean trustProxy?: boolean [key: string]: any } ``` **属性:** * `port`: 服务器端口 * `host`: 服务器主机 * `cors`: CORS 配置 * `compression`: 是否启用压缩 * `trustProxy`: 是否信任代理 ### CorsOptions CORS 配置选项。 ```typescript interface CorsOptions { origin?: string | string[] | boolean methods?: string[] allowedHeaders?: string[] credentials?: boolean maxAge?: number } ``` **属性:** * `origin`: 允许的源 * `methods`: 允许的 HTTP 方法 * `allowedHeaders`: 允许的请求头 * `credentials`: 是否允许凭据 * `maxAge`: 预检请求缓存时间 ## 中间件类型 ### 内置中间件 Vafast 提供了一些内置中间件: #### authMiddleware 身份验证中间件。 ```typescript import { authMiddleware } from 'vafast' const routes: any[] = [ { method: 'GET', path: '/admin', middleware: [authMiddleware], handler: () => 'Admin panel' } ] ``` #### corsMiddleware CORS 中间件。 ```typescript import { corsMiddleware } from 'vafast' const routes: any[] = [ { path: '/api', middleware: [corsMiddleware], children: [ // API 路由 ] } ] ``` #### rateLimitMiddleware 速率限制中间件。 ```typescript import { rateLimitMiddleware } from 'vafast' const routes: any[] = [ { method: 'POST', path: '/login', middleware: [rateLimitMiddleware], handler: () => 'Login' } ] ``` ## 工具函数 ### defineRoute 用于定义类型安全的路由。 ```typescript import { defineRoute } from 'vafast' const userRoute = defineRoute({ method: 'GET', path: '/users/:id', handler: (req, params) => `User ${params?.id}` }) ``` ### createMiddleware 用于创建可配置的中间件。 ```typescript import { createMiddleware } from 'vafast' const logMiddleware = createMiddleware({ level: 'info', format: 'json' }) ``` ## 错误处理 ### 内置错误类型 ```typescript class VafastError extends Error { constructor( message: string, public status: number = 500, public code?: string ) { super(message) this.name = 'VafastError' } } ``` ### 错误响应 ```typescript const routes: any[] = [ { method: 'GET', path: '/error', handler: () => { throw new VafastError('Something went wrong', 500, 'INTERNAL_ERROR') } } ] ``` ## 验证配置 ### 请求体验证 ```typescript const routes: any[] = [ { method: 'POST', path: '/users', body: { type: 'object', properties: { name: { type: 'string', minLength: 2 }, email: { type: 'string', format: 'email' }, age: { type: 'number', minimum: 18 } }, required: ['name', 'email'] }, handler: async (req) => { const body = await req.json() // body 已经通过验证 return new Response('User created', { status: 201 }) } } ] ``` ### 查询参数验证 ```typescript const routes: any[] = [ { method: 'GET', path: '/users', query: { type: 'object', properties: { page: { type: 'number', minimum: 1 }, limit: { type: 'number', minimum: 1, maximum: 100 }, sort: { type: 'string', enum: ['name', 'email', 'created_at'] } } }, handler: (req) => { const url = new URL(req.url) const page = url.searchParams.get('page') const limit = url.searchParams.get('limit') const sort = url.searchParams.get('sort') // 参数已经通过验证 return `Page: ${page}, Limit: ${limit}, Sort: ${sort}` } } ] ``` ## 生命周期钩子 ### 服务器生命周期 ```typescript const server = new Server(routes) // 启动前 server.on('beforeStart', () => { console.log('Server starting...') }) // 启动后 server.on('afterStart', () => { console.log('Server started') }) // 关闭前 server.on('beforeClose', () => { console.log('Server closing...') }) // 关闭后 server.on('afterClose', () => { console.log('Server closed') }) ``` ## 性能优化 ### 路由缓存 Vafast 自动缓存路由匹配结果以提高性能: ```typescript const routes: any[] = [ { method: 'GET', path: '/users/:id', handler: (req, params) => `User ${params?.id}`, cache: { ttl: 300, // 5 分钟缓存 key: (req, params) => `user:${params?.id}` } } ] ``` ### 中间件优化 ```typescript // 使用条件中间件避免不必要的执行 const conditionalMiddleware = (condition: (req: Request) => boolean, middleware: Middleware) => { return async (req: Request, next: () => Promise) => { if (condition(req)) { return middleware(req, next) } return next() } } const routes: any[] = [ { method: 'GET', path: '/admin', middleware: [ conditionalMiddleware( (req) => req.url.includes('/admin'), authMiddleware ) ], handler: () => 'Admin panel' } ] ``` ## 部署配置 ### 生产环境配置 ```typescript const productionConfig: ServerOptions = { port: process.env.PORT || 3000, host: '0.0.0.0', cors: { origin: ['https://yourdomain.com'], credentials: true }, compression: true, trustProxy: true } const server = new Server(routes, productionConfig) ``` ### 环境变量 ```typescript const config: ServerOptions = { port: parseInt(process.env.PORT || '3000'), host: process.env.HOST || 'localhost', cors: { origin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'] } } ``` ## 测试 ### 单元测试 ```typescript import { test, expect } from 'bun:test' import { Server } from 'vafast' test('GET /users returns users list', async () => { const routes: any[] = [ { method: 'GET', path: '/users', handler: () => new Response(JSON.stringify(['user1', 'user2'])) } ] const server = new Server(routes) const response = await server.fetch(new Request('http://localhost:3000/users')) const data = await response.json() expect(response.status).toBe(200) expect(data).toEqual(['user1', 'user2']) }) ``` ### 集成测试 ```typescript test('POST /users creates new user', async () => { const routes: any[] = [ { method: 'POST', path: '/users', handler: async (req) => { const body = await req.json() return new Response(JSON.stringify({ id: 1, ...body }), { status: 201 }) } } ] const server = new Server(routes) const response = await server.fetch( new Request('http://localhost:3000/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John', email: 'john@example.com' }) }) ) const data = await response.json() expect(response.status).toBe(201) expect(data.name).toBe('John') expect(data.email).toBe('john@example.com') expect(data.id).toBe(1) }) ``` ## 总结 Vafast 提供了完整的 API 参考,包括: * ✅ 核心类和接口 * ✅ 类型定义和类型安全 * ✅ 中间件系统 * ✅ 验证配置 * ✅ 生命周期钩子 * ✅ 性能优化 * ✅ 部署配置 * ✅ 测试支持 ### 下一步 * 查看 [路由指南](/routing) 了解路由系统 * 学习 [中间件系统](/middleware) 了解中间件用法 * 探索 [组件路由](/component-routing) 了解组件路由功能 * 查看 [最佳实践](/essential/best-practice) 获取开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/api-client/overview.md' --- # API 客户端概述 Vafast API 客户端是一个专门为 Vafast 框架打造的现代化、类型安全的 API 客户端中间件。它提供了完整的 HTTP 和 WebSocket 支持,让您能够轻松地与各种 API 服务进行交互。 ## ✨ 核心特性 ### 🚀 专为 Vafast 设计 * 完全兼容 Vafast 框架架构 * 与 Vafast 的类型系统无缝集成 * 支持 Vafast 的中间件和验证系统 ### 🔒 类型安全 * 完整的 TypeScript 类型支持 * 自动类型推断和检查 * 编译时错误检测 ### 🎯 智能路由 * 自动推断路由类型和方法 * 支持动态路径参数 * 智能的查询参数处理 ### 🔄 自动重试 * 内置指数退避重试机制 * 可配置的重试策略 * 智能的错误处理 ### 📡 WebSocket 支持 * 完整的 WebSocket 客户端 * 自动重连机制 * 事件驱动的消息处理 ### 🧩 中间件系统 * 灵活的请求/响应处理 * 可组合的中间件链 * 支持异步中间件 ### 🎛️ 拦截器 * 强大的请求/响应拦截能力 * 支持请求和响应转换 * 错误处理和日志记录 ## 🏗️ 架构设计 Vafast API 客户端采用模块化设计,主要包含以下核心组件: ``` VafastApiClient ├── HTTP 客户端 │ ├── 请求处理器 │ ├── 响应处理器 │ ├── 错误处理器 │ └── 重试机制 ├── WebSocket 客户端 │ ├── 连接管理 │ ├── 消息处理 │ └── 重连机制 ├── 中间件系统 │ ├── 请求中间件 │ ├── 响应中间件 │ └── 错误中间件 └── 类型系统 ├── 类型推断 ├── 验证器 └── 类型检查 ``` ## 🔧 核心概念 ### 客户端实例 每个 `VafastApiClient` 实例代表一个独立的 API 客户端,可以配置不同的基础 URL、超时时间、重试策略等。 ### 中间件 中间件是处理请求和响应的函数,可以用于添加认证头、记录日志、处理错误等。中间件按照添加顺序依次执行。 ### 拦截器 拦截器允许您在请求发送前和响应接收后执行自定义逻辑,比如添加认证信息、转换数据格式等。 ### 类型安全 通过 TypeScript 和 Vafast 的类型系统,API 客户端提供完整的类型检查,确保请求和响应的类型安全。 ## 📱 使用场景 ### 前端应用 * 与后端 API 交互 * 处理用户认证 * 管理应用状态 ### 后端服务 * 微服务间通信 * 第三方 API 集成 * 数据同步 ### 移动应用 * 与服务器通信 * 实时数据更新 * 离线数据同步 ### 桌面应用 * 本地服务调用 * 远程 API 访问 * 数据备份和同步 ## 🚀 快速开始 ### 安装 ```bash bun add @vafast/api-client ``` ### 基础用法 ```typescript import { VafastApiClient } from '@vafast/api-client' // 创建客户端 const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 10000, retries: 3 }) // 发送请求 const response = await client.get('/users', { page: 1, limit: 10 }) if (response.error) { console.error('Error:', response.error) } else { console.log('Users:', response.data) } ``` ### 类型安全客户端 ```typescript import { createTypedClient } from '@vafast/api-client' import type { Server } from 'vafast' // 从 Vafast 服务器创建类型安全客户端 const typedClient = createTypedClient(server, { baseURL: 'https://api.example.com' }) // 现在有完整的类型检查 const users = await typedClient.get('/users', { page: 1, limit: 10 }) const user = await typedClient.post('/users', { name: 'John', email: 'john@example.com' }) ``` ## 🔗 相关链接 * [安装指南](/api-client/installation) - 了解如何安装和配置 * [基础用法](/api-client/fetch) - 学习基本的 HTTP 请求 * [类型安全](/api-client/treaty/overview) - 探索类型安全特性 * [WebSocket 支持](/api-client/treaty/websocket) - 了解实时通信 * [配置选项](/api-client/treaty/config) - 查看所有配置选项 * [单元测试](/api-client/treaty/unit-test) - 学习如何测试 ## 📚 下一步 现在您已经了解了 Vafast API 客户端的基本概念和特性,接下来可以: 1. **安装和配置** - 按照安装指南设置您的项目 2. **学习基础用法** - 掌握基本的 HTTP 请求方法 3. **探索高级特性** - 了解中间件、拦截器等高级功能 4. **构建类型安全应用** - 利用 TypeScript 和 Vafast 的类型系统 如果您有任何问题或需要帮助,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/middleware/graphql-apollo.md' --- # GraphQL Apollo 中间件 用于 [vafast](https://github.com/vafastjs/vafast) 的中间件,可以使用 GraphQL Apollo。 使用以下命令安装: ```bash bun add graphql @vafastjs/apollo @apollo/server ``` 然后使用它: ```typescript import { Vafast } from 'vafast' import { apollo, gql } from '@vafastjs/apollo' const app = new Vafast() .use( apollo({ typeDefs: gql` type Book { title: String author: String } type Query { books: [Book] } `, resolvers: { Query: { books: () => { return [ { title: 'Vafast', author: 'saltyAom' } ] } } } }) ) .listen(3000) ``` 访问 `/graphql` 应该会显示 Apollo GraphQL playground 工作情况。 ## 背景 由于 Vafast 基于 Web 标准请求和响应,这与 Express 使用的 Node 的 `HttpRequest` 和 `HttpResponse` 不同,导致 `req, res` 在上下文中为未定义。 因此,Vafast 用 `context` 替代两者,类似于路由参数。 ```typescript const app = new Vafast() .use( apollo({ typeDefs, resolvers, context: async ({ request }) => { const authorization = request.headers.get('Authorization') return { authorization } } }) ) .listen(3000) ``` ## 配置 该中间件扩展了 Apollo 的 [ServerRegistration](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#options)(即 `ApolloServer` 的构造参数)。 以下是用于使用 Vafast 配置 Apollo Server 的扩展参数。 ### path @default `"/graphql"` 暴露 Apollo Server 的路径。 ### enablePlayground @default `process.env.ENV !== 'production'` 确定 Apollo 是否应提供 Apollo Playground。 --- --- url: 'https://vafast.dev/integrations/astro.md' --- # Astro 集成 Vafast 可以与 Astro 无缝集成,为您提供强大的后端 API 和现代化的前端开发体验。 ## 项目结构 ``` my-vafast-astro-app/ ├── src/ │ ├── pages/ # Astro 页面 │ ├── components/ # Astro 组件 │ ├── layouts/ # Astro 布局 │ ├── api/ # Vafast API 路由 │ │ ├── routes.ts # 路由定义 │ │ ├── server.ts # Vafast 服务器 │ │ └── types.ts # 类型定义 │ └── lib/ # 共享库 ├── package.json ├── astro.config.mjs └── tsconfig.json ``` ## 安装依赖 ```bash bun add vafast @vafast/cors @vafast/helmet bun add -D @types/node ``` ## 创建 Vafast API 服务器 ```typescript // src/api/server.ts import { defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' import { routes } from './routes' export const app = createHandler(routes) .use(cors({ origin: process.env.NODE_ENV === 'development' ? ['http://localhost:4321'] : [process.env.PUBLIC_APP_URL], credentials: true })) .use(helmet()) export const handler = app.handler ``` ## 定义 API 路由 ```typescript // src/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' export const routes = defineRoutes([ { method: 'GET', path: '/api/posts', handler: createHandler(async () => { // 模拟数据库查询 const posts = [ { id: 1, title: 'First Post', content: 'Hello World!' }, { id: 2, title: 'Second Post', content: 'Another post' } ] return { posts } }) }, { method: 'POST', path: '/api/posts', handler: createHandler(async ({ body }) => { // 创建新文章 const newPost = { id: Date.now(), ...body, createdAt: new Date().toISOString() } return { post: newPost }, { status: 201 } }), body: Type.Object({ title: Type.String({ minLength: 1 }), content: Type.String({ minLength: 1 }) }) }, { method: 'GET', path: '/api/posts/:id', handler: createHandler(async ({ params }) => { const postId = parseInt(params.id) // 模拟数据库查询 const post = { id: postId, title: 'Sample Post', content: 'Sample content' } if (!post) { return { error: 'Post not found' }, { status: 404 } } return { post } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }) } ]) ``` ## 创建 API 端点 ```typescript // src/pages/api/[...path].ts import type { APIRoute } from 'astro' import { handler } from '../../api/server' export const GET: APIRoute = async ({ request }) => { return handler(request) } export const POST: APIRoute = async ({ request }) => { return handler(request) } export const PUT: APIRoute = async ({ request }) => { return handler(request) } export const DELETE: APIRoute = async ({ request }) => { return handler(request) } export const PATCH: APIRoute = async ({ request }) => { return handler(request) } ``` ## 类型定义 ```typescript // src/api/types.ts import { Type } from '@sinclair/typebox' export const PostSchema = Type.Object({ id: Type.Number(), title: Type.String(), content: Type.String(), createdAt: Type.String({ format: 'date-time' }) }) export const CreatePostSchema = Type.Object({ title: Type.String({ minLength: 1 }), content: Type.String({ minLength: 1 }) }) export type Post = typeof PostSchema.T export type CreatePost = typeof CreatePostSchema.T ``` ## 前端集成 ### 使用 API 端点 ```astro --- // src/pages/posts.astro import Layout from '../layouts/Layout.astro' // 获取文章列表 const response = await fetch(`${import.meta.env.SITE}/api/posts`) const data = await response.json() const posts = data.posts ---

Blog Posts

{posts.map((post: Post) => (

{post.title}

{post.content}

))}
``` ### 创建文章表单 ```astro --- // src/pages/posts/create.astro import Layout from '../../layouts/Layout.astro ---

Create New Post

``` ## 中间件集成 ### 认证中间件 ```typescript // src/api/middleware/auth.ts export interface AuthenticatedRequest extends Request { user?: { id: string email: string role: string } } export const authMiddleware = async ( request: Request, next: () => Promise ) => { const token = request.headers.get('authorization')?.replace('Bearer ', '') if (!token) { return new Response('Unauthorized', { status: 401 }) } try { // 验证 JWT token const user = await verifyToken(token) ;(request as AuthenticatedRequest).user = user return next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } async function verifyToken(token: string) { // 实现 JWT 验证逻辑 // 这里应该使用 @vafast/jwt 中间件 return { id: '123', email: 'user@example.com', role: 'user' } } ``` ### 使用认证中间件 ```typescript // src/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { authMiddleware } from './middleware/auth' export const routes = defineRoutes([ { method: 'GET', path: '/api/profile', handler: createHandler(async ({ request }) => { const user = (request as AuthenticatedRequest).user return { user } }), middleware: [authMiddleware] } ]) ``` ## Astro 配置 ```typescript // astro.config.mjs import { defineConfig } from 'astro/config' export default defineConfig({ output: 'server', adapter: 'node', vite: { ssr: { external: ['vafast'] } }, server: { port: 4321, host: true } }) ``` ## 环境配置 ```typescript // src/api/config.ts export const config = { development: { cors: { origin: ['http://localhost:4321', 'http://localhost:3000'] }, logging: true }, production: { cors: { origin: [process.env.PUBLIC_APP_URL] }, logging: false } } export const getConfig = () => { const env = import.meta.env.MODE || 'development' return config[env as keyof typeof config] } ``` ## 测试 ### API 测试 ```typescript // src/api/__tests__/posts.test.ts import { describe, expect, it } from 'bun:test' import { handler } from '../server' describe('Posts API', () => { it('should get posts', async () => { const request = new Request('http://localhost/api/posts') const response = await handler(request) const data = await response.json() expect(response.status).toBe(200) expect(data.posts).toBeDefined() expect(Array.isArray(data.posts)).toBe(true) }) it('should create post', async () => { const postData = { title: 'Test Post', content: 'Test content' } const request = new Request('http://localhost/api/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(postData) }) const response = await handler(request) const data = await response.json() expect(response.status).toBe(201) expect(data.post.title).toBe(postData.title) expect(data.post.content).toBe(postData.content) }) }) ``` ## 部署 ### Node.js 部署 ```typescript // dist/server/entry.mjs import { handler } from './api/server.js' import { createServer } from 'http' const server = createServer(async (req, res) => { try { const response = await handler(req) // 复制响应头 for (const [key, value] of response.headers.entries()) { res.setHeader(key, value) } res.statusCode = response.status res.end(await response.text()) } catch (error) { console.error('Server error:', error) res.statusCode = 500 res.end('Internal Server Error') } }) const port = process.env.PORT || 3000 server.listen(port, () => { console.log(`Server running on port ${port}`) }) ``` ### Docker 部署 ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --production COPY . . RUN bun run build EXPOSE 3000 CMD ["bun", "run", "start"] ``` ## 最佳实践 1. **类型安全**:使用 TypeBox 确保前后端类型一致 2. **错误处理**:实现统一的错误处理机制 3. **中间件顺序**:注意中间件的执行顺序 4. **环境配置**:根据环境配置不同的设置 5. **测试覆盖**:为 API 路由编写完整的测试 6. **性能优化**:使用适当的缓存和压缩策略 7. **SSR 优化**:利用 Astro 的 SSR 能力优化性能 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [Astro 文档](https://docs.astro.build) - Astro 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/middleware/bearer.md' --- # Bearer 中间件 用于 [Vafast](https://github.com/vafastjs/vafast) 的中间件,用于获取 Bearer 令牌。 ## 安装 通过以下命令安装: ```bash bun add @vafast/bearer ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { bearer, createTypedHandler } from '@vafast/bearer' // 定义路由处理器 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Bearer Token API' } }) }, { method: 'GET', path: '/sign', handler: createTypedHandler({}, ({ bearer }) => { // 访问 bearer 令牌,具有完整的类型安全 if (!bearer) { return { error: 'Unauthorized', message: 'Bearer token required' } } return { token: bearer } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用 bearer 中间件 export default { fetch: (req: Request) => { // 应用 bearer 中间件 return bearer()(req, () => server.fetch(req)) } } ``` ## 配置选项 ### BearerOptions ```typescript interface BearerOptions { extract: { /** * 确定从请求体中提取令牌的字段名 * @default 'access_token' */ body?: string /** * 确定从查询参数中提取令牌的字段名 * @default 'access_token' */ query?: string /** * 确定哪种类型的认证应该是 Bearer 令牌 * @default 'Bearer' */ header?: string } } ``` ## 令牌提取策略 该中间件按照以下优先级从请求中提取 Bearer 令牌: 1. **Authorization 头部** - 默认格式:`Bearer ` 2. **查询参数** - 默认参数名:`access_token` 3. **请求体** - 默认字段名:`access_token`(仅非 GET 请求) ## 使用模式 ### 1. 基本认证检查 ```typescript import { bearer, createTypedHandler } from '@vafast/bearer' const routes = [ { method: 'GET', path: '/protected', handler: createTypedHandler({}, ({ bearer }) => { if (!bearer) { return { status: 401, error: 'Unauthorized', message: 'Bearer token required' } } // 验证令牌逻辑 if (!isValidToken(bearer)) { return { status: 401, error: 'Unauthorized', message: 'Invalid token' } } return { message: 'Access granted', user: getUserFromToken(bearer) } }) } ] ``` ### 2. 自定义令牌提取 ```typescript import { bearer, createTypedHandler } from '@vafast/bearer' // 自定义令牌提取配置 const customBearer = bearer({ extract: { body: 'token', // 从请求体的 'token' 字段提取 query: 'auth_token', // 从查询参数的 'auth_token' 提取 header: 'Token' // 从 'Token' 头部提取 } }) const routes = [ { method: 'POST', path: '/login', handler: createTypedHandler({}, ({ bearer }) => { // 现在可以从自定义字段中获取令牌 return { receivedToken: bearer } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return customBearer(req, () => server.fetch(req)) } } ``` ### 3. 中间件链式应用 ```typescript import { bearer, createTypedHandler } from '@vafast/bearer' import { cors } from '@vafast/cors' const routes = [ { method: 'GET', path: '/api/user', handler: createTypedHandler({}, ({ bearer }) => { return { user: getUserProfile(bearer) } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { // 应用多个中间件 return cors()(req, () => { return bearer()(req, () => server.fetch(req)) }) } } ``` ### 4. 条件中间件应用 ```typescript import { bearer, createTypedHandler } from '@vafast/bearer' const routes = [ { method: 'GET', path: '/public', handler: createHandler(() => { return { message: 'Public endpoint' } }) }, { method: 'GET', path: '/private', handler: createTypedHandler({}, ({ bearer }) => { return { message: 'Private endpoint', token: bearer } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { const url = new URL(req.url) // 只为私有端点应用 bearer 中间件 if (url.pathname.startsWith('/private')) { return bearer()(req, () => server.fetch(req)) } return server.fetch(req) } } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { bearer, createTypedHandler } from '@vafast/bearer' // 模拟用户验证函数 const validateToken = (token: string): boolean => { return token === 'valid-token-123' } const getUserFromToken = (token: string) => { return { id: 1, username: 'john_doe', email: 'john@example.com' } } // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Bearer Token Authentication API' } }) }, { method: 'POST', path: '/login', handler: createHandler(async (req: Request) => { const body = await req.json() const { username, password } = body // 简单的登录逻辑 if (username === 'admin' && password === 'password') { return { message: 'Login successful', token: 'valid-token-123' } } return { status: 401, error: 'Invalid credentials' } }) }, { method: 'GET', path: '/profile', handler: createTypedHandler({}, ({ bearer }) => { if (!bearer) { return { status: 401, error: 'Unauthorized', message: 'Bearer token required' } } if (!validateToken(bearer)) { return { status: 401, error: 'Unauthorized', message: 'Invalid token' } } const user = getUserFromToken(bearer) return { message: 'Profile retrieved successfully', user } }) }, { method: 'GET', path: '/admin', handler: createTypedHandler({}, ({ bearer }) => { if (!bearer) { return { status: 401, error: 'Unauthorized' } } // 检查管理员权限 if (bearer !== 'admin-token-456') { return { status: 403, error: 'Forbidden', message: 'Admin access required' } } return { message: 'Admin panel accessed', adminData: { users: 100, systemStatus: 'healthy' } } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用 bearer 中间件 export default { fetch: (req: Request) => { return bearer()(req, () => server.fetch(req)) } } console.log('🚀 Bearer Token API 服务器启动成功!') console.log('📝 登录端点: POST /login') console.log('👤 个人资料: GET /profile (需要 Bearer 令牌)') console.log('🔒 管理面板: GET /admin (需要管理员令牌)') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' describe('Bearer Token API', () => { it('should require bearer token for protected routes', async () => { const res = await app.fetch(new Request('http://localhost/profile')) const data = await res.json() expect(res.status).toBe(401) expect(data.error).toBe('Unauthorized') }) it('should accept valid bearer token', async () => { const res = await app.fetch(new Request('http://localhost/profile', { headers: { 'Authorization': 'Bearer valid-token-123' } })) const data = await res.json() expect(res.status).toBe(200) expect(data.message).toBe('Profile retrieved successfully') expect(data.user).toBeDefined() }) it('should extract token from query parameters', async () => { const res = await app.fetch(new Request('http://localhost/profile?access_token=valid-token-123')) const data = await res.json() expect(res.status).toBe(200) expect(data.user).toBeDefined() }) }) ``` ## 特性 * ✅ **RFC6750 兼容**: 完全符合 Bearer 令牌规范 * ✅ **多种提取方式**: 支持头部、查询参数和请求体提取 * ✅ **类型安全**: 使用 `createTypedHandler` 提供完整的类型安全 * ✅ **灵活配置**: 可自定义令牌提取字段和头部名称 * ✅ **中间件集成**: 无缝集成到 Vafast 应用 * ✅ **高性能**: 轻量级实现,最小化性能开销 ## 注意事项 1. **令牌验证**: 该中间件只负责提取令牌,不处理验证逻辑。开发者需要自己实现令牌验证。 2. **安全性**: 在生产环境中,确保使用 HTTPS 传输令牌,并实现适当的令牌过期和刷新机制。 3. **错误处理**: 建议在令牌无效时返回适当的 HTTP 状态码和错误信息。 4. **中间件顺序**: Bearer 中间件应该在路由处理之前应用,以确保令牌在处理器中可用。 ## 相关链接 * [RFC6750 - OAuth 2.0 Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750) * [Vafast 官方文档](https://vafast.dev) * [Bearer 认证最佳实践](https://oauth.net/2/bearer-tokens/) --- --- url: 'https://vafast.dev/integrations/better-auth.md' --- # Better Auth 集成 Better Auth 是一个现代化的身份验证库,专为现代 Web 应用设计。它提供了一整套全面的功能,并包括一个中间件生态系统,可以简化添加高级功能。 ## 安装 ```bash bun add better-auth ``` ## 基本设置 首先,创建一个 Better Auth 配置文件: ```typescript // src/auth/config.ts import { BetterAuth } from 'better-auth' import { VafastAdapter } from 'better-auth/adapters/vafast' export const auth = new BetterAuth({ adapter: VafastAdapter({ // 数据库配置 database: { url: process.env.DATABASE_URL, type: 'postgresql' }, // 会话配置 session: { secret: process.env.SESSION_SECRET, expiresIn: 60 * 60 * 24 * 7, // 7天 updateAge: 60 * 60 * 24 // 1天 }, // 认证配置 auth: { providers: ['credentials', 'oauth'], pages: { signIn: '/auth/signin', signUp: '/auth/signup', error: '/auth/error' } } }) }) ``` ## 在 Vafast 中使用 ```typescript // src/index.ts import { defineRoutes, createHandler } from 'vafast' import { auth } from './auth/config' import { authMiddleware } from './auth/middleware' const routes = defineRoutes([ { method: 'GET', path: '/api/user', handler: createHandler(async ({ request }) => { const session = await auth.api.getSession(request) if (!session) { return { error: 'Unauthorized' }, { status: 401 } } return { user: session.user } }), middleware: [authMiddleware] }, { method: 'POST', path: '/api/auth/signin', handler: createHandler(async ({ body, request }) => { const result = await auth.api.signIn('credentials', { email: body.email, password: body.password, request }) if (result.error) { return { error: result.error }, { status: 400 } } return { success: true, user: result.user } }), body: Type.Object({ email: Type.String({ format: 'email' }), password: Type.String({ minLength: 6 }) }) } ]) const app = createHandler(routes) .use(authMiddleware) ``` ## 认证中间件 创建认证中间件来保护路由: ```typescript // src/auth/middleware.ts import { auth } from './config' export const authMiddleware = async (request: Request, next: () => Promise) => { const session = await auth.api.getSession(request) if (!session) { return new Response('Unauthorized', { status: 401 }) } // 将用户信息添加到请求上下文 request.user = session.user return next() } export const requireAuth = (handler: Function) => { return async (request: Request) => { const session = await auth.api.getSession(request) if (!session) { return { error: 'Authentication required' }, { status: 401 } } // 将用户信息添加到请求上下文 request.user = session.user return handler(request) } } ``` ## 路由保护 使用中间件保护需要认证的路由: ```typescript import { defineRoutes, createHandler } from 'vafast' import { requireAuth } from './auth/middleware' const routes = defineRoutes([ { method: 'GET', path: '/api/profile', handler: requireAuth(createHandler(({ request }) => { // request.user 现在可用 return { profile: request.user } })) }, { method: 'PUT', path: '/api/profile', handler: requireAuth(createHandler(async ({ body, request }) => { const updatedProfile = await updateProfile(request.user.id, body) return { profile: updatedProfile } })), body: Type.Object({ name: Type.Optional(Type.String()), bio: Type.Optional(Type.String()) }) } ]) ``` ## OAuth 集成 配置 OAuth 提供商: ```typescript // src/auth/config.ts import { BetterAuth } from 'better-auth' import { VafastAdapter } from 'better-auth/adapters/vafast' import { GoogleProvider } from 'better-auth/providers/google' import { GitHubProvider } from 'better-auth/providers/github' export const auth = new BetterAuth({ adapter: VafastAdapter({ // ... 其他配置 providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET }), GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET }) ] }) }) ``` ## 会话管理 ```typescript import { defineRoutes, createHandler } from 'vafast' import { auth } from './auth/config' const routes = defineRoutes([ { method: 'POST', path: '/api/auth/signout', handler: createHandler(async ({ request }) => { await auth.api.signOut(request) return { success: true } }) }, { method: 'GET', path: '/api/auth/session', handler: createHandler(async ({ request }) => { const session = await auth.api.getSession(request) return { session } }) } ]) ``` ## 角色和权限 Better Auth 支持基于角色的访问控制: ```typescript // src/auth/config.ts export const auth = new BetterAuth({ adapter: VafastAdapter({ // ... 其他配置 callbacks: { session: async ({ session, user }) => { if (session.user) { session.user.role = user.role session.user.permissions = user.permissions } return session } } }) }) ``` 使用角色保护路由: ```typescript import { defineRoutes, createHandler } from 'vafast' const requireRole = (role: string) => { return async (request: Request) => { const session = await auth.api.getSession(request) if (!session || session.user.role !== role) { return { error: 'Insufficient permissions' }, { status: 403 } } request.user = session.user return true } } const routes = defineRoutes([ { method: 'GET', path: '/api/admin/users', handler: createHandler(async ({ request }) => { const authResult = await requireRole('admin')(request) if (authResult !== true) return authResult const users = await getAllUsers() return { users } }) } ]) ``` ## 错误处理 ```typescript import { defineRoutes, createHandler } from 'vafast' import { auth } from './auth/config' const routes = defineRoutes([ { method: 'POST', path: '/api/auth/signin', handler: createHandler(async ({ body, request }) => { try { const result = await auth.api.signIn('credentials', { email: body.email, password: body.password, request }) if (result.error) { return { error: result.error }, { status: 400 } } return { success: true, user: result.user } } catch (error) { console.error('Authentication error:', error) return { error: 'Internal server error' }, { status: 500 } } }) } ]) ``` ## 与 CORS 集成 要配置 CORS,您可以使用 `@vafast/cors` 中的 `cors` 中间件。 ```typescript import { defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { auth } from './auth/config' const routes = defineRoutes([ // 你的路由定义 ]) const app = createHandler(routes) .use(cors({ origin: ['http://localhost:3000', 'https://yourdomain.com'], credentials: true })) .use(auth.middleware) ``` ## 环境变量 创建 `.env` 文件: ```env # 数据库 DATABASE_URL="postgresql://user:password@localhost:5432/mydb" # 会话密钥 SESSION_SECRET="your-super-secret-key-here" # OAuth 提供商 GOOGLE_CLIENT_ID="your-google-client-id" GOOGLE_CLIENT_SECRET="your-google-client-secret" GITHUB_CLIENT_ID="your-github-client-id" GITHUB_CLIENT_SECRET="your-github-client-secret" # 其他配置 NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="your-nextauth-secret" ``` ## 最佳实践 1. **安全配置**:使用强密码和 HTTPS 2. **会话管理**:定期轮换会话密钥 3. **错误处理**:不要暴露敏感信息 4. **日志记录**:记录认证事件用于审计 5. **速率限制**:防止暴力攻击 ## 相关链接 * [Better Auth 文档](https://better-auth.com) - 官方文档 * [Vafast 中间件](/middleware) - 探索其他可用的中间件 * [认证最佳实践](/patterns/auth) - 了解认证模式 * [安全指南](/essential/security) - 安全最佳实践 --- --- url: 'https://vafast.dev/middleware/compress.md' --- # Compress 中间件 用于 [Vafast](https://github.com/vafastjs/vafast) 的压缩中间件,支持 Brotli、GZIP 和 Deflate 压缩算法。 ## 安装 通过以下命令安装: ```bash bun add @vafast/compress ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' // 定义路由处理器 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World!'.repeat(100) } // 生成足够长的响应以触发压缩 }), middleware: [ compression({ encodings: ['br', 'gzip', 'deflate'], threshold: 1024, compressStream: false }) ] } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } ``` ## 配置选项 ### CompressionOptions ```typescript interface CompressionOptions { /** * Brotli 压缩选项 * @see https://nodejs.org/api/zlib.html#compressor-options */ brotliOptions?: BrotliOptions /** * GZIP 或 Deflate 压缩选项 * @see https://nodejs.org/api/zlib.html#class-options */ zlibOptions?: ZlibOptions /** * 支持的压缩编码 * 默认优先级:1. br (Brotli) 2. gzip 3. deflate * 如果客户端不支持某个编码或缺少 accept-encoding 头部,将不会压缩 * 示例:encodings: ['gzip', 'deflate'] */ encodings?: CompressionEncoding[] /** * 是否通过 x-no-compression 头部禁用压缩 * 默认情况下,如果请求包含 x-no-compression 头部,将不会压缩响应 * @default true */ disableByHeader?: boolean /** * 触发压缩的最小字节大小 * @default 1024 */ threshold?: number /** * 是否压缩流数据 * 通常用于 Server-Sent Events * @link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events * @default false */ compressStream?: boolean } ``` ### LifeCycleOptions ```typescript interface LifeCycleOptions { /** * 中间件执行顺序和作用域 * @default 'after' */ as?: 'before' | 'after' } ``` ### CacheOptions ```typescript interface CacheOptions { /** * 缓存的生存时间(秒) * @default 86400 (24 小时) */ TTL?: number } ``` ## 压缩算法 ### 支持的编码 * **Brotli (`br`)**: 现代压缩算法,通常提供最佳的压缩比 * **GZIP (`gzip`)**: 广泛支持的压缩算法,兼容性好 * **Deflate (`deflate`)**: 轻量级压缩算法 ### 默认优先级 1. `br` (Brotli) - 最高优先级 2. `gzip` - 中等优先级 3. `deflate` - 最低优先级 ## 使用模式 ### 1. 基本压缩配置 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' const routes = [ { method: 'GET', path: '/api/data', handler: createHandler(() => { // 返回大量数据,触发压缩 return { data: Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}`, description: `This is a description for item ${i}`.repeat(10) })) } }), middleware: [ compression({ encodings: ['br', 'gzip'], threshold: 512, // 降低阈值,更容易触发压缩 compressStream: false }) ] } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 2. 自定义压缩选项 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' import { constants } from 'node:zlib' const routes = [ { method: 'GET', path: '/optimized', handler: createHandler(() => { return { message: 'Optimized compression response' } }), middleware: [ compression({ encodings: ['br', 'gzip'], threshold: 100, // 非常低的阈值 compressStream: true, // 启用流压缩 brotliOptions: { params: { [constants.BROTLI_PARAM_QUALITY]: 11, // 最高质量 [constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_GENERIC } }, zlibOptions: { level: 9, // 最高压缩级别 memLevel: 9 // 最高内存使用 }, TTL: 3600 // 1 小时缓存 }) ] } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 3. 条件压缩 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' const routes = [ { method: 'GET', path: '/public', handler: createHandler(() => { return { message: 'Public endpoint - no compression' } }) // 不应用压缩中间件 }, { method: 'GET', path: '/api/large', handler: createHandler(() => { return { data: 'Large response data'.repeat(1000), timestamp: Date.now() } }), middleware: [ compression({ encodings: ['br'], threshold: 100, compressStream: false }) ] } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 4. 流数据压缩 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' const routes = [ { method: 'GET', path: '/stream', handler: createHandler(() => { // 创建 Server-Sent Events 流 const stream = new ReadableStream({ start(controller) { let count = 0 const interval = setInterval(() => { if (count >= 100) { clearInterval(interval) controller.close() return } const data = `data: ${JSON.stringify({ id: count, message: `Event ${count}`, timestamp: Date.now() })}\n\n` controller.enqueue(new TextEncoder().encode(data)) count++ }, 100) } }) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }) }), middleware: [ compression({ encodings: ['gzip'], threshold: 1, compressStream: true, // 启用流压缩 zlibOptions: { level: 6 } }) ] } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 5. 全局压缩配置 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' // 创建全局压缩中间件 const globalCompression = compression({ encodings: ['br', 'gzip'], threshold: 1024, compressStream: false, TTL: 7200 // 2 小时缓存 }) const routes = [ { method: 'GET', path: '/api/users', handler: createHandler(() => { return { users: generateLargeUserList() } }) }, { method: 'GET', path: '/api/products', handler: createHandler(() => { return { products: generateLargeProductList() } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { // 为所有请求应用压缩中间件 return globalCompression(req, () => server.fetch(req)) } } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { compression } from '@vafast/compress' import { constants } from 'node:zlib' // 模拟数据生成函数 const generateLargeDataset = (size: number) => { return Array.from({ length: size }, (_, i) => ({ id: i, name: `Item ${i}`, description: `This is a detailed description for item ${i}`.repeat(5), metadata: { category: `Category ${i % 10}`, tags: [`tag${i}`, `tag${i + 1}`, `tag${i + 2}`], createdAt: new Date(Date.now() - i * 86400000).toISOString() } })) } const generateMarkdownContent = () => { return `# 大型文档内容 ## 章节 1 ${'这是章节 1 的详细内容,包含大量文本信息。'.repeat(100)} ## 章节 2 ${'这是章节 2 的详细内容,同样包含大量文本信息。'.repeat(100)} ## 章节 3 ${'这是章节 3 的详细内容,继续包含大量文本信息。'.repeat(100)} ## 总结 ${'这是一个包含大量内容的文档,用于演示压缩效果。'.repeat(50)} ` } // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Compression API', endpoints: [ '/api/data - 获取大型数据集', '/api/markdown - 获取 Markdown 文档', '/api/stream - 获取流式数据', '/api/optimized - 获取优化压缩的数据' ] } }) }, { method: 'GET', path: '/api/data', handler: createHandler(() => { return { message: 'Large dataset retrieved successfully', data: generateLargeDataset(500), totalItems: 500, timestamp: Date.now() } }), middleware: [ compression({ encodings: ['br', 'gzip'], threshold: 1024, compressStream: false, TTL: 3600 // 1 小时缓存 }) ] }, { method: 'GET', path: '/api/markdown', handler: createHandler(() => { return { content: generateMarkdownContent(), format: 'markdown', size: generateMarkdownContent().length, timestamp: Date.now() } }), middleware: [ compression({ encodings: ['br', 'gzip', 'deflate'], threshold: 512, compressStream: false }) ] }, { method: 'GET', path: '/api/stream', handler: createHandler(() => { const stream = new ReadableStream({ start(controller) { let count = 0 const interval = setInterval(() => { if (count >= 50) { clearInterval(interval) controller.close() return } const data = `data: ${JSON.stringify({ id: count, message: `Stream event ${count}`, data: `Event data ${count}`.repeat(20), timestamp: Date.now() })}\n\n` controller.enqueue(new TextEncoder().encode(data)) count++ }, 200) } }) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }) }), middleware: [ compression({ encodings: ['gzip'], threshold: 1, compressStream: true, zlibOptions: { level: 6 } }) ] }, { method: 'GET', path: '/api/optimized', handler: createHandler(() => { return { message: 'Optimized compression response', data: generateLargeDataset(200), compression: { algorithm: 'brotli', quality: 'maximum', cache: 'enabled' } } }), middleware: [ compression({ encodings: ['br'], threshold: 100, compressStream: false, brotliOptions: { params: { [constants.BROTLI_PARAM_QUALITY]: 11, [constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_GENERIC } }, TTL: 7200 // 2 小时缓存 }) ] } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } console.log('🚀 Vafast Compression API 服务器启动成功!') console.log('📊 数据端点: GET /api/data (启用 Brotli/GZIP 压缩)') console.log('📝 文档端点: GET /api/markdown (启用多种压缩算法)') console.log('🌊 流式端点: GET /api/stream (启用流压缩)') console.log('⚡ 优化端点: GET /api/optimized (启用 Brotli 最高质量压缩)') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' describe('Vafast Compression API', () => { it('should compress large responses', async () => { const res = await app.fetch(new Request('http://localhost/api/data')) expect(res.headers.get('Content-Encoding')).toBeTruthy() expect(res.headers.get('Vary')).toBe('accept-encoding') expect(res.ok).toBe(true) }) it('should not compress small responses below threshold', async () => { const res = await app.fetch(new Request('http://localhost/')) expect(res.headers.get('Content-Encoding')).toBeNull() expect(res.headers.get('Vary')).toBeNull() expect(res.ok).toBe(true) }) it('should respect x-no-compression header', async () => { const res = await app.fetch(new Request('http://localhost/api/data', { headers: { 'x-no-compression': 'true' } })) expect(res.headers.get('Content-Encoding')).toBeNull() expect(res.ok).toBe(true) }) it('should handle different accept-encoding preferences', async () => { const res = await app.fetch(new Request('http://localhost/api/data', { headers: { 'accept-encoding': 'gzip, deflate' } })) expect(res.headers.get('Content-Encoding')).toBe('gzip') expect(res.ok).toBe(true) }) }) ``` ## 特性 * ✅ **多种压缩算法**: 支持 Brotli、GZIP 和 Deflate * ✅ **智能编码选择**: 根据客户端的 `accept-encoding` 头部自动选择最佳压缩方式 * ✅ **可配置阈值**: 可设置最小压缩字节大小 * ✅ **流数据支持**: 支持压缩 Server-Sent Events 等流数据 * ✅ **缓存机制**: 内置压缩结果缓存,提高性能 * ✅ **HTTP 标准兼容**: 自动设置 `Content-Encoding` 和 `Vary` 头部 * ✅ **条件压缩**: 支持通过请求头部禁用压缩 * ✅ **类型安全**: 完整的 TypeScript 类型支持 ## 性能优化建议 ### 1. 压缩级别选择 ```typescript // 生产环境 - 平衡压缩比和性能 compression({ zlibOptions: { level: 6 }, // 默认级别 brotliOptions: { params: { [constants.BROTLI_PARAM_QUALITY]: 6 // 平衡质量 } } }) // 开发环境 - 快速压缩 compression({ zlibOptions: { level: 1 }, brotliOptions: { params: { [constants.BROTLI_PARAM_QUALITY]: 3 } } }) ``` ### 2. 缓存策略 ```typescript // 静态内容 - 长缓存 compression({ TTL: 86400 * 7 // 7 天 }) // 动态内容 - 短缓存 compression({ TTL: 3600 // 1 小时 }) ``` ### 3. 阈值优化 ```typescript // 文本内容 - 低阈值 compression({ threshold: 256 // 256 字节 }) // 二进制内容 - 高阈值 compression({ threshold: 2048 // 2KB }) ``` ## 注意事项 1. **压缩开销**: 压缩会增加 CPU 开销,对于小文件可能得不偿失 2. **缓存策略**: 合理设置 TTL 值,避免内存泄漏 3. **流压缩**: 启用 `compressStream` 时注意内存使用 4. **内容类型**: 某些二进制格式(如图片、视频)不适合压缩 5. **HTTPS 影响**: 在 HTTPS 连接中,压缩可能被 TLS 层处理 ## 相关链接 * [HTTP 压缩 - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Compression) * [Accept-Encoding - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) * [Content-Encoding - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) * [Brotli 压缩算法](https://en.wikipedia.org/wiki/Brotli) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/patterns/cookie.md' --- # Cookie 处理 Vafast 提供了简单而强大的 Cookie 处理功能,支持读取、设置和删除 Cookie,以及各种 Cookie 属性的配置。 ## 基本用法 在 Vafast 中,您可以通过请求对象访问 Cookie,并通过响应对象设置 Cookie: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(({ req }) => { // 读取 Cookie const cookies = req.headers.get('cookie') const sessionId = getCookieValue(cookies, 'sessionId') if (sessionId) { return `Welcome back! Session: ${sessionId}` } else { return 'Welcome! Please log in.' } }) }, { method: 'POST', path: '/login', handler: createHandler(async ({ req }) => { const body = await req.json() const { username, password } = body // 验证用户... if (username === 'admin' && password === 'password') { const sessionId = generateSessionId() // 创建响应并设置 Cookie const response = new Response('Login successful', { status: 200 }) response.headers.set('Set-Cookie', `sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=3600`) return response } return new Response('Invalid credentials', { status: 401 }) }) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## Cookie 工具函数 创建实用的 Cookie 处理函数: ```typescript // Cookie 工具函数 export class CookieUtils { // 解析 Cookie 字符串 static parse(cookieString: string | null): Record { if (!cookieString) return {} return cookieString .split(';') .map(cookie => cookie.trim().split('=')) .reduce((acc, [key, value]) => { if (key && value) { acc[decodeURIComponent(key)] = decodeURIComponent(value) } return acc }, {} as Record) } // 获取特定 Cookie 值 static get(cookieString: string | null, name: string): string | undefined { const cookies = this.parse(cookieString) return cookies[name] } // 创建 Set-Cookie 头 static set(name: string, value: string, options: CookieOptions = {}): string { const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`] if (options.domain) parts.push(`Domain=${options.domain}`) if (options.path) parts.push(`Path=${options.path}`) if (options.maxAge) parts.push(`Max-Age=${options.maxAge}`) if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`) if (options.httpOnly) parts.push('HttpOnly') if (options.secure) parts.push('Secure') if (options.sameSite) parts.push(`SameSite=${options.sameSite}`) return parts.join('; ') } // 删除 Cookie static delete(name: string, options: CookieOptions = {}): string { return this.set(name, '', { ...options, maxAge: 0, expires: new Date(0) }) } } interface CookieOptions { domain?: string path?: string maxAge?: number expires?: Date httpOnly?: boolean secure?: boolean sameSite?: 'Strict' | 'Lax' | 'None' } // 使用 Cookie 工具函数 const routes = defineRoutes([ { method: 'GET', path: '/profile', handler: createHandler(({ req }) => { const cookies = req.headers.get('cookie') const sessionId = CookieUtils.get(cookies, 'sessionId') if (!sessionId) { return new Response('Unauthorized', { status: 401 }) } // 验证 session 并返回用户信息 return { username: 'admin', email: 'admin@example.com' } }) }, { method: 'POST', path: '/logout', handler: createHandler(() => { const response = new Response('Logged out successfully') // 删除 Cookie response.headers.set('Set-Cookie', CookieUtils.delete('sessionId', { path: '/' })) return response }) } ]) ``` ## 中间件方式处理 Cookie 创建专门的 Cookie 中间件: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // Cookie 中间件 const cookieMiddleware = async (req: Request, next: () => Promise) => { // 解析 Cookie 并添加到请求对象 const cookieString = req.headers.get('cookie') ;(req as any).cookies = CookieUtils.parse(cookieString) // 添加 Cookie 方法到请求对象 ;(req as any).getCookie = (name: string) => { return (req as any).cookies[name] } ;(req as any).hasCookie = (name: string) => { return !!(req as any).cookies[name] } const response = await next() // 处理响应中的 Cookie 设置 const cookiesToSet = (req as any)._cookiesToSet || [] if (cookiesToSet.length > 0) { cookiesToSet.forEach((cookie: string) => { response.headers.append('Set-Cookie', cookie) }) } return response } // 扩展的 Cookie 中间件 const enhancedCookieMiddleware = async (req: Request, next: () => Promise) => { const cookieString = req.headers.get('cookie') ;(req as any).cookies = CookieUtils.parse(cookieString) // 添加响应 Cookie 设置方法 ;(req as any).setCookie = (name: string, value: string, options: CookieOptions = {}) => { if (!(req as any)._cookiesToSet) { ;(req as any)._cookiesToSet = [] } const cookieString = CookieUtils.set(name, value, options) ;(req as any)._cookiesToSet.push(cookieString) } ;(req as any).deleteCookie = (name: string, options: CookieOptions = {}) => { if (!(req as any)._cookiesToSet) { ;(req as any)._cookiesToSet = [] } const cookieString = CookieUtils.delete(name, options) ;(req as any)._cookiesToSet.push(cookieString) } return await next() } const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(({ req }) => { // 使用中间件添加的 Cookie 方法 const sessionId = (req as any).getCookie('sessionId') if (sessionId) { return `Welcome back! Session: ${sessionId}` } return 'Welcome! Please log in.' }) }, { method: 'POST', path: '/login', handler: createHandler(async ({ req }) => { const body = await req.json() const { username, password } = body if (username === 'admin' && password === 'password') { const sessionId = generateSessionId() // 使用中间件添加的 setCookie 方法 ;(req as any).setCookie('sessionId', sessionId, { httpOnly: true, path: '/', maxAge: 3600, secure: true, sameSite: 'Strict' }) return 'Login successful' } return new Response('Invalid credentials', { status: 401 }) }) } ]) const server = new Server(routes) server.use(cookieMiddleware) server.use(enhancedCookieMiddleware) export default { fetch: server.fetch } ``` ## 会话管理 实现完整的会话管理系统: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' interface Session { id: string userId: string username: string createdAt: Date expiresAt: Date } class SessionManager { private sessions = new Map() private readonly sessionTimeout = 24 * 60 * 60 * 1000 // 24小时 createSession(userId: string, username: string): Session { const sessionId = this.generateSessionId() const now = new Date() const session: Session = { id: sessionId, userId, username, createdAt: now, expiresAt: new Date(now.getTime() + this.sessionTimeout) } this.sessions.set(sessionId, session) // 清理过期会话 this.cleanupExpiredSessions() return session } getSession(sessionId: string): Session | undefined { const session = this.sessions.get(sessionId) if (session && session.expiresAt > new Date()) { return session } if (session) { this.sessions.delete(sessionId) } return undefined } deleteSession(sessionId: string): boolean { return this.sessions.delete(sessionId) } private generateSessionId(): string { return `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` } private cleanupExpiredSessions(): void { const now = new Date() for (const [sessionId, session] of this.sessions.entries()) { if (session.expiresAt <= now) { this.sessions.delete(sessionId) } } } } const sessionManager = new SessionManager() // 会话中间件 const sessionMiddleware = async (req: Request, next: () => Promise) => { const cookieString = req.headers.get('cookie') const sessionId = CookieUtils.get(cookieString, 'sessionId') if (sessionId) { const session = sessionManager.getSession(sessionId) if (session) { ;(req as any).session = session ;(req as any).user = { id: session.userId, username: session.username } } } return await next() } const routes = defineRoutes([ { method: 'POST', path: '/login', handler: createHandler(async ({ req }) => { const body = await req.json() const { username, password } = body // 验证用户... if (username === 'admin' && password === 'password') { const session = sessionManager.createSession('1', username) const response = new Response('Login successful') response.headers.set('Set-Cookie', CookieUtils.set('sessionId', session.id, { httpOnly: true, path: '/', maxAge: 24 * 60 * 60, // 24小时 secure: true, sameSite: 'Strict' })) return response } return new Response('Invalid credentials', { status: 401 }) }) }, { method: 'GET', path: '/profile', handler: createHandler(({ req }) => { const user = (req as any).user if (!user) { return new Response('Unauthorized', { status: 401 }) } return { id: user.id, username: user.username, message: 'Welcome to your profile!' } }) }, { method: 'POST', path: '/logout', handler: createHandler(({ req }) => { const sessionId = CookieUtils.get(req.headers.get('cookie'), 'sessionId') if (sessionId) { sessionManager.deleteSession(sessionId) } const response = new Response('Logged out successfully') response.headers.set('Set-Cookie', CookieUtils.delete('sessionId', { path: '/' })) return response }) } ]) const server = new Server(routes) server.use(sessionMiddleware) export default { fetch: server.fetch } ``` ## Cookie 安全配置 实现安全的 Cookie 配置: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 安全 Cookie 配置 const createSecureCookie = (name: string, value: string, options: CookieOptions = {}) => { return CookieUtils.set(name, value, { httpOnly: true, // 防止 XSS 攻击 secure: true, // 仅通过 HTTPS 传输 sameSite: 'Strict', // 防止 CSRF 攻击 path: '/', // 限制 Cookie 路径 maxAge: 3600, // 1小时过期 ...options }) } // 环境相关的 Cookie 配置 const createEnvironmentCookie = (name: string, value: string, options: CookieOptions = {}) => { const isProduction = process.env.NODE_ENV === 'production' return CookieUtils.set(name, value, { httpOnly: true, secure: isProduction, // 生产环境使用 HTTPS sameSite: isProduction ? 'Strict' : 'Lax', path: '/', maxAge: 3600, ...options }) } const routes = defineRoutes([ { method: 'POST', path: '/auth/login', handler: createHandler(async ({ req }) => { const body = await req.json() const { username, password } = body if (username === 'admin' && password === 'password') { const sessionId = generateSecureSessionId() const response = new Response('Login successful') response.headers.set('Set-Cookie', createSecureCookie('sessionId', sessionId)) return response } return new Response('Invalid credentials', { status: 401 }) }) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## Cookie 测试 测试 Cookie 功能: ```typescript import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Cookie Handling', () => { it('should set and read cookies', async () => { const routes = defineRoutes([ { method: 'POST', path: '/set-cookie', handler: createHandler(({ req }) => { const body = (req as any).body const { name, value } = body const response = new Response('Cookie set') response.headers.set('Set-Cookie', CookieUtils.set(name, value)) return response }) }, { method: 'GET', path: '/read-cookie', handler: createHandler(({ req }) => { const cookies = req.headers.get('cookie') const sessionId = CookieUtils.get(cookies, 'sessionId') return { sessionId } }) } ]) const server = new Server(routes) // 设置 Cookie const setResponse = await server.fetch(new Request('http://localhost/set-cookie', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'sessionId', value: 'test123' }) })) expect(setResponse.status).toBe(200) // 读取 Cookie const cookieHeader = setResponse.headers.get('Set-Cookie') const readResponse = await server.fetch(new Request('http://localhost/read-cookie', { headers: { cookie: cookieHeader } })) const data = await readResponse.json() expect(data.sessionId).toBe('test123') }) it('should handle cookie deletion', async () => { const routes = defineRoutes([ { method: 'POST', path: '/delete-cookie', handler: createHandler(() => { const response = new Response('Cookie deleted') response.headers.set('Set-Cookie', CookieUtils.delete('sessionId', { path: '/' })) return response }) } ]) const server = new Server(routes) const response = await server.fetch(new Request('http://localhost/delete-cookie', { method: 'POST' })) expect(response.status).toBe(200) const cookieHeader = response.headers.get('Set-Cookie') expect(cookieHeader).toContain('Max-Age=0') expect(cookieHeader).toContain('Expires=') }) }) ``` ## 总结 Vafast 的 Cookie 处理系统提供了: * ✅ 简单的 Cookie 读取和设置 * ✅ 完整的 Cookie 属性配置 * ✅ 安全的 Cookie 默认值 * ✅ 会话管理支持 * ✅ 中间件集成 * ✅ 类型安全的 API * ✅ 完整的测试支持 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强功能 * 探索 [验证系统](/essential/validation) 了解类型安全 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/middleware/cors.md' --- # CORS 中间件 这个中间件为自定义 [跨源资源共享](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 行为提供支持。 安装命令: ```bash bun add @vafastjs/cors ``` 然后使用它: ```typescript import { Vafast } from 'vafast' import { cors } from '@vafastjs/cors' new Vafast().use(cors()).listen(3000) ``` 这样将使 Vafast 接受来自任何源的请求。 ## 配置 以下是该中间件接受的配置 ### origin @默认 `true` 指示是否可以与来自给定来源的请求代码共享响应。 值可以是以下之一: * **字符串** - 源的名称,会直接分配给 [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) 头部。 * **布尔值** - 如果设置为 true, [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) 将设置为 `*`(任何来源)。 * **RegExp** - 匹配请求 URL 的模式,如果匹配则允许。 * **函数** - 自定义逻辑以允许资源共享,如果返回 true 则允许。 * 预期具有以下类型: ```typescript cors(context: Context) => boolean | void ``` * **Array\** - 按顺序迭代上述所有情况,只要有任何一个值为 `true` 则允许。 *** ### methods @默认 `*` 允许的跨源请求方法。 分配 [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) 头部。 值可以是以下之一: * **undefined | null | ''** - 忽略所有方法。 * **\*** - 允许所有方法。 * **字符串** - 期望单个方法或逗号分隔的字符串 * (例如: `'GET, PUT, POST'`) * **string\[]** - 允许多个 HTTP 方法。 * 例如: `['GET', 'PUT', 'POST']` *** ### allowedHeaders @默认 `*` 允许的传入请求头。 分配 [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) 头部。 值可以是以下之一: * **字符串** - 期望单个头或逗号分隔的字符串 * 例如: `'Content-Type, Authorization'`。 * **string\[]** - 允许多个 HTTP 头。 * 例如: `['Content-Type', 'Authorization']` *** ### exposeHeaders @默认 `*` 响应 CORS 中包含指定的头部。 分配 [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) 头部。 值可以是以下之一: * **字符串** - 期望单个头或逗号分隔的字符串。 * 例如: `'Content-Type, X-Powered-By'`。 * **string\[]** - 允许多个 HTTP 头。 * 例如: `['Content-Type', 'X-Powered-By']` *** ### credentials @默认 `true` Access-Control-Allow-Credentials 响应头告诉浏览器在请求的凭证模式 [Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) 为 `include` 时,是否将响应暴露给前端 JavaScript 代码。 当请求的凭证模式 [Request.credentials](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials) 为 `include` 时,浏览器仅在 Access-Control-Allow-Credentials 值为 true 的情况下,将响应暴露给前端 JavaScript 代码。 凭证包括 cookies、授权头或 TLS 客户端证书。 分配 [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) 头部。 *** ### maxAge @默认 `5` 指示 [预检请求](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) 的结果(即包含在 [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) 和 [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) 头部中的信息)可以缓存多久。 分配 [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age) 头部。 *** ### preflight 预检请求是用来检查 CORS 协议是否被理解以及服务器是否知道如何使用特定方法和头部的请求。 使用 **OPTIONS** 请求的响应中包含 3 个 HTTP 请求头: * **Access-Control-Request-Method** * **Access-Control-Request-Headers** * **Origin** 此配置指示服务器是否应该响应预检请求。 ## 示例 以下是使用该中间件的常见模式。 ## 按顶级域名允许 CORS ```typescript import { Vafast } from 'vafast' import { cors } from '@vafastjs/cors' const app = new Vafast() .use( cors({ origin: /.*\.saltyaom\.com$/ }) ) .get('/', () => '你好') .listen(3000) ``` 这将允许来自顶级域名 `saltyaom.com` 的请求。 --- --- url: 'https://vafast.dev/middleware/cron.md' --- # Cron 中间件 此中间件为 [Vafast](https://github.com/vafastjs/vafast) 服务器添加了运行 cronjob 的支持。 ## 安装 通过以下方式安装: ```bash bun add @vafast/cron ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { cron } from '@vafast/cron' // 创建 cron 任务 const heartbeatCron = cron({ name: 'heartbeat', pattern: '*/30 * * * * *', // 每30秒执行一次 run(store) { console.log('Heartbeat - Working') } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Cron API' } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } ``` 上述代码将每 30 秒记录一次 `heartbeat`。 ## API 参考 ### cron 为 Vafast 服务器创建一个 cronjob。 ```typescript cron(config: CronConfig, callback: (store: Cron) => void): Cron ``` ### CronConfig `CronConfig` 接受以下参数: #### name 注册到 `store` 的作业名称。 这将以指定的名称将 cron 实例注册到 `store`,可供后续过程引用,例如停止作业。 #### pattern 根据下面的 [cron 语法](https://en.wikipedia.org/wiki/Cron) 指定作业运行时间: ``` ┌────────────── 秒(可选) │ ┌──────────── 分钟 │ │ ┌────────── 小时 │ │ │ ┌──────── 每月的日期 │ │ │ │ ┌────── 月 │ │ │ │ │ ┌──── 星期几 │ │ │ │ │ │ * * * * * * ``` 可以使用 [Crontab Guru](https://crontab.guru/) 等工具生成。 #### run 在指定时间执行的函数。 ```typescript run: (store: Cron) => any | Promise ``` *** 此中间件通过 [croner](https://github.com/hexagon/croner) 扩展了 Vafast 的 cron 方法。 以下是 croner 接受的配置选项: ### timezone 以欧洲/斯德哥尔摩格式表示的时区。 ### startAt 作业的调度开始时间。 ### stopAt 作业的调度停止时间。 ### maxRuns 最大执行次数。 ### catch 即使触发的函数抛出未处理错误,也继续执行。 ### interval 执行之间的最小间隔(秒)。 ## 使用模式 ### 1. 基本定时任务 ```typescript import { Server, createHandler } from 'vafast' import { cron } from '@vafast/cron' // 创建定时任务 const cleanupCron = cron({ name: 'cleanup', pattern: '0 2 * * *', // 每天凌晨2点执行 run(store) { console.log('执行清理任务:', new Date().toISOString()) // 执行清理逻辑 cleanupOldFiles() cleanupDatabase() } }) const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Cleanup cron job is running' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 2. 任务生命周期管理 ```typescript import { Server, createHandler } from 'vafast' import { cron } from '@vafast/cron' // 创建可控制的 cron 任务 const loggerCron = cron({ name: 'logger', pattern: '*/1 * * * * *', // 每秒执行一次 run(store) { console.log(new Date().toISOString()) } }) const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { // 停止 logger 任务 loggerCron.stop() return { message: 'Logger stopped' } }) }, { method: 'GET', path: '/status', handler: createHandler(() => { return { logger: loggerCron.isRunning(), nextRun: loggerCron.nextRun() } }) }, { method: 'POST', path: '/start-logger', handler: createHandler(() => { loggerCron.resume() return { message: 'Logger started' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 3. 多个定时任务 ```typescript import { Server, createHandler } from 'vafast' import { cron } from '@vafast/cron' // 创建多个 cron 任务 const heartbeatCron = cron({ name: 'heartbeat', pattern: '*/30 * * * * *', // 每30秒 run(store) { console.log('Heartbeat check') checkSystemHealth() } }) const backupCron = cron({ name: 'backup', pattern: '0 3 * * *', // 每天凌晨3点 run(store) { console.log('Starting backup') performBackup() } }) const maintenanceCron = cron({ name: 'maintenance', pattern: '0 4 * * 0', // 每周日凌晨4点 run(store) { console.log('Starting maintenance') performMaintenance() } }) const routes = [ { method: 'GET', path: '/cron/status', handler: createHandler(() => { return { heartbeat: { running: heartbeatCron.isRunning(), nextRun: heartbeatCron.nextRun() }, backup: { running: backupCron.isRunning(), nextRun: backupCron.nextRun() }, maintenance: { running: maintenanceCron.isRunning(), nextRun: maintenanceCron.nextRun() } } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 4. 条件执行和错误处理 ```typescript import { Server, createHandler } from 'vafast' import { cron } from '@vafast/cron' const dataSyncCron = cron({ name: 'dataSync', pattern: '*/5 * * * *', // 每5分钟 catch: true, // 即使出错也继续执行 maxRuns: 1000, // 最大执行1000次 run(store) { try { console.log('开始数据同步...') // 检查系统状态 if (!isSystemReady()) { console.log('系统未就绪,跳过本次同步') return } // 执行数据同步 const result = syncData() console.log('数据同步完成:', result) } catch (error) { console.error('数据同步出错:', error) // 发送告警 sendAlert('数据同步失败', error) } } }) const routes = [ { method: 'GET', path: '/sync/status', handler: createHandler(() => { return { running: dataSyncCron.isRunning(), nextRun: dataSyncCron.nextRun(), lastRun: dataSyncCron.lastRun() } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ## 预定义模式 您可以使用 `@vafast/cron/schedule` 中的预定义模式。 ```typescript import { cron, Patterns } from '@vafast/cron' // 使用预定义模式 const job = cron({ name: 'scheduled', pattern: Patterns.EVERY_SECOND, // 每秒执行 run(store) { console.log('Scheduled task') } }) // 使用函数模式 const customJob = cron({ name: 'custom', pattern: Patterns.everyMinutes(5), // 每5分钟 run(store) { console.log('Custom scheduled task') } }) ``` ### 函数模式 | 函数 | 描述 | | -------------------------------------- | --------------------------------------------------- | | `.everySenconds(2)` | 每 2 秒运行一次任务 | | `.everyMinutes(5)` | 每 5 分钟运行一次任务 | | `.everyHours(3)` | 每 3 小时运行一次任务 | | `.everyHoursAt(3, 15)` | 每 3 小时在 15 分钟时运行一次任务 | | `.everyDayAt('04:19')` | 每天在 04:19 运行一次任务 | | `.everyWeekOn(Patterns.MONDAY, '19:30')` | 每周一在 19:30 运行一次任务 | | `.everyWeekdayAt('17:00')` | 每个工作日的 17:00 运行一次任务 | | `.everyWeekendAt('11:00')` | 每周六和周日在 11:00 运行一次任务 | ### 函数别名到常量 | 函数 | 常量 | | ----------------- | ------------------------------ | | `.everySecond()` | EVERY\_SECOND | | `.everyMinute()` | EVERY\_MINUTE | | `.hourly()` | EVERY\_HOUR | | `.daily()` | EVERY\_DAY\_AT\_MIDNIGHT | | `.everyWeekday()` | EVERY\_WEEKDAY | | `.everyWeekend()` | EVERY\_WEEKEND | | `.weekly()` | EVERY\_WEEK | | `.monthly()` | EVERY\_1ST\_DAY\_OF\_MONTH\_AT\_MIDNIGHT | | `.everyQuarter()` | EVERY\_QUARTER | | `.yearly()` | EVERY\_YEAR | ### 常量模式 | 常量 | 模式 | | --------------------------------------- | ----------------------- | | `.EVERY_SECOND` | `* * * * * *` | | `.EVERY_5_SECONDS` | `*/5 * * * * *` | | `.EVERY_10_SECONDS` | `*/10 * * * * *` | | `.EVERY_30_SECONDS` | `*/30 * * * * *` | | `.EVERY_MINUTE` | `*/1 * * * *` | | `.EVERY_5_MINUTES` | `0 */5 * * * *` | | `.EVERY_10_MINUTES` | `0 */10 * * * *` | | `.EVERY_30_MINUTES` | `0 */30 * * * *` | | `.EVERY_HOUR` | `0 0-23/1 * * *` | | `.EVERY_2_HOURS` | `0 0-23/2 * * *` | | `.EVERY_3_HOURS` | `0 0-23/3 * * *` | | `.EVERY_4_HOURS` | `0 0-23/4 * * *` | | `.EVERY_5_HOURS` | `0 0-23/5 * * *` | | `.EVERY_6_HOURS` | `0 0-23/6 * * *` | | `.EVERY_7_HOURS` | `0 0-23/7 * * *` | | `.EVERY_8_HOURS` | `0 0-23/8 * * *` | | `.EVERY_9_HOURS` | `0 0-23/9 * * *` | | `.EVERY_10_HOURS` | `0 0-23/10 * * *` | | `.EVERY_11_HOURS` | `0 0-23/11 * * *` | | `.EVERY_12_HOURS` | `0 0-23/12 * * *` | | `.EVERY_DAY_AT_1AM` | `0 01 * * *` | | `.EVERY_DAY_AT_2AM` | `0 02 * * *` | | `.EVERY_DAY_AT_3AM` | `0 03 * * *` | | `.EVERY_DAY_AT_4AM` | `0 04 * * *` | | `.EVERY_DAY_AT_5AM` | `0 05 * * *` | | `.EVERY_DAY_AT_6AM` | `0 06 * * *` | | `.EVERY_DAY_AT_7AM` | `0 07 * * *` | | `.EVERY_DAY_AT_8AM` | `0 08 * * *` | | `.EVERY_DAY_AT_9AM` | `0 09 * * *` | | `.EVERY_DAY_AT_10AM` | `0 10 * * *` | | `.EVERY_DAY_AT_11AM` | `0 11 * * *` | | `.EVERY_DAY_AT_NOON` | `0 12 * * *` | | `.EVERY_DAY_AT_1PM` | `0 13 * * *` | | `.EVERY_DAY_AT_2PM` | `0 14 * * *` | | `.EVERY_DAY_AT_3PM` | `0 15 * * *` | | `.EVERY_DAY_AT_4PM` | `0 16 * * *` | | `.EVERY_DAY_AT_5PM` | `0 17 * * *` | | `.EVERY_DAY_AT_6PM` | `0 18 * * *` | | `.EVERY_DAY_AT_7PM` | `0 19 * * *` | | `.EVERY_DAY_AT_8PM` | `0 20 * * *` | | `.EVERY_DAY_AT_9PM` | `0 21 * * *` | | `.EVERY_DAY_AT_10PM` | `0 22 * * *` | | `.EVERY_DAY_AT_11PM` | `0 23 * * *` | | `.EVERY_DAY_AT_MIDNIGHT` | `0 0 * * *` | | `.EVERY_WEEK` | `0 0 * * 0` | | `.EVERY_WEEKDAY` | `0 0 * * 1-5` | | `.EVERY_WEEKEND` | `0 0 * * 6,0` | | `.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT` | `0 0 1 * *` | | `.EVERY_1ST_DAY_OF_MONTH_AT_NOON` | `0 12 1 * *` | | `.EVERY_2ND_HOUR` | `0 */2 * * *` | | `.EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM` | `0 1-23/2 * * *` | | `.EVERY_2ND_MONTH` | `0 0 1 */2 *` | | `.EVERY_QUARTER` | `0 0 1 */3 *` | | `.EVERY_6_MONTHS` | `0 0 1 */6 *` | | `.EVERY_YEAR` | `0 0 1 1 *` | | `.EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM` | `0 */30 9-17 * * *` | | `.EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM` | `0 */30 9-18 * * *` | | `.EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM`| `0 */30 10-19 * * *` | ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { cron, Patterns } from '@vafast/cron' // 模拟业务函数 const checkSystemHealth = () => { const health = { cpu: Math.random() * 100, memory: Math.random() * 100, disk: Math.random() * 100, timestamp: new Date().toISOString() } if (health.cpu > 80 || health.memory > 80 || health.disk > 80) { console.warn('系统资源使用率过高:', health) sendAlert('系统告警', health) } return health } const performBackup = async () => { console.log('开始数据库备份...') try { // 模拟备份过程 await new Promise(resolve => setTimeout(resolve, 2000)) console.log('数据库备份完成') return { success: true, timestamp: new Date().toISOString() } } catch (error) { console.error('备份失败:', error) return { success: false, error: error.message } } } const cleanupOldFiles = () => { console.log('清理旧文件...') // 清理逻辑 return { cleaned: Math.floor(Math.random() * 100) } } const sendAlert = (title: string, data: any) => { console.log(`告警: ${title}`, data) // 发送告警逻辑 } // 创建多个 cron 任务 const healthCheckCron = cron({ name: 'healthCheck', pattern: '*/30 * * * * *', // 每30秒 run(store) { console.log('执行健康检查...') const health = checkSystemHealth() console.log('健康检查结果:', health) } }) const backupCron = cron({ name: 'backup', pattern: '0 2 * * *', // 每天凌晨2点 run(store) { console.log('开始定时备份...') performBackup() } }) const cleanupCron = cron({ name: 'cleanup', pattern: '0 3 * * *', // 每天凌晨3点 run(store) { console.log('开始清理任务...') const result = cleanupOldFiles() console.log('清理完成:', result) } }) const maintenanceCron = cron({ name: 'maintenance', pattern: '0 4 * * 0', // 每周日凌晨4点 run(store) { console.log('开始系统维护...') // 维护逻辑 console.log('系统维护完成') } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Cron Management API', endpoints: [ '/cron/status - 查看所有任务状态', '/cron/health - 手动执行健康检查', '/cron/backup - 手动执行备份', '/cron/cleanup - 手动执行清理', '/cron/stop/:name - 停止指定任务', '/cron/start/:name - 启动指定任务' ] } }) }, { method: 'GET', path: '/cron/status', handler: createHandler(() => { return { healthCheck: { name: 'healthCheck', running: healthCheckCron.isRunning(), nextRun: healthCheckCron.nextRun(), lastRun: healthCheckCron.lastRun() }, backup: { name: 'backup', running: backupCron.isRunning(), nextRun: backupCron.nextRun(), lastRun: backupCron.lastRun() }, cleanup: { name: 'cleanup', running: cleanupCron.isRunning(), nextRun: cleanupCron.nextRun(), lastRun: cleanupCron.lastRun() }, maintenance: { name: 'maintenance', running: maintenanceCron.isRunning(), nextRun: maintenanceCron.nextRun(), lastRun: maintenanceCron.lastRun() } } }) }, { method: 'POST', path: '/cron/health', handler: createHandler(() => { const health = checkSystemHealth() return { message: '手动健康检查完成', result: health } }) }, { method: 'POST', path: '/cron/backup', handler: createHandler(async () => { const result = await performBackup() return { message: '手动备份完成', result } }) }, { method: 'POST', path: '/cron/cleanup', handler: createHandler(() => { const result = cleanupOldFiles() return { message: '手动清理完成', result } }) }, { method: 'POST', path: '/cron/stop/:name', handler: createHandler((req: Request) => { const url = new URL(req.url) const name = url.pathname.split('/').pop() let cronJob: any switch (name) { case 'healthCheck': cronJob = healthCheckCron break case 'backup': cronJob = backupCron break case 'cleanup': cronJob = cleanupCron break case 'maintenance': cronJob = maintenanceCron break default: return { error: '未知的任务名称' } } if (cronJob.isRunning()) { cronJob.stop() return { message: `任务 ${name} 已停止` } } else { return { message: `任务 ${name} 已经停止` } } }) }, { method: 'POST', path: '/cron/start/:name', handler: createHandler((req: Request) => { const url = new URL(req.url) const name = url.pathname.split('/').pop() let cronJob: any switch (name) { case 'healthCheck': cronJob = healthCheckCron break case 'backup': cronJob = backupCron break case 'cleanup': cronJob = cleanupCron break case 'maintenance': cronJob = maintenanceCron break default: return { error: '未知的任务名称' } } if (!cronJob.isRunning()) { cronJob.resume() return { message: `任务 ${name} 已启动` } } else { return { message: `任务 ${name} 已经在运行` } } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } console.log('🚀 Vafast Cron Management API 服务器启动成功!') console.log('📊 健康检查: 每30秒执行一次') console.log('💾 数据备份: 每天凌晨2点执行') console.log('🧹 文件清理: 每天凌晨3点执行') console.log('🔧 系统维护: 每周日凌晨4点执行') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { cron } from '@vafast/cron' import { Patterns } from '@vafast/cron/schedule' describe('Vafast Cron API', () => { it('should create cron job', () => { let executed = false const job = cron({ pattern: '*/1 * * * * *', name: 'test', run() { executed = true } }) expect(job).toBeDefined() expect(typeof job.isRunning).toBe('function') expect(typeof job.stop).toBe('function') expect(typeof job.resume).toBe('function') }) it('should use predefined patterns', () => { const job = cron({ pattern: Patterns.EVERY_SECOND, name: 'test', run() { // 测试函数 } }) expect(job).toBeDefined() expect(job.isRunning()).toBe(true) }) it('should use function patterns', () => { const job = cron({ pattern: Patterns.everyMinutes(5), name: 'test', run() { // 测试函数 } }) expect(job).toBeDefined() }) it('should handle cron job lifecycle', async () => { const job = cron({ pattern: '*/1 * * * * *', name: 'test', run() { // 测试函数 } }) expect(job.isRunning()).toBe(true) // 测试停止功能 job.stop() expect(job.isRunning()).toBe(false) // 测试恢复功能 job.resume() // 注意:resume() 可能不会立即设置 isRunning 为 true // 这是 croner 的预期行为 }) it('should handle cron job with options', () => { const job = cron({ pattern: '*/1 * * * * *', name: 'test', maxRuns: 5, catch: true, run() { // 测试函数 } }) expect(job).toBeDefined() expect(job.isRunning()).toBe(true) }) }) ``` ## 特性 * ✅ **灵活调度**: 支持标准的 cron 语法和预定义模式 * ✅ **任务管理**: 提供启动、停止、恢复等生命周期管理 * ✅ **错误处理**: 支持错误捕获和最大执行次数限制 * ✅ **时区支持**: 支持自定义时区设置 * ✅ **性能优化**: 基于 croner 的高性能实现 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **易于集成**: 无缝集成到 Vafast 应用 ## 最佳实践 ### 1. 任务命名 ```typescript // 使用描述性的名称 const healthCheckCron = cron({ name: 'system-health-check', // 清晰的名称 pattern: '*/30 * * * * *', run(store) { checkSystemHealth() } }) ``` ### 2. 错误处理 ```typescript const criticalCron = cron({ name: 'critical-task', pattern: '0 * * * *', catch: true, // 捕获错误 run(store) { try { performCriticalTask() } catch (error) { console.error('关键任务执行失败:', error) // 发送告警 sendAlert('关键任务失败', error) } } }) ``` ### 3. 资源管理 ```typescript const resourceIntensiveCron = cron({ name: 'resource-task', pattern: '0 2 * * *', // 在低峰期执行 interval: 300, // 最小间隔5分钟 run(store) { // 检查系统负载 if (getSystemLoad() > 0.8) { console.log('系统负载过高,跳过本次执行') return } performResourceIntensiveTask() } }) ``` ### 4. 监控和日志 ```typescript const monitoredCron = cron({ name: 'monitored-task', pattern: '*/5 * * * *', run(store) { const startTime = Date.now() try { console.log('开始执行监控任务...') performTask() const duration = Date.now() - startTime console.log(`监控任务完成,耗时: ${duration}ms`) // 记录指标 recordMetrics('monitored-task', { duration, success: true }) } catch (error) { const duration = Date.now() - startTime console.error(`监控任务失败,耗时: ${duration}ms`, error) // 记录错误指标 recordMetrics('monitored-task', { duration, success: false, error: error.message }) } } }) ``` ## 注意事项 1. **任务执行**: cron 任务会在后台自动执行,无需手动启动 2. **错误处理**: 建议在 `run` 函数中添加适当的错误处理逻辑 3. **资源管理**: 长时间运行的任务应该检查系统资源状态 4. **时区设置**: 在生产环境中,建议明确设置时区 5. **任务依赖**: 复杂的任务依赖关系应该通过任务队列或工作流引擎处理 ## 相关链接 * [Cron 语法 - Wikipedia](https://en.wikipedia.org/wiki/Cron) * [Crontab Guru](https://crontab.guru/) - 在线 cron 表达式生成器 * [Croner 文档](https://github.com/hexagon/croner) - 底层 cron 库 * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/integrations/drizzle.md' --- # Drizzle 集成 Vafast 可以与 Drizzle ORM 无缝集成,为您提供类型安全的数据库操作和优秀的开发体验。 ## 安装依赖 ```bash bun add drizzle-orm @vafast/db bun add -D drizzle-kit @types/node ``` ## 数据库配置 ```typescript // src/db/config.ts import { drizzle } from 'drizzle-orm/bun-sqlite' import { Database } from 'bun:sqlite3' import { migrate } from 'drizzle-orm/bun-sqlite/migrator' // 创建数据库连接 const sqlite = new Database('sqlite.db') export const db = drizzle(sqlite) // 运行迁移 export async function runMigrations() { await migrate(db, { migrationsFolder: './drizzle' }) } ``` ## 定义数据库模式 ```typescript // src/db/schema.ts import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core' import { sql } from 'drizzle-orm' // 用户表 export const users = sqliteTable('users', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), email: text('email').notNull().unique(), name: text('name').notNull(), passwordHash: text('password_hash').notNull(), createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()), updatedAt: text('updated_at').notNull().$defaultFn(() => new Date().toISOString()) }) // 文章表 export const posts = sqliteTable('posts', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), title: text('title').notNull(), content: text('content').notNull(), authorId: text('author_id').notNull().references(() => users.id), published: integer('published', { mode: 'boolean' }).notNull().default(false), createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()), updatedAt: text('updated_at').notNull().$defaultFn(() => new Date().toISOString()) }) // 标签表 export const tags = sqliteTable('tags', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), name: text('name').notNull().unique(), createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()) }) // 文章标签关联表 export const postTags = sqliteTable('post_tags', { postId: text('post_id').notNull().references(() => posts.id), tagId: text('tag_id').notNull().references(() => tags.id) }, (table) => ({ pk: sql`primary key(${table.postId}, ${table.tagId})` })) // 导出类型 export type User = typeof users.$inferSelect export type NewUser = typeof users.$inferInsert export type Post = typeof posts.$inferSelect export type NewPost = typeof posts.$inferInsert export type Tag = typeof tags.$inferSelect export type NewTag = typeof tags.$inferInsert ``` ## 数据库查询函数 ```typescript // src/db/queries.ts import { eq, and, like, desc, asc, count } from 'drizzle-orm' import { db } from './config' import { users, posts, tags, postTags } from './schema' import type { NewUser, NewPost, NewTag } from './schema' // 用户相关查询 export const userQueries = { // 根据邮箱查找用户 async findByEmail(email: string) { const result = await db.select().from(users).where(eq(users.email, email)).limit(1) return result[0] || null }, // 根据ID查找用户 async findById(id: string) { const result = await db.select().from(users).where(eq(users.id, id)).limit(1) return result[0] || null }, // 创建用户 async create(userData: NewUser) { const result = await db.insert(users).values(userData).returning() return result[0] }, // 更新用户 async update(id: string, userData: Partial) { const result = await db .update(users) .set({ ...userData, updatedAt: new Date().toISOString() }) .where(eq(users.id, id)) .returning() return result[0] }, // 删除用户 async delete(id: string) { await db.delete(users).where(eq(users.id, id)) }, // 获取用户列表(分页) async findAll(page = 1, limit = 20) { const offset = (page - 1) * limit const [usersList, totalCount] = await Promise.all([ db.select().from(users).limit(limit).offset(offset).orderBy(desc(users.createdAt)), db.select({ count: count() }).from(users) ]) return { users: usersList, total: totalCount[0].count, page, limit, totalPages: Math.ceil(totalCount[0].count / limit) } } } // 文章相关查询 export const postQueries = { // 获取所有已发布的文章 async findPublished(page = 1, limit = 10) { const offset = (page - 1) * limit const [postsList, totalCount] = await Promise.all([ db .select({ id: posts.id, title: posts.title, content: posts.content, published: posts.published, createdAt: posts.createdAt, updatedAt: posts.updatedAt, author: { id: users.id, name: users.name, email: users.email } }) .from(posts) .innerJoin(users, eq(posts.authorId, users.id)) .where(eq(posts.published, true)) .limit(limit) .offset(offset) .orderBy(desc(posts.createdAt)), db.select({ count: count() }).from(posts).where(eq(posts.published, true)) ]) return { posts: postsList, total: totalCount[0].count, page, limit, totalPages: Math.ceil(totalCount[0].count / limit) } }, // 根据ID获取文章 async findById(id: string) { const result = await db .select({ id: posts.id, title: posts.title, content: posts.content, published: posts.published, createdAt: posts.createdAt, updatedAt: posts.updatedAt, author: { id: users.id, name: users.name, email: users.email } }) .from(posts) .innerJoin(users, eq(posts.authorId, users.id)) .where(eq(posts.id, id)) .limit(1) return result[0] || null }, // 创建文章 async create(postData: NewPost) { const result = await db.insert(posts).values(postData).returning() return result[0] }, // 更新文章 async update(id: string, postData: Partial) { const result = await db .update(posts) .set({ ...postData, updatedAt: new Date().toISOString() }) .where(eq(posts.id, id)) .returning() return result[0] }, // 删除文章 async delete(id: string) { await db.delete(posts).where(eq(posts.id, id)) }, // 搜索文章 async search(query: string, page = 1, limit = 10) { const offset = (page - 1) * limit const searchTerm = `%${query}%` const [postsList, totalCount] = await Promise.all([ db .select({ id: posts.id, title: posts.title, content: posts.content, published: posts.published, createdAt: posts.createdAt, updatedAt: posts.updatedAt, author: { id: users.id, name: users.name, email: users.email } }) .from(posts) .innerJoin(users, eq(posts.authorId, users.id)) .where( and( eq(posts.published, true), like(posts.title, searchTerm) ) ) .limit(limit) .offset(offset) .orderBy(desc(posts.createdAt)), db .select({ count: count() }) .from(posts) .where( and( eq(posts.published, true), like(posts.title, searchTerm) ) ) ]) return { posts: postsList, total: totalCount[0].count, page, limit, totalPages: Math.ceil(totalCount[0].count / limit) } } } // 标签相关查询 export const tagQueries = { // 获取所有标签 async findAll() { return await db.select().from(tags).orderBy(asc(tags.name)) }, // 根据ID获取标签 async findById(id: string) { const result = await db.select().from(tags).where(eq(tags.id, id)).limit(1) return result[0] || null }, // 创建标签 async create(tagData: NewTag) { const result = await db.insert(tags).values(tagData).returning() return result[0] }, // 删除标签 async delete(id: string) { await db.delete(tags).where(eq(tags.id, id)) } } ``` ## 在 Vafast 路由中使用 ```typescript // src/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' import { userQueries, postQueries, tagQueries } from './db/queries' import { hashPassword, verifyPassword } from './utils/auth' export const routes = defineRoutes([ // 用户认证路由 { method: 'POST', path: '/api/auth/register', handler: createHandler(async ({ body }) => { const { email, name, password } = body // 检查用户是否已存在 const existingUser = await userQueries.findByEmail(email) if (existingUser) { return { error: '用户已存在' }, { status: 400 } } // 创建新用户 const hashedPassword = await hashPassword(password) const newUser = await userQueries.create({ email, name, passwordHash: hashedPassword }) return { user: { id: newUser.id, email: newUser.email, name: newUser.name }, message: '注册成功' } }), body: Type.Object({ email: Type.String({ format: 'email' }), name: Type.String({ minLength: 1 }), password: Type.String({ minLength: 6 }) }) }, { method: 'POST', path: '/api/auth/login', handler: createHandler(async ({ body }) => { const { email, password } = body // 查找用户 const user = await userQueries.findByEmail(email) if (!user) { return { error: '用户不存在' }, { status: 401 } } // 验证密码 const isValidPassword = await verifyPassword(password, user.passwordHash) if (!isValidPassword) { return { error: '密码错误' }, { status: 401 } } return { user: { id: user.id, email: user.email, name: user.name }, message: '登录成功' } }), body: Type.Object({ email: Type.String({ format: 'email' }), password: Type.String({ minLength: 1 }) }) }, // 文章路由 { method: 'GET', path: '/api/posts', handler: createHandler(async ({ query }) => { const page = parseInt(query.page || '1') const limit = parseInt(query.limit || '10') const result = await postQueries.findPublished(page, limit) return result }), query: Type.Object({ page: Type.Optional(Type.String({ pattern: '^\\d+$' })), limit: Type.Optional(Type.String({ pattern: '^\\d+$' })) }) }, { method: 'GET', path: '/api/posts/:id', handler: createHandler(async ({ params }) => { const post = await postQueries.findById(params.id) if (!post) { return { error: '文章不存在' }, { status: 404 } } return { post } }), params: Type.Object({ id: Type.String() }) }, { method: 'POST', path: '/api/posts', handler: createHandler(async ({ body, request }) => { // 这里应该验证用户身份 const authorId = 'user-id-from-auth' // 从认证中间件获取 const newPost = await postQueries.create({ ...body, authorId }) return { post: newPost }, { status: 201 } }), body: Type.Object({ title: Type.String({ minLength: 1 }), content: Type.String({ minLength: 1 }), published: Type.Optional(Type.Boolean()) }) }, { method: 'PUT', path: '/api/posts/:id', handler: createHandler(async ({ params, body }) => { // 这里应该验证用户身份和权限 const updatedPost = await postQueries.update(params.id, body) if (!updatedPost) { return { error: '文章不存在' }, { status: 404 } } return { post: updatedPost } }), params: Type.Object({ id: Type.String() }), body: Type.Object({ title: Type.Optional(Type.String({ minLength: 1 })), content: Type.Optional(Type.String({ minLength: 1 })), published: Type.Optional(Type.Boolean()) }) }, { method: 'DELETE', path: '/api/posts/:id', handler: createHandler(async ({ params }) => { // 这里应该验证用户身份和权限 await postQueries.delete(params.id) return { message: '文章删除成功' } }), params: Type.Object({ id: Type.String() }) }, // 标签路由 { method: 'GET', path: '/api/tags', handler: createHandler(async () => { const tags = await tagQueries.findAll() return { tags } }) }, { method: 'POST', path: '/api/tags', handler: createHandler(async ({ body }) => { const newTag = await tagQueries.create(body) return { tag: newTag }, { status: 201 } }), body: Type.Object({ name: Type.String({ minLength: 1 }) }) } ]) ``` ## 数据库迁移 ```typescript // drizzle.config.ts import { defineConfig } from 'drizzle-kit' export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle', dialect: 'sqlite', dbCredentials: { url: 'sqlite.db' } }) ``` ```bash # 生成迁移文件 bun run drizzle-kit generate # 运行迁移 bun run drizzle-kit migrate # 查看数据库状态 bun run drizzle-kit studio ``` ## 事务处理 ```typescript // src/db/transactions.ts import { db } from './config' import { users, posts } from './schema' export async function createUserWithPost(userData: any, postData: any) { return await db.transaction(async (tx) => { // 创建用户 const [newUser] = await tx.insert(users).values(userData).returning() // 创建文章 const [newPost] = await tx.insert(posts).values({ ...postData, authorId: newUser.id }).returning() return { user: newUser, post: newPost } }) } ``` ## 连接池管理 ```typescript // src/db/pool.ts import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' import { migrate } from 'drizzle-orm/postgres-js/migrator' // PostgreSQL 连接池 const connectionString = process.env.DATABASE_URL! const client = postgres(connectionString, { max: 10 }) export const db = drizzle(client) // 运行迁移 export async function runMigrations() { await migrate(db, { migrationsFolder: './drizzle' }) } // 关闭连接池 export async function closePool() { await client.end() } ``` ## 性能优化 ```typescript // src/db/optimizations.ts import { eq, and, like, desc, asc, count, sql } from 'drizzle-orm' import { db } from './config' import { posts, users } from './schema' // 使用索引优化查询 export async function findPostsWithAuthorOptimized(page = 1, limit = 10) { const offset = (page - 1) * limit // 使用子查询优化 const result = await db .select({ id: posts.id, title: posts.title, content: posts.content, published: posts.published, createdAt: posts.createdAt, authorName: users.name, authorEmail: users.email }) .from(posts) .innerJoin(users, eq(posts.authorId, users.id)) .where(eq(posts.published, true)) .limit(limit) .offset(offset) .orderBy(desc(posts.createdAt)) return result } // 批量操作 export async function batchCreatePosts(postsData: any[]) { return await db.insert(posts).values(postsData).returning() } // 使用原生 SQL 进行复杂查询 export async function findPostsByTag(tagName: string) { const result = await db.execute(sql` SELECT p.*, u.name as author_name FROM posts p INNER JOIN users u ON p.author_id = u.id INNER JOIN post_tags pt ON p.id = pt.post_id INNER JOIN tags t ON pt.tag_id = t.id WHERE t.name = ${tagName} AND p.published = true ORDER BY p.created_at DESC `) return result } ``` ## 测试 ```typescript // src/db/__tests__/queries.test.ts import { describe, expect, it, beforeEach, afterEach } from 'bun:test' import { db } from '../config' import { userQueries, postQueries } from '../queries' import { users, posts } from '../schema' describe('Database Queries', () => { beforeEach(async () => { // 清理测试数据 await db.delete(posts) await db.delete(users) }) afterEach(async () => { // 清理测试数据 await db.delete(posts) await db.delete(users) }) describe('User Queries', () => { it('should create and find user', async () => { const userData = { email: 'test@example.com', name: 'Test User', passwordHash: 'hashed_password' } const newUser = await userQueries.create(userData) expect(newUser).toBeDefined() expect(newUser.email).toBe(userData.email) const foundUser = await userQueries.findByEmail(userData.email) expect(foundUser).toBeDefined() expect(foundUser?.id).toBe(newUser.id) }) }) describe('Post Queries', () => { it('should create and find post', async () => { // 先创建用户 const user = await userQueries.create({ email: 'author@example.com', name: 'Author', passwordHash: 'hashed_password' }) const postData = { title: 'Test Post', content: 'Test content', authorId: user.id, published: true } const newPost = await postQueries.create(postData) expect(newPost).toBeDefined() expect(newPost.title).toBe(postData.title) const foundPost = await postQueries.findById(newPost.id) expect(foundPost).toBeDefined() expect(foundPost?.title).toBe(postData.title) }) }) }) ``` ## 最佳实践 1. **类型安全**:充分利用 Drizzle 的类型推断功能 2. **查询优化**:使用适当的索引和查询策略 3. **事务管理**:在需要原子性的操作中使用事务 4. **连接池**:在生产环境中使用连接池管理数据库连接 5. **迁移管理**:使用 Drizzle Kit 管理数据库模式变更 6. **测试覆盖**:为数据库操作编写完整的测试 7. **性能监控**:监控查询性能并优化慢查询 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [Drizzle 文档](https://orm.drizzle.team) - Drizzle ORM 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/integrations/expo.md' --- # Expo 集成 Vafast 可以与 Expo React Native 应用无缝集成,为您提供强大的后端 API 和跨平台移动应用开发体验。 ## 项目结构 ``` my-vafast-expo-app/ ├── app/ # Expo Router 应用 ├── src/ │ ├── components/ # React Native 组件 │ ├── screens/ # 应用屏幕 │ ├── api/ # Vafast API 客户端 │ │ ├── client.ts # API 客户端配置 │ │ ├── types.ts # 类型定义 │ │ └── hooks.ts # React Hooks │ └── lib/ # 共享库 ├── package.json ├── app.json └── tsconfig.json ``` ## 安装依赖 ```bash bun add vafast @vafast/api-client bun add -D @types/react @types/react-native ``` ## 创建 Vafast API 客户端 ```typescript // src/api/client.ts import { VafastApiClient } from '@vafast/api-client' const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:3000' export const apiClient = new VafastApiClient({ baseURL: API_BASE_URL, timeout: 10000, retries: 3, // 请求拦截器 interceptors: { request: (config) => { // 添加认证头 const token = getAuthToken() if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, response: (response) => { // 处理响应 return response }, error: (error) => { // 处理错误 if (error.status === 401) { // 清除认证状态并重定向到登录 clearAuthToken() // 重定向到登录页面 } return Promise.reject(error) } } }) // 认证 token 管理 function getAuthToken(): string | null { // 从 AsyncStorage 或其他存储中获取 token return null } function clearAuthToken() { // 清除存储的 token } ``` ## 类型定义 ```typescript // src/api/types.ts export interface User { id: string name: string email: string avatar?: string createdAt: string } export interface Post { id: string title: string content: string authorId: string author: User createdAt: string updatedAt: string } export interface CreatePostRequest { title: string content: string } export interface LoginRequest { email: string password: string } export interface LoginResponse { user: User token: string } export interface ApiResponse { data: T message?: string } export interface ApiError { error: string message: string statusCode: number } ``` ## API 服务函数 ```typescript // src/api/services.ts import { apiClient } from './client' import type { User, Post, CreatePostRequest, LoginRequest, LoginResponse, ApiResponse } from './types' export const authService = { async login(credentials: LoginRequest): Promise { const response = await apiClient.post('/auth/login', credentials) return response.data }, async register(userData: Omit): Promise { const response = await apiClient.post('/auth/register', userData) return response.data }, async logout(): Promise { await apiClient.post('/auth/logout') }, async getProfile(): Promise { const response = await apiClient.get>('/auth/profile') return response.data.data } } export const postService = { async getPosts(page = 1, limit = 10): Promise { const response = await apiClient.get>('/posts', { page, limit }) return response.data.data }, async getPost(id: string): Promise { const response = await apiClient.get>(`/posts/${id}`) return response.data.data }, async createPost(postData: CreatePostRequest): Promise { const response = await apiClient.post>('/posts', postData) return response.data.data }, async updatePost(id: string, postData: Partial): Promise { const response = await apiClient.put>(`/posts/${id}`, postData) return response.data.data }, async deletePost(id: string): Promise { await apiClient.delete(`/posts/${id}`) } } export const userService = { async getUsers(page = 1, limit = 20): Promise { const response = await apiClient.get>('/users', { page, limit }) return response.data.data }, async getUser(id: string): Promise { const response = await apiClient.get>(`/users/${id}`) return response.data.data }, async updateProfile(userData: Partial): Promise { const response = await apiClient.put>('/users/profile', userData) return response.data.data } } ``` ## React Hooks ```typescript // src/api/hooks.ts import { useState, useEffect, useCallback } from 'react' import { authService, postService, userService } from './services' import type { User, Post, CreatePostRequest, LoginRequest } from './types' // 认证 Hook export const useAuth = () => { const [user, setUser] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const login = useCallback(async (credentials: LoginRequest) => { try { setLoading(true) setError(null) const response = await authService.login(credentials) setUser(response.user) // 保存 token 到存储 return response } catch (err: any) { setError(err.message || '登录失败') throw err } finally { setLoading(false) } }, []) const logout = useCallback(async () => { try { await authService.logout() setUser(null) // 清除存储的 token } catch (err) { console.error('登出失败:', err) } }, []) const checkAuth = useCallback(async () => { try { setLoading(true) const profile = await authService.getProfile() setUser(profile) } catch (err) { setUser(null) } finally { setLoading(false) } }, []) useEffect(() => { checkAuth() }, [checkAuth]) return { user, loading, error, login, logout, checkAuth, isAuthenticated: !!user } } // 文章列表 Hook export const usePosts = (page = 1, limit = 10) => { const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [hasMore, setHasMore] = useState(true) const fetchPosts = useCallback(async (pageNum = page) => { try { setLoading(true) setError(null) const newPosts = await postService.getPosts(pageNum, limit) if (pageNum === 1) { setPosts(newPosts) } else { setPosts(prev => [...prev, ...newPosts]) } setHasMore(newPosts.length === limit) } catch (err: any) { setError(err.message || '获取文章失败') } finally { setLoading(false) } }, [page, limit]) const refresh = useCallback(() => { fetchPosts(1) }, [fetchPosts]) const loadMore = useCallback(() => { if (!loading && hasMore) { fetchPosts(Math.floor(posts.length / limit) + 1) } }, [loading, hasMore, posts.length, limit, fetchPosts]) useEffect(() => { fetchPosts(1) }, [fetchPosts]) return { posts, loading, error, hasMore, refresh, loadMore } } // 单个文章 Hook export const usePost = (id: string) => { const [post, setPost] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const fetchPost = useCallback(async () => { try { setLoading(true) setError(null) const postData = await postService.getPost(id) setPost(postData) } catch (err: any) { setError(err.message || '获取文章失败') } finally { setLoading(false) } }, [id]) useEffect(() => { if (id) { fetchPost() } }, [id, fetchPost]) return { post, loading, error, refresh: fetchPost } } ``` ## 在组件中使用 ### 登录屏幕 ```typescript // src/screens/LoginScreen.tsx import React, { useState } from 'react' import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native' import { useAuth } from '../api/hooks' export default function LoginScreen() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const { login, loading, error } = useAuth() const handleLogin = async () => { if (!email || !password) { Alert.alert('错误', '请填写邮箱和密码') return } try { await login({ email, password }) // 登录成功后导航到主页面 } catch (err: any) { Alert.alert('登录失败', err.message) } } return ( 登录 {error && {error}} {loading ? '登录中...' : '登录'} ) } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', padding: 20, backgroundColor: '#fff' }, title: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', marginBottom: 30 }, input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 15, marginBottom: 15, fontSize: 16 }, button: { backgroundColor: '#007AFF', padding: 15, borderRadius: 8, alignItems: 'center' }, buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' }, error: { color: 'red', textAlign: 'center', marginBottom: 15 } }) ``` ### 文章列表屏幕 ```typescript // src/screens/PostsScreen.tsx import React from 'react' import { View, Text, FlatList, TouchableOpacity, StyleSheet, RefreshControl } from 'react-native' import { usePosts } from '../api/hooks' import type { Post } from '../api/types' export default function PostsScreen({ navigation }: any) { const { posts, loading, error, hasMore, refresh, loadMore } = usePosts() const renderPost = ({ item }: { item: Post }) => ( navigation.navigate('PostDetail', { id: item.id })} > {item.title} {item.content} by {item.author.name} {new Date(item.createdAt).toLocaleDateString()} ) if (error) { return ( {error} 重试 ) } return ( item.id} refreshControl={ } onEndReached={loadMore} onEndReachedThreshold={0.1} ListFooterComponent={ hasMore ? ( 加载更多... ) : null } /> ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5' }, centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }, postCard: { backgroundColor: '#fff', margin: 10, padding: 15, borderRadius: 8, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3 }, postTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 8 }, postContent: { fontSize: 14, color: '#666', marginBottom: 12 }, postMeta: { flexDirection: 'row', justifyContent: 'space-between' }, author: { fontSize: 12, color: '#999' }, date: { fontSize: 12, color: '#999' }, error: { color: 'red', textAlign: 'center', marginBottom: 20 }, retryButton: { backgroundColor: '#007AFF', padding: 10, borderRadius: 6 }, retryButtonText: { color: '#fff', fontSize: 14 }, loadingMore: { padding: 20, alignItems: 'center' } }) ``` ## 环境配置 ```typescript // app.config.ts import { ExpoConfig, ConfigContext } from 'expo/config' export default ({ config }: ConfigContext): ExpoConfig => ({ ...config, name: 'Vafast Expo App', slug: 'vafast-expo-app', extra: { apiUrl: process.env.EXPO_PUBLIC_API_URL || 'http://localhost:3000' }, plugins: [ 'expo-router' ] }) ``` ## 错误处理 ```typescript // src/api/errorHandler.ts import { Alert } from 'react-native' export class ApiError extends Error { constructor( public statusCode: number, public message: string, public originalError?: any ) { super(message) this.name = 'ApiError' } } export const handleApiError = (error: any, showAlert = true) => { let message = '发生未知错误' let statusCode = 500 if (error instanceof ApiError) { message = error.message statusCode = error.statusCode } else if (error.response) { message = error.response.data?.message || '请求失败' statusCode = error.response.status } else if (error.request) { message = '网络连接失败,请检查网络设置' } else if (error.message) { message = error.message } if (showAlert) { Alert.alert('错误', message) } return new ApiError(statusCode, message, error) } ``` ## 最佳实践 1. **类型安全**:使用 TypeScript 确保前后端类型一致 2. **错误处理**:实现统一的错误处理机制 3. **状态管理**:使用 React Hooks 管理 API 状态 4. **缓存策略**:实现适当的缓存和离线支持 5. **网络状态**:处理网络连接状态变化 6. **性能优化**:使用 FlatList 优化长列表性能 7. **用户体验**:提供加载状态和错误反馈 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [Expo 文档](https://docs.expo.dev) - Expo 官方文档 * [React Native 文档](https://reactnative.dev) - React Native 官方文档 * [API 客户端](/api-client/overview) - Vafast API 客户端指南 * [类型验证](/patterns/type) - 了解类型验证系统 --- --- url: 'https://vafast.dev/middleware/graphql-yoga.md' --- # GraphQL Yoga 中间件 此中间件将 GraphQL Yoga 集成到 Vafast 中 安装方法: ```bash bun add @vafastjs/graphql-yoga ``` 然后使用它: ```typescript import { Vafast } from 'vafast' import { yoga } from '@vafastjs/graphql-yoga' const app = new Vafast() .use( yoga({ typeDefs: /* GraphQL */ ` type Query { hi: String } `, resolvers: { Query: { hi: () => 'Hello from Vafast' } } }) ) .listen(3000) ``` 在浏览器中访问 `/graphql`(GET 请求)将显示一个 GraphiQL 实例,用于支持 GraphQL 的 Vafast 服务器。 可选:您还可以安装自定义版本的可选对等依赖项: ```bash bun add graphql graphql-yoga ``` ## 解析器 Vafast 使用 [Mobius](https://github.com/saltyaom/mobius) 自动从 **typeDefs** 字段推断类型,允许您在输入 **resolver** 类型时获得完全的类型安全和自动完成。 ## 上下文 您可以通过添加 **context** 为解析器函数添加自定义上下文 ```ts import { Vafast } from 'vafast' import { yoga } from '@vafastjs/graphql-yoga' const app = new Vafast() .use( yoga({ typeDefs: /* GraphQL */ ` type Query { hi: String } `, context: { name: 'Mobius' }, // 如果上下文是一个函数,它不出现在这里 // 由于某种原因,它不会推断上下文类型 useContext(_) {}, resolvers: { Query: { hi: async (parent, args, context) => context.name } } }) ) .listen(3000) ``` ## 配置 此中间件扩展了 [GraphQL Yoga 的 createYoga 选项,请参考 GraphQL Yoga 文档](https://the-guild.dev/graphql/yoga-server/docs),并将 `schema` 配置内联到根部。 以下是中间件接受的配置 ### path @default `/graphql` 公开 GraphQL 处理程序的端点 --- --- url: 'https://vafast.dev/middleware/helmet.md' --- # Helmet 中间件 用于 [Vafast](https://github.com/vafastjs/vafast) 的安全头中间件,通过添加各种 HTTP 安全头部来增强 Web 应用的安全性。 ## 安装 通过以下命令安装: ```bash bun add @vafast/helmet ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet } from '@vafast/helmet' // 创建安全头中间件 const helmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], }, frameOptions: "DENY", xssProtection: true, referrerPolicy: "strict-origin-when-cross-origin", }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World with Security Headers!' } }), middleware: [helmet], }, { method: 'GET', path: '/api/data', handler: createHandler(() => { return { data: 'Protected API endpoint' } }), middleware: [helmet], }, ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req), } ``` ## 配置选项 ### SecurityConfig ```typescript interface SecurityConfig { /** Content Security Policy 配置 */ csp?: CSPConfig /** 启用或禁用 X-Frame-Options (DENY, SAMEORIGIN, ALLOW-FROM) */ frameOptions?: "DENY" | "SAMEORIGIN" | "ALLOW-FROM" /** 启用或禁用 XSS Protection */ xssProtection?: boolean /** 启用或禁用 DNS Prefetch Control */ dnsPrefetch?: boolean /** 配置 Referrer Policy */ referrerPolicy?: | "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url" /** 配置 Permissions Policy */ permissionsPolicy?: Record /** 配置 HSTS (HTTP Strict Transport Security) */ hsts?: HSTSConfig /** 启用或禁用 Cross-Origin Resource Policy */ corp?: "same-origin" | "same-site" | "cross-origin" /** 启用或禁用 Cross-Origin Opener Policy */ coop?: "unsafe-none" | "same-origin-allow-popups" | "same-origin" /** 配置 Report-To 头部 */ reportTo?: ReportToConfig[] /** 自定义头部 */ customHeaders?: Record } ``` ### CSPConfig ```typescript interface CSPConfig { /** 默认源指令 */ defaultSrc?: string[] /** 脚本源指令 */ scriptSrc?: string[] /** 样式源指令 */ styleSrc?: string[] /** 图片源指令 */ imgSrc?: string[] /** 字体源指令 */ fontSrc?: string[] /** 连接源指令 */ connectSrc?: string[] /** 框架源指令 */ frameSrc?: string[] /** 对象源指令 */ objectSrc?: string[] /** 基础 URI 指令 */ baseUri?: string[] /** 报告 URI 指令 */ reportUri?: string /** 为脚本和样式标签使用 nonce */ useNonce?: boolean /** 仅报告模式 */ reportOnly?: boolean } ``` ### HSTSConfig ```typescript interface HSTSConfig { /** 最大年龄(秒) */ maxAge?: number /** 包含子域名 */ includeSubDomains?: boolean /** 预加载 */ preload?: boolean } ``` ### ReportToConfig ```typescript interface ReportToConfig { /** 端点组名 */ group: string /** 端点配置的最大年龄(秒) */ maxAge: number /** 发送报告的端点 */ endpoints: Array<{ url: string priority?: number weight?: number }> /** 在报告中包含子域名 */ includeSubdomains?: boolean } ``` ## 权限常量 中间件提供了一些常用的权限常量: ```typescript import { permission } from '@vafast/helmet' const helmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], // "'self'" scriptSrc: [permission.SELF, permission.UNSAFE_INLINE], // "'self'" "'unsafe-inline'" imgSrc: [permission.SELF, permission.DATA, permission.BLOB], // "'self'" "data:" "blob:" objectSrc: [permission.NONE], // "'none'" connectSrc: [permission.HTTPS], // "https:" } }) ``` ### 可用权限 | 常量 | 值 | 描述 | |------|-----|------| | `permission.SELF` | `"'self'"` | 允许同源资源 | | `permission.UNSAFE_INLINE` | `"'unsafe-inline'"` | 允许内联脚本和样式 | | `permission.HTTPS` | `"https:"` | 允许 HTTPS 资源 | | `permission.DATA` | `"data:"` | 允许 data URI | | `permission.BLOB` | `"blob:"` | 允许 blob URI | | `permission.NONE` | `"'none'"` | 禁止所有资源 | ## 使用模式 ### 1. 基本安全配置 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet } from '@vafast/helmet' // 使用默认安全配置 const helmet = vafastHelmet() const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Secure by default' } }), middleware: [helmet], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 2. 自定义 CSP 配置 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet, permission } from '@vafast/helmet' const helmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [ permission.SELF, permission.UNSAFE_INLINE, 'https://cdn.jsdelivr.net', 'https://unpkg.com' ], styleSrc: [ permission.SELF, permission.UNSAFE_INLINE, 'https://fonts.googleapis.com' ], fontSrc: [ permission.SELF, 'https://fonts.gstatic.com' ], imgSrc: [ permission.SELF, permission.DATA, permission.BLOB, 'https:' ], connectSrc: [ permission.SELF, 'https://api.example.com', 'wss://ws.example.com' ], frameSrc: [permission.SELF], objectSrc: [permission.NONE], baseUri: [permission.SELF], reportUri: '/csp-report' } }) const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Custom CSP configuration' } }), middleware: [helmet], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 3. 严格的 CSP 配置 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet, permission } from '@vafast/helmet' const strictHelmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [permission.SELF], // 不允许内联脚本 styleSrc: [permission.SELF], // 不允许内联样式 imgSrc: [permission.SELF], fontSrc: [permission.SELF], connectSrc: [permission.SELF], frameSrc: [permission.NONE], // 禁止所有框架 objectSrc: [permission.NONE], // 禁止所有对象 baseUri: [permission.SELF], useNonce: true // 启用 nonce 支持 }, frameOptions: 'DENY', xssProtection: true, referrerPolicy: 'strict-origin', corp: 'same-origin', coop: 'same-origin' }) const routes = [ { method: 'GET', path: '/secure', handler: createHandler(() => { return { message: 'Strict security configuration' } }), middleware: [strictHelmet], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 4. 生产环境 HSTS 配置 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet } from '@vafast/helmet' const productionHelmet = vafastHelmet({ hsts: { maxAge: 31536000, // 1 年 includeSubDomains: true, // 包含子域名 preload: true // 预加载到浏览器 }, referrerPolicy: 'strict-origin-when-cross-origin', permissionsPolicy: { camera: [], microphone: [], geolocation: [], 'interest-cohort': [], // 禁用 FLoC 'payment': [], 'usb': [] } }) const routes = [ { method: 'GET', path: '/api', handler: createHandler(() => { return { message: 'Production security headers' } }), middleware: [productionHelmet], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 5. 自定义头部和报告配置 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet } from '@vafast/helmet' const advancedHelmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], reportUri: '/csp-report' }, reportTo: [ { group: 'csp-endpoint', maxAge: 86400, // 24 小时 endpoints: [ { url: 'https://reports.example.com/csp', priority: 1, weight: 1 } ], includeSubdomains: true } ], customHeaders: { 'X-Custom-Security': 'enabled', 'X-Security-Level': 'high', 'X-Content-Security': 'strict' } }) const routes = [ { method: 'GET', path: '/advanced', handler: createHandler(() => { return { message: 'Advanced security configuration' } }), middleware: [advancedHelmet], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 6. 条件安全配置 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet } from '@vafast/helmet' // 根据环境选择不同的安全配置 const getSecurityConfig = () => { if (process.env.NODE_ENV === 'production') { return { csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], styleSrc: ["'self'"], imgSrc: ["'self'", "https:"], connectSrc: ["'self'", "https://api.example.com"] }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, frameOptions: 'DENY', xssProtection: true } } else { // 开发环境:更宽松的配置 return { csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "http://localhost:*", "ws://localhost:*"] }, frameOptions: 'SAMEORIGIN', xssProtection: false } } } const helmet = vafastHelmet(getSecurityConfig()) const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Environment-aware security', environment: process.env.NODE_ENV || 'development' } }), middleware: [helmet], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { vafastHelmet, permission } from '@vafast/helmet' // 创建不同安全级别的中间件 const basicHelmet = vafastHelmet({ frameOptions: 'DENY', xssProtection: true, referrerPolicy: 'strict-origin-when-cross-origin' }) const standardHelmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [permission.SELF, permission.UNSAFE_INLINE], styleSrc: [permission.SELF, permission.UNSAFE_INLINE], imgSrc: [permission.SELF, permission.DATA, permission.BLOB], fontSrc: [permission.SELF], connectSrc: [permission.SELF], frameSrc: [permission.SELF], objectSrc: [permission.NONE], baseUri: [permission.SELF] }, frameOptions: 'DENY', xssProtection: true, referrerPolicy: 'strict-origin-when-cross-origin', dnsPrefetch: false, corp: 'same-origin', coop: 'same-origin' }) const strictHelmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [permission.SELF], styleSrc: [permission.SELF], imgSrc: [permission.SELF], fontSrc: [permission.SELF], connectSrc: [permission.SELF], frameSrc: [permission.NONE], objectSrc: [permission.NONE], baseUri: [permission.SELF], useNonce: true }, frameOptions: 'DENY', xssProtection: true, referrerPolicy: 'strict-origin', dnsPrefetch: false, corp: 'same-origin', coop: 'same-origin', permissionsPolicy: { camera: [], microphone: [], geolocation: [], 'interest-cohort': [], payment: [], usb: [], magnetometer: [], gyroscope: [], accelerometer: [] } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Security Headers API', endpoints: [ '/basic - 基本安全头部', '/standard - 标准安全配置', '/strict - 严格安全配置', '/custom - 自定义安全配置' ] } }) }, { method: 'GET', path: '/basic', handler: createHandler(() => { return { message: 'Basic security headers applied', security: 'Basic level' } }), middleware: [basicHelmet], }, { method: 'GET', path: '/standard', handler: createHandler(() => { return { message: 'Standard security configuration applied', security: 'Standard level', csp: 'Enabled', frameOptions: 'DENY', xssProtection: 'Enabled' } }), middleware: [standardHelmet], }, { method: 'GET', path: '/strict', handler: createHandler(() => { return { message: 'Strict security configuration applied', security: 'Strict level', csp: 'Strict mode', frameOptions: 'DENY', xssProtection: 'Enabled', permissionsPolicy: 'Restricted' } }), middleware: [strictHelmet], }, { method: 'GET', path: '/custom', handler: createHandler(() => { return { message: 'Custom security configuration applied', security: 'Custom level' } }), middleware: [ vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [permission.SELF, 'https://cdn.example.com'], styleSrc: [permission.SELF, 'https://fonts.googleapis.com'], imgSrc: [permission.SELF, 'https:', permission.DATA], connectSrc: [permission.SELF, 'https://api.example.com'], reportUri: '/csp-report' }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, customHeaders: { 'X-Security-Version': '2.0', 'X-Content-Security': 'enabled' } }) ], }, { method: 'POST', path: '/csp-report', handler: createHandler(async (req: Request) => { const report = await req.json() console.log('CSP Violation Report:', report) // 这里可以记录到日志系统或数据库 // logCSPViolation(report) return { message: 'CSP report received', timestamp: new Date().toISOString() } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req), } console.log('🚀 Vafast Security Headers API 服务器启动成功!') console.log('🔒 基本安全: GET /basic') console.log('🛡️ 标准安全: GET /standard') console.log('⚡ 严格安全: GET /strict') console.log('🎯 自定义安全: GET /custom') console.log('📊 CSP 报告: POST /csp-report') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { vafastHelmet, permission } from '@vafast/helmet' describe('Vafast Helmet Security Headers', () => { it('should add basic security headers', async () => { const helmet = vafastHelmet({ frameOptions: 'DENY', xssProtection: true, }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World with Security Headers!' } }), middleware: [helmet], }, ]) const res = await app.fetch(new Request('http://localhost/')) expect(res.headers.get('X-Frame-Options')).toBe('DENY') expect(res.headers.get('X-XSS-Protection')).toBe('1; mode=block') expect(res.headers.get('X-Content-Type-Options')).toBe('nosniff') }) it('should add CSP headers', async () => { const helmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [permission.SELF, permission.UNSAFE_INLINE], styleSrc: [permission.SELF, permission.UNSAFE_INLINE], }, }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World!' } }), middleware: [helmet], }, ]) const res = await app.fetch(new Request('http://localhost/')) const csp = res.headers.get('Content-Security-Policy') expect(csp).toContain("default-src 'self'") expect(csp).toContain("script-src 'self' 'unsafe-inline'") expect(csp).toContain("style-src 'self' 'unsafe-inline'") }) it('should handle custom headers', async () => { const helmet = vafastHelmet({ customHeaders: { 'X-Custom-Header': 'custom-value', 'X-Another-Header': 'another-value', }, }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World!' } }), middleware: [helmet], }, ]) const res = await app.fetch(new Request('http://localhost/')) expect(res.headers.get('X-Custom-Header')).toBe('custom-value') expect(res.headers.get('X-Another-Header')).toBe('another-value') }) it('should handle HSTS headers in production', async () => { // 模拟生产环境 const originalEnv = process.env.NODE_ENV process.env.NODE_ENV = 'production' const helmet = vafastHelmet({ hsts: { maxAge: 31536000, includeSubDomains: true, preload: true, }, }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World!' } }), middleware: [helmet], }, ]) const res = await app.fetch(new Request('http://localhost/')) expect(res.headers.get('Strict-Transport-Security')).toBe('max-age=31536000; includeSubDomains; preload') // 恢复环境变量 process.env.NODE_ENV = originalEnv }) it('should handle nonce generation', async () => { const helmet = vafastHelmet({ csp: { defaultSrc: [permission.SELF], scriptSrc: [permission.SELF], useNonce: true, }, }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Hello World!' } }), middleware: [helmet], }, ]) const res = await app.fetch(new Request('http://localhost/')) const csp = res.headers.get('Content-Security-Policy') const nonce = res.headers.get('X-Nonce') expect(csp).toContain("'nonce-") expect(nonce).toBeTruthy() expect(nonce?.length).toBeGreaterThan(10) }) }) ``` ## 特性 * ✅ **内容安全策略 (CSP)**: 防止 XSS 攻击和资源注入 * ✅ **HTTP 严格传输安全 (HSTS)**: 强制 HTTPS 连接 * ✅ **XSS 保护**: 启用浏览器内置的 XSS 保护 * ✅ **框架选项**: 防止点击劫持攻击 * ✅ **引用策略**: 控制引用信息的泄露 * ✅ **权限策略**: 限制浏览器功能的访问 * ✅ **跨域策略**: 控制跨域资源的访问 * ✅ **报告机制**: 支持 CSP 违规报告 * ✅ **Nonce 支持**: 安全的内联脚本和样式支持 * ✅ **性能优化**: 高效的头部分析和生成 * ✅ **类型安全**: 完整的 TypeScript 类型支持 ## 最佳实践 ### 1. 渐进式安全策略 ```typescript // 开发环境:宽松配置 const devHelmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], } }) // 生产环境:严格配置 const prodHelmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], styleSrc: ["'self'"], useNonce: true }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true } }) ``` ### 2. CSP 监控和报告 ```typescript const monitoredHelmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], reportUri: '/csp-report', reportOnly: false // 生产环境设为 false }, reportTo: [ { group: 'csp-violations', maxAge: 86400, endpoints: [ { url: 'https://reports.example.com/csp', priority: 1 } ] } ] }) ``` ### 3. 资源白名单管理 ```typescript const resourceHelmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: [ "'self'", 'https://cdn.jsdelivr.net', 'https://unpkg.com' ], styleSrc: [ "'self'", 'https://fonts.googleapis.com', 'https://cdn.jsdelivr.net' ], fontSrc: [ "'self'", 'https://fonts.gstatic.com' ], imgSrc: [ "'self'", 'data:', 'https:', 'blob:' ], connectSrc: [ "'self'", 'https://api.example.com', 'wss://ws.example.com' ] } }) ``` ### 4. 安全头部组合 ```typescript const comprehensiveHelmet = vafastHelmet({ csp: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], styleSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "https://api.example.com"], frameSrc: ["'self'"], objectSrc: ["'none'"], baseUri: ["'self'"] }, frameOptions: 'DENY', xssProtection: true, referrerPolicy: 'strict-origin-when-cross-origin', dnsPrefetch: false, corp: 'same-origin', coop: 'same-origin', permissionsPolicy: { camera: [], microphone: [], geolocation: [], 'interest-cohort': [], payment: [], usb: [] } }) ``` ## 注意事项 1. **CSP 配置**: 过于严格的 CSP 可能会破坏现有功能,建议逐步收紧 2. **HSTS 设置**: 一旦启用 HSTS,很难撤销,确保 HTTPS 配置正确 3. **性能影响**: 安全头部会增加响应大小,但影响通常很小 4. **兼容性**: 某些安全头部在旧浏览器中可能不被支持 5. **测试**: 在生产环境部署前,充分测试安全配置 ## 相关链接 * [Content Security Policy - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) * [HTTP Strict Transport Security - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) * [X-Frame-Options - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) * [Referrer Policy - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) * [Permissions Policy - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/middleware/html.md' --- # HTML 中间件 允许您在 Vafast 服务器中使用 [JSX](#jsx) 和 HTML,并提供适当的头部和支持。 ## ✨ 特性 * 🚀 **快速 HTML 渲染** - 高效的 HTML 响应处理 * 🔧 **简单集成** - 与 Vafast 的简单中间件集成 * 📝 **JSX 支持** - 支持 JSX 元素和流式渲染 * 🎯 **自动检测** - 自动检测和处理 HTML 响应 * ⚡ **流式支持** - 内置流式 HTML 响应 ## 安装 通过以下命令安装: ```bash bun add @vafast/html ``` ## 快速开始 ```typescript import { Server, createHandler } from 'vafast' import { html } from '@vafast/html' // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return ` Hello World

Hello from Vafast!

` }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用 HTML 中间件 export default { fetch: (req: Request) => { // 应用 HTML 中间件 return html()(req, () => server.fetch(req)) } } ``` ## 基本用法 ### 基础 HTML 响应 ```typescript import { html } from '@vafast/html' // 使用 HTML 中间件 app.use(html()) app.get('/page', (req) => { return req.html.html(` My Page

Welcome!

This is a simple HTML page.

`) }) ``` ### JSX 支持 Vafast HTML 基于 [@kitajs/html](https://github.com/kitajs/html),允许我们在编译时将 JSX 定义为字符串,以实现高性能。 需要使用 JSX 的文件名称应以后缀 **"x"** 结尾: * .js -> .jsx * .ts -> .tsx 要注册 TypeScript 类型,请将以下内容添加到 **tsconfig.json**: ```jsonc // tsconfig.json { "compilerOptions": { "jsx": "react", "jsxFactory": "Html.createElement", "jsxFragmentFactory": "Html.Fragment" } } ``` 使用 JSX 的示例: ```tsx import { Server, createHandler } from 'vafast' import { html, Html } from '@vafast/html' const routes = [ { method: 'GET', path: '/', handler: createHandler(() => ( Hello World

Hello World

)) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return html()(req, () => server.fetch(req)) } } ``` ### 流式响应 ```typescript app.get('/stream', (req) => { return req.html.stream(({ id }) => ` Streaming

Stream ID: ${id}

Generated at: ${new Date().toISOString()}

`, { timestamp: Date.now() }) }) ``` ## API 参考 ### `html(options?: HtmlOptions)` 创建一个具有指定选项的 HTML 中间件。 #### 选项 * `contentType` - HTML 响应的 Content-Type 头部(默认:`"text/html; charset=utf8"`) * `autoDetect` - 自动检测 HTML 响应(默认:`true`) * `autoDoctype` - 自动为 HTML 添加 DOCTYPE(默认:`true`) * `isHtml` - 检测 HTML 内容的自定义函数 ### `req.html.html(value: string | JSX.Element)` 渲染 HTML 内容并返回一个 Response 对象。 ### `req.html.stream(value: Function, args: T)` 创建一个流式 HTML 响应。 ## 配置选项 ### HtmlOptions ```typescript interface HtmlOptions { /** * 响应的内容类型 * @default 'text/html; charset=utf8' */ contentType?: string /** * 是否自动检测 HTML 内容并设置内容类型 * @default true */ autoDetect?: boolean /** * 是否在响应开头是 时自动添加 ,如果未找到 * * 使用 'full' 还可以在没有此中间件的响应中自动添加文档类型 * * @default true */ autoDoctype?: boolean | 'full' /** * 用于检测字符串是否为 HTML 的函数 * * 默认实现是如果长度大于 3,且以 < 开头并以 > 结尾 * * 没有真正的方法来验证 HTML,所以这只是一个最佳猜测 * @default isHtml */ isHtml?: (this: void, value: string) => boolean } ``` ## 高级用法 ### 自定义选项 ```typescript app.use(html({ contentType: "text/html; charset=UTF-8", autoDetect: true, autoDoctype: false })) ``` ### XSS 防护 Vafast HTML 基于 Kita HTML 中间件,在编译时检测可能的 XSS 攻击。 您可以使用专用的 `safe` 属性来清理用户值,以防止 XSS 漏洞: ```tsx import { Server, createHandler } from 'vafast' import { html, Html } from '@vafast/html' const routes = [ { method: 'POST', path: '/', handler: createHandler(({ body }) => ( Hello World

{body}

)) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return html()(req, () => server.fetch(req)) } } ``` ### 类型安全提醒 要添加类型安全提醒,请安装: ```bash bun add @kitajs/ts-html-plugin ``` 然后在 **tsconfig.json** 中添加以下内容: ```jsonc // tsconfig.json { "compilerOptions": { "jsx": "react", "jsxFactory": "Html.createElement", "jsxFragmentFactory": "Html.Fragment", "plugins": [{ "name": "@kitajs/ts-html-plugin" }] } } ``` ## 从 Vafast 迁移 如果您正在从 `@vafastjs/html` 迁移,主要变化是: 1. **导入**: 从 `import { html } from '@vafastjs/html'` 改为 `import { html } from '@vafast/html'` 2. **用法**: 使用 `app.use(html())` 而不是 `app.use(html())` 3. **API**: API 保持不变:`req.html.html()` 和 `req.html.stream()` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { html, Html } from '@vafast/html' // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => ( Vafast HTML Plugin

Welcome to Vafast!

This page is rendered using the HTML plugin.

  • Fast HTML rendering
  • JSX support
  • Streaming responses
  • Auto-detection
)) }, { method: 'GET', path: '/dynamic/:name', handler: createHandler(({ params }) => ( Hello {params.name}

Hello, {params.name}!

Welcome to your personalized page.

)) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用 HTML 中间件 export default { fetch: (req: Request) => { return html()(req, () => server.fetch(req)) } } ``` ## 测试 ```bash bun test ``` ## 相关链接 * [GitHub 仓库](https://github.com/vafastjs/vafast-html) * [@kitajs/html](https://github.com/kitajs/html) - 底层 HTML 渲染库 * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/middleware/ip.md' --- # IP 中间件 用于 [Vafast](https://github.com/vafastjs/vafast) 的 IP 地址获取中间件,支持从各种代理头部中提取真实的客户端 IP 地址。 ## 安装 通过以下命令安装: ```bash bun add @vafast/ip ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' // 创建 IP 中间件 const ipMiddleware = ip() // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler((request: Request) => { // 访问客户端 IP 地址 const clientIP = (request as any).ip return { message: 'Hello World!', clientIP: clientIP || 'Unknown' } }), middleware: [ipMiddleware], }, { method: 'GET', path: '/api/client-info', handler: createHandler((request: Request) => { return { ip: (request as any).ip, userAgent: request.headers.get('user-agent'), timestamp: new Date().toISOString() } }), middleware: [ipMiddleware], } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req), } ``` ## 配置选项 ### Options ```typescript interface Options { /** * 自定义检查 IP 地址的头部 * @default ['x-real-ip', 'x-client-ip', 'cf-connecting-ip', 'fastly-client-ip', 'x-cluster-client-ip', 'x-forwarded', 'forwarded-for', 'forwarded', 'x-forwarded', 'appengine-user-ip', 'true-client-ip', 'cf-pseudo-ipv4'] */ checkHeaders?: IPHeaders[] /** * 仅检查头部,不考虑运行时环境 * @default false */ headersOnly?: boolean /** * 注入服务器实例的函数 */ injectServer: InjectServer } ``` ### IPHeaders ```typescript type IPHeaders = | "x-real-ip" // Nginx proxy/FastCGI | "x-client-ip" // Apache mod_remoteip | "cf-connecting-ip" // Cloudflare | "fastly-client-ip" // Fastly | "x-cluster-client-ip" // GCP | "x-forwarded" // General Forwarded | "forwarded-for" // RFC 7239 | "forwarded" // RFC 7239 | "x-forwarded" // RFC 7239 | "appengine-user-ip" // GCP | "true-client-ip" // Akamai and Cloudflare | "cf-pseudo-ipv4" // Cloudflare | "fly-client-ip" // Fly.io | (string & {}) // 自定义头部 ``` ## 支持的代理头部 中间件默认检查以下头部,按优先级排序: ### 1. 标准代理头部 * **`x-real-ip`**: Nginx 反向代理和 FastCGI * **`x-client-ip`**: Apache mod\_remoteip 模块 * **`x-forwarded-for`**: 标准转发头部(RFC 7239) ### 2. 云服务提供商头部 * **`cf-connecting-ip`**: Cloudflare CDN * **`fastly-client-ip`**: Fastly CDN * **`x-cluster-client-ip`**: Google Cloud Platform * **`appengine-user-ip`**: Google App Engine * **`true-client-ip`**: Akamai 和 Cloudflare * **`cf-pseudo-ipv4`**: Cloudflare IPv4 映射 ### 3. 平台特定头部 * **`fly-client-ip`**: Fly.io 平台 * **`forwarded`**: RFC 7239 标准头部 ## 使用模式 ### 1. 基本 IP 获取 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' const ipMiddleware = ip() const routes = [ { method: 'GET', path: '/', handler: createHandler((request: Request) => { const clientIP = (request as any).ip return { message: 'Welcome!', yourIP: clientIP || 'Unknown', timestamp: new Date().toISOString() } }), middleware: [ipMiddleware], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 2. 自定义头部检查 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' // 自定义检查的头部,按优先级排序 const customIpMiddleware = ip({ checkHeaders: [ 'x-custom-ip', // 自定义头部 'x-real-ip', // Nginx 代理 'x-forwarded-for', // 标准转发 'cf-connecting-ip' // Cloudflare ] }) const routes = [ { method: 'GET', path: '/api/ip', handler: createHandler((request: Request) => { return { ip: (request as any).ip, headers: { 'x-custom-ip': request.headers.get('x-custom-ip'), 'x-real-ip': request.headers.get('x-real-ip'), 'x-forwarded-for': request.headers.get('x-forwarded-for'), 'cf-connecting-ip': request.headers.get('cf-connecting-ip') } } }), middleware: [customIpMiddleware], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 3. 多实例注入 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' const ipMiddleware = ip() // 创建多个实例,每个都使用 IP 中间件 const aInstance = { method: 'GET', path: '/a', handler: createHandler((request: Request) => { return { instance: 'A', clientIP: (request as any).ip, timestamp: new Date().toISOString() } }), middleware: [ipMiddleware], } const bInstance = { method: 'GET', path: '/b', handler: createHandler((request: Request) => { return { instance: 'B', clientIP: (request as any).ip, timestamp: new Date().toISOString() } }), middleware: [ipMiddleware], } const routes = [ aInstance, bInstance, { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Multi-instance IP tracking', endpoints: ['/a', '/b'] } }), }, ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 4. 条件 IP 获取 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' const routes = [ { method: 'GET', path: '/public', handler: createHandler(() => { return { message: 'Public endpoint - no IP tracking' } }) // 不应用 IP 中间件 }, { method: 'GET', path: '/tracked', handler: createHandler((request: Request) => { return { message: 'IP tracked endpoint', clientIP: (request as any).ip } }), middleware: [ip()], // 动态创建中间件 }, { method: 'GET', path: '/admin', handler: createHandler((request: Request) => { const clientIP = (request as any).ip // 基于 IP 的访问控制 if (clientIP === '192.168.1.100') { return { message: 'Admin access granted', clientIP, role: 'admin' } } else { return { message: 'Access denied', clientIP, requiredIP: '192.168.1.100' } } }), middleware: [ip()], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 5. 高级配置 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' const advancedIpMiddleware = ip({ checkHeaders: [ 'x-real-ip', 'x-forwarded-for', 'cf-connecting-ip' ], headersOnly: true, // 仅检查头部 injectServer: (app) => { // 注入服务器实例的逻辑 console.log('IP middleware injected into server') return app } }) const routes = [ { method: 'GET', path: '/advanced', handler: createHandler((request: Request) => { const clientIP = (request as any).ip return { message: 'Advanced IP tracking', clientIP, headers: { 'x-real-ip': request.headers.get('x-real-ip'), 'x-forwarded-for': request.headers.get('x-forwarded-for'), 'cf-connecting-ip': request.headers.get('cf-connecting-ip') }, timestamp: new Date().toISOString() } }), middleware: [advancedIpMiddleware], } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' // 创建不同配置的 IP 中间件 const basicIpMiddleware = ip() const customIpMiddleware = ip({ checkHeaders: ['x-custom-ip', 'x-real-ip', 'x-forwarded-for'] }) const strictIpMiddleware = ip({ checkHeaders: ['x-real-ip', 'cf-connecting-ip'], headersOnly: true }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast IP Tracking API', endpoints: [ '/basic - 基本 IP 获取', '/custom - 自定义头部检查', '/strict - 严格头部检查', '/multi - 多实例 IP 跟踪', '/admin - 基于 IP 的访问控制' ] } }) }, { method: 'GET', path: '/basic', handler: createHandler((request: Request) => { return { message: 'Basic IP tracking', clientIP: (request as any).ip || 'Unknown', method: 'Default headers check' } }), middleware: [basicIpMiddleware], }, { method: 'GET', path: '/custom', handler: createHandler((request: Request) => { return { message: 'Custom headers IP tracking', clientIP: (request as any).ip || 'Unknown', method: 'Custom headers check', checkedHeaders: [ 'x-custom-ip', 'x-real-ip', 'x-forwarded-for' ] } }), middleware: [customIpMiddleware], }, { method: 'GET', path: '/strict', handler: createHandler((request: Request) => { return { message: 'Strict headers IP tracking', clientIP: (request as any).ip || 'Unknown', method: 'Strict headers only', checkedHeaders: [ 'x-real-ip', 'cf-connecting-ip' ] } }), middleware: [strictIpMiddleware], }, { method: 'GET', path: '/multi', handler: createHandler((request: Request) => { return { message: 'Multi-instance IP tracking', clientIP: (request as any).ip || 'Unknown', instances: ['/multi/a', '/multi/b'] } }), middleware: [basicIpMiddleware], }, { method: 'GET', path: '/multi/a', handler: createHandler((request: Request) => { return { instance: 'A', clientIP: (request as any).ip || 'Unknown', timestamp: new Date().toISOString() } }), middleware: [basicIpMiddleware], }, { method: 'GET', path: '/multi/b', handler: createHandler((request: Request) => { return { instance: 'B', clientIP: (request as any).ip || 'Unknown', timestamp: new Date().toISOString() } }), middleware: [basicIpMiddleware], }, { method: 'GET', path: '/admin', handler: createHandler((request: Request) => { const clientIP = (request as any).ip // 简单的 IP 白名单 const allowedIPs = ['192.168.1.100', '10.0.0.1', '127.0.0.1'] if (allowedIPs.includes(clientIP)) { return { message: 'Admin access granted', clientIP, role: 'admin', allowedIPs, timestamp: new Date().toISOString() } } else { return { message: 'Access denied', clientIP: clientIP || 'Unknown', requiredIPs: allowedIPs, timestamp: new Date().toISOString() } } }), middleware: [basicIpMiddleware], }, { method: 'POST', path: '/api/log', handler: createHandler(async (request: Request) => { const clientIP = (request as any).ip const body = await request.json() // 记录客户端活动 console.log(`[${new Date().toISOString()}] IP: ${clientIP}, Action: ${body.action}`) return { message: 'Activity logged', clientIP, action: body.action, timestamp: new Date().toISOString() } }), middleware: [basicIpMiddleware], } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req), } console.log('🚀 Vafast IP Tracking API 服务器启动成功!') console.log('📱 基本 IP 获取: GET /basic') console.log('🎯 自定义头部: GET /custom') console.log('🔒 严格检查: GET /strict') console.log('🔄 多实例跟踪: GET /multi') console.log('👑 管理访问: GET /admin') console.log('📝 活动记录: POST /api/log') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { ip } from '@vafast/ip' describe('Vafast IP Plugin', () => { it('should extract IP from X-Real-IP header', async () => { const ipMiddleware = ip() const app = new Server([ { method: 'GET', path: '/', handler: createHandler(({ req }: { req: Request }) => { return { ip: (req as any).ip } }), middleware: [ipMiddleware], }, ]) const req = new Request('http://localhost/', { headers: { 'X-Real-IP': '192.168.1.100', }, }) const res = await app.fetch(req) const data = await res.json() expect(data.ip).toBe('192.168.1.100') }) it('should extract IP from X-Forwarded-For header', async () => { const ipMiddleware = ip() const app = new Server([ { method: 'GET', path: '/', handler: createHandler(({ req }: { req: Request }) => { return { ip: (req as any).ip } }), middleware: [ipMiddleware], }, ]) const req = new Request('http://localhost/', { headers: { 'X-Forwarded-For': '203.0.113.1, 192.168.1.100', }, }) const res = await app.fetch(req) const data = await res.json() // X-Forwarded-For 应该返回第一个 IP(最原始的客户端 IP) expect(data.ip).toBe('203.0.113.1') }) it('should extract IP from Cloudflare header', async () => { const ipMiddleware = ip() const app = new Server([ { method: 'GET', path: '/', handler: createHandler(({ req }: { req: Request }) => { return { ip: (req as any).ip } }), middleware: [ipMiddleware], }, ]) const req = new Request('http://localhost/', { headers: { 'CF-Connecting-IP': '104.16.123.456', }, }) const res = await app.fetch(req) const data = await res.json() expect(data.ip).toBe('104.16.123.456') }) it('should handle custom headers configuration', async () => { const ipMiddleware = ip({ checkHeaders: ['X-Custom-IP', 'X-Real-IP'], }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(({ req }: { req: Request }) => { return { ip: (req as any).ip } }), middleware: [ipMiddleware], }, ]) const req = new Request('http://localhost/', { headers: { 'X-Custom-IP': '10.0.0.1', 'X-Real-IP': '192.168.1.100', }, }) const res = await app.fetch(req) const data = await res.json() // 应该优先使用 X-Custom-IP expect(data.ip).toBe('10.0.0.1') }) it('should handle missing IP headers', async () => { const ipMiddleware = ip() const app = new Server([ { method: 'GET', path: '/', handler: createHandler(({ req }: { req: Request }) => { return { ip: (req as any).ip } }), middleware: [ipMiddleware], }, ]) const req = new Request('http://localhost/') const res = await app.fetch(req) const data = await res.json() // 没有 IP 头部时应该返回空字符串 expect(data.ip).toBe('') }) }) ``` ## 特性 * ✅ **多头部支持**: 支持所有常见的代理头部 * ✅ **优先级排序**: 按配置顺序检查头部 * ✅ **云服务兼容**: 支持 Cloudflare、Fastly、GCP 等 * ✅ **自定义配置**: 可自定义检查的头部列表 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **高性能**: 轻量级实现,最小化性能开销 * ✅ **易于集成**: 无缝集成到 Vafast 应用 ## 最佳实践 ### 1. 头部优先级配置 ```typescript const ipMiddleware = ip({ checkHeaders: [ 'x-real-ip', // 最高优先级:直接代理 'cf-connecting-ip', // 次优先级:Cloudflare 'x-forwarded-for', // 标准优先级:通用代理 'x-client-ip' // 最低优先级:备用选项 ] }) ``` ### 2. 环境特定配置 ```typescript const getIpConfig = () => { if (process.env.NODE_ENV === 'production') { // 生产环境:严格的头部检查 return { checkHeaders: ['x-real-ip', 'cf-connecting-ip'], headersOnly: true } } else { // 开发环境:宽松的头部检查 return { checkHeaders: ['x-real-ip', 'x-forwarded-for', 'x-client-ip'] } } } const ipMiddleware = ip(getIpConfig()) ``` ### 3. 安全考虑 ```typescript const secureIpMiddleware = ip({ checkHeaders: [ 'x-real-ip', // 可信的代理头部 'cf-connecting-ip' // 可信的 CDN 头部 ], headersOnly: true // 仅检查头部,不依赖环境 }) // 在路由中使用 const routes = [ { method: 'GET', path: '/secure', handler: createHandler((request: Request) => { const clientIP = (request as any).ip // 验证 IP 地址格式 if (!isValidIP(clientIP)) { return { error: 'Invalid IP address' } } return { message: 'Secure endpoint', clientIP, validated: true } }), middleware: [secureIpMiddleware], } ] ``` ### 4. 监控和日志 ```typescript const monitoredIpMiddleware = ip({ checkHeaders: ['x-real-ip', 'x-forwarded-for'], injectServer: (app) => { console.log('IP middleware injected with monitoring') return app } }) // 在处理器中添加 IP 监控 const routes = [ { method: 'GET', path: '/monitored', handler: createHandler((request: Request) => { const clientIP = (request as any).ip // 记录 IP 访问 logIPAccess(clientIP, request.url, new Date()) return { message: 'IP access monitored', clientIP, timestamp: new Date().toISOString() } }), middleware: [monitoredIpMiddleware], } ] ``` ## 注意事项 1. **头部优先级**: 配置的头部按顺序检查,第一个找到的 IP 将被使用 2. **X-Forwarded-For 处理**: 该头部可能包含多个 IP,中间件会使用第一个(最原始的客户端 IP) 3. **安全考虑**: 在生产环境中,建议仅信任可信的代理头部 4. **性能影响**: IP 中间件对性能影响很小,但建议在需要时使用 5. **类型断言**: 当前版本需要类型断言 `(request as any).ip` 来访问 IP 地址 ## 相关链接 * [X-Forwarded-For - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) * [Forwarded - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) * [RFC 7239 - Forwarded HTTP Extension](https://tools.ietf.org/html/rfc7239) * [Cloudflare Headers](https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/middleware/jwt.md' --- # JWT 中间件 该中间件增强了在 [Vafast](https://github.com/vafastjs/vafast) 处理程序中使用 JWT 的支持。 ## 安装 安装命令: ```bash bun add @vafast/jwt ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' // 创建 JWT 中间件 const jwtMiddleware = jwt({ name: 'jwt', secret: 'your-secret-key-here', sub: 'auth', iss: 'your-domain.com', exp: '7d' }) // 定义路由 const routes = [ { method: 'GET', path: '/sign/:name', handler: createHandler(async ({ req, params }: { req: Request, params: Record }) => { // 应用 JWT 中间件 jwtMiddleware(req, () => Promise.resolve(new Response())) const name = params.name // 创建 JWT 令牌 const token = await (req as any).jwt.sign({ name }) return { data: `Sign in as ${name}`, headers: { 'Set-Cookie': `auth=${token}; HttpOnly; Max-Age=${7 * 86400}; Path=/` } } }) }, { method: 'GET', path: '/profile', handler: createHandler(async ({ req }: { req: Request }) => { // 应用 JWT 中间件 jwtMiddleware(req, () => Promise.resolve(new Response())) // 从 Cookie 中获取令牌 const cookies = req.headers.get('cookie') const authCookie = cookies?.split(';').find((c) => c.trim().startsWith('auth=')) const token = authCookie?.split('=')[1] // 验证 JWT 令牌 const profile = await (req as any).jwt.verify(token) if (!profile) { return { status: 401, data: 'Unauthorized' } } return { message: `Hello ${profile.name}` } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } ``` ## 配置选项 该中间件扩展了 [jose](https://github.com/panva/jose) 的配置。 ### JWTOption ```typescript interface JWTOption { /** 注册方法的名称,默认为 'jwt' */ name?: Name /** JWT 密钥 */ secret: string | Uint8Array | JWK /** JWT 负载的严格类型验证 */ schema?: Schema /** 签名算法,默认为 'HS256' */ alg?: string /** JWT 类型,默认为 'JWT' */ typ?: string /** 发行者声明 */ iss?: string /** 主体声明 */ sub?: string /** 受众声明 */ aud?: string | string[] /** JWT ID 声明 */ jti?: string /** "未生效" 声明 */ nbf?: string | number /** 过期时间声明 */ exp?: string | number /** "签发时间" 声明 */ iat?: boolean /** 其他 jose 支持的头部参数 */ b64?: true crit?: string[] kid?: string x5t?: string x5c?: string[] x5u?: string jku?: string jwk?: JWK cty?: string } ``` ### 支持的算法 中间件支持以下签名算法: * **HS256, HS384, HS512**: HMAC 算法 * **PS256, PS384, PS512**: RSA-PSS 算法 * **RS256, RS384, RS512**: RSA 算法 * **ES256, ES256K, ES384, ES512**: ECDSA 算法 * **EdDSA**: Edwards-curve 数字签名算法 ## 使用模式 ### 1. 基本 JWT 认证 ```typescript import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' const jwtMiddleware = jwt({ name: 'jwt', secret: 'your-secret-key', exp: '1h', iss: 'your-app.com' }) const routes = [ { method: 'POST', path: '/login', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const body = await req.json() const { username, password } = body // 验证用户凭据 if (username === 'admin' && password === 'password') { const token = await (req as any).jwt.sign({ username, role: 'admin', id: 1 }) return { message: 'Login successful', token, user: { username, role: 'admin' } } } else { return { status: 401, message: 'Invalid credentials' } } }) }, { method: 'GET', path: '/protected', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const authHeader = req.headers.get('authorization') const token = authHeader?.replace('Bearer ', '') if (!token) { return { status: 401, message: 'No token provided' } } const payload = await (req as any).jwt.verify(token) if (!payload) { return { status: 401, message: 'Invalid token' } } return { message: 'Access granted', user: payload } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 2. 带类型验证的 JWT ```typescript import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' import { Type as t } from '@sinclair/typebox' // 定义用户模式 const UserSchema = t.Object({ id: t.Number(), username: t.String(), email: t.String(), role: t.Union([t.Literal('user'), t.Literal('admin')]) }) const jwtMiddleware = jwt({ name: 'jwt', secret: 'your-secret-key', exp: '24h', schema: UserSchema }) const routes = [ { method: 'POST', path: '/register', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const body = await req.json() // 创建用户令牌 const token = await (req as any).jwt.sign({ id: 1, username: body.username, email: body.email, role: 'user' }) return { message: 'User registered successfully', token, user: { id: 1, username: body.username, email: body.email, role: 'user' } } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 3. 多 JWT 实例 ```typescript import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' // 创建不同配置的 JWT 中间件 const accessTokenMiddleware = jwt({ name: 'accessToken', secret: 'access-secret', exp: '15m', iss: 'your-app.com' }) const refreshTokenMiddleware = jwt({ name: 'refreshToken', secret: 'refresh-secret', exp: '7d', iss: 'your-app.com' }) const routes = [ { method: 'POST', path: '/auth/login', handler: createHandler(async ({ req }: { req: Request }) => { // 应用两个 JWT 中间件 accessTokenMiddleware(req, () => Promise.resolve(new Response())) refreshTokenMiddleware(req, () => Promise.resolve(new Response())) const body = await req.json() const { username, password } = body if (username === 'admin' && password === 'password') { const accessToken = await (req as any).accessToken.sign({ username, role: 'admin', type: 'access' }) const refreshToken = await (req as any).refreshToken.sign({ username, type: 'refresh' }) return { message: 'Login successful', accessToken, refreshToken, expiresIn: '15m' } } else { return { status: 401, message: 'Invalid credentials' } } }) }, { method: 'POST', path: '/auth/refresh', handler: createHandler(async ({ req }: { req: Request }) => { accessTokenMiddleware(req, () => Promise.resolve(new Response())) refreshTokenMiddleware(req, () => Promise.resolve(new Response())) const body = await req.json() const { refreshToken } = body const payload = await (req as any).refreshToken.verify(refreshToken) if (!payload || payload.type !== 'refresh') { return { status: 401, message: 'Invalid refresh token' } } // 生成新的访问令牌 const newAccessToken = await (req as any).accessToken.sign({ username: payload.username, role: 'admin', type: 'access' }) return { message: 'Token refreshed', accessToken: newAccessToken, expiresIn: '15m' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 4. 高级 JWT 配置 ```typescript import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' const advancedJwtMiddleware = jwt({ name: 'jwt', secret: 'your-secret-key', alg: 'HS512', // 使用更强的算法 typ: 'JWT', iss: 'your-app.com', // 发行者 sub: 'authentication', // 主题 aud: ['web', 'mobile'], // 受众 exp: '1h', // 过期时间 nbf: '0s', // 立即生效 iat: true, // 包含签发时间 jti: 'unique-id', // JWT ID kid: 'key-1', // 密钥 ID b64: true, // Base64 编码 crit: ['b64'] // 关键参数 }) const routes = [ { method: 'POST', path: '/auth/advanced', handler: createHandler(async ({ req }: { req: Request }) => { advancedJwtMiddleware(req, () => Promise.resolve(new Response())) const token = await (req as any).jwt.sign({ username: 'admin', role: 'admin', permissions: ['read', 'write', 'delete'] }) return { message: 'Advanced JWT created', token, config: { algorithm: 'HS512', issuer: 'your-app.com', audience: ['web', 'mobile'], expiresIn: '1h' } } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => server.fetch(req) } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' import { Type as t } from '@sinclair/typebox' // 定义用户模式 const UserSchema = t.Object({ id: t.Number(), username: t.String(), email: t.String(), role: t.Union([t.Literal('user'), t.Literal('admin')]) }) // 创建 JWT 中间件 const jwtMiddleware = jwt({ name: 'jwt', secret: 'your-super-secret-key-here', sub: 'authentication', iss: 'your-app.com', exp: '24h', schema: UserSchema }) // 模拟用户数据库 const users = [ { id: 1, username: 'admin', email: 'admin@example.com', password: 'admin123', role: 'admin' }, { id: 2, username: 'user', email: 'user@example.com', password: 'user123', role: 'user' } ] // 辅助函数:验证用户凭据 const validateUser = (username: string, password: string) => { return users.find(user => user.username === username && user.password === password) } // 辅助函数:从请求中提取令牌 const extractToken = (req: Request) => { const authHeader = req.headers.get('authorization') if (authHeader?.startsWith('Bearer ')) { return authHeader.substring(7) } return null } // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast JWT Authentication API', endpoints: [ 'POST /auth/register - 用户注册', 'POST /auth/login - 用户登录', 'GET /profile - 获取用户资料', 'PUT /profile - 更新用户资料', 'POST /auth/logout - 用户登出', 'GET /admin - 管理员专用端点' ] } }) }, { method: 'POST', path: '/auth/register', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const body = await req.json() const { username, email, password } = body // 检查用户是否已存在 if (users.find(user => user.username === username)) { return { status: 400, message: 'Username already exists' } } // 创建新用户 const newUser = { id: users.length + 1, username, email, password, role: 'user' as const } users.push(newUser) // 生成 JWT 令牌 const token = await (req as any).jwt.sign({ id: newUser.id, username: newUser.username, email: newUser.email, role: newUser.role }) return { message: 'User registered successfully', token, user: { id: newUser.id, username: newUser.username, email: newUser.email, role: newUser.role } } }) }, { method: 'POST', path: '/auth/login', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const body = await req.json() const { username, password } = body const user = validateUser(username, password) if (!user) { return { status: 401, message: 'Invalid credentials' } } // 生成 JWT 令牌 const token = await (req as any).jwt.sign({ id: user.id, username: user.username, email: user.email, role: user.role }) return { message: 'Login successful', token, user: { id: user.id, username: user.username, email: user.email, role: user.role } } }) }, { method: 'GET', path: '/profile', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const token = extractToken(req) if (!token) { return { status: 401, message: 'No token provided' } } const payload = await (req as any).jwt.verify(token) if (!payload) { return { status: 401, message: 'Invalid token' } } const user = users.find(u => u.id === payload.id) if (!user) { return { status: 404, message: 'User not found' } } return { message: 'Profile retrieved successfully', user: { id: user.id, username: user.username, email: user.email, role: user.role } } }) }, { method: 'PUT', path: '/profile', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const token = extractToken(req) if (!token) { return { status: 401, message: 'No token provided' } } const payload = await (req as any).jwt.verify(token) if (!payload) { return { status: 401, message: 'Invalid token' } } const body = await req.json() const { email } = body const user = users.find(u => u.id === payload.id) if (!user) { return { status: 404, message: 'User not found' } } // 更新用户信息 user.email = email return { message: 'Profile updated successfully', user: { id: user.id, username: user.username, email: user.email, role: user.role } } }) }, { method: 'GET', path: '/admin', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const token = extractToken(req) if (!token) { return { status: 401, message: 'No token provided' } } const payload = await (req as any).jwt.verify(token) if (!payload) { return { status: 401, message: 'Invalid token' } } if (payload.role !== 'admin') { return { status: 403, message: 'Access denied. Admin role required.' } } return { message: 'Admin access granted', adminData: { totalUsers: users.length, systemStatus: 'healthy', lastMaintenance: new Date().toISOString() } } }) }, { method: 'POST', path: '/auth/logout', handler: createHandler(async ({ req }: { req: Request }) => { // 在实际应用中,你可能需要将令牌加入黑名单 return { message: 'Logout successful', note: 'Token has been invalidated' } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } console.log('🚀 Vafast JWT Authentication API 服务器启动成功!') console.log('📝 用户注册: POST /auth/register') console.log('🔐 用户登录: POST /auth/login') console.log('👤 获取资料: GET /profile') console.log('✏️ 更新资料: PUT /profile') console.log('👑 管理端点: GET /admin') console.log('🚪 用户登出: POST /auth/logout') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' describe('Vafast JWT Plugin', () => { it('should sign JWT tokens', async () => { const jwtMiddleware = jwt({ name: 'jwt', secret: 'test-secret', sub: 'auth', iss: 'test.com', exp: '1h' }) const app = new Server([ { method: 'GET', path: '/sign', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) const token = await (req as any).jwt.sign({ name: 'testuser' }) return { token } }) } ]) const res = await app.fetch(new Request('http://localhost/sign')) const data = await res.json() expect(data.token).toBeDefined() expect(typeof data.token).toBe('string') expect(data.token.split('.')).toHaveLength(3) // JWT 有 3 个部分 }) it('should verify JWT tokens', async () => { const jwtMiddleware = jwt({ name: 'jwt', secret: 'test-secret', sub: 'auth', iss: 'test.com', exp: '1h' }) const app = new Server([ { method: 'GET', path: '/verify', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) // 首先签名一个令牌 const token = await (req as any).jwt.sign({ name: 'testuser', id: 123 }) // 然后验证它 const payload = await (req as any).jwt.verify(token) return { payload } }) } ]) const res = await app.fetch(new Request('http://localhost/verify')) const data = await res.json() expect(data.payload).toBeDefined() expect(data.payload.name).toBe('testuser') expect(data.payload.id).toBe(123) }) it('should handle invalid JWT tokens', async () => { const jwtMiddleware = jwt({ name: 'jwt', secret: 'test-secret', sub: 'auth', iss: 'test.com', exp: '1h' }) const app = new Server([ { method: 'GET', path: '/verify-invalid', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) // 尝试验证无效令牌 const payload = await (req as any).jwt.verify('invalid.token.here') return { payload } }) } ]) const res = await app.fetch(new Request('http://localhost/verify-invalid')) const data = await res.json() expect(data.payload).toBe(false) }) it('should handle missing tokens', async () => { const jwtMiddleware = jwt({ name: 'jwt', secret: 'test-secret', sub: 'auth', iss: 'test.com', exp: '1h' }) const app = new Server([ { method: 'GET', path: '/verify-missing', handler: createHandler(async ({ req }: { req: Request }) => { jwtMiddleware(req, () => Promise.resolve(new Response())) // 尝试验证缺失的令牌 const payload = await (req as any).jwt.verify() return { payload } }) } ]) const res = await app.fetch(new Request('http://localhost/verify-missing')) const data = await res.json() expect(data.payload).toBe(false) }) }) ``` ## 特性 * ✅ **JWT 签名**: 支持创建和验证 JWT 令牌 * ✅ **多种算法**: 支持 HS256、RS256、ES256 等多种签名算法 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **模式验证**: 使用 TypeBox 进行负载验证 * ✅ **灵活配置**: 支持所有标准 JWT 声明 * ✅ **高性能**: 基于 jose 库的高性能实现 * ✅ **易于集成**: 无缝集成到 Vafast 应用 ## 最佳实践 ### 1. 密钥管理 ```typescript // 使用环境变量存储密钥 const jwtMiddleware = jwt({ name: 'jwt', secret: process.env.JWT_SECRET || 'fallback-secret', exp: '1h' }) // 使用强密钥 const strongSecret = crypto.randomBytes(64).toString('hex') ``` ### 2. 令牌过期策略 ```typescript const jwtMiddleware = jwt({ name: 'jwt', secret: 'your-secret', exp: '15m', // 访问令牌:15 分钟 nbf: '0s', // 立即生效 iat: true // 包含签发时间 }) ``` ### 3. 错误处理 ```typescript const payload = await (req as any).jwt.verify(token) if (!payload) { return { status: 401, message: 'Invalid or expired token', code: 'TOKEN_INVALID' } } ``` ### 4. 安全考虑 ```typescript // 在生产环境中使用强算法 const productionJwt = jwt({ name: 'jwt', secret: process.env.JWT_SECRET, alg: 'HS512', // 使用更强的算法 exp: '1h', iss: 'your-domain.com', aud: ['web', 'api'] }) ``` ## 注意事项 1. **密钥安全**: 确保 JWT 密钥的安全性,不要将其暴露在客户端代码中 2. **令牌过期**: 合理设置令牌过期时间,平衡安全性和用户体验 3. **算法选择**: 根据安全需求选择合适的签名算法 4. **负载大小**: JWT 令牌会增加请求大小,避免在负载中存储过多数据 5. **类型断言**: 当前版本需要使用类型断言 `(req as any).jwt` 来访问 JWT 方法 ## 相关链接 * [JWT.io](https://jwt.io/) - JWT 调试器和文档 * [RFC 7519 - JSON Web Token](https://tools.ietf.org/html/rfc7519) * [jose 库文档](https://github.com/panva/jose) * [TypeBox 文档](https://github.com/sinclairzx81/typebox) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/integrations/nextjs.md' --- # Next.js 集成 Vafast 可以与 Next.js 无缝集成,为您提供强大的后端 API 和现代化的前端开发体验。 ## 项目结构 ``` my-vafast-nextjs-app/ ├── src/ │ ├── app/ # Next.js App Router │ ├── api/ # Vafast API 路由 │ │ ├── routes.ts # 路由定义 │ │ ├── server.ts # Vafast 服务器 │ │ └── types.ts # 类型定义 │ └── lib/ # 共享库 ├── package.json └── next.config.js ``` ## 安装依赖 ```bash bun add vafast @vafast/cors @vafast/helmet bun add -D @types/node ``` ## 创建 Vafast API 服务器 ```typescript // src/api/server.ts import { defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' import { routes } from './routes' export const app = createHandler(routes) .use(cors({ origin: process.env.NODE_ENV === 'development' ? ['http://localhost:3000'] : [process.env.NEXT_PUBLIC_APP_URL], credentials: true })) .use(helmet()) export const handler = app.handler ``` ## 定义 API 路由 ```typescript // src/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' export const routes = defineRoutes([ { method: 'GET', path: '/api/users', handler: createHandler(async () => { // 模拟数据库查询 const users = [ { id: 1, name: 'John Doe', email: 'john@example.com' }, { id: 2, name: 'Jane Smith', email: 'jane@example.com' } ] return { users } }) }, { method: 'POST', path: '/api/users', handler: createHandler(async ({ body }) => { // 创建新用户 const newUser = { id: Date.now(), ...body, createdAt: new Date().toISOString() } return { user: newUser }, { status: 201 } }), body: Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) }, { method: 'GET', path: '/api/users/:id', handler: createHandler(async ({ params }) => { const userId = parseInt(params.id) // 模拟数据库查询 const user = { id: userId, name: 'John Doe', email: 'john@example.com' } if (!user) { return { error: 'User not found' }, { status: 404 } } return { user } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }) } ]) ``` ## 创建 API 路由处理器 ```typescript // src/app/api/[...path]/route.ts import { handler } from '@/api/server' export async function GET(request: Request) { return handler(request) } export async function POST(request: Request) { return handler(request) } export async function PUT(request: Request) { return handler(request) } export async function DELETE(request: Request) { return handler(request) } export async function PATCH(request: Request) { return handler(request) } ``` ## 类型定义 ```typescript // src/api/types.ts import { Type } from '@sinclair/typebox' export const UserSchema = Type.Object({ id: Type.Number(), name: Type.String(), email: Type.String({ format: 'email' }), createdAt: Type.String({ format: 'date-time' }) }) export const CreateUserSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) export type User = typeof UserSchema.T export type CreateUser = typeof CreateUserSchema.T ``` ## 前端集成 ### 使用 API 路由 ```typescript // src/app/users/page.tsx 'use client' import { useState, useEffect } from 'react' import { User } from '@/api/types' export default function UsersPage() { const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { fetch('/api/users') .then(res => res.json()) .then(data => { setUsers(data.users) setLoading(false) }) .catch(error => { console.error('Error fetching users:', error) setLoading(false) }) }, []) if (loading) return
Loading...
return (

Users

    {users.map(user => (
  • {user.name} ({user.email})
  • ))}
) } ``` ### 创建用户表单 ```typescript // src/app/users/create/page.tsx 'use client' import { useState } from 'react' import { CreateUser } from '@/api/types' export default function CreateUserPage() { const [formData, setFormData] = useState({ name: '', email: '' }) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() try { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }) if (response.ok) { const result = await response.json() console.log('User created:', result.user) // 重定向到用户列表 window.location.href = '/users' } else { const error = await response.json() console.error('Error creating user:', error) } } catch (error) { console.error('Error:', error) } } return (

Create User

setFormData({ ...formData, name: e.target.value })} required />
setFormData({ ...formData, email: e.target.value })} required />
) } ``` ## 中间件集成 ### 认证中间件 ```typescript // src/api/middleware/auth.ts import { NextFunction } from 'next/server' export interface AuthenticatedRequest extends Request { user?: { id: string email: string role: string } } export const authMiddleware = async ( request: Request, next: NextFunction ) => { const token = request.headers.get('authorization')?.replace('Bearer ', '') if (!token) { return new Response('Unauthorized', { status: 401 }) } try { // 验证 JWT token const user = await verifyToken(token) ;(request as AuthenticatedRequest).user = user return next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } async function verifyToken(token: string) { // 实现 JWT 验证逻辑 // 这里应该使用 @vafast/jwt 中间件 return { id: '123', email: 'user@example.com', role: 'user' } } ``` ### 使用认证中间件 ```typescript // src/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { authMiddleware } from './middleware/auth' export const routes = defineRoutes([ { method: 'GET', path: '/api/profile', handler: createHandler(async ({ request }) => { const user = (request as AuthenticatedRequest).user return { user } }), middleware: [authMiddleware] } ]) ``` ## 环境配置 ```typescript // next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { serverComponentsExternalPackages: ['vafast'] }, async rewrites() { return [ { source: '/api/:path*', destination: '/api/[...path]' } ] } } module.exports = nextConfig ``` ## 开发和生产配置 ```typescript // src/api/config.ts export const config = { development: { cors: { origin: ['http://localhost:3000', 'http://localhost:3001'] }, logging: true }, production: { cors: { origin: [process.env.NEXT_PUBLIC_APP_URL] }, logging: false } } export const getConfig = () => { const env = process.env.NODE_ENV || 'development' return config[env as keyof typeof config] } ``` ## 测试 ### API 测试 ```typescript // src/api/__tests__/users.test.ts import { describe, expect, it } from 'bun:test' import { handler } from '../server' describe('Users API', () => { it('should get users', async () => { const request = new Request('http://localhost/api/users') const response = await handler(request) const data = await response.json() expect(response.status).toBe(200) expect(data.users).toBeDefined() expect(Array.isArray(data.users)).toBe(true) }) it('should create user', async () => { const userData = { name: 'Test User', email: 'test@example.com' } const request = new Request('http://localhost/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }) const response = await handler(request) const data = await response.json() expect(response.status).toBe(201) expect(data.user.name).toBe(userData.name) expect(data.user.email).toBe(userData.email) }) }) ``` ## 部署 ### Vercel 部署 ```json // vercel.json { "functions": { "src/app/api/[...path]/route.ts": { "runtime": "nodejs18.x" } }, "rewrites": [ { "source": "/api/:path*", "destination": "/api/[...path]" } ] } ``` ### Docker 部署 ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --production COPY . . RUN bun run build EXPOSE 3000 CMD ["bun", "run", "start"] ``` ## 最佳实践 1. **类型安全**:使用 TypeBox 确保前后端类型一致 2. **错误处理**:实现统一的错误处理机制 3. **中间件顺序**:注意中间件的执行顺序 4. **环境配置**:根据环境配置不同的设置 5. **测试覆盖**:为 API 路由编写完整的测试 6. **性能优化**:使用适当的缓存和压缩策略 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [Next.js 文档](https://nextjs.org/docs) - Next.js 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 --- --- url: 'https://vafast.dev/integrations/nuxt.md' --- # Nuxt 集成 Vafast 可以与 Nuxt 无缝集成,为您提供强大的后端 API 和现代化的前端开发体验。 ## 项目结构 ``` my-vafast-nuxt-app/ ├── server/ # Nuxt 服务器路由 │ └── api/ # Vafast API 路由 │ ├── routes.ts # 路由定义 │ ├── server.ts # Vafast 服务器 │ └── types.ts # 类型定义 ├── components/ # Vue 组件 ├── pages/ # 页面组件 ├── composables/ # 组合式函数 ├── package.json ├── nuxt.config.ts └── tsconfig.json ``` ## 安装依赖 ```bash bun add vafast @vafast/cors @vafast/helmet bun add -D @types/node ``` ## 创建 Vafast API 服务器 ```typescript // server/api/server.ts import { defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' import { routes } from './routes' export const app = createHandler(routes) .use(cors({ origin: process.env.NODE_ENV === 'development' ? ['http://localhost:3000'] : [process.env.NUXT_PUBLIC_APP_URL], credentials: true })) .use(helmet()) export const handler = app.handler ``` ## 定义 API 路由 ```typescript // server/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' export const routes = defineRoutes([ { method: 'GET', path: '/api/products', handler: createHandler(async () => { // 模拟数据库查询 const products = [ { id: 1, name: 'Product 1', price: 99.99, description: 'Amazing product' }, { id: 2, name: 'Product 2', price: 149.99, description: 'Another great product' } ] return { products } }) }, { method: 'POST', path: '/api/products', handler: createHandler(async ({ body }) => { // 创建新产品 const newProduct = { id: Date.now(), ...body, createdAt: new Date().toISOString() } return { product: newProduct }, { status: 201 } }), body: Type.Object({ name: Type.String({ minLength: 1 }), price: Type.Number({ minimum: 0 }), description: Type.Optional(Type.String()) }) }, { method: 'GET', path: '/api/products/:id', handler: createHandler(async ({ params }) => { const productId = parseInt(params.id) // 模拟数据库查询 const product = { id: productId, name: 'Sample Product', price: 99.99, description: 'Sample description' } if (!product) { return { error: 'Product not found' }, { status: 404 } } return { product } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }) }, { method: 'PUT', path: '/api/products/:id', handler: createHandler(async ({ params, body }) => { const productId = parseInt(params.id) // 模拟数据库更新 const updatedProduct = { id: productId, ...body, updatedAt: new Date().toISOString() } return { product: updatedProduct } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }), body: Type.Object({ name: Type.Optional(Type.String({ minLength: 1 })), price: Type.Optional(Type.Number({ minimum: 0 })), description: Type.Optional(Type.String()) }) }, { method: 'DELETE', path: '/api/products/:id', handler: createHandler(async ({ params }) => { const productId = parseInt(params.id) // 模拟数据库删除 console.log(`Deleting product ${productId}`) return { success: true } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }) } ]) ``` ## 创建 Nuxt 服务器路由 ```typescript // server/api/[...path].ts import { handler } from './server' export default defineEventHandler(async (event) => { const request = event.node.req const response = await handler(request) // 设置响应状态和头 setResponseStatus(event, response.status) // 复制响应头 for (const [key, value] of response.headers.entries()) { setResponseHeader(event, key, value) } // 返回响应体 return response.json() }) ``` ## 类型定义 ```typescript // server/api/types.ts import { Type } from '@sinclair/typebox' export const ProductSchema = Type.Object({ id: Type.Number(), name: Type.String(), price: Type.Number({ minimum: 0 }), description: Type.Optional(Type.String()), createdAt: Type.String({ format: 'date-time' }), updatedAt: Type.Optional(Type.String({ format: 'date-time' })) }) export const CreateProductSchema = Type.Object({ name: Type.String({ minLength: 1 }), price: Type.Number({ minimum: 0 }), description: Type.Optional(Type.String()) }) export const UpdateProductSchema = Type.Partial(CreateProductSchema) export type Product = typeof ProductSchema.T export type CreateProduct = typeof CreateProductSchema.T export type UpdateProduct = typeof UpdateProductSchema.T ``` ## 前端集成 ### 使用 API 路由 ```vue ``` ### 产品详情页面 ```vue ``` ## 组合式函数 ```typescript // composables/useProducts.ts import type { Product, CreateProduct, UpdateProduct } from '~/server/api/types' export const useProducts = () => { const products = ref([]) const loading = ref(false) const error = ref(null) // 获取产品列表 const fetchProducts = async () => { try { loading.value = true error.value = null const response = await $fetch('/api/products') products.value = response.products } catch (err: any) { error.value = err.message || '获取产品失败' throw err } finally { loading.value = false } } // 获取单个产品 const fetchProduct = async (id: number) => { try { loading.value = true error.value = null const response = await $fetch(`/api/products/${id}`) return response.product } catch (err: any) { error.value = err.message || '获取产品失败' throw err } finally { loading.value = false } } // 创建产品 const createProduct = async (productData: CreateProduct) => { try { loading.value = true error.value = null const response = await $fetch('/api/products', { method: 'POST', body: productData }) products.value.push(response.product) return response.product } catch (err: any) { error.value = err.message || '创建产品失败' throw err } finally { loading.value = false } } // 更新产品 const updateProduct = async (id: number, productData: UpdateProduct) => { try { loading.value = true error.value = null const response = await $fetch(`/api/products/${id}`, { method: 'PUT', body: productData }) const index = products.value.findIndex(p => p.id === id) if (index !== -1) { products.value[index] = response.product } return response.product } catch (err: any) { error.value = err.message || '更新产品失败' throw err } finally { loading.value = false } } // 删除产品 const deleteProduct = async (id: number) => { try { loading.value = true error.value = null await $fetch(`/api/products/${id}`, { method: 'DELETE' }) products.value = products.value.filter(p => p.id !== id) } catch (err: any) { error.value = err.message || '删除产品失败' throw err } finally { loading.value = false } } return { products: readonly(products), loading: readonly(loading), error: readonly(error), fetchProducts, fetchProduct, createProduct, updateProduct, deleteProduct } } ``` ## 中间件集成 ### 认证中间件 ```typescript // server/api/middleware/auth.ts export interface AuthenticatedRequest extends Request { user?: { id: string email: string role: string } } export const authMiddleware = async ( request: Request, next: () => Promise ) => { const token = request.headers.get('authorization')?.replace('Bearer ', '') if (!token) { return new Response('Unauthorized', { status: 401 }) } try { // 验证 JWT token const user = await verifyToken(token) ;(request as AuthenticatedRequest).user = user return next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } async function verifyToken(token: string) { // 实现 JWT 验证逻辑 // 这里应该使用 @vafast/jwt 中间件 return { id: '123', email: 'user@example.com', role: 'user' } } ``` ### 使用认证中间件 ```typescript // server/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { authMiddleware } from './middleware/auth' export const routes = defineRoutes([ { method: 'GET', path: '/api/profile', handler: createHandler(async ({ request }) => { const user = (request as AuthenticatedRequest).user return { user } }), middleware: [authMiddleware] } ]) ``` ## Nuxt 配置 ```typescript // nuxt.config.ts export default defineNuxtConfig({ // 启用 SSR ssr: true, // 开发工具 devtools: { enabled: true }, // 模块 modules: [ '@nuxtjs/tailwindcss' ], // 运行时配置 runtimeConfig: { // 私有配置(仅在服务器端可用) apiSecret: process.env.API_SECRET, // 公共配置(客户端和服务器端都可用) public: { apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api' } }, // Nitro 配置 nitro: { // 服务器端配置 experimental: { wasm: true } } }) ``` ## 环境配置 ```typescript // server/api/config.ts export const config = { development: { cors: { origin: ['http://localhost:3000', 'http://localhost:3001'] }, logging: true }, production: { cors: { origin: [process.env.NUXT_PUBLIC_APP_URL] }, logging: false } } export const getConfig = () => { const env = process.env.NODE_ENV || 'development' return config[env as keyof typeof config] } ``` ## 测试 ### API 测试 ```typescript // server/api/__tests__/products.test.ts import { describe, expect, it } from 'bun:test' import { handler } from '../server' describe('Products API', () => { it('should get products', async () => { const request = new Request('http://localhost/api/products') const response = await handler(request) const data = await response.json() expect(response.status).toBe(200) expect(data.products).toBeDefined() expect(Array.isArray(data.products)).toBe(true) }) it('should create product', async () => { const productData = { name: 'Test Product', price: 99.99, description: 'Test description' } const request = new Request('http://localhost/api/products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(productData) }) const response = await handler(request) const data = await response.json() expect(response.status).toBe(201) expect(data.product.name).toBe(productData.name) expect(data.product.price).toBe(productData.price) }) }) ``` ## 部署 ### 静态部署 ```bash # 构建应用 nuxt build # 生成静态文件 nuxt generate # 部署到静态托管服务 ``` ### 服务器部署 ```bash # 构建应用 nuxt build # 启动生产服务器 node .output/server/index.mjs ``` ### Docker 部署 ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --production COPY . . RUN bun run build EXPOSE 3000 CMD ["bun", "run", "start"] ``` ## 最佳实践 1. **类型安全**:使用 TypeScript 确保前后端类型一致 2. **错误处理**:实现统一的错误处理机制 3. **中间件顺序**:注意中间件的执行顺序 4. **环境配置**:根据环境配置不同的设置 5. **测试覆盖**:为 API 路由编写完整的测试 6. **性能优化**:使用适当的缓存和压缩策略 7. **SSR 优化**:利用 Nuxt 的 SSR 能力优化性能 8. **组合式函数**:将 API 逻辑封装到可复用的组合式函数中 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [Nuxt 文档](https://nuxt.com/docs) - Nuxt 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/integrations/openapi.md' --- # OpenAPI Vafast 提供了一流的支持,并默认遵循 OpenAPI 模式。 Vafast 可以通过提供 Swagger 中间件自动生成 API 文档页面。 要生成 Swagger 页面,请安装中间件: ```bash bun add @vafast/swagger ``` 并将中间件注册到服务器: ```typescript import { defineRoutes, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' const routes = defineRoutes([ // 你的路由定义 ]) const app = createHandler(routes) .use(swagger()) ``` 默认情况下,Vafast 使用 OpenAPI V3 模式和 [Scalar UI](http://scalar.com)。 有关 Swagger 中间件配置,请参见 [Swagger 中间件页面](/middleware/swagger)。 ## 路由定义 我们通过提供模式类型添加路由信息。 然而,有时候仅定义类型并不能清楚表达路由的功能。您可以使用 `detail` 字段明确地定义路由的目的。 ```typescript import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' import { swagger } from '@vafast/swagger' const routes = defineRoutes([ { method: 'POST', path: '/sign-in', handler: createHandler(({ body }) => body), body: Type.Object( { username: Type.String(), password: Type.String({ minLength: 8, description: '用户密码(至少 8 个字符)' }) }, { description: '期望的用户名和密码' } ), detail: { summary: '用户登录', tags: ['身份验证'] } } ]) const app = createHandler(routes) .use(swagger()) ``` 详细字段遵循 OpenAPI V3 定义,并默认具有自动补全和类型安全。 详细信息随后传递给 Swagger,以便将描述放入 Swagger 路由中。 ### detail `detail` 扩展了 [OpenAPI 操作对象](https://swagger.io/specification#operation-object) 详细字段是一个对象,可以用来描述有关该路由的 API 文档信息。 该字段可能包含以下内容: ### tags 该操作的标签数组。标签可用于根据资源或任何其他标识符逻辑分组操作。 ### summary 该操作执行的简短摘要。 ### description 该操作行为的详细解释。 ### externalDocs 该操作的额外外部文档。 ### operationId 用于唯一标识操作的字符串。该 ID 必须在 API 中所有描述的操作中唯一。operationId 值对大小写敏感。 ### deprecated 声明该操作已被弃用。消费者应避免使用已声明的操作。默认值为 `false`。 ### security 声明该操作的安全要求。 ## 类型安全 Vafast 的 OpenAPI 集成提供了完整的类型安全: ```typescript import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params, query }) => { // params.id 和 query.page 都是类型安全的 return { userId: params.id, page: query.page } }), params: Type.Object({ id: Type.String({ description: '用户ID' }) }), query: Type.Object({ page: Type.Optional(Type.Number({ minimum: 1, description: '页码' })) }), detail: { summary: '获取用户信息', tags: ['用户管理'], description: '根据用户ID获取用户详细信息' } } ]) ``` ## 响应模式 您还可以定义响应模式: ```typescript import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return { id: '123', ...body, createdAt: new Date() } }), body: Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }), response: Type.Object({ id: Type.String(), name: Type.String(), email: Type.String(), createdAt: Type.String({ format: 'date-time' }) }), detail: { summary: '创建用户', tags: ['用户管理'], responses: { 201: { description: '用户创建成功', content: { 'application/json': { schema: Type.Object({ id: Type.String(), name: Type.String(), email: Type.String(), createdAt: Type.String() }) } } }, 400: { description: '请求参数错误', content: { 'application/json': { schema: Type.Object({ error: Type.String(), details: Type.Array(Type.String()) }) } } } } } } ]) ``` ## 中间件集成 Vafast 的 Swagger 中间件可以与其他中间件无缝集成: ```typescript import { defineRoutes, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' const routes = defineRoutes([ // 你的路由定义 ]) const app = createHandler(routes) .use(cors()) .use(helmet()) .use(swagger({ documentation: { info: { title: 'Vafast API', version: '1.0.0', description: 'Vafast 框架 API 文档' }, tags: [ { name: '用户管理', description: '用户相关操作' }, { name: '身份验证', description: '登录注册等操作' } ] } })) ``` ## 自定义配置 Swagger 中间件支持丰富的配置选项: ```typescript import { swagger } from '@vafast/swagger' app.use(swagger({ documentation: { info: { title: '我的 API', version: '1.0.0', description: '这是一个使用 Vafast 框架构建的 API' }, servers: [ { url: 'http://localhost:3000', description: '开发环境' }, { url: 'https://api.example.com', description: '生产环境' } ], components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } } }, swagger: { path: '/swagger', uiConfig: { docExpansion: 'list', filter: true } } })) ``` ## 最佳实践 1. **使用描述性标签**:为相关路由使用一致的标签 2. **提供详细摘要**:每个路由都应该有清晰的 summary 3. **定义响应模式**:明确指定成功和错误的响应格式 4. **使用类型验证**:利用 TypeBox 的类型系统确保数据完整性 5. **版本控制**:在 API 文档中明确版本信息 ## 相关链接 * [Swagger 中间件](/middleware/swagger) - 完整的 Swagger 配置选项 * [TypeBox 集成](/patterns/type) - 了解类型验证系统 * [路由定义](/essential/route) - 学习如何定义路由 * [中间件系统](/middleware) - 探索其他可用的中间件 --- --- url: 'https://vafast.dev/middleware/opentelemetry.md' --- # OpenTelemetry 中间件 该中间件为 [Vafast](https://github.com/vafastjs/vafast) 提供了完整的 OpenTelemetry 集成支持,包括分布式追踪、指标收集和日志聚合。 ## 安装 安装命令: ```bash bun add @vafast/opentelemetry ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry } from '@vafast/opentelemetry' // 创建 OpenTelemetry 中间件 const telemetryMiddleware = opentelemetry({ serviceName: 'example-app', instrumentations: [] }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, Vafast with OpenTelemetry!' }) }, { method: 'GET', path: '/health', handler: createHandler(() => { return { status: 200, data: 'OK' } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用中间件 export default { fetch: (req: Request) => { // 应用 OpenTelemetry 中间件 return telemetryMiddleware(req, () => server.fetch(req)) } } ``` ## 预加载配置(推荐) 为了获得最佳性能和完整的 OpenTelemetry 功能,建议使用预加载配置: ```typescript // preload.ts import * as otel from '@opentelemetry/api' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' import { BatchSpanProcessor, ConsoleSpanExporter, Span } from '@opentelemetry/sdk-trace-node' import { Resource } from '@opentelemetry/resources' import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions' import { NodeSDK } from '@opentelemetry/sdk-node' import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' const sdk = new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()], resource: new Resource({ [SEMRESATTRS_SERVICE_NAME]: 'your-service-name' }), spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter({ // 配置你的 OTLP 导出器 // url: 'https://your-collector.com/v1/traces', // headers: { // Authorization: `Bearer ${process.env.OTEL_TOKEN}`, // } }) ), new BatchSpanProcessor(new ConsoleSpanExporter()) ] }) sdk.start() console.log('OpenTelemetry SDK 已启动') ``` 然后在你的主应用中: ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry } from '@vafast/opentelemetry' // 导入预加载配置 import './preload' // 创建 OpenTelemetry 中间件 const telemetryMiddleware = opentelemetry({ serviceName: 'your-service-name' }) // 定义路由 const routes = [ { method: 'GET', path: '/api/users', handler: createHandler(async () => { // 这个请求会自动被 OpenTelemetry 追踪 return { users: ['Alice', 'Bob', 'Charlie'] } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return telemetryMiddleware(req, () => server.fetch(req)) } } ``` ## 配置选项 该中间件扩展了 OpenTelemetry NodeSDK 的参数选项。 ### VafastOpenTelemetryOptions ```typescript interface VafastOpenTelemetryOptions { /** 服务名称,用于标识追踪和指标 */ serviceName?: string /** 自动检测环境中的资源 */ autoDetectResources?: boolean /** 自定义上下文管理器 */ contextManager?: ContextManager /** 自定义传播器 */ textMapPropagator?: TextMapPropagator /** 指标读取器 */ metricReader?: MetricReader /** 指标视图配置 */ views?: View[] /** 自动检测的仪器 */ instrumentations?: (Instrumentation | Instrumentation[])[] /** 资源配置 */ resource?: IResource /** 资源探测器 */ resourceDetectors?: Array /** 自定义采样器 */ sampler?: Sampler /** 跨度处理器 */ spanProcessors?: SpanProcessor[] /** 追踪导出器 */ traceExporter?: SpanExporter /** 跨度限制 */ spanLimits?: SpanLimits } ``` ## 使用模式 ### 1. 基本追踪 ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry, getTracer, startActiveSpan } from '@vafast/opentelemetry' const telemetryMiddleware = opentelemetry({ serviceName: 'basic-tracing-app' }) const routes = [ { method: 'GET', path: '/api/data', handler: createHandler(async () => { const tracer = getTracer() // 创建自定义跨度 return tracer.startActiveSpan('fetch-data', async (span) => { try { // 模拟数据获取 const data = await fetchDataFromDatabase() // 设置跨度属性 span.setAttributes({ 'data.source': 'database', 'data.count': data.length }) return { data } } finally { span.end() } }) }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return telemetryMiddleware(req, () => server.fetch(req)) } } async function fetchDataFromDatabase() { // 模拟数据库查询 return ['item1', 'item2', 'item3'] } ``` ### 2. 自定义跨度和属性 ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry, getTracer, setAttributes } from '@vafast/opentelemetry' const telemetryMiddleware = opentelemetry({ serviceName: 'custom-spans-app' }) const routes = [ { method: 'POST', path: '/api/users', handler: createHandler(async ({ req }) => { const tracer = getTracer() return tracer.startActiveSpan('create-user', { attributes: { 'http.method': 'POST', 'http.route': '/api/users' } }, async (span) => { try { const body = await req.json() // 设置用户相关属性 span.setAttributes({ 'user.email': body.email, 'user.role': body.role }) // 模拟用户创建 const user = await createUser(body) // 记录成功事件 span.addEvent('user.created', { 'user.id': user.id, 'user.email': user.email }) return { success: true, user } } catch (error) { // 记录错误 span.recordException(error as Error) throw error } finally { span.end() } }) }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return telemetryMiddleware(req, () => server.fetch(req)) } } async function createUser(userData: any) { // 模拟用户创建 return { id: Math.random().toString(36).substr(2, 9), ...userData } } ``` ### 3. 分布式追踪 ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry, getTracer, getCurrentSpan } from '@vafast/opentelemetry' const telemetryMiddleware = opentelemetry({ serviceName: 'distributed-tracing-app' }) const routes = [ { method: 'GET', path: '/api/orders/:id', handler: createHandler(async ({ params }) => { const tracer = getTracer() return tracer.startActiveSpan('get-order', async (span) => { try { const orderId = params.id // 设置订单相关属性 span.setAttributes({ 'order.id': orderId, 'operation.type': 'read' }) // 获取订单信息 const order = await getOrder(orderId) // 获取用户信息(创建子跨度) const user = await tracer.startActiveSpan('get-user', async (userSpan) => { userSpan.setAttributes({ 'user.id': order.userId }) const userData = await getUser(order.userId) userSpan.end() return userData }) // 获取产品信息(创建子跨度) const products = await tracer.startActiveSpan('get-products', async (productsSpan) => { productsSpan.setAttributes({ 'products.count': order.productIds.length }) const productsData = await getProducts(order.productIds) productsSpan.end() return productsData }) return { order, user, products } } finally { span.end() } }) }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return telemetryMiddleware(req, () => server.fetch(req)) } } async function getOrder(orderId: string) { // 模拟获取订单 return { id: orderId, userId: 'user123', productIds: ['prod1', 'prod2'], total: 299.99 } } async function getUser(userId: string) { // 模拟获取用户 return { id: userId, name: 'John Doe', email: 'john@example.com' } } async function getProducts(productIds: string[]) { // 模拟获取产品 return productIds.map(id => ({ id, name: `Product ${id}`, price: 149.99 })) } ``` ### 4. 错误追踪和监控 ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry, getTracer } from '@vafast/opentelemetry' const telemetryMiddleware = opentelemetry({ serviceName: 'error-tracking-app' }) const routes = [ { method: 'GET', path: '/api/risky-operation', handler: createHandler(async () => { const tracer = getTracer() return tracer.startActiveSpan('risky-operation', async (span) => { try { // 模拟有风险的操作 const result = await performRiskyOperation() span.setAttributes({ 'operation.success': true, 'operation.result': result }) return { success: true, result } } catch (error) { // 详细记录错误信息 span.setAttributes({ 'operation.success': false, 'error.type': error.constructor.name, 'error.message': error instanceof Error ? error.message : String(error) }) // 记录异常 span.recordException(error as Error) // 设置错误状态 span.setStatus({ code: 2, // ERROR message: error instanceof Error ? error.message : 'Unknown error' }) throw error } finally { span.end() } }) }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return telemetryMiddleware(req, () => server.fetch(req)) } } async function performRiskyOperation() { // 模拟有风险的操作 if (Math.random() > 0.5) { throw new Error('Operation failed due to random chance') } return 'Operation completed successfully' } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { opentelemetry, getTracer, startActiveSpan } from '@vafast/opentelemetry' // 导入预加载配置 import './preload' // 创建 OpenTelemetry 中间件 const telemetryMiddleware = opentelemetry({ serviceName: 'ecommerce-api', instrumentations: [] }) // 模拟数据库操作 class UserService { async getUser(id: string) { const tracer = getTracer() return tracer.startActiveSpan('db.get-user', { attributes: { 'db.operation': 'select', 'db.table': 'users', 'db.user.id': id } }, async (span) => { try { // 模拟数据库查询延迟 await new Promise(resolve => setTimeout(resolve, 50)) const user = { id, name: `User ${id}`, email: `user${id}@example.com` } span.setAttributes({ 'db.result.count': 1, 'db.user.found': true }) return user } catch (error) { span.recordException(error as Error) throw error } finally { span.end() } }) } async createUser(userData: any) { const tracer = getTracer() return tracer.startActiveSpan('db.create-user', { attributes: { 'db.operation': 'insert', 'db.table': 'users' } }, async (span) => { try { await new Promise(resolve => setTimeout(resolve, 100)) const user = { id: Math.random().toString(36).substr(2, 9), ...userData } span.setAttributes({ 'db.result.count': 1, 'db.user.id': user.id }) return user } catch (error) { span.recordException(error as Error) throw error } finally { span.end() } }) } } class ProductService { async getProducts(ids: string[]) { const tracer = getTracer() return tracer.startActiveSpan('db.get-products', { attributes: { 'db.operation': 'select', 'db.table': 'products', 'db.products.count': ids.length } }, async (span) => { try { await new Promise(resolve => setTimeout(resolve, 30)) const products = ids.map(id => ({ id, name: `Product ${id}`, price: Math.random() * 1000 })) span.setAttributes({ 'db.result.count': products.length }) return products } catch (error) { span.recordException(error as Error) throw error } finally { span.end() } }) } } // 创建服务实例 const userService = new UserService() const productService = new ProductService() // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'E-commerce API with OpenTelemetry', version: '1.0.0', endpoints: [ 'GET /api/users/:id - 获取用户信息', 'POST /api/users - 创建用户', 'GET /api/orders/:id - 获取订单详情', 'POST /api/orders - 创建订单' ] } }) }, { method: 'GET', path: '/api/users/:id', handler: createHandler(async ({ params }) => { const tracer = getTracer() return tracer.startActiveSpan('api.get-user', { attributes: { 'http.method': 'GET', 'http.route': '/api/users/:id', 'user.id': params.id } }, async (span) => { try { const user = await userService.getUser(params.id) span.setAttributes({ 'response.status': 200, 'response.type': 'success' }) return { success: true, data: user } } catch (error) { span.setAttributes({ 'response.status': 500, 'response.type': 'error' }) span.recordException(error as Error) throw error } finally { span.end() } }) }) }, { method: 'POST', path: '/api/users', handler: createHandler(async ({ req }) => { const tracer = getTracer() return tracer.startActiveSpan('api.create-user', { attributes: { 'http.method': 'POST', 'http.route': '/api/users' } }, async (span) => { try { const body = await req.json() span.setAttributes({ 'user.email': body.email, 'user.role': body.role || 'user' }) const user = await userService.createUser(body) span.setAttributes({ 'response.status': 201, 'response.type': 'success', 'user.id': user.id }) return { success: true, data: user, message: 'User created successfully' } } catch (error) { span.setAttributes({ 'response.status': 500, 'response.type': 'error' }) span.recordException(error as Error) throw error } finally { span.end() } }) }) }, { method: 'GET', path: '/api/orders/:id', handler: createHandler(async ({ params }) => { const tracer = getTracer() return tracer.startActiveSpan('api.get-order', { attributes: { 'http.method': 'GET', 'http.route': '/api/orders/:id', 'order.id': params.id } }, async (span) => { try { // 模拟获取订单 const order = { id: params.id, userId: 'user123', productIds: ['prod1', 'prod2', 'prod3'], total: 299.99, status: 'completed' } // 并行获取用户和产品信息 const [user, products] = await Promise.all([ userService.getUser(order.userId), productService.getProducts(order.productIds) ]) const result = { order, user, products } span.setAttributes({ 'response.status': 200, 'response.type': 'success', 'order.total': order.total, 'products.count': products.length }) return { success: true, data: result } } catch (error) { span.setAttributes({ 'response.status': 500, 'response.type': 'error' }) span.recordException(error as Error) throw error } finally { span.end() } }) }) }, { method: 'POST', path: '/api/orders', handler: createHandler(async ({ req }) => { const tracer = getTracer() return tracer.startActiveSpan('api.create-order', { attributes: { 'http.method': 'POST', 'http.route': '/api/orders' } }, async (span) => { try { const body = await req.json() span.setAttributes({ 'order.user.id': body.userId, 'order.products.count': body.productIds?.length || 0 }) // 验证用户存在 const user = await userService.getUser(body.userId) // 获取产品信息 const products = await productService.getProducts(body.productIds || []) // 计算总价 const total = products.reduce((sum, product) => sum + product.price, 0) const order = { id: Math.random().toString(36).substr(2, 9), userId: body.userId, productIds: body.productIds || [], total, status: 'pending', createdAt: new Date().toISOString() } span.setAttributes({ 'response.status': 201, 'response.type': 'success', 'order.id': order.id, 'order.total': order.total }) return { success: true, data: order, message: 'Order created successfully' } } catch (error) { span.setAttributes({ 'response.status': 500, 'response.type': 'error' }) span.recordException(error as Error) throw error } finally { span.end() } }) }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用中间件 export default { fetch: (req: Request) => { return telemetryMiddleware(req, () => server.fetch(req)) } } console.log('🚀 E-commerce API with OpenTelemetry 服务器启动成功!') console.log('📊 所有请求都将被自动追踪和监控') console.log('🔍 查看 Jaeger 或其他 OpenTelemetry 后端以获取追踪数据') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { opentelemetry } from '@vafast/opentelemetry' describe('Vafast OpenTelemetry Plugin', () => { it('should create OpenTelemetry middleware', () => { const telemetryMiddleware = opentelemetry({ serviceName: 'test-app', instrumentations: [] }) expect(telemetryMiddleware).toBeDefined() expect(typeof telemetryMiddleware).toBe('function') }) it('should process requests through OpenTelemetry middleware', async () => { const telemetryMiddleware = opentelemetry({ serviceName: 'test-app', instrumentations: [] }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, OpenTelemetry!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return telemetryMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/')) const data = await res.text() expect(data).toBe('Hello, OpenTelemetry!') expect(res.status).toBe(200) }) it('should handle errors in OpenTelemetry middleware', async () => { const telemetryMiddleware = opentelemetry({ serviceName: 'test-app', instrumentations: [] }) const app = new Server([ { method: 'GET', path: '/error', handler: createHandler(() => { throw new Error('Test error') }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return telemetryMiddleware(req, () => app.fetch(req)) } // 测试错误处理 - OpenTelemetry 中间件应该能够处理错误 const result = await wrappedFetch(new Request('http://localhost/error')) // 如果中间件正确处理了错误,我们应该得到一个响应而不是抛出异常 expect(result).toBeDefined() }) it('should work with custom service name', () => { const telemetryMiddleware = opentelemetry({ serviceName: 'custom-service', instrumentations: [] }) expect(telemetryMiddleware).toBeDefined() }) it('should work with instrumentations', () => { const telemetryMiddleware = opentelemetry({ serviceName: 'test-app', instrumentations: [] }) expect(telemetryMiddleware).toBeDefined() }) it('should handle different HTTP methods', async () => { const telemetryMiddleware = opentelemetry({ serviceName: 'test-app', instrumentations: [] }) const app = new Server([ { method: 'POST', path: '/', handler: createHandler(() => { return { message: 'POST request' } }) } ]) const wrappedFetch = (req: Request) => { return telemetryMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/', { method: 'POST' })) const data = await res.json() expect(data.message).toBe('POST request') expect(res.status).toBe(200) }) }) ``` ## 特性 * ✅ **自动追踪**: 自动为所有 HTTP 请求创建追踪跨度 * ✅ **分布式追踪**: 支持跨服务调用链追踪 * ✅ **性能监控**: 自动收集请求响应时间、状态码等指标 * ✅ **错误追踪**: 自动捕获和记录异常信息 * ✅ **自定义跨度**: 支持创建自定义业务逻辑跨度 * ✅ **属性设置**: 支持为跨度添加自定义属性 * ✅ **上下文传播**: 支持 W3C Trace Context 和 Baggage 传播 * ✅ **多种导出器**: 支持 OTLP、Jaeger、Zipkin 等导出器 * ✅ **自动检测**: 支持自动检测 Node.js 环境资源 * ✅ **类型安全**: 完整的 TypeScript 类型支持 ## 最佳实践 ### 1. 使用预加载配置 ```typescript // 在应用启动前初始化 OpenTelemetry SDK import './preload' // 然后在你的应用中使用简化的配置 const telemetryMiddleware = opentelemetry({ serviceName: 'your-service' }) ``` ### 2. 合理的跨度命名 ```typescript // 使用有意义的跨度名称 tracer.startActiveSpan('db.query-users', async (span) => { // 数据库查询逻辑 }) tracer.startActiveSpan('api.process-order', async (span) => { // 订单处理逻辑 }) ``` ### 3. 设置有用的属性 ```typescript span.setAttributes({ 'user.id': userId, 'order.total': orderTotal, 'db.table': 'orders', 'cache.hit': true }) ``` ### 4. 错误处理 ```typescript try { // 业务逻辑 } catch (error) { span.recordException(error as Error) span.setStatus({ code: 2, // ERROR message: error.message }) throw error } ``` ### 5. 性能优化 ```typescript // 避免创建过多的小跨度 // 好的做法:为重要操作创建跨度 tracer.startActiveSpan('process-batch', async (span) => { for (const item of items) { // 处理单个项目,不需要为每个项目创建跨度 await processItem(item) } }) // 不好的做法:为每个小操作创建跨度 for (const item of items) { await tracer.startActiveSpan('process-item', async (span) => { await processItem(item) span.end() }) } ``` ## 注意事项 1. **性能影响**: OpenTelemetry 会为每个请求创建追踪跨度,在生产环境中注意采样配置 2. **存储成本**: 追踪数据可能产生大量存储成本,合理配置数据保留策略 3. **敏感信息**: 避免在追踪属性中包含敏感信息(如密码、令牌等) 4. **采样策略**: 在高流量环境中使用适当的采样策略以控制数据量 5. **导出器配置**: 确保导出器配置正确,避免数据丢失 ## 相关链接 * [OpenTelemetry 官方文档](https://opentelemetry.io/docs/) * [OpenTelemetry JavaScript](https://opentelemetry.io/docs/languages/js/) * [Jaeger 追踪系统](https://www.jaegertracing.io/) * [Zipkin 追踪系统](https://zipkin.io/) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/integrations/opentelemetry.md' --- # OpenTelemetry 集成 Vafast 提供了完整的 OpenTelemetry 集成支持,包括分布式追踪、指标收集和日志聚合。 要开始使用 OpenTelemetry,请安装 `@vafast/opentelemetry` 并将中间件应用于任何实例。 ## 安装 ```bash bun add @vafast/opentelemetry ``` ## 基本用法 ```typescript import { defineRoutes, createHandler } from 'vafast' import { opentelemetry } from '@vafast/opentelemetry' const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { return { users: [] } }) } ]) const app = createHandler(routes) .use(opentelemetry({ serviceName: 'my-vafast-app', serviceVersion: '1.0.0' })) ``` ## 配置选项 OpenTelemetry 中间件支持丰富的配置选项: ```typescript import { opentelemetry } from '@vafast/opentelemetry' app.use(opentelemetry({ // 服务信息 serviceName: 'my-vafast-app', serviceVersion: '1.0.0', serviceNamespace: 'production', // 追踪配置 tracing: { enabled: true, sampler: { type: 'always_on' }, exporter: { type: 'otlp', endpoint: 'http://localhost:4317' } }, // 指标配置 metrics: { enabled: true, exporter: { type: 'prometheus', port: 9464 } }, // 日志配置 logging: { enabled: true, level: 'info', exporter: { type: 'otlp', endpoint: 'http://localhost:4317' } } })) ``` ## 分布式追踪 OpenTelemetry 中间件自动为所有请求创建追踪: ```typescript import { defineRoutes, createHandler } from 'vafast' import { opentelemetry } from '@vafast/opentelemetry' const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(async ({ params }) => { // 这个请求会自动创建追踪 const user = await fetchUser(params.id) return user }), params: Type.Object({ id: Type.String() }) } ]) const app = createHandler(routes) .use(opentelemetry({ serviceName: 'user-service', tracing: { enabled: true, exporter: { type: 'otlp', endpoint: 'http://jaeger:4317' } } })) ``` ## 自定义追踪 您可以在处理程序中添加自定义追踪: ```typescript import { defineRoutes, createHandler } from 'vafast' import { trace } from '@opentelemetry/api' const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { const tracer = trace.getTracer('user-service') return await tracer.startActiveSpan('create-user', async (span) => { try { span.setAttribute('user.email', body.email) const user = await createUser(body) span.setStatus({ code: trace.SpanStatusCode.OK }) return user } catch (error) { span.setStatus({ code: trace.SpanStatusCode.ERROR, message: error.message }) throw error } finally { span.end() } }) }), body: Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) }) } ]) ``` ## 指标收集 中间件自动收集关键指标: ```typescript import { opentelemetry } from '@vafast/opentelemetry' app.use(opentelemetry({ metrics: { enabled: true, exporter: { type: 'prometheus', port: 9464, path: '/metrics' } } })) ``` 自动收集的指标包括: * **HTTP 请求计数**:按方法、路径、状态码分组 * **请求持续时间**:响应时间分布 * **活跃连接数**:当前活跃的 HTTP 连接 * **错误率**:按错误类型分组的错误计数 ## 日志聚合 OpenTelemetry 中间件提供结构化日志: ```typescript import { opentelemetry } from '@vafast/opentelemetry' app.use(opentelemetry({ logging: { enabled: true, level: 'info', exporter: { type: 'otlp', endpoint: 'http://localhost:4317' } } })) ``` ## 环境配置 根据环境配置 OpenTelemetry: ```typescript import { opentelemetry } from '@vafast/opentelemetry' const isDevelopment = process.env.NODE_ENV === 'development' app.use(opentelemetry({ serviceName: 'my-vafast-app', serviceVersion: process.env.APP_VERSION || '1.0.0', tracing: { enabled: !isDevelopment, exporter: { type: 'otlp', endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317' } }, metrics: { enabled: true, exporter: { type: 'prometheus', port: parseInt(process.env.METRICS_PORT || '9464') } }, logging: { enabled: true, level: process.env.LOG_LEVEL || 'info', exporter: { type: isDevelopment ? 'console' : 'otlp', endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT } } })) ``` ## 与监控系统集成 ### Jaeger 追踪 ```typescript app.use(opentelemetry({ tracing: { exporter: { type: 'otlp', endpoint: 'http://jaeger:4317' } } })) ``` ### Prometheus 指标 ```typescript app.use(opentelemetry({ metrics: { exporter: { type: 'prometheus', port: 9464, path: '/metrics' } } })) ``` ### Grafana Loki 日志 ```typescript app.use(opentelemetry({ logging: { exporter: { type: 'otlp', endpoint: 'http://loki:4317' } } })) ``` ## 性能优化 OpenTelemetry 中间件经过优化,对性能影响最小: ```typescript app.use(opentelemetry({ tracing: { sampler: { type: 'traceidratio', ratio: 0.1 // 只追踪 10% 的请求 } }, metrics: { collectionInterval: 5000 // 5秒收集一次指标 } })) ``` ## 最佳实践 1. **服务命名**:使用有意义的服务名称,便于识别 2. **采样策略**:在生产环境中使用适当的采样策略 3. **错误处理**:确保错误被正确记录和追踪 4. **性能监控**:监控中间件本身的性能影响 5. **安全考虑**:在生产环境中保护监控端点 ## 相关链接 * [OpenTelemetry 中间件](/middleware/opentelemetry) - 完整的配置选项 * [性能监控](/patterns/trace) - 了解性能追踪 * [中间件系统](/middleware) - 探索其他可用的中间件 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/integrations/prisma.md' --- # Prisma 集成 Vafast 可以与 Prisma ORM 无缝集成,为您提供类型安全的数据库操作和优秀的开发体验。 ## 安装依赖 ```bash bun add @prisma/client bun add -D prisma ``` ## 初始化 Prisma ```bash # 初始化 Prisma 项目 bunx prisma init # 选择数据库类型(例如:PostgreSQL, MySQL, SQLite) ``` ## 数据库模式定义 ```prisma // prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique name String password String role Role @default(USER) posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("users") } model Post { id String @id @default(cuid()) title String content String published Boolean @default(false) authorId String author User @relation(fields: [authorId], references: [id], onDelete: Cascade) tags Tag[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("posts") } model Tag { id String @id @default(cuid()) name String @unique posts Post[] @@map("tags") } enum Role { USER ADMIN } ``` ## 数据库客户端配置 ```typescript // src/db/client.ts import { PrismaClient } from '@prisma/client' // 创建 Prisma 客户端实例 const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined } export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'] }) if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma // 优雅关闭 process.on('beforeExit', async () => { await prisma.$disconnect() }) ``` ## 数据库服务层 ```typescript // src/services/userService.ts import { prisma } from '../db/client' import type { User, Prisma } from '@prisma/client' import { hashPassword, verifyPassword } from '../utils/auth' export class UserService { // 根据邮箱查找用户 async findByEmail(email: string): Promise { return await prisma.user.findUnique({ where: { email } }) } // 根据ID查找用户 async findById(id: string): Promise { return await prisma.user.findUnique({ where: { id } }) } // 创建用户 async create(userData: Prisma.UserCreateInput): Promise { const hashedPassword = await hashPassword(userData.password) return await prisma.user.create({ data: { ...userData, password: hashedPassword } }) } // 更新用户 async update(id: string, userData: Prisma.UserUpdateInput): Promise { if (userData.password) { userData.password = await hashPassword(userData.password as string) } return await prisma.user.update({ where: { id }, data: userData }) } // 删除用户 async delete(id: string): Promise { await prisma.user.delete({ where: { id } }) } // 获取用户列表(分页) async findAll(page = 1, limit = 20) { const skip = (page - 1) * limit const [users, total] = await Promise.all([ prisma.user.findMany({ skip, take: limit, orderBy: { createdAt: 'desc' }, select: { id: true, email: true, name: true, role: true, createdAt: true, updatedAt: true } }), prisma.user.count() ]) return { users, total, page, limit, totalPages: Math.ceil(total / limit) } } } export const userService = new UserService() ``` ```typescript // src/services/postService.ts import { prisma } from '../db/client' import type { Post, Prisma } from '@prisma/client' export class PostService { // 获取所有已发布的文章 async findPublished(page = 1, limit = 10) { const skip = (page - 1) * limit const [posts, total] = await Promise.all([ prisma.post.findMany({ where: { published: true }, include: { author: { select: { id: true, name: true, email: true } }, tags: { select: { id: true, name: true } } }, skip, take: limit, orderBy: { createdAt: 'desc' } }), prisma.post.count({ where: { published: true } }) ]) return { posts, total, page, limit, totalPages: Math.ceil(total / limit) } } // 根据ID获取文章 async findById(id: string) { return await prisma.post.findUnique({ where: { id }, include: { author: { select: { id: true, name: true, email: true } }, tags: { select: { id: true, name: true } } } }) } // 创建文章 async create(postData: Prisma.PostCreateInput): Promise { return await prisma.post.create({ data: postData, include: { author: { select: { id: true, name: true, email: true } }, tags: { select: { id: true, name: true } } } }) } // 更新文章 async update(id: string, postData: Prisma.PostUpdateInput): Promise { return await prisma.post.update({ where: { id }, data: postData, include: { author: { select: { id: true, name: true, email: true } }, tags: { select: { id: true, name: true } } } }) } // 删除文章 async delete(id: string): Promise { await prisma.post.delete({ where: { id } }) } // 搜索文章 async search(query: string, page = 1, limit = 10) { const skip = (page - 1) * limit const [posts, total] = await Promise.all([ prisma.post.findMany({ where: { AND: [ { published: true }, { OR: [ { title: { contains: query, mode: 'insensitive' } }, { content: { contains: query, mode: 'insensitive' } } ] } ] }, include: { author: { select: { id: true, name: true, email: true } }, tags: { select: { id: true, name: true } } }, skip, take: limit, orderBy: { createdAt: 'desc' } }), prisma.post.count({ where: { AND: [ { published: true }, { OR: [ { title: { contains: query, mode: 'insensitive' } }, { content: { contains: query, mode: 'insensitive' } } ] } ] } }) ]) return { posts, total, page, limit, totalPages: Math.ceil(total / limit) } } } export const postService = new PostService() ``` ```typescript // src/services/tagService.ts import { prisma } from '../db/client' import type { Tag, Prisma } from '@prisma/client' export class TagService { // 获取所有标签 async findAll(): Promise { return await prisma.tag.findMany({ orderBy: { name: 'asc' } }) } // 根据ID获取标签 async findById(id: string): Promise { return await prisma.tag.findUnique({ where: { id } }) } // 创建标签 async create(tagData: Prisma.TagCreateInput): Promise { return await prisma.tag.create({ data: tagData }) } // 删除标签 async delete(id: string): Promise { await prisma.tag.delete({ where: { id } }) } } export const tagService = new TagService() ``` ## 在 Vafast 路由中使用 ```typescript // src/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' import { userService, postService, tagService } from './services' import { authMiddleware } from './middleware/auth' export const routes = defineRoutes([ // 用户认证路由 { method: 'POST', path: '/api/auth/register', handler: createHandler(async ({ body }) => { const { email, name, password } = body // 检查用户是否已存在 const existingUser = await userService.findByEmail(email) if (existingUser) { return { error: '用户已存在' }, { status: 400 } } // 创建新用户 const newUser = await userService.create({ email, name, password, role: 'USER' }) return { user: { id: newUser.id, email: newUser.email, name: newUser.name, role: newUser.role }, message: '注册成功' } }), body: Type.Object({ email: Type.String({ format: 'email' }), name: Type.String({ minLength: 1 }), password: Type.String({ minLength: 6 }) }) }, { method: 'POST', path: '/api/auth/login', handler: createHandler(async ({ body }) => { const { email, password } = body // 查找用户 const user = await userService.findByEmail(email) if (!user) { return { error: '用户不存在' }, { status: 401 } } // 验证密码 const isValidPassword = await verifyPassword(password, user.password) if (!isValidPassword) { return { error: '密码错误' }, { status: 401 } } return { user: { id: user.id, email: user.email, name: user.name, role: user.role }, message: '登录成功' } }), body: Type.Object({ email: Type.String({ format: 'email' }), password: Type.String({ minLength: 1 }) }) }, // 文章路由 { method: 'GET', path: '/api/posts', handler: createHandler(async ({ query }) => { const page = parseInt(query.page || '1') const limit = parseInt(query.limit || '10') const result = await postService.findPublished(page, limit) return result }), query: Type.Object({ page: Type.Optional(Type.String({ pattern: '^\\d+$' })), limit: Type.Optional(Type.String({ pattern: '^\\d+$' })) }) }, { method: 'GET', path: '/api/posts/:id', handler: createHandler(async ({ params }) => { const post = await postService.findById(params.id) if (!post) { return { error: '文章不存在' }, { status: 404 } } return { post } }), params: Type.Object({ id: Type.String() }) }, { method: 'POST', path: '/api/posts', handler: createHandler(async ({ body, request }) => { // 这里应该验证用户身份 const authorId = 'user-id-from-auth' // 从认证中间件获取 const newPost = await postService.create({ ...body, authorId }) return { post: newPost }, { status: 201 } }), body: Type.Object({ title: Type.String({ minLength: 1 }), content: Type.String({ minLength: 1 }), published: Type.Optional(Type.Boolean()), tagIds: Type.Optional(Type.Array(Type.String())) }), middleware: [authMiddleware] }, { method: 'PUT', path: '/api/posts/:id', handler: createHandler(async ({ params, body }) => { // 这里应该验证用户身份和权限 const updatedPost = await postService.update(params.id, body) if (!updatedPost) { return { error: '文章不存在' }, { status: 404 } } return { post: updatedPost } }), params: Type.Object({ id: Type.String() }), body: Type.Object({ title: Type.Optional(Type.String({ minLength: 1 })), content: Type.Optional(Type.String({ minLength: 1 })), published: Type.Optional(Type.Boolean()), tagIds: Type.Optional(Type.Array(Type.String())) }), middleware: [authMiddleware] }, { method: 'DELETE', path: '/api/posts/:id', handler: createHandler(async ({ params }) => { // 这里应该验证用户身份和权限 await postService.delete(params.id) return { message: '文章删除成功' } }), params: Type.Object({ id: Type.String() }), middleware: [authMiddleware] }, // 标签路由 { method: 'GET', path: '/api/tags', handler: createHandler(async () => { const tags = await tagService.findAll() return { tags } }) }, { method: 'POST', path: '/api/tags', handler: createHandler(async ({ body }) => { const newTag = await tagService.create(body) return { tag: newTag }, { status: 201 } }), body: Type.Object({ name: Type.String({ minLength: 1 }) }), middleware: [authMiddleware] } ]) ``` ## 数据库迁移 ```bash # 生成迁移文件 bunx prisma migrate dev --name init # 应用迁移到数据库 bunx prisma migrate deploy # 重置数据库(开发环境) bunx prisma migrate reset # 查看数据库状态 bunx prisma studio ``` ## 种子数据 ```typescript // prisma/seed.ts import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function main() { // 创建用户 const user1 = await prisma.user.upsert({ where: { email: 'admin@example.com' }, update: {}, create: { email: 'admin@example.com', name: 'Admin User', password: 'hashed_password_here', role: 'ADMIN' } }) const user2 = await prisma.user.upsert({ where: { email: 'user@example.com' }, update: {}, create: { email: 'user@example.com', name: 'Regular User', password: 'hashed_password_here', role: 'USER' } }) // 创建标签 const tag1 = await prisma.tag.upsert({ where: { name: 'Technology' }, update: {}, create: { name: 'Technology' } }) const tag2 = await prisma.tag.upsert({ where: { name: 'Programming' }, update: {}, create: { name: 'Programming' } }) // 创建文章 const post1 = await prisma.post.upsert({ where: { id: 'post-1' }, update: {}, create: { id: 'post-1', title: 'Getting Started with Vafast', content: 'Vafast is a modern, type-safe web framework...', published: true, authorId: user1.id, tags: { connect: [{ id: tag1.id }, { id: tag2.id }] } } }) console.log({ user1, user2, tag1, tag2, post1 }) } main() .catch((e) => { console.error(e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() }) ``` ## 事务处理 ```typescript // src/services/transactionService.ts import { prisma } from '../db/client' export class TransactionService { // 创建用户和文章的事务 async createUserWithPost(userData: any, postData: any) { return await prisma.$transaction(async (tx) => { // 创建用户 const user = await tx.user.create({ data: userData }) // 创建文章 const post = await tx.post.create({ data: { ...postData, authorId: user.id } }) return { user, post } }) } // 批量操作事务 async batchCreatePosts(postsData: any[], authorId: string) { return await prisma.$transaction(async (tx) => { const posts = [] for (const postData of postsData) { const post = await tx.post.create({ data: { ...postData, authorId } }) posts.push(post) } return posts }) } } export const transactionService = new TransactionService() ``` ## 性能优化 ```typescript // src/services/optimizedPostService.ts import { prisma } from '../db/client' export class OptimizedPostService { // 使用 select 优化查询 async findPostsOptimized(page = 1, limit = 10) { const skip = (page - 1) * limit const [posts, total] = await Promise.all([ prisma.post.findMany({ where: { published: true }, select: { id: true, title: true, createdAt: true, author: { select: { name: true } } }, skip, take: limit, orderBy: { createdAt: 'desc' } }), prisma.post.count({ where: { published: true } }) ]) return { posts, total, page, limit } } // 使用 include 进行关联查询 async findPostWithRelations(id: string) { return await prisma.post.findUnique({ where: { id }, include: { author: { select: { id: true, name: true, email: true } }, tags: { select: { id: true, name: true } } } }) } } export const optimizedPostService = new OptimizedPostService() ``` ## 测试 ```typescript // src/services/__tests__/userService.test.ts import { describe, expect, it, beforeEach, afterEach } from 'bun:test' import { prisma } from '../../db/client' import { userService } from '../userService' describe('UserService', () => { beforeEach(async () => { // 清理测试数据 await prisma.post.deleteMany() await prisma.user.deleteMany() }) afterEach(async () => { // 清理测试数据 await prisma.post.deleteMany() await prisma.user.deleteMany() }) it('should create and find user', async () => { const userData = { email: 'test@example.com', name: 'Test User', password: 'hashed_password', role: 'USER' as const } const newUser = await userService.create(userData) expect(newUser).toBeDefined() expect(newUser.email).toBe(userData.email) const foundUser = await userService.findByEmail(userData.email) expect(foundUser).toBeDefined() expect(foundUser?.id).toBe(newUser.id) }) it('should update user', async () => { const user = await userService.create({ email: 'test@example.com', name: 'Test User', password: 'hashed_password', role: 'USER' }) const updatedUser = await userService.update(user.id, { name: 'Updated Name' }) expect(updatedUser.name).toBe('Updated Name') }) }) ``` ## 最佳实践 1. **类型安全**:充分利用 Prisma 的类型推断功能 2. **查询优化**:使用 select 和 include 优化查询性能 3. **事务管理**:在需要原子性的操作中使用事务 4. **连接管理**:在生产环境中使用连接池 5. **迁移管理**:使用 Prisma Migrate 管理数据库模式变更 6. **测试覆盖**:为数据库操作编写完整的测试 7. **性能监控**:监控查询性能并优化慢查询 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [Prisma 文档](https://www.prisma.io/docs) - Prisma ORM 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/middleware/rate-limit.md' --- # Rate Limit 中间件 该中间件为 [Vafast](https://github.com/vafastjs/vafast) 提供了灵活的速率限制功能,保护你的 API 免受滥用和 DDoS 攻击。 ## 安装 安装命令: ```bash bun add @vafast/rate-limit ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' // 创建速率限制中间件 const rateLimitMiddleware = rateLimit({ duration: 60000, // 1分钟 max: 5, // 最多5个请求 errorResponse: 'Rate limit exceeded. Please try again later.', headers: true, skip: (req) => { // 跳过健康检查请求 return req.url.includes('/health') } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, Vafast with Rate Limiting!' }) }, { method: 'GET', path: '/health', handler: createHandler(() => { return { status: 'OK', timestamp: new Date().toISOString() } }) }, { method: 'POST', path: '/api/data', handler: createHandler(() => { return { message: 'Data created successfully' } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用中间件 export default { fetch: (req: Request) => { // 应用速率限制中间件 return rateLimitMiddleware(req, () => server.fetch(req)) } } ``` ## 配置选项 ### Options ```typescript interface Options { /** 速率限制的时间窗口(毫秒),默认:60000ms (1分钟) */ duration: number /** 在指定时间窗口内允许的最大请求数,默认:10 */ max: number /** 当达到速率限制时的错误响应,可以是字符串、Response 对象或 Error 对象 */ errorResponse: string | Response | Error /** 速率限制的作用域(保持兼容性,在 vafast 中未使用) */ scoping: 'global' | 'scoped' /** 是否在请求失败时也计入速率限制,默认:false */ countFailedRequest: boolean /** 生成客户端标识密钥的函数 */ generator: Generator /** 存储请求计数的上下文对象 */ context: Context /** 跳过速率限制的函数,返回 true 时跳过计数 */ skip: (req: Request, key?: string) => boolean | Promise /** 注入服务器实例到生成器函数的显式方式(仅作为最后手段使用) */ injectServer?: () => any | null /** 是否让中间件控制 RateLimit-* 头部,默认:true */ headers: boolean } ``` ### 默认选项 ```typescript const defaultOptions = { duration: 60000, // 1分钟 max: 10, // 最多10个请求 errorResponse: 'rate-limit reached', scoping: 'global', countFailedRequest: false, generator: defaultKeyGenerator, // 基于 IP 地址的默认生成器 headers: true, skip: () => false, // 默认不跳过任何请求 } ``` ## 使用模式 ### 1. 基本速率限制 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' const rateLimitMiddleware = rateLimit({ duration: 60000, // 1分钟 max: 10, // 最多10个请求 errorResponse: 'Too many requests. Please try again later.', headers: true }) const routes = [ { method: 'GET', path: '/api/users', handler: createHandler(() => { return { users: ['Alice', 'Bob', 'Charlie'] } }) }, { method: 'POST', path: '/api/users', handler: createHandler(async ({ req }) => { const body = await req.json() return { message: 'User created', user: body } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return rateLimitMiddleware(req, () => server.fetch(req)) } } ``` ### 2. 自定义密钥生成器 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' import type { Generator } from '@vafast/rate-limit' // 基于用户 ID 的密钥生成器 const userBasedGenerator: Generator<{ userId: string }> = async (req, server, { userId }) => { // 从请求头或查询参数获取用户 ID const authHeader = req.headers.get('authorization') if (authHeader) { // 这里应该验证 JWT 令牌并提取用户 ID // 为了演示,我们使用一个简单的实现 return `user:${userId || 'anonymous'}` } // 如果没有认证,使用 IP 地址 const clientIp = req.headers.get('x-real-ip') || req.headers.get('x-forwarded-for')?.split(',')[0].trim() || 'unknown' return `ip:${clientIp}` } const rateLimitMiddleware = rateLimit({ duration: 300000, // 5分钟 max: 100, // 每个用户最多100个请求 generator: userBasedGenerator, errorResponse: 'User rate limit exceeded', headers: true }) const routes = [ { method: 'GET', path: '/api/profile', handler: createHandler(() => { return { message: 'User profile' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return rateLimitMiddleware(req, () => server.fetch(req)) } } ``` ### 3. 条件跳过 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' const rateLimitMiddleware = rateLimit({ duration: 60000, max: 20, errorResponse: 'Rate limit exceeded', headers: true, skip: (req) => { const url = new URL(req.url) // 跳过健康检查 if (url.pathname === '/health') return true // 跳过静态资源 if (url.pathname.startsWith('/static/')) return true // 跳过管理员 IP const clientIp = req.headers.get('x-real-ip') if (clientIp === '192.168.1.100') return true // 跳过特定用户代理 const userAgent = req.headers.get('user-agent') if (userAgent?.includes('GoogleBot')) return true return false } }) const routes = [ { method: 'GET', path: '/health', handler: createHandler(() => { return { status: 'OK', timestamp: new Date().toISOString() } }) }, { method: 'GET', path: '/api/data', handler: createHandler(() => { return { data: 'Protected data' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return rateLimitMiddleware(req, () => server.fetch(req)) } } ``` ### 4. 多实例速率限制 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' import type { Generator } from '@vafast/rate-limit' // 自定义密钥生成器,基于 IP 地址 const keyGenerator: Generator<{ ip: string }> = async (req, server, { ip }) => { const clientIp = req.headers.get('x-real-ip') || req.headers.get('x-forwarded-for')?.split(',')[0].trim() || 'unknown' // 使用 IP 地址生成哈希作为密钥 return Bun.hash(JSON.stringify(clientIp)).toString() } // 创建第一个实例的速率限制中间件 const aInstanceRateLimit = rateLimit({ scoping: 'scoped', duration: 200 * 1000, // 200秒 max: 10, generator: keyGenerator, errorResponse: 'Instance A rate limit exceeded', headers: true }) // 创建第二个实例的速率限制中间件 const bInstanceRateLimit = rateLimit({ scoping: 'scoped', duration: 100 * 1000, // 100秒 max: 5, generator: keyGenerator, errorResponse: 'Instance B rate limit exceeded', headers: true }) // 定义第一个实例的路由 const aInstanceRoutes = [ { method: 'GET', path: '/a', handler: createHandler(() => { return 'Instance A - Rate limited to 10 requests per 200 seconds' }) } ] // 定义第二个实例的路由 const bInstanceRoutes = [ { method: 'GET', path: '/b', handler: createHandler(() => { return 'Instance B - Rate limited to 5 requests per 100 seconds' }) } ] // 定义主应用路由 const mainRoutes = [ { method: 'GET', path: '/', handler: createHandler(() => { return 'Main application - No rate limiting' }) }, { method: 'GET', path: '/status', handler: createHandler(() => { return { message: 'Application status', instances: ['A', 'B'], timestamp: new Date().toISOString() } }) } ] // 创建实例服务器 const aInstance = new Server(aInstanceRoutes) const bInstance = new Server(bInstanceRoutes) const mainServer = new Server(mainRoutes) // 导出 fetch 函数,应用不同的速率限制中间件 export default { fetch: (req: Request) => { const url = new URL(req.url) const path = url.pathname // 根据路径应用不同的速率限制中间件 if (path.startsWith('/a')) { return aInstanceRateLimit(req, () => aInstance.fetch(req)) } else if (path.startsWith('/b')) { return bInstanceRateLimit(req, () => bInstance.fetch(req)) } else { // 主应用不应用速率限制 return mainServer.fetch(req) } } } ``` ### 5. 自定义错误响应 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' // 自定义错误响应 const customErrorResponse = new Response( JSON.stringify({ error: 'Rate limit exceeded', message: 'You have exceeded the rate limit. Please try again later.', retryAfter: 60, code: 'RATE_LIMIT_EXCEEDED' }), { status: 429, statusText: 'Too Many Requests', headers: { 'Content-Type': 'application/json' } } ) const rateLimitMiddleware = rateLimit({ duration: 60000, max: 5, errorResponse: customErrorResponse, headers: true }) const routes = [ { method: 'GET', path: '/api/sensitive', handler: createHandler(() => { return { message: 'Sensitive data' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return rateLimitMiddleware(req, () => server.fetch(req)) } } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' import type { Generator } from '@vafast/rate-limit' // 自定义密钥生成器 const customGenerator: Generator<{ userId?: string }> = async (req, server, { userId }) => { // 优先使用用户 ID if (userId) { return `user:${userId}` } // 尝试从 JWT 令牌中获取用户 ID const authHeader = req.headers.get('authorization') if (authHeader?.startsWith('Bearer ')) { try { // 这里应该验证 JWT 令牌 // 为了演示,我们使用一个简单的实现 const token = authHeader.substring(7) // const decoded = jwt.verify(token, secret) // return `user:${decoded.userId}` } catch (error) { // 令牌无效,继续使用 IP } } // 使用 IP 地址作为备用 const clientIp = req.headers.get('x-real-ip') || req.headers.get('x-forwarded-for')?.split(',')[0].trim() || req.headers.get('cf-connecting-ip') || 'unknown' return `ip:${clientIp}` } // 创建不同级别的速率限制中间件 const strictRateLimit = rateLimit({ duration: 60000, // 1分钟 max: 5, // 最多5个请求 generator: customGenerator, errorResponse: 'Strict rate limit exceeded. Please wait before making more requests.', headers: true, skip: (req) => { // 跳过健康检查和状态端点 const url = new URL(req.url) return url.pathname === '/health' || url.pathname === '/status' } }) const moderateRateLimit = rateLimit({ duration: 300000, // 5分钟 max: 50, // 最多50个请求 generator: customGenerator, errorResponse: 'Moderate rate limit exceeded. Please reduce your request frequency.', headers: true, skip: (req) => { // 跳过健康检查、状态端点和静态资源 const url = new URL(req.url) return url.pathname === '/health' || url.pathname === '/status' || url.pathname.startsWith('/static/') } }) const lenientRateLimit = rateLimit({ duration: 3600000, // 1小时 max: 1000, // 最多1000个请求 generator: customGenerator, errorResponse: 'Lenient rate limit exceeded. Please contact support if you need higher limits.', headers: true, skip: (req) => { // 只跳过健康检查 const url = new URL(req.url) return url.pathname === '/health' } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Rate Limiting API', version: '1.0.0', endpoints: [ 'GET /health - 健康检查(无限制)', 'GET /status - 状态信息(无限制)', 'GET /api/public - 公开 API(宽松限制)', 'GET /api/user - 用户 API(中等限制)', 'POST /api/admin - 管理 API(严格限制)', 'GET /static/* - 静态资源(无限制)' ] } }) }, { method: 'GET', path: '/health', handler: createHandler(() => { return { status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime() } }) }, { method: 'GET', path: '/status', handler: createHandler(() => { return { message: 'System status', timestamp: new Date().toISOString(), version: '1.0.0' } }) }, { method: 'GET', path: '/api/public', handler: createHandler(() => { return { message: 'Public API endpoint', data: 'This endpoint has lenient rate limiting (1000 requests per hour)', timestamp: new Date().toISOString() } }) }, { method: 'GET', path: '/api/user', handler: createHandler(() => { return { message: 'User API endpoint', data: 'This endpoint has moderate rate limiting (50 requests per 5 minutes)', user: { id: 'user123', name: 'John Doe', email: 'john@example.com' }, timestamp: new Date().toISOString() } }) }, { method: 'POST', path: '/api/admin', handler: createHandler(async ({ req }) => { const body = await req.json() return { message: 'Admin API endpoint', data: 'This endpoint has strict rate limiting (5 requests per minute)', received: body, timestamp: new Date().toISOString() } }) }, { method: 'GET', path: '/static/:file', handler: createHandler(({ params }) => { return { message: 'Static file endpoint', file: params.file, data: 'This endpoint has no rate limiting', timestamp: new Date().toISOString() } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用速率限制中间件 export default { fetch: (req: Request) => { const url = new URL(req.url) const path = url.pathname // 根据路径应用不同的速率限制 if (path.startsWith('/api/admin')) { return strictRateLimit(req, () => server.fetch(req)) } else if (path.startsWith('/api/user')) { return moderateRateLimit(req, () => server.fetch(req)) } else if (path.startsWith('/api/public')) { return lenientRateLimit(req, () => server.fetch(req)) } else { // 其他端点不应用速率限制 return server.fetch(req) } } } console.log('🚀 Vafast Rate Limiting API 服务器启动成功!') console.log('📊 不同端点应用了不同级别的速率限制') console.log('🔒 管理 API:5 请求/分钟') console.log('👤 用户 API:50 请求/5分钟') console.log('🌐 公开 API:1000 请求/小时') console.log('✅ 健康检查和状态端点无限制') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' describe('Vafast Rate Limit Plugin', () => { it('should create rate limit middleware', () => { const rateLimitMiddleware = rateLimit({ duration: 60000, max: 5, errorResponse: 'Rate limit exceeded', headers: true }) expect(rateLimitMiddleware).toBeDefined() expect(typeof rateLimitMiddleware).toBe('function') }) it('should allow requests within rate limit', async () => { const rateLimitMiddleware = rateLimit({ duration: 60000, max: 3, headers: true }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, Rate Limited!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return rateLimitMiddleware(req, () => app.fetch(req)) } // 前3个请求应该成功 for (let i = 0; i < 3; i++) { const res = await wrappedFetch(new Request('http://localhost/')) expect(res.status).toBe(200) const data = await res.text() expect(data).toBe('Hello, Rate Limited!') // 检查速率限制头部 expect(res.headers.get('RateLimit-Limit')).toBe('3') expect(res.headers.get('RateLimit-Remaining')).toBe(String(2 - i)) expect(res.headers.get('RateLimit-Reset')).toBeDefined() } }) it('should block requests when rate limit exceeded', async () => { const rateLimitMiddleware = rateLimit({ duration: 60000, max: 2, errorResponse: 'Too many requests', headers: true }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, Rate Limited!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return rateLimitMiddleware(req, () => app.fetch(req)) } // 前2个请求应该成功 for (let i = 0; i < 2; i++) { const res = await wrappedFetch(new Request('http://localhost/')) expect(res.status).toBe(200) } // 第3个请求应该被阻止 const blockedRes = await wrappedFetch(new Request('http://localhost/')) expect(blockedRes.status).toBe(429) const errorData = await blockedRes.text() expect(errorData).toBe('Too many requests') // 检查错误响应头部 expect(blockedRes.headers.get('RateLimit-Limit')).toBe('2') expect(blockedRes.headers.get('RateLimit-Remaining')).toBe('0') expect(blockedRes.headers.get('Retry-After')).toBeDefined() }) it('should skip rate limiting when skip function returns true', async () => { const rateLimitMiddleware = rateLimit({ duration: 60000, max: 1, headers: true, skip: (req) => req.url.includes('/health') }) const app = new Server([ { method: 'GET', path: '/health', handler: createHandler(() => { return 'Health check' }) } ]) const wrappedFetch = (req: Request) => { return rateLimitMiddleware(req, () => app.fetch(req)) } // 健康检查请求应该被跳过,不应用速率限制 const res = await wrappedFetch(new Request('http://localhost/health')) expect(res.status).toBe(200) // 不应该有速率限制头部 expect(res.headers.get('RateLimit-Limit')).toBeNull() expect(res.headers.get('RateLimit-Remaining')).toBeNull() }) it('should handle custom error responses', async () => { const customError = new Response('Custom error message', { status: 429 }) const rateLimitMiddleware = rateLimit({ duration: 60000, max: 1, errorResponse: customError, headers: true }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello' }) } ]) const wrappedFetch = (req: Request) => { return rateLimitMiddleware(req, () => app.fetch(req)) } // 第一个请求应该成功 const res1 = await wrappedFetch(new Request('http://localhost/')) expect(res1.status).toBe(200) // 第二个请求应该被阻止,返回自定义错误 const res2 = await wrappedFetch(new Request('http://localhost/')) expect(res2.status).toBe(429) const errorData = await res2.text() expect(errorData).toBe('Custom error message') }) it('should work with different HTTP methods', async () => { const rateLimitMiddleware = rateLimit({ duration: 60000, max: 2, headers: true }) const app = new Server([ { method: 'POST', path: '/', handler: createHandler(() => { return { message: 'POST request' } }) } ]) const wrappedFetch = (req: Request) => { return rateLimitMiddleware(req, () => app.fetch(req)) } // 前2个 POST 请求应该成功 for (let i = 0; i < 2; i++) { const res = await wrappedFetch(new Request('http://localhost/', { method: 'POST', body: JSON.stringify({ test: i }) })) expect(res.status).toBe(200) } // 第3个 POST 请求应该被阻止 const blockedRes = await wrappedFetch(new Request('http://localhost/', { method: 'POST', body: JSON.stringify({ test: 3 }) })) expect(blockedRes.status).toBe(429) }) }) ``` ## 特性 * ✅ **灵活配置**: 支持自定义时间窗口和请求限制 * ✅ **智能跳过**: 支持条件跳过速率限制 * ✅ **自定义密钥**: 支持基于 IP、用户 ID 等的自定义密钥生成 * ✅ **标准头部**: 自动添加 RateLimit-\* 标准头部 * ✅ **错误处理**: 支持自定义错误响应和状态码 * ✅ **高性能**: 使用 LRU 缓存存储,内存占用低 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **易于集成**: 无缝集成到 Vafast 应用 ## 最佳实践 ### 1. 合理的限制设置 ```typescript // 根据端点的重要性设置不同的限制 const apiRateLimit = rateLimit({ duration: 60000, // 1分钟 max: 100, // 100个请求 errorResponse: 'API rate limit exceeded' }) const authRateLimit = rateLimit({ duration: 300000, // 5分钟 max: 10, // 10个请求(防止暴力破解) errorResponse: 'Too many authentication attempts' }) ``` ### 2. 智能跳过策略 ```typescript skip: (req) => { const url = new URL(req.url) // 跳过健康检查 if (url.pathname === '/health') return true // 跳过静态资源 if (url.pathname.startsWith('/static/')) return true // 跳过管理员 IP const clientIp = req.headers.get('x-real-ip') if (adminIps.includes(clientIp)) return true return false } ``` ### 3. 自定义密钥生成 ```typescript const userBasedGenerator: Generator = async (req, server, { userId }) => { // 优先使用用户 ID if (userId) return `user:${userId}` // 备用使用 IP 地址 const clientIp = req.headers.get('x-real-ip') || 'unknown' return `ip:${clientIp}` } ``` ### 4. 错误响应处理 ```typescript const customErrorResponse = new Response( JSON.stringify({ error: 'Rate limit exceeded', retryAfter: 60, message: 'Please wait before making more requests' }), { status: 429, headers: { 'Content-Type': 'application/json' } } ) ``` ## 注意事项 1. **内存使用**: 速率限制数据存储在内存中,注意设置合理的 `maxSize` 2. **分布式环境**: 在多个实例环境中,每个实例独立计数 3. **时间同步**: 确保服务器时间同步,避免速率限制不准确 4. **IP 地址**: 在代理环境中,确保正确获取真实客户端 IP 5. **错误处理**: 合理设置错误响应,避免暴露过多系统信息 ## 相关链接 * [RFC 6585 - Rate Limiting](https://tools.ietf.org/html/rfc6585) * [Rate Limiting Best Practices](https://cloud.google.com/architecture/rate-limiting-strategies-techniques) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/integrations/react-email.md' --- # React Email 集成 Vafast 可以与 React Email 无缝集成,为您提供类型安全的邮件模板和强大的邮件发送功能。 ## 安装依赖 ```bash bun add react-email @react-email/components @react-email/render bun add -D @types/nodemailer nodemailer ``` ## 邮件模板组件 ```tsx // src/emails/WelcomeEmail.tsx import { Body, Container, Head, Heading, Html, Img, Link, Preview, Section, Text } from '@react-email/components' import * as React from 'react' interface WelcomeEmailProps { userFirstname: string userEmail: string verificationUrl: string } export const WelcomeEmail = ({ userFirstname, userEmail, verificationUrl }: WelcomeEmailProps) => ( 欢迎加入我们的平台! Logo 欢迎,{userFirstname}! 感谢您注册我们的平台。我们很高兴您能加入我们!
请点击下面的按钮验证您的邮箱地址: 验证邮箱
如果您没有注册我们的平台,请忽略此邮件。 此邮件发送给 {userEmail}
) export default WelcomeEmail const main = { backgroundColor: '#ffffff', fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif' } const container = { margin: '0 auto', padding: '20px 0 48px', maxWidth: '560px' } const logo = { margin: '0 auto' } const h1 = { color: '#333', fontSize: '24px', fontWeight: 'bold', margin: '40px 0', padding: '0' } const heroText = { color: '#333', fontSize: '16px', lineHeight: '24px', margin: '16px 0' } const codeBox = { background: 'rgb(245, 244, 245)', borderRadius: '4px', margin: '16px auto 14px', verticalAlign: 'middle', width: '280px' } const verificationCodeText = { color: '#333', display: 'inline', fontSize: '14px', fontWeight: 500, lineHeight: '24px', textAlign: 'center' as const } const button = { backgroundColor: '#000', borderRadius: '4px', color: '#fff', display: 'inline-block', fontSize: '14px', fontWeight: 500, lineHeight: '50px', textAlign: 'center' as const, textDecoration: 'none', textTransform: 'uppercase', width: '100%', marginTop: '16px' } const text = { color: '#333', fontSize: '14px', lineHeight: '24px' } const footer = { color: '#898989', fontSize: '12px', lineHeight: '22px', marginTop: '12px', marginBottom: '24px' } ``` ```tsx // src/emails/PasswordResetEmail.tsx import { Body, Container, Head, Heading, Html, Img, Link, Preview, Section, Text } from '@react-email/components' import * as React from 'react' interface PasswordResetEmailProps { userFirstname: string resetUrl: string expiryTime: string } export const PasswordResetEmail = ({ userFirstname, resetUrl, expiryTime }: PasswordResetEmailProps) => ( 重置您的密码 Logo 密码重置请求 您好 {userFirstname},我们收到了您的密码重置请求。
点击下面的按钮重置您的密码: 重置密码
此链接将在 {expiryTime} 后过期。如果您没有请求重置密码,请忽略此邮件。 为了您的账户安全,请勿将此链接分享给他人。
) export default PasswordResetEmail const main = { backgroundColor: '#ffffff', fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif' } const container = { margin: '0 auto', padding: '20px 0 48px', maxWidth: '560px' } const logo = { margin: '0 auto' } const h1 = { color: '#333', fontSize: '24px', fontWeight: 'bold', margin: '40px 0', padding: '0' } const heroText = { color: '#333', fontSize: '16px', lineHeight: '24px', margin: '16px 0' } const codeBox = { background: 'rgb(245, 244, 245)', borderRadius: '4px', margin: '16px auto 14px', verticalAlign: 'middle', width: '280px' } const verificationCodeText = { color: '#333', display: 'inline', fontSize: '14px', fontWeight: 500, lineHeight: '24px', textAlign: 'center' as const } const button = { backgroundColor: '#dc3545', borderRadius: '4px', color: '#fff', display: 'inline-block', fontSize: '14px', fontWeight: 500, lineHeight: '50px', textAlign: 'center' as const, textDecoration: 'none', textTransform: 'uppercase', width: '100%', marginTop: '16px' } const text = { color: '#333', fontSize: '14px', lineHeight: '24px' } const footer = { color: '#898989', fontSize: '12px', lineHeight: '22px', marginTop: '12px', marginBottom: '24px' } ``` ```tsx // src/emails/NotificationEmail.tsx import { Body, Container, Head, Heading, Html, Img, Link, Preview, Section, Text } from '@react-email/components' import * as React from 'react' interface NotificationEmailProps { userFirstname: string notificationTitle: string notificationMessage: string actionUrl?: string actionText?: string } export const NotificationEmail = ({ userFirstname, notificationTitle, notificationMessage, actionUrl, actionText }: NotificationEmailProps) => ( {notificationTitle} Logo {notificationTitle} 您好 {userFirstname},{notificationMessage} {actionUrl && actionText && (
{actionText}
)} 感谢您使用我们的平台!
) export default NotificationEmail const main = { backgroundColor: '#ffffff', fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif' } const container = { margin: '0 auto', padding: '20px 0 48px', maxWidth: '560px' } const logo = { margin: '0 auto' } const h1 = { color: '#333', fontSize: '24px', fontWeight: 'bold', margin: '40px 0', padding: '0' } const heroText = { color: '#333', fontSize: '16px', lineHeight: '24px', margin: '16px 0' } const codeBox = { background: 'rgb(245, 244, 245)', borderRadius: '4px', margin: '16px auto 14px', verticalAlign: 'middle', width: '280px' } const button = { backgroundColor: '#007bff', borderRadius: '4px', color: '#fff', display: 'inline-block', fontSize: '14px', fontWeight: 500, lineHeight: '50px', textAlign: 'center' as const, textDecoration: 'none', textTransform: 'uppercase', width: '100%' } const footer = { color: '#898989', fontSize: '12px', lineHeight: '22px', marginTop: '12px', marginBottom: '24px' } ``` ## 邮件服务配置 ```typescript // src/services/emailService.ts import nodemailer from 'nodemailer' import { render } from '@react-email/render' import WelcomeEmail from '../emails/WelcomeEmail' import PasswordResetEmail from '../emails/PasswordResetEmail' import NotificationEmail from '../emails/NotificationEmail' export interface EmailConfig { host: string port: number secure: boolean auth: { user: string pass: string } } export interface EmailOptions { to: string subject: string html: string from?: string } export class EmailService { private transporter: nodemailer.Transporter constructor(config: EmailConfig) { this.transporter = nodemailer.createTransport(config) } // 发送欢迎邮件 async sendWelcomeEmail( to: string, userFirstname: string, verificationUrl: string ) { const html = render( WelcomeEmail({ userFirstname, userEmail: to, verificationUrl }) ) return await this.sendEmail({ to, subject: '欢迎加入我们的平台!', html }) } // 发送密码重置邮件 async sendPasswordResetEmail( to: string, userFirstname: string, resetUrl: string, expiryTime: string ) { const html = render( PasswordResetEmail({ userFirstname, resetUrl, expiryTime }) ) return await this.sendEmail({ to, subject: '重置您的密码', html }) } // 发送通知邮件 async sendNotificationEmail( to: string, userFirstname: string, notificationTitle: string, notificationMessage: string, actionUrl?: string, actionText?: string ) { const html = render( NotificationEmail({ userFirstname, notificationTitle, notificationMessage, actionUrl, actionText }) ) return await this.sendEmail({ to, subject: notificationTitle, html }) } // 通用邮件发送方法 private async sendEmail(options: EmailOptions) { const mailOptions = { from: options.from || process.env.EMAIL_FROM || 'noreply@example.com', to: options.to, subject: options.subject, html: options.html } try { const info = await this.transporter.sendMail(mailOptions) console.log('Email sent successfully:', info.messageId) return info } catch (error) { console.error('Error sending email:', error) throw error } } // 验证邮件配置 async verifyConnection() { try { await this.transporter.verify() console.log('Email service is ready') return true } catch (error) { console.error('Email service verification failed:', error) return false } } } // 创建邮件服务实例 export const emailService = new EmailService({ host: process.env.SMTP_HOST || 'smtp.gmail.com', port: parseInt(process.env.SMTP_PORT || '587'), secure: process.env.SMTP_SECURE === 'true', auth: { user: process.env.SMTP_USER || '', pass: process.env.SMTP_PASS || '' } }) ``` ## 在 Vafast 路由中使用 ```typescript // src/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' import { emailService } from './services/emailService' import { userService } from './services/userService' import { authMiddleware } from './middleware/auth' export const routes = defineRoutes([ // 用户注册 { method: 'POST', path: '/api/auth/register', handler: createHandler(async ({ body }) => { const { email, name, password } = body // 检查用户是否已存在 const existingUser = await userService.findByEmail(email) if (existingUser) { return { error: '用户已存在' }, { status: 400 } } // 创建新用户 const newUser = await userService.create({ email, name, password }) // 生成验证链接 const verificationToken = generateVerificationToken(newUser.id) const verificationUrl = `${process.env.APP_URL}/verify-email?token=${verificationToken}` // 发送欢迎邮件 try { await emailService.sendWelcomeEmail( email, name, verificationUrl ) } catch (error) { console.error('Failed to send welcome email:', error) // 邮件发送失败不应该阻止用户注册 } return { user: { id: newUser.id, email: newUser.email, name: newUser.name }, message: '注册成功,请检查您的邮箱进行验证' } }), body: Type.Object({ email: Type.String({ format: 'email' }), name: Type.String({ minLength: 1 }), password: Type.String({ minLength: 6 }) }) }, // 密码重置请求 { method: 'POST', path: '/api/auth/forgot-password', handler: createHandler(async ({ body }) => { const { email } = body // 查找用户 const user = await userService.findByEmail(email) if (!user) { // 为了安全,即使用户不存在也返回成功 return { message: '如果邮箱存在,重置链接已发送' } } // 生成重置令牌 const resetToken = generateResetToken(user.id) const resetUrl = `${process.env.APP_URL}/reset-password?token=${resetToken}` const expiryTime = '1小时' // 发送密码重置邮件 try { await emailService.sendPasswordResetEmail( email, user.name, resetUrl, expiryTime ) return { message: '密码重置链接已发送到您的邮箱' } } catch (error) { console.error('Failed to send password reset email:', error) return { error: '邮件发送失败,请稍后重试' }, { status: 500 } } }), body: Type.Object({ email: Type.String({ format: 'email' }) }) }, // 发送通知邮件 { method: 'POST', path: '/api/notifications/send-email', handler: createHandler(async ({ body, request }) => { // 这里应该验证用户身份和权限 const { userId, notificationTitle, notificationMessage, actionUrl, actionText } = body // 获取用户信息 const user = await userService.findById(userId) if (!user) { return { error: '用户不存在' }, { status: 404 } } // 发送通知邮件 try { await emailService.sendNotificationEmail( user.email, user.name, notificationTitle, notificationMessage, actionUrl, actionText ) return { message: '通知邮件发送成功' } } catch (error) { console.error('Failed to send notification email:', error) return { error: '邮件发送失败' }, { status: 500 } } }), body: Type.Object({ userId: Type.String(), notificationTitle: Type.String({ minLength: 1 }), notificationMessage: Type.String({ minLength: 1 }), actionUrl: Type.Optional(Type.String({ format: 'uri' })), actionText: Type.Optional(Type.String()) }), middleware: [authMiddleware] }, // 批量发送邮件 { method: 'POST', path: '/api/notifications/send-bulk-email', handler: createHandler(async ({ body }) => { const { userIds, notificationTitle, notificationMessage, actionUrl, actionText } = body // 获取所有用户信息 const users = await Promise.all( userIds.map(id => userService.findById(id)) ) const validUsers = users.filter(user => user !== null) // 批量发送邮件 const results = await Promise.allSettled( validUsers.map(user => emailService.sendNotificationEmail( user!.email, user!.name, notificationTitle, notificationMessage, actionUrl, actionText ) ) ) const successful = results.filter(result => result.status === 'fulfilled').length const failed = results.filter(result => result.status === 'rejected').length return { message: `批量邮件发送完成`, total: validUsers.length, successful, failed } }), body: Type.Object({ userIds: Type.Array(Type.String()), notificationTitle: Type.String({ minLength: 1 }), notificationMessage: Type.String({ minLength: 1 }), actionUrl: Type.Optional(Type.String({ format: 'uri' })), actionText: Type.Optional(Type.String()) }), middleware: [authMiddleware] } ]) ``` ## 邮件队列系统 ```typescript // src/services/emailQueueService.ts import { emailService } from './emailService' interface EmailJob { id: string type: 'welcome' | 'password-reset' | 'notification' data: any priority: 'high' | 'normal' | 'low' retries: number maxRetries: number } export class EmailQueueService { private queue: EmailJob[] = [] private processing = false private maxConcurrent = 5 private currentProcessing = 0 // 添加邮件任务到队列 async addToQueue(job: Omit) { const emailJob: EmailJob = { ...job, id: crypto.randomUUID(), retries: 0 } this.queue.push(emailJob) this.sortQueue() if (!this.processing) { this.processQueue() } } // 处理队列 private async processQueue() { if (this.processing || this.currentProcessing >= this.maxConcurrent) { return } this.processing = true while (this.queue.length > 0 && this.currentProcessing < this.maxConcurrent) { const job = this.queue.shift() if (job) { this.currentProcessing++ this.processJob(job).finally(() => { this.currentProcessing-- }) } } this.processing = false // 如果还有任务,继续处理 if (this.queue.length > 0) { setTimeout(() => this.processQueue(), 1000) } } // 处理单个邮件任务 private async processJob(job: EmailJob) { try { switch (job.type) { case 'welcome': await emailService.sendWelcomeEmail( job.data.to, job.data.userFirstname, job.data.verificationUrl ) break case 'password-reset': await emailService.sendPasswordResetEmail( job.data.to, job.data.userFirstname, job.data.resetUrl, job.data.expiryTime ) break case 'notification': await emailService.sendNotificationEmail( job.data.to, job.data.userFirstname, job.data.notificationTitle, job.data.notificationMessage, job.data.actionUrl, job.data.actionText ) break } console.log(`Email job ${job.id} completed successfully`) } catch (error) { console.error(`Email job ${job.id} failed:`, error) // 重试逻辑 if (job.retries < job.maxRetries) { job.retries++ this.queue.push(job) this.sortQueue() } else { console.error(`Email job ${job.id} failed after ${job.maxRetries} retries`) } } } // 按优先级排序队列 private sortQueue() { const priorityOrder = { high: 3, normal: 2, low: 1 } this.queue.sort((a, b) => priorityOrder[b.priority] - priorityOrder[a.priority]) } // 获取队列状态 getQueueStatus() { return { total: this.queue.length, processing: this.currentProcessing, maxConcurrent: this.maxConcurrent } } } export const emailQueueService = new EmailQueueService() ``` ## 环境配置 ```env # .env SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password EMAIL_FROM=noreply@yourdomain.com APP_URL=http://localhost:3000 ``` ## 测试 ```typescript // src/services/__tests__/emailService.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { render } from '@react-email/render' import WelcomeEmail from '../../emails/WelcomeEmail' import PasswordResetEmail from '../../emails/PasswordResetEmail' describe('Email Templates', () => { it('should render welcome email correctly', () => { const emailHtml = render( WelcomeEmail({ userFirstname: 'John', userEmail: 'john@example.com', verificationUrl: 'https://example.com/verify?token=123' }) ) expect(emailHtml).toContain('John') expect(emailHtml).toContain('john@example.com') expect(emailHtml).toContain('验证邮箱') }) it('should render password reset email correctly', () => { const emailHtml = render( PasswordResetEmail({ userFirstname: 'Jane', resetUrl: 'https://example.com/reset?token=456', expiryTime: '1小时' }) ) expect(emailHtml).toContain('Jane') expect(emailHtml).toContain('重置密码') expect(emailHtml).toContain('1小时') }) }) ``` ## 最佳实践 1. **模板设计**:使用响应式设计确保邮件在不同设备上正常显示 2. **类型安全**:充分利用 TypeScript 的类型检查功能 3. **错误处理**:实现完善的错误处理和重试机制 4. **队列管理**:使用队列系统处理大量邮件发送 5. **测试覆盖**:为邮件模板和服务编写完整的测试 6. **性能优化**:使用异步处理和并发控制优化性能 7. **安全考虑**:避免在邮件中包含敏感信息 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [React Email 文档](https://react.email/docs) - React Email 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/middleware/server-timing.md' --- # Server Timing 中间件 该中间件为 [Vafast](https://github.com/vafastjs/vafast) 提供了服务器计时功能,通过添加 `Server-Timing` 响应头部来帮助开发者监控和优化应用性能。 ## 安装 安装命令: ```bash bun add @vafast/server-timing ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' // 创建 Server Timing 中间件 const timing = serverTiming() const routes = [ { method: 'GET', path: '/', handler: createHandler(async () => { // 模拟一些异步操作 await new Promise(resolve => setTimeout(resolve, 100)) return 'Server Timing Example' }) } ] const server = new Server(routes) // 导出 fetch 函数,应用中间件 export default { fetch: (req: Request) => { // 应用 Server-Timing 中间件 return timing(req, () => server.fetch(req)) } } ``` ## 配置选项 ### ServerTimingOptions ```typescript interface ServerTimingOptions { /** 是否启用 Server-Timing 中间件,默认:NODE_ENV !== 'production' */ enabled?: boolean /** 允许/拒绝写入响应头 * - boolean: 是否允许 * - function: 基于上下文动态判断 */ allow?: boolean | Promise | ((context: any) => boolean | Promise) /** 追踪开关 */ trace?: { /** 是否追踪处理时间,默认:true */ handle?: boolean /** 是否追踪总时间,默认:true */ total?: boolean } } ``` ### 默认配置 ```typescript const defaultOptions = { enabled: process.env.NODE_ENV !== 'production', // 生产环境默认禁用 allow: true, // 默认允许添加头部 trace: { handle: true, // 默认追踪处理时间 total: true // 默认追踪总时间 } } ``` ## 使用模式 ### 1. 基本性能监控 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' const timing = serverTiming({ enabled: true, trace: { handle: true, total: true } }) const routes = [ { method: 'GET', path: '/api/users', handler: createHandler(async () => { // 模拟数据库查询 await new Promise(resolve => setTimeout(resolve, 50)) return { users: ['Alice', 'Bob', 'Charlie'] } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return timing(req, () => server.fetch(req)) } } ``` ### 2. 条件启用 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' const timing = serverTiming({ enabled: process.env.NODE_ENV === 'development', allow: (ctx) => { // 只对特定路径启用 return ctx.request.url.includes('/api/') }, trace: { handle: true, total: true } }) const routes = [ { method: 'GET', path: '/api/data', handler: createHandler(async () => { await new Promise(resolve => setTimeout(resolve, 100)) return { data: 'Performance monitored data' } }) }, { method: 'GET', path: '/static/info', handler: createHandler(() => { // 这个端点不会被监控 return { info: 'Static information' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return timing(req, () => server.fetch(req)) } } ``` ### 3. 自定义追踪配置 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' // 只追踪总时间,不追踪处理时间 const timing = serverTiming({ enabled: true, trace: { handle: false, total: true } }) const routes = [ { method: 'POST', path: '/api/process', handler: createHandler(async ({ req }) => { const body = await req.json() // 模拟复杂处理 await new Promise(resolve => setTimeout(resolve, 200)) return { message: 'Data processed successfully', result: body } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return timing(req, () => server.fetch(req)) } } ``` ### 4. 动态控制 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' const timing = serverTiming({ enabled: true, allow: async (ctx) => { const url = new URL(ctx.request.url) // 只对管理员启用 const isAdmin = ctx.request.headers.get('x-admin-key') === 'secret' if (isAdmin) return true // 只对特定路径启用 if (url.pathname.startsWith('/api/admin/')) return false // 根据查询参数控制 const enableTiming = url.searchParams.get('timing') === 'true' return enableTiming }, trace: { handle: true, total: true } }) const routes = [ { method: 'GET', path: '/api/admin/users', handler: createHandler(() => { return { users: ['Admin1', 'Admin2'] } }) }, { method: 'GET', path: '/api/public/info', handler: createHandler(() => { return { info: 'Public information' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return timing(req, () => server.fetch(req)) } } ``` ### 5. 生产环境配置 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' const timing = serverTiming({ enabled: process.env.NODE_ENV !== 'production', allow: process.env.ENABLE_TIMING === 'true', trace: { handle: true, total: true } }) const routes = [ { method: 'GET', path: '/api/health', handler: createHandler(() => { return { status: 'healthy', timestamp: new Date().toISOString() } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return timing(req, () => server.fetch(req)) } } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' // 模拟数据库操作 class DatabaseService { async query(sql: string, delay: number = 50) { await new Promise(resolve => setTimeout(resolve, delay)) return { sql, result: 'data', timestamp: new Date().toISOString() } } async insert(data: any, delay: number = 100) { await new Promise(resolve => setTimeout(resolve, delay)) return { id: Math.random().toString(36).substr(2, 9), ...data } } } // 模拟缓存服务 class CacheService { async get(key: string, delay: number = 10) { await new Promise(resolve => setTimeout(resolve, delay)) return { key, value: 'cached_value', hit: true } } async set(key: string, value: any, delay: number = 20) { await new Promise(resolve => setTimeout(resolve, delay)) return { key, value, success: true } } } // 创建服务实例 const db = new DatabaseService() const cache = new CacheService() // 创建不同配置的 Server Timing 中间件 const developmentTiming = serverTiming({ enabled: process.env.NODE_ENV === 'development', allow: true, trace: { handle: true, total: true } }) const productionTiming = serverTiming({ enabled: false, // 生产环境禁用 allow: false, trace: { handle: false, total: false } }) const adminTiming = serverTiming({ enabled: true, allow: (ctx) => { // 只对管理员请求启用 return ctx.request.headers.get('x-admin-key') === process.env.ADMIN_KEY }, trace: { handle: true, total: true } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Server Timing API', version: '1.0.0', endpoints: [ 'GET /api/users - 获取用户列表(开发环境监控)', 'GET /api/users/:id - 获取单个用户(开发环境监控)', 'POST /api/users - 创建用户(开发环境监控)', 'GET /api/admin/stats - 管理员统计(管理员监控)', 'GET /api/health - 健康检查(无监控)' ] } }) }, { method: 'GET', path: '/api/users', handler: createHandler(async () => { // 模拟数据库查询 const users = await db.query('SELECT * FROM users', 80) return { message: 'Users retrieved successfully', count: 3, users: ['Alice', 'Bob', 'Charlie'], query: users } }) }, { method: 'GET', path: '/api/users/:id', handler: createHandler(async ({ params }) => { const userId = params.id // 先尝试从缓存获取 const cachedUser = await cache.get(`user:${userId}`) if (cachedUser.hit) { return { message: 'User retrieved from cache', user: { id: userId, name: 'Cached User' }, source: 'cache' } } // 缓存未命中,从数据库获取 const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`, 60) // 更新缓存 await cache.set(`user:${userId}`, user) return { message: 'User retrieved from database', user: { id: userId, name: 'Database User' }, source: 'database', query: user } }) }, { method: 'POST', path: '/api/users', handler: createHandler(async ({ req }) => { const body = await req.json() // 模拟用户创建 const newUser = await db.insert(body, 120) return { message: 'User created successfully', user: newUser, timestamp: new Date().toISOString() } }) }, { method: 'GET', path: '/api/admin/stats', handler: createHandler(async () => { // 模拟管理员统计查询 const userStats = await db.query('SELECT COUNT(*) as count FROM users', 150) const cacheStats = await cache.get('cache:stats', 30) return { message: 'Admin statistics retrieved', stats: { totalUsers: userStats.result, cacheHitRate: '85%', systemLoad: 'medium', lastUpdated: new Date().toISOString() } } }) }, { method: 'GET', path: '/api/health', handler: createHandler(() => { return { status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), version: '1.0.0' } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,根据环境应用不同的中间件 export default { fetch: (req: Request) => { const url = new URL(req.url) const path = url.pathname // 根据路径和环境应用不同的 Server Timing 中间件 if (path.startsWith('/api/admin/')) { return adminTiming(req, () => server.fetch(req)) } else if (path.startsWith('/api/')) { // 根据环境变量决定使用哪个中间件 if (process.env.NODE_ENV === 'production') { return productionTiming(req, () => server.fetch(req)) } else { return developmentTiming(req, () => server.fetch(req)) } } else { // 其他路径不应用 Server Timing return server.fetch(req) } } } console.log('🚀 Vafast Server Timing API 服务器启动成功!') console.log('📊 开发环境:所有 API 端点都将被监控') console.log('🔒 生产环境:Server Timing 默认禁用') console.log('👑 管理员端点:需要正确的 x-admin-key 头部') console.log('💚 健康检查:无性能监控') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' describe('Vafast Server Timing Plugin', () => { it('should create server timing middleware', () => { const timingMiddleware = serverTiming({ enabled: true, trace: { handle: true, total: true } }) expect(timingMiddleware).toBeDefined() expect(typeof timingMiddleware).toBe('function') }) it('should add Server-Timing header when enabled', async () => { const timingMiddleware = serverTiming({ enabled: true, trace: { handle: true, total: true } }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, Server Timing!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return timingMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/')) const data = await res.text() expect(data).toBe('Hello, Server Timing!') expect(res.status).toBe(200) // 检查 Server-Timing 头部 const timingHeader = res.headers.get('Server-Timing') expect(timingHeader).toBeDefined() expect(timingHeader).toContain('handle;dur=') expect(timingHeader).toContain('total;dur=') }) it('should not add Server-Timing header when disabled', async () => { const timingMiddleware = serverTiming({ enabled: false, trace: { handle: true, total: true } }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, No Timing!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return timingMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/')) const data = await res.text() expect(data).toBe('Hello, No Timing!') expect(res.status).toBe(200) // 检查 Server-Timing 头部不应该存在 const timingHeader = res.headers.get('Server-Timing') expect(timingHeader).toBeNull() }) it('should respect allow function for adding headers', async () => { const timingMiddleware = serverTiming({ enabled: true, allow: (ctx) => ctx.request.url.includes('/allow'), trace: { handle: true, total: true } }) const app = new Server([ { method: 'GET', path: '/allow', handler: createHandler(() => { return 'Allowed with timing' }) }, { method: 'GET', path: '/deny', handler: createHandler(() => { return 'Denied timing' }) } ]) const wrappedFetch = (req: Request) => { return timingMiddleware(req, () => app.fetch(req)) } // 允许的路径应该有 Server-Timing 头部 const allowedRes = await wrappedFetch(new Request('http://localhost/allow')) expect(allowedRes.headers.get('Server-Timing')).toBeDefined() // 拒绝的路径不应该有 Server-Timing 头部 const deniedRes = await wrappedFetch(new Request('http://localhost/deny')) expect(deniedRes.headers.get('Server-Timing')).toBeNull() }) it('should handle custom trace configuration', async () => { const timingMiddleware = serverTiming({ enabled: true, trace: { handle: false, total: true } }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Custom trace config' }) } ]) const wrappedFetch = (req: Request) => { return timingMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/')) const timingHeader = res.headers.get('Server-Timing') expect(timingHeader).toBeDefined() expect(timingHeader).toContain('total;dur=') expect(timingHeader).not.toContain('handle;dur=') }) it('should work with async operations', async () => { const timingMiddleware = serverTiming({ enabled: true, trace: { handle: true, total: true } }) const app = new Server([ { method: 'GET', path: '/async', handler: createHandler(async () => { // 模拟异步操作 await new Promise(resolve => setTimeout(resolve, 50)) return 'Async operation completed' }) } ]) const wrappedFetch = (req: Request) => { return timingMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/async')) const data = await res.text() expect(data).toBe('Async operation completed') expect(res.status).toBe(200) // 检查 Server-Timing 头部 const timingHeader = res.headers.get('Server-Timing') expect(timingHeader).toBeDefined() // 解析时间值 const totalMatch = timingHeader.match(/total;dur=(\d+\.?\d*)/) expect(totalMatch).toBeDefined() const totalTime = parseFloat(totalMatch![1]) expect(totalTime).toBeGreaterThan(0) }) }) ``` ## 特性 * ✅ **性能监控**: 自动追踪请求处理时间和总时间 * ✅ **灵活配置**: 支持开发/生产环境的不同配置 * ✅ **条件控制**: 支持基于请求上下文的动态控制 * ✅ **标准头部**: 自动添加 RFC 标准的 Server-Timing 头部 * ✅ **轻量级**: 最小化性能开销 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **易于集成**: 无缝集成到 Vafast 应用 ## 最佳实践 ### 1. 环境配置 ```typescript const timing = serverTiming({ enabled: process.env.NODE_ENV === 'development', allow: process.env.ENABLE_TIMING === 'true' }) ``` ### 2. 路径过滤 ```typescript allow: (ctx) => { const url = new URL(ctx.request.url) // 只对 API 端点启用 return url.pathname.startsWith('/api/') } ``` ### 3. 权限控制 ```typescript allow: (ctx) => { const isAdmin = ctx.request.headers.get('x-admin-key') === process.env.ADMIN_KEY return isAdmin } ``` ### 4. 性能考虑 ```typescript // 在生产环境中谨慎启用 const timing = serverTiming({ enabled: process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true', trace: { handle: false, total: true } // 只追踪总时间以减少开销 }) ``` ## 注意事项 1. **性能影响**: Server Timing 中间件会添加少量性能开销 2. **生产环境**: 默认在生产环境中禁用,避免暴露性能信息 3. **头部大小**: Server-Timing 头部会增加响应大小 4. **浏览器支持**: 确保目标浏览器支持 Server-Timing 头部 5. **调试工具**: 使用浏览器开发者工具查看 Server-Timing 信息 ## 相关链接 * [Server-Timing MDN 文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) * [Server-Timing RFC 标准](https://www.w3.org/TR/server-timing/) * [Performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance) * [Vafast 官方文档](https://vafast.dev) --- --- url: 'https://vafast.dev/middleware/static.md' --- # Static 中间件 该中间件为 [Vafast](https://github.com/vafastjs/vafast) 提供了高性能的静态文件服务功能,支持智能缓存、ETag 验证、自定义头部和灵活的配置选项。 ## 安装 安装命令: ```bash bun add @vafast/static ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' import { join } from 'path' // 创建静态文件路由 const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static' }) // 添加自定义路由 const customRoutes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Static file server is running' } }) } ] // 合并路由 const allRoutes = [...customRoutes, ...staticRoutes] const server = new Server(allRoutes) export default { fetch: (req: Request) => server.fetch(req) } ``` ## 配置选项 ### StaticPluginOptions ```typescript interface StaticPluginOptions { /** 静态文件目录路径,默认:'public' */ assets?: string /** URL 前缀,默认:'/public' */ prefix?: string /** 静态路由文件数量限制,默认:1024 */ staticLimit?: number /** 是否始终使用静态路由,默认:NODE_ENV === 'production' */ alwaysStatic?: boolean /** 忽略的文件模式,默认:['.DS_Store', '.git', '.env'] */ ignorePatterns?: Array /** 是否不需要文件扩展名,默认:false */ noExtension?: boolean /** 是否启用 URI 解码,默认:false */ enableDecodeURI?: boolean /** 路径解析函数,默认:Node.js resolve */ resolve?: (...pathSegments: string[]) => string /** 自定义响应头部 */ headers?: Record /** 是否禁用缓存,默认:false */ noCache?: boolean /** 缓存控制指令,默认:'public' */ directive?: 'public' | 'private' | 'must-revalidate' | 'no-cache' | 'no-store' | 'no-transform' | 'proxy-revalidate' | 'immutable' /** 缓存最大年龄(秒),默认:86400 (24小时) */ maxAge?: number | null /** 是否启用 index.html 服务,默认:true */ indexHTML?: boolean } ``` ### 默认配置 ```typescript const defaultOptions = { assets: 'public', // 静态文件目录 prefix: '/public', // URL 前缀 staticLimit: 1024, // 文件数量限制 alwaysStatic: process.env.NODE_ENV === 'production', // 生产环境默认启用 ignorePatterns: ['.DS_Store', '.git', '.env'], // 忽略的文件 noExtension: false, // 需要文件扩展名 enableDecodeURI: false, // 不启用 URI 解码 resolve: resolveFn, // 使用 Node.js resolve headers: {}, // 无自定义头部 noCache: false, // 启用缓存 directive: 'public', // 公共缓存 maxAge: 86400, // 24小时缓存 indexHTML: true // 启用 index.html } ``` ## 使用模式 ### 1. 基本静态文件服务 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static' }) const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Welcome to Static File Server' } }) } ] const server = new Server([...routes, ...staticRoutes]) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 2. 生产环境优化 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' const staticRoutes = await staticPlugin({ assets: 'dist', prefix: '/assets', alwaysStatic: true, // 生产环境使用静态路由 noCache: false, // 启用缓存 maxAge: 31536000, // 1年缓存 directive: 'public, immutable' // 不可变缓存 }) const server = new Server(staticRoutes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 3. 自定义头部和缓存控制 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', headers: { 'X-Served-By': 'Vafast Static Plugin', 'X-Version': '1.0.0' }, noCache: false, directive: 'public', maxAge: 3600 // 1小时缓存 }) const server = new Server(staticRoutes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 4. 忽略特定文件 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', ignorePatterns: [ '.DS_Store', '.git', '.env', '*.log', 'temp/*', /\.(config|local)$/ // 使用正则表达式 ] }) const server = new Server(staticRoutes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 5. 无扩展名支持 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', noExtension: true, // 不需要文件扩展名 alwaysStatic: true // 必须启用 }) const server = new Server(staticRoutes) export default { fetch: (req: Request) => server.fetch(req) } ``` ### 6. 混合路由配置 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' // 创建静态文件路由 const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', alwaysStatic: false, // 使用通配符路由 staticLimit: 500 // 降低限制以使用通配符 }) // 添加自定义路由 const customRoutes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Mixed routes server' } }) }, { method: 'GET', path: '/api/status', handler: createHandler(() => { return { status: 'running', staticRoutes: staticRoutes.length } }) } ] const server = new Server([...customRoutes, ...staticRoutes]) export default { fetch: (req: Request) => server.fetch(req) } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' import { join } from 'path' // 创建不同配置的静态文件路由 const publicStaticRoutes = await staticPlugin({ assets: 'public', prefix: '/public', alwaysStatic: true, headers: { 'X-Served-By': 'Vafast Static Plugin', 'X-Category': 'Public Assets' }, noCache: false, directive: 'public', maxAge: 86400 // 24小时 }) const assetsStaticRoutes = await staticPlugin({ assets: 'dist/assets', prefix: '/assets', alwaysStatic: true, headers: { 'X-Served-By': 'Vafast Static Plugin', 'X-Category': 'Build Assets' }, noCache: false, directive: 'public, immutable', maxAge: 31536000 // 1年 }) const docsStaticRoutes = await staticPlugin({ assets: 'docs', prefix: '/docs', alwaysStatic: false, // 使用通配符路由 staticLimit: 100, // 降低限制 enableDecodeURI: true, // 启用 URI 解码 headers: { 'X-Served-By': 'Vafast Static Plugin', 'X-Category': 'Documentation' }, noCache: true, // 文档不缓存 indexHTML: true // 支持 index.html }) // 定义自定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Static File Server', version: '1.0.0', endpoints: [ 'GET /public/* - 公共静态文件(24小时缓存)', 'GET /assets/* - 构建资源(1年缓存)', 'GET /docs/* - 文档文件(无缓存)', 'GET /api/status - 服务器状态', 'GET /api/files - 文件统计' ] } }) }, { method: 'GET', path: '/api/status', handler: createHandler(() => { return { status: 'running', timestamp: new Date().toISOString(), staticRoutes: { public: publicStaticRoutes.length, assets: assetsStaticRoutes.length, docs: docsStaticRoutes.length } } }) }, { method: 'GET', path: '/api/files', handler: createHandler(async () => { // 这里可以添加文件统计逻辑 return { message: 'File statistics', totalRoutes: publicStaticRoutes.length + assetsStaticRoutes.length + docsStaticRoutes.length, categories: { public: publicStaticRoutes.length, assets: assetsStaticRoutes.length, docs: docsStaticRoutes.length } } }) } ] // 合并所有路由 const allRoutes = [ ...routes, ...publicStaticRoutes, ...assetsStaticRoutes, ...docsStaticRoutes ] // 创建服务器 const server = new Server(allRoutes) // 导出 fetch 函数 export default { fetch: (req: Request) => server.fetch(req) } console.log('🚀 Vafast Static File Server 启动成功!') console.log('📁 公共文件:/public/* (24小时缓存)') console.log('🔧 构建资源:/assets/* (1年缓存)') console.log('📚 文档文件:/docs/* (无缓存)') console.log('📊 总路由数:', allRoutes.length) ``` ## 测试示例 ```typescript import { describe, expect, it, beforeAll, afterAll } from 'bun:test' import { Server, createHandler } from 'vafast' import { staticPlugin } from '@vafast/static' import { writeFile, mkdir, rm } from 'fs/promises' import { join } from 'path' import { tmpdir } from 'os' describe('Vafast Static Plugin', () => { let tempDir: string let testFilePath: string let testHtmlPath: string beforeAll(async () => { // 创建临时目录和测试文件 tempDir = join(tmpdir(), 'vafast-static-test-' + Date.now()) await mkdir(tempDir, { recursive: true }) // 创建测试文件 testFilePath = join(tempDir, 'test.txt') await writeFile(testFilePath, 'Hello, Static File!') // 创建测试 HTML 文件 testHtmlPath = join(tempDir, 'index.html') await writeFile(testHtmlPath, 'Test HTML') }) afterAll(async () => { // 清理临时文件 await rm(tempDir, { recursive: true, force: true }) }) it('should create static routes', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true }) expect(routes).toBeDefined() expect(Array.isArray(routes)).toBe(true) expect(routes.length).toBeGreaterThan(0) }) it('should serve static files with correct paths', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true }) const app = new Server(routes) // 测试访问静态文件 const res = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(res.status).toBe(200) const data = await res.text() expect(data).toBe('Hello, Static File!') }) it('should handle index.html correctly', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true, indexHTML: true }) const app = new Server(routes) // 测试访问目录根路径(应该返回 index.html) const res = await app.fetch(new Request('http://localhost/static/')) expect(res.status).toBe(200) const data = await res.text() expect(data).toContain('Test HTML') }) it('should respect custom headers', async () => { const customHeaders = { 'X-Custom-Header': 'custom-value', 'X-Another-Header': 'another-value' } const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true, headers: customHeaders }) const app = new Server(routes) const res = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(res.status).toBe(200) // 检查自定义头部 expect(res.headers.get('X-Custom-Header')).toBe('custom-value') expect(res.headers.get('X-Another-Header')).toBe('another-value') }) it('should handle caching correctly', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true, noCache: false, directive: 'public', maxAge: 3600 }) const app = new Server(routes) const res = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(res.status).toBe(200) // 检查缓存头部 expect(res.headers.get('Cache-Control')).toContain('public') expect(res.headers.get('Cache-Control')).toContain('max-age=3600') expect(res.headers.get('Etag')).toBeDefined() }) it('should handle no-cache option', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true, noCache: true }) const app = new Server(routes) const res = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(res.status).toBe(200) // 检查无缓存头部 expect(res.headers.get('Cache-Control')).toBeUndefined() expect(res.headers.get('Etag')).toBeUndefined() }) it('should handle wildcard routes when not alwaysStatic', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: false, staticLimit: 1 // 强制使用通配符路由 }) const app = new Server(routes) // 应该有一个通配符路由 const wildcardRoute = routes.find(route => route.path === '/static/*') expect(wildcardRoute).toBeDefined() // 测试通配符路由 const res = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(res.status).toBe(200) const data = await res.text() expect(data).toBe('Hello, Static File!') }) it('should ignore specified patterns', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true, ignorePatterns: ['test.txt'] }) // 被忽略的文件不应该有路由 const ignoredRoute = routes.find(route => route.path === '/static/test.txt') expect(ignoredRoute).toBeUndefined() }) it('should handle root prefix correctly', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/', // 根前缀 alwaysStatic: true }) const app = new Server(routes) // 测试使用根前缀访问文件 const res = await app.fetch(new Request('http://localhost/test.txt')) expect(res.status).toBe(200) const data = await res.text() expect(data).toBe('Hello, Static File!') }) it('should handle 304 Not Modified responses', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true, noCache: false }) const app = new Server(routes) // 第一次请求 const res1 = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(res1.status).toBe(200) const etag = res1.headers.get('Etag') expect(etag).toBeDefined() // 第二次请求,带 If-None-Match 头部 const res2 = await app.fetch( new Request('http://localhost/static/test.txt', { headers: { 'If-None-Match': etag! } }) ) // 应该返回 304 Not Modified expect(res2.status).toBe(304) }) it('should work with custom routes', async () => { const staticRoutes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true }) // 添加自定义路由 const customRoutes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Static server is running' } }) } ] const allRoutes = [...customRoutes, ...staticRoutes] const app = new Server(allRoutes) // 测试自定义路由 const customRes = await app.fetch(new Request('http://localhost/')) expect(customRes.status).toBe(200) const customData = await customRes.json() expect(customData.message).toBe('Static server is running') // 测试静态文件路由 const staticRes = await app.fetch( new Request('http://localhost/static/test.txt') ) expect(staticRes.status).toBe(200) const staticData = await staticRes.text() expect(staticData).toBe('Hello, Static File!') }) it('should handle file not found correctly', async () => { const routes = await staticPlugin({ assets: tempDir, prefix: '/static', alwaysStatic: true }) const app = new Server(routes) // 测试访问不存在的文件 try { await app.fetch( new Request('http://localhost/static/nonexistent.txt') ) // 如果到这里,说明错误没有被正确处理 expect(true).toBe(false) } catch (error) { expect(error).toBeDefined() } }) }) ``` ## 特性 * ✅ **高性能**: 支持静态路由和通配符路由两种模式 * ✅ **智能缓存**: 自动生成 ETag 和缓存控制头部 * ✅ **灵活配置**: 支持自定义头部、缓存策略和忽略模式 * ✅ **文件扩展名**: 可选的扩展名支持 * ✅ **索引文件**: 自动服务 index.html 文件 * ✅ **路径安全**: 防止目录遍历攻击 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **易于集成**: 无缝集成到 Vafast 应用 ## 最佳实践 ### 1. 生产环境配置 ```typescript const staticRoutes = await staticPlugin({ assets: 'dist', prefix: '/assets', alwaysStatic: true, // 生产环境使用静态路由 noCache: false, // 启用缓存 maxAge: 31536000, // 1年缓存 directive: 'public, immutable' // 不可变缓存 }) ``` ### 2. 开发环境配置 ```typescript const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', alwaysStatic: false, // 开发环境使用通配符路由 noCache: true, // 禁用缓存便于调试 maxAge: null // 无缓存年龄 }) ``` ### 3. 安全配置 ```typescript const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', ignorePatterns: [ '.env', '.git', '*.log', 'temp/*', /\.(config|local)$/ ] }) ``` ### 4. 性能优化 ```typescript const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', alwaysStatic: files.length <= 1000, // 根据文件数量动态选择 staticLimit: 1000, // 设置合理的限制 headers: { 'X-Content-Type-Options': 'nosniff' } }) ``` ### 5. 监控和调试 ```typescript const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', alwaysStatic: true }) // 添加监控路由 const monitorRoutes = [ { method: 'GET', path: '/admin/static-stats', handler: createHandler(() => { return { totalRoutes: staticRoutes.length, mode: 'static', timestamp: new Date().toISOString(), cacheInfo: { statCache: statCache.keys().length, fileCache: fileCache.keys().length, htmlCache: htmlCache.keys().length } } }) } ] ``` ### 6. 错误处理和日志 ```typescript import { createHandler } from 'vafast' const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', alwaysStatic: false, // 使用通配符路由以支持错误处理 staticLimit: 100 }) // 添加错误处理中间件 const errorHandlingRoutes = [ { method: 'GET', path: '/static/*', handler: createHandler(async (req: Request) => { try { // 这里可以添加自定义的错误处理逻辑 const response = await handleStaticRequest(req) return response } catch (error) { console.error('Static file error:', error) if (error instanceof NotFoundError) { return new Response('File not found', { status: 404 }) } return new Response('Internal server error', { status: 500 }) } }) } ] ``` ## 高级特性 ### 1. 智能路由选择 中间件会根据文件数量和配置自动选择最优的路由模式: ```typescript // 文件数量 <= staticLimit (默认 1024) 时使用静态路由 const staticRoutes = await staticPlugin({ assets: 'public', prefix: '/static', alwaysStatic: false, // 让中间件自动选择 staticLimit: 500 // 降低阈值以使用通配符路由 }) // 文件数量 > staticLimit 时自动使用通配符路由 // 这样可以减少内存占用,提高启动速度 ``` ### 2. 多层缓存系统 中间件内置了三层缓存系统: ```typescript // 文件状态缓存 (3小时 TTL) const statCache = new Cache({ useClones: false, checkperiod: 5 * 60, // 5分钟检查一次 stdTTL: 3 * 60 * 60, // 3小时过期 maxKeys: 250 // 最多缓存250个文件状态 }) // 文件路径缓存 (3小时 TTL) const fileCache = new Cache({ useClones: false, checkperiod: 5 * 60, stdTTL: 3 * 60 * 60, maxKeys: 250 }) // HTML 文件缓存 (3小时 TTL) const htmlCache = new Cache({ useClones: false, checkperiod: 5 * 60, stdTTL: 3 * 60 * 60, maxKeys: 250 }) ``` ### 3. 智能 ETag 生成 中间件使用 MD5 哈希算法生成 ETag,支持 HTTP 304 响应: ```typescript // 自动生成 ETag const etag = await generateETag(filePath) // 支持 If-None-Match 头部 if (await isCached(headersRecord, etag, filePath)) { return new Response(null, { status: 304, headers }) } ``` ### 4. 跨平台路径处理 中间件自动处理不同操作系统的路径分隔符: ```typescript const URL_PATH_SEP = '/' const isFSSepUnsafe = sep !== URL_PATH_SEP // 自动转换路径分隔符 const pathName = isFSSepUnsafe ? prefix + relativePath.split(sep).join(URL_PATH_SEP) : join(prefix, relativePath) ``` ## 注意事项 1. **文件数量**: 大量文件时建议使用通配符路由以减少内存占用 2. **缓存策略**: 根据文件类型和更新频率设置合适的缓存策略 3. **安全考虑**: 避免暴露敏感文件,使用 ignorePatterns 过滤 4. **性能影响**: 静态路由模式在启动时扫描所有文件,大目录可能影响启动时间 5. **路径安全**: 中间件已内置路径安全检查,防止目录遍历攻击 6. **缓存清理**: 中间件会自动清理过期的缓存项,无需手动管理 7. **内存使用**: 通配符路由模式内存占用更少,适合大文件目录 8. **启动时间**: 静态路由模式启动更快,但内存占用更多 ## 版本信息 * **当前版本**: 0.0.1 * **Vafast 兼容性**: >= 0.1.12 * **Node.js 支持**: >= 18.0.0 * **Bun 支持**: 完全支持 ## 更新日志 查看完整的更新历史:[CHANGELOG.md](https://github.com/vafastjs/vafast-static/blob/main/CHANGELOG.md) ### 最新特性 * ✅ 智能路由选择(静态路由 vs 通配符路由) * ✅ 多层缓存系统(文件状态、路径、HTML) * ✅ 智能 ETag 生成和 HTTP 304 支持 * ✅ 跨平台路径处理 * ✅ 完整的 TypeScript 类型支持 * ✅ 灵活的缓存策略配置 * ✅ 安全的文件访问控制 ## 相关链接 * [HTTP 缓存 MDN 文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) * [ETag MDN 文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) * [Cache-Control MDN 文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) * [Vafast 官方文档](https://vafast.dev) * [GitHub 仓库](https://github.com/vafastjs/vafast-static) * [问题反馈](https://github.com/vafastjs/vafast-static/issues) --- --- url: 'https://vafast.dev/integrations/sveltekit.md' --- # SvelteKit 集成 Vafast 可以与 SvelteKit 无缝集成,为您提供强大的后端 API 和现代化的前端开发体验。 ## 项目结构 ``` my-vafast-sveltekit-app/ ├── src/ │ ├── lib/ # 共享库 │ ├── routes/ # SvelteKit 路由 │ ├── api/ # Vafast API 路由 │ │ ├── routes.ts # 路由定义 │ │ ├── server.ts # Vafast 服务器 │ │ └── types.ts # 类型定义 │ └── app.html # HTML 模板 ├── package.json ├── svelte.config.js ├── vite.config.ts └── tsconfig.json ``` ## 安装依赖 ```bash bun add vafast @vafast/cors @vafast/helmet bun add -D @types/node ``` ## 创建 Vafast API 服务器 ```typescript // src/api/server.ts import { defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' import { routes } from './routes' export const app = createHandler(routes) .use(cors({ origin: process.env.NODE_ENV === 'development' ? ['http://localhost:5173'] : [process.env.PUBLIC_APP_URL], credentials: true })) .use(helmet()) export const handler = app.handler ``` ## 定义 API 路由 ```typescript // src/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' export const routes = defineRoutes([ { method: 'GET', path: '/api/todos', handler: createHandler(async () => { // 模拟数据库查询 const todos = [ { id: 1, title: 'Learn Vafast', completed: false }, { id: 2, title: 'Build SvelteKit app', completed: true } ] return { todos } }) }, { method: 'POST', path: '/api/todos', handler: createHandler(async ({ body }) => { // 创建新待办事项 const newTodo = { id: Date.now(), ...body, completed: false, createdAt: new Date().toISOString() } return { todo: newTodo }, { status: 201 } }), body: Type.Object({ title: Type.String({ minLength: 1 }), description: Type.Optional(Type.String()) }) }, { method: 'PUT', path: '/api/todos/:id', handler: createHandler(async ({ params, body }) => { const todoId = parseInt(params.id) // 模拟数据库更新 const updatedTodo = { id: todoId, ...body, updatedAt: new Date().toISOString() } return { todo: updatedTodo } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }), body: Type.Object({ title: Type.Optional(Type.String({ minLength: 1 })), description: Type.Optional(Type.String()), completed: Type.Optional(Type.Boolean()) }) }, { method: 'DELETE', path: '/api/todos/:id', handler: createHandler(async ({ params }) => { const todoId = parseInt(params.id) // 模拟数据库删除 console.log(`Deleting todo ${todoId}`) return { success: true } }), params: Type.Object({ id: Type.String({ pattern: '^\\d+$' }) }) } ]) ``` ## 创建 SvelteKit API 路由 ```typescript // src/routes/api/[...path]/+server.ts import { handler } from '../../../api/server' import type { RequestHandler } from './$types' export const GET: RequestHandler = async ({ request }) => { return handler(request) } export const POST: RequestHandler = async ({ request }) => { return handler(request) } export const PUT: RequestHandler = async ({ request }) => { return handler(request) } export const DELETE: RequestHandler = async ({ request }) => { return handler(request) } export const PATCH: RequestHandler = async ({ request }) => { return handler(request) } ``` ## 类型定义 ```typescript // src/api/types.ts import { Type } from '@sinclair/typebox' export const TodoSchema = Type.Object({ id: Type.Number(), title: Type.String(), description: Type.Optional(Type.String()), completed: Type.Boolean(), createdAt: Type.String({ format: 'date-time' }), updatedAt: Type.Optional(Type.String({ format: 'date-time' })) }) export const CreateTodoSchema = Type.Object({ title: Type.String({ minLength: 1 }), description: Type.Optional(Type.String()) }) export const UpdateTodoSchema = Type.Partial(CreateTodoSchema) export type Todo = typeof TodoSchema.T export type CreateTodo = typeof CreateTodoSchema.T export type UpdateTodo = typeof UpdateTodoSchema.T ``` ## 前端集成 ### 使用 API 路由 ```svelte 待办事项

待办事项

{ if (e.key === 'Enter' && e.target.value.trim()) { createTodo(e.target.value.trim()) e.target.value = '' } }} />
{#if error}
{error}
{/if} {#if loading}
加载中...
{:else}
{#each todos as todo (todo.id)}
toggleTodo(todo)} />

{todo.title}

{#if todo.description}

{todo.description}

{/if}
{/each}
{/if}
``` ### 创建待办事项页面 ```svelte 创建待办事项

创建新待办事项

``` ## 中间件集成 ### 认证中间件 ```typescript // src/api/middleware/auth.ts export interface AuthenticatedRequest extends Request { user?: { id: string email: string role: string } } export const authMiddleware = async ( request: Request, next: () => Promise ) => { const token = request.headers.get('authorization')?.replace('Bearer ', '') if (!token) { return new Response('Unauthorized', { status: 401 }) } try { // 验证 JWT token const user = await verifyToken(token) ;(request as AuthenticatedRequest).user = user return next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } async function verifyToken(token: string) { // 实现 JWT 验证逻辑 // 这里应该使用 @vafast/jwt 中间件 return { id: '123', email: 'user@example.com', role: 'user' } } ``` ### 使用认证中间件 ```typescript // src/api/routes.ts import { defineRoutes, createHandler } from 'vafast' import { authMiddleware } from './middleware/auth' export const routes = defineRoutes([ { method: 'GET', path: '/api/profile', handler: createHandler(async ({ request }) => { const user = (request as AuthenticatedRequest).user return { user } }), middleware: [authMiddleware] } ]) ``` ## SvelteKit 配置 ```typescript // svelte.config.js import adapter from '@sveltejs/adapter-node' import { vitePreprocess } from '@sveltejs/kit/vite' /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter(), // 配置 API 路由 routes: { 'api/[...path]': 'src/routes/api/[...path]/+server.ts' } }, preprocess: vitePreprocess() } export default config ``` ## 环境配置 ```typescript // src/api/config.ts export const config = { development: { cors: { origin: ['http://localhost:5173', 'http://localhost:3000'] }, logging: true }, production: { cors: { origin: [process.env.PUBLIC_APP_URL] }, logging: false } } export const getConfig = () => { const env = process.env.NODE_ENV || 'development' return config[env as keyof typeof config] } ``` ## 测试 ### API 测试 ```typescript // src/api/__tests__/todos.test.ts import { describe, expect, it } from 'bun:test' import { handler } from '../server' describe('Todos API', () => { it('should get todos', async () => { const request = new Request('http://localhost/api/todos') const response = await handler(request) const data = await response.json() expect(response.status).toBe(200) expect(data.todos).toBeDefined() expect(Array.isArray(data.todos)).toBe(true) }) it('should create todo', async () => { const todoData = { title: 'Test Todo', description: 'Test description' } const request = new Request('http://localhost/api/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(todoData) }) const response = await handler(request) const data = await response.json() expect(response.status).toBe(201) expect(data.todo.title).toBe(todoData.title) expect(data.todo.description).toBe(todoData.description) }) }) ``` ## 部署 ### Node.js 部署 ```typescript // build/server/entry.js import { handler } from './api/server.js' import { createServer } from 'http' const server = createServer(async (req, res) => { try { const response = await handler(req) // 复制响应头 for (const [key, value] of response.headers.entries()) { res.setHeader(key, value) } res.statusCode = response.status res.end(await response.text()) } catch (error) { console.error('Server error:', error) res.statusCode = 500 res.end('Internal Server Error') } }) const port = process.env.PORT || 3000 server.listen(port, () => { console.log(`Server running on port ${port}`) }) ``` ### Docker 部署 ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --production COPY . . RUN bun run build EXPOSE 3000 CMD ["bun", "run", "start"] ``` ## 最佳实践 1. **类型安全**:使用 TypeScript 确保前后端类型一致 2. **错误处理**:实现统一的错误处理机制 3. **中间件顺序**:注意中间件的执行顺序 4. **环境配置**:根据环境配置不同的设置 5. **测试覆盖**:为 API 路由编写完整的测试 6. **性能优化**:使用适当的缓存和压缩策略 7. **SSR 优化**:利用 SvelteKit 的 SSR 能力优化性能 ## 相关链接 * [Vafast 文档](/getting-started/quickstart) - 快速开始指南 * [SvelteKit 文档](https://kit.svelte.dev) - SvelteKit 官方文档 * [中间件系统](/middleware) - 探索可用的中间件 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/middleware/swagger.md' --- # Swagger 中间件 该中间件为 [Vafast](https://github.com/vafastjs/vafast) 提供了完整的 Swagger/OpenAPI 文档生成和 UI 展示功能,支持 Scalar 和 Swagger UI 两种界面,让 API 文档更加专业和易用。 ## 安装 安装命令: ```bash bun add @vafast/swagger ``` ## 基本用法 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' // 创建 Swagger 中间件 const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { info: { title: 'My API', version: '1.0.0' } } }) // 定义路由 const routes = [ { method: 'GET', path: '/api/', handler: createHandler(() => { return { message: 'Hello API' } }) } ] // 创建服务器 const server = new Server(routes) // 导出 fetch 函数,应用 Swagger 中间件 export default { fetch: (req: Request) => { return swaggerMiddleware(req, () => server.fetch(req)) } } ``` ## 配置选项 ### VafastSwaggerConfig ```typescript interface VafastSwaggerConfig { /** 文档提供者,默认:'scalar' */ provider?: 'scalar' | 'swagger-ui' /** Scalar 版本,默认:'latest' */ scalarVersion?: string /** Scalar CDN 地址,默认:官方 CDN */ scalarCDN?: string /** Scalar 配置选项 */ scalarConfig?: Record /** OpenAPI 文档配置 */ documentation?: OpenAPIDocumentation /** Swagger UI 版本,默认:'4.18.2' */ version?: string /** 是否排除静态文件,默认:true */ excludeStaticFile?: boolean /** 文档路径,默认:'/swagger' */ path?: Path /** OpenAPI 规范路径,默认:'${path}/json' */ specPath?: string /** 排除的路径或方法 */ exclude?: string | RegExp | (string | RegExp)[] /** Swagger UI 选项 */ swaggerOptions?: Record /** 主题配置 */ theme?: string | { light: string; dark: string } /** 是否启用自动暗色模式,默认:true */ autoDarkMode?: boolean /** 排除的 HTTP 方法,默认:['OPTIONS'] */ excludeMethods?: string[] /** 排除的标签 */ excludeTags?: string[] } ``` ### OpenAPIDocumentation ```typescript interface OpenAPIDocumentation { /** API 基本信息 */ info?: OpenAPIInfo /** API 标签 */ tags?: OpenAPITag[] /** 组件定义 */ components?: OpenAPIComponents /** API 路径定义 */ paths?: Record } interface OpenAPIInfo { title?: string description?: string version?: string } interface OpenAPITag { name: string description?: string } interface OpenAPISchema { type: string properties?: Record required?: string[] } interface OpenAPISecurityScheme { type: string scheme?: string bearerFormat?: string description?: string } interface OpenAPIComponents { schemas?: Record securitySchemes?: Record } ``` ## 使用模式 ### 1. 基本 Scalar 文档 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { info: { title: 'Vafast API', description: 'A modern TypeScript web framework API', version: '1.0.0' }, tags: [ { name: 'Users', description: 'User management endpoints' }, { name: 'Auth', description: 'Authentication endpoints' } ] } }) const routes = [ { method: 'GET', path: '/api/users', handler: createHandler(() => { return { users: [] } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return swaggerMiddleware(req, () => server.fetch(req)) } } ``` ### 2. Swagger UI 文档 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' const swaggerMiddleware = swagger({ provider: 'swagger-ui', version: '4.18.2', documentation: { info: { title: 'Vafast API', description: 'API documentation with Swagger UI', version: '1.0.0' } }, swaggerOptions: { persistAuthorization: true, displayOperationId: true, filter: true }, theme: 'https://unpkg.com/swagger-ui-dist@4.18.2/swagger-ui.css', autoDarkMode: true }) const routes = [ { method: 'GET', path: '/api/health', handler: createHandler(() => { return { status: 'OK' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return swaggerMiddleware(req, () => server.fetch(req)) } } ``` ### 3. 自定义路径和配置 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' const swaggerMiddleware = swagger({ provider: 'scalar', path: '/docs', // 自定义文档路径 specPath: '/docs/openapi.json', // 自定义规范路径 scalarVersion: '1.0.0', // 指定 Scalar 版本 scalarCDN: 'https://cdn.example.com/scalar', // 自定义 CDN scalarConfig: { theme: 'light', search: true, navigation: true }, documentation: { info: { title: 'Custom API', version: '2.0.0' } } }) const routes = [ { method: 'GET', path: '/api/data', handler: createHandler(() => { return { data: 'example' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return swaggerMiddleware(req, () => server.fetch(req)) } } ``` ### 4. 完整的 API 文档 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { info: { title: 'Complete API', description: 'A comprehensive API with full documentation', version: '1.0.0' }, tags: [ { name: 'Users', description: 'User management operations' }, { name: 'Posts', description: 'Blog post operations' }, { name: 'Auth', description: 'Authentication and authorization' } ], components: { schemas: { User: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, username: { type: 'string', minLength: 3 }, email: { type: 'string', format: 'email' }, createdAt: { type: 'string', format: 'date-time' } }, required: ['username', 'email'] }, Post: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, title: { type: 'string', minLength: 1 }, content: { type: 'string', minLength: 10 }, authorId: { type: 'string', format: 'uuid' }, published: { type: 'boolean', default: false } }, required: ['title', 'content', 'authorId'] } }, securitySchemes: { BearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', description: 'Enter your JWT token in the format: Bearer ' }, ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'X-API-Key', description: 'Enter your API key' } } }, paths: { '/api/users': { get: { summary: 'Get all users', tags: ['Users'], responses: { '200': { description: 'List of users', content: { 'application/json': { schema: { type: 'array', items: { $ref: '#/components/schemas/User' } } } } } } }, post: { summary: 'Create a new user', tags: ['Users'], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/User' } } } }, responses: { '201': { description: 'User created successfully', content: { 'application/json': { schema: { $ref: '#/components/schemas/User' } } } } } } } } } }) const routes = [ { method: 'GET', path: '/api/users', handler: createHandler(() => { return { users: [] } }) }, { method: 'POST', path: '/api/users', handler: createHandler(async (req: Request) => { const userData = await req.json() return { ...userData, id: 'generated-id' } }) } ] const server = new Server(routes) export default { fetch: (req: Request) => { return swaggerMiddleware(req, () => server.fetch(req)) } } ``` ### 5. 中间件集成 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' // 创建 Swagger 中间件 const swaggerMiddleware = swagger({ provider: 'scalar', path: '/api-docs', documentation: { info: { title: 'Middleware API', version: '1.0.0' } } }) // 定义路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'API is running' } }) }, { method: 'GET', path: '/api/status', handler: createHandler(() => { return { status: 'healthy', timestamp: new Date().toISOString() } }) } ] const server = new Server(routes) // 创建中间件包装器 const createMiddlewareWrapper = (middleware: any, handler: any) => { return async (req: Request) => { return middleware(req, () => handler(req)) } } // 导出带中间件的 fetch 函数 export default { fetch: createMiddlewareWrapper(swaggerMiddleware, server.fetch.bind(server)) } ``` ## 完整示例 ```typescript import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' // 创建 Swagger 中间件 const swaggerMiddleware = swagger({ provider: 'scalar', scalarVersion: 'latest', scalarConfig: { theme: 'light', search: true, navigation: true, sidebar: true }, documentation: { info: { title: 'Vafast Swagger Example', description: 'A complete example of Vafast with Swagger documentation', version: '0.8.1' }, tags: [ { name: 'Test', description: 'Test endpoints for demonstration' }, { name: 'Users', description: 'User management endpoints' }, { name: 'Files', description: 'File upload and management' } ], components: { schemas: { User: { type: 'object', properties: { username: { type: 'string', minLength: 3 }, email: { type: 'string', format: 'email' }, age: { type: 'number', minimum: 0 } }, required: ['username', 'email'] }, ApiResponse: { type: 'object', properties: { success: { type: 'boolean' }, message: { type: 'string' }, data: { type: 'object' } } } }, securitySchemes: { JwtAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', description: 'Enter JWT Bearer token **_only_**' }, ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'X-API-Key', description: 'Enter your API key' } } }, paths: { '/api/test': { get: { summary: 'Test endpoint', description: 'A simple test endpoint', tags: ['Test'], responses: { '200': { description: 'Successful response', content: { 'application/json': { schema: { $ref: '#/components/schemas/ApiResponse' } } } } } } }, '/api/users': { get: { summary: 'Get all users', tags: ['Users'], security: [{ JwtAuth: [] }], responses: { '200': { description: 'List of users', content: { 'application/json': { schema: { type: 'array', items: { $ref: '#/components/schemas/User' } } } } }, '401': { description: 'Unauthorized' } } }, post: { summary: 'Create a new user', tags: ['Users'], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/User' } } } }, responses: { '201': { description: 'User created successfully', content: { 'application/json': { schema: { $ref: '#/components/schemas/User' } } } }, '400': { description: 'Bad request' } } } } } }, swaggerOptions: { persistAuthorization: true, displayOperationId: true, filter: true, showExtensions: true } }) // 定义 API 路由 const routes = [ { method: 'GET', path: '/', handler: createHandler(() => { return { message: 'Vafast Swagger Example API', version: '1.0.0', documentation: '/swagger', openapi: '/swagger/json' } }) }, { method: 'GET', path: '/api/test', handler: createHandler(() => { return { success: true, message: 'Test endpoint working', data: { timestamp: new Date().toISOString() } } }) }, { method: 'GET', path: '/api/users', handler: createHandler(() => { return [ { username: 'john_doe', email: 'john@example.com', age: 30 }, { username: 'jane_smith', email: 'jane@example.com', age: 25 } ] }) }, { method: 'POST', path: '/api/users', handler: createHandler(async (req: Request) => { const userData = await req.json() return { ...userData, id: `user_${Date.now()}`, createdAt: new Date().toISOString() } }) }, { method: 'GET', path: '/api/health', handler: createHandler(() => { return { status: 'healthy', uptime: process.uptime(), timestamp: new Date().toISOString() } }) } ] // 创建服务器 const server = new Server(routes) // 导出带 Swagger 中间件的 fetch 函数 export default { fetch: (req: Request) => { return swaggerMiddleware(req, () => server.fetch(req)) } } console.log('🚀 Vafast Swagger Example Server 启动成功!') console.log('📚 API 文档:/swagger') console.log('🔗 OpenAPI 规范:/swagger/json') console.log('🌐 健康检查:/api/health') ``` ## 测试示例 ```typescript import { describe, expect, it } from 'bun:test' import { Server, createHandler } from 'vafast' import { swagger } from '@vafast/swagger' describe('Vafast Swagger Plugin', () => { it('should create swagger middleware', () => { const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { info: { title: 'Test API', version: '1.0.0' } } }) expect(swaggerMiddleware).toBeDefined() expect(typeof swaggerMiddleware).toBe('function') }) it('should serve Scalar documentation page', async () => { const swaggerMiddleware = swagger({ provider: 'scalar', path: '/docs' }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, API!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return swaggerMiddleware(req, () => app.fetch(req)) } // 测试访问 Scalar 文档页面 const res = await wrappedFetch(new Request('http://localhost/docs')) expect(res.status).toBe(200) expect(res.headers.get('content-type')).toContain('text/html') const html = await res.text() expect(html).toContain('scalar') }) it('should serve OpenAPI specification', async () => { const swaggerMiddleware = swagger({ provider: 'scalar', path: '/docs', specPath: '/docs/json' }) const app = new Server([ { method: 'GET', path: '/', handler: createHandler(() => { return 'Hello, API!' }) } ]) // 应用中间件 const wrappedFetch = (req: Request) => { return swaggerMiddleware(req, () => app.fetch(req)) } // 测试访问 OpenAPI 规范 const res = await wrappedFetch( new Request('http://localhost/docs/json') ) expect(res.status).toBe(200) expect(res.headers.get('content-type')).toContain('application/json') const spec = await res.json() expect(spec.openapi).toBe('3.0.3') expect(spec.info.title).toBe('Vafast API') }) it('should handle custom documentation info', async () => { const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { info: { title: 'Custom API', description: 'Custom API description', version: '2.0.0' }, tags: [ { name: 'Users', description: 'User management endpoints' } ] } }) const app = new Server([]) const wrappedFetch = (req: Request) => { return swaggerMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch( new Request('http://localhost/swagger/json') ) const spec = await res.json() expect(spec.info.title).toBe('Custom API') expect(spec.info.description).toBe('Custom API description') expect(spec.info.version).toBe('2.0.0') expect(spec.tags).toHaveLength(1) expect(spec.tags[0].name).toBe('Users') }) it('should handle Swagger UI provider', async () => { const swaggerMiddleware = swagger({ provider: 'swagger-ui', version: '4.18.2' }) const app = new Server([]) const wrappedFetch = (req: Request) => { return swaggerMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch(new Request('http://localhost/swagger')) const html = await res.text() expect(res.status).toBe(200) expect(html).toContain('swagger-ui') expect(html).toContain('4.18.2') }) it('should pass through non-swagger requests', async () => { const swaggerMiddleware = swagger({ provider: 'scalar' }) const app = new Server([ { method: 'GET', path: '/api/data', handler: createHandler(() => { return { message: 'Data endpoint' } }) } ]) const wrappedFetch = (req: Request) => { return swaggerMiddleware(req, () => app.fetch(req)) } const res = await wrappedFetch( new Request('http://localhost/api/data') ) const data = await res.json() expect(res.status).toBe(200) expect(data.message).toBe('Data endpoint') }) it('should handle custom path configuration', async () => { const swaggerMiddleware = swagger({ provider: 'scalar', path: '/custom-docs', specPath: '/custom-docs/spec' }) const app = new Server([]) const wrappedFetch = (req: Request) => { return swaggerMiddleware(req, () => app.fetch(req)) } // 测试自定义文档路径 const docsRes = await wrappedFetch( new Request('http://localhost/custom-docs') ) expect(docsRes.status).toBe(200) // 测试自定义规范路径 const specRes = await wrappedFetch( new Request('http://localhost/custom-docs/spec') ) expect(specRes.status).toBe(200) expect(specRes.headers.get('content-type')).toContain('application/json') }) }) ``` ## 特性 * ✅ **双界面支持**: 支持 Scalar 和 Swagger UI 两种文档界面 * ✅ **自动生成**: 自动生成 OpenAPI 3.0.3 规范 * ✅ **灵活配置**: 丰富的配置选项和自定义支持 * ✅ **中间件集成**: 无缝集成到 Vafast 应用 * ✅ **类型安全**: 完整的 TypeScript 类型支持 * ✅ **主题定制**: 支持自定义主题和暗色模式 * ✅ **CDN 配置**: 支持自定义 CDN 地址 * ✅ **路径配置**: 灵活的文档和规范路径配置 ## 最佳实践 ### 1. 选择合适的提供者 ```typescript // 现代、美观的界面,推荐用于生产环境 const scalarMiddleware = swagger({ provider: 'scalar', scalarVersion: 'latest' }) // 传统、功能丰富的界面,适合开发调试 const swaggerUIMiddleware = swagger({ provider: 'swagger-ui', version: '4.18.2' }) ``` ### 2. 结构化文档组织 ```typescript const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { info: { title: 'API Name', description: 'Clear API description', version: '1.0.0' }, tags: [ { name: 'Users', description: 'User management' }, { name: 'Auth', description: 'Authentication' } ], components: { schemas: { // 定义可重用的数据模型 }, securitySchemes: { // 定义安全方案 } } } }) ``` ### 3. 安全配置 ```typescript const swaggerMiddleware = swagger({ provider: 'scalar', documentation: { components: { securitySchemes: { BearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } } } }) ``` ### 4. 生产环境配置 ```typescript const swaggerMiddleware = swagger({ provider: 'scalar', path: '/api/docs', // 使用子路径 excludeStaticFile: true, // 排除静态文件 scalarCDN: 'https://cdn.example.com/scalar', // 使用自己的 CDN documentation: { info: { title: 'Production API', version: process.env.API_VERSION || '1.0.0' } } }) ``` ## 注意事项 1. **路径冲突**: 确保 Swagger 路径不与 API 路由冲突 2. **安全考虑**: 生产环境建议使用子路径,避免暴露在根路径 3. **CDN 配置**: 生产环境建议使用自己的 CDN 地址 4. **版本管理**: 保持 Swagger UI 和 Scalar 版本更新 5. **文档维护**: 及时更新 API 文档以保持同步 6. **性能影响**: 中间件会拦截所有请求,注意性能影响 7. **类型定义**: 充分利用 TypeScript 类型来生成准确的文档 ## 版本信息 * **当前版本**: 0.0.1 * **Vafast 兼容性**: >= 0.1.12 * **OpenAPI 版本**: 3.0.3 * **支持界面**: Scalar (最新版本) + Swagger UI (4.18.2) ## 相关链接 * [OpenAPI 规范](https://swagger.io/specification/) * [Scalar 官方文档](https://docs.scalar.com/) * [Swagger UI 官方文档](https://swagger.io/tools/swagger-ui/) * [Vafast 官方文档](https://vafast.dev) * [GitHub 仓库](https://github.com/vafastjs/vafast-swagger) * [问题反馈](https://github.com/vafastjs/vafast-swagger/issues) --- --- url: 'https://vafast.dev/patterns/trace.md' --- # Trace 性能是 Vafast 一个重要的方面。 我们不仅希望在基准测试中快速运行,我们希望您在真实场景中拥有一个真正快速的服务器。 有许多因素可能会减慢我们的应用程序 - 并且很难识别它们,但 **trace** 可以通过在每个请求处理阶段中注入开始和停止代码来帮助解决这个问题。 Vafast 提供了内置的监控系统,允许我们跟踪请求处理时间、中间件执行时间和其他性能指标。 ## 内置监控 Vafast 提供了 `withMonitoring` 函数来为服务器添加监控能力: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' import { withMonitoring } from 'vafast/monitoring' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }, { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { // 模拟一些异步操作 await new Promise(resolve => setTimeout(resolve, 100)) return { success: true, user: body } }) } ]) const server = new Server(routes) // 添加监控功能 const monitoredServer = withMonitoring(server, { enableMetrics: true, enableLogging: true }) export default { fetch: monitoredServer.fetch } ``` ## 自定义性能追踪 您可以使用中间件来实现自定义的性能追踪: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 性能追踪中间件 const performanceTraceMiddleware = async (req: Request, next: () => Promise) => { const startTime = performance.now() const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` console.log(`[${requestId}] 开始处理请求: ${req.method} ${req.url}`) try { const response = await next() const endTime = performance.now() const duration = endTime - startTime console.log(`[${requestId}] 请求处理完成: ${response.status} (${duration.toFixed(2)}ms)`) // 添加性能头 response.headers.set('X-Request-ID', requestId) response.headers.set('X-Response-Time', `${duration.toFixed(2)}ms`) return response } catch (error) { const endTime = performance.now() const duration = endTime - startTime console.error(`[${requestId}] 请求处理失败: ${error} (${duration.toFixed(2)}ms)`) throw error } } // 中间件执行时间追踪 const middlewareTraceMiddleware = (middlewareName: string) => { return async (req: Request, next: () => Promise) => { const startTime = performance.now() try { const response = await next() const endTime = performance.now() const duration = endTime - startTime console.log(`[${middlewareName}] 执行时间: ${duration.toFixed(2)}ms`) return response } catch (error) { const endTime = performance.now() const duration = endTime - startTime console.error(`[${middlewareName}] 执行失败: ${error} (${duration.toFixed(2)}ms)`) throw error } } } const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') } ]) const server = new Server(routes) // 应用性能追踪中间件 server.use(performanceTraceMiddleware) server.use(middlewareTraceMiddleware('Global')) export default { fetch: server.fetch } ``` ## 详细性能分析 实现更详细的性能分析: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' interface PerformanceMetrics { requestId: string method: string url: string startTime: number middlewareTimes: Map totalTime: number status: number memoryUsage?: NodeJS.MemoryUsage } class PerformanceTracker { private metrics = new Map() startRequest(req: Request): string { const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` const metrics: PerformanceMetrics = { requestId, method: req.method, url: req.url, startTime: performance.now(), middlewareTimes: new Map(), totalTime: 0, status: 0 } this.metrics.set(requestId, metrics) return requestId } recordMiddlewareTime(requestId: string, middlewareName: string, duration: number) { const metrics = this.metrics.get(requestId) if (metrics) { metrics.middlewareTimes.set(middlewareName, duration) } } endRequest(requestId: string, status: number, memoryUsage?: NodeJS.MemoryUsage) { const metrics = this.metrics.get(requestId) if (metrics) { metrics.totalTime = performance.now() - metrics.startTime metrics.status = status metrics.memoryUsage = memoryUsage this.logMetrics(metrics) this.metrics.delete(requestId) } } private logMetrics(metrics: PerformanceMetrics) { console.log(`\n=== 性能报告 [${metrics.requestId}] ===`) console.log(`请求: ${metrics.method} ${metrics.url}`) console.log(`状态: ${metrics.status}`) console.log(`总时间: ${metrics.totalTime.toFixed(2)}ms`) if (metrics.middlewareTimes.size > 0) { console.log('中间件执行时间:') metrics.middlewareTimes.forEach((time, name) => { console.log(` ${name}: ${time.toFixed(2)}ms`) }) } if (metrics.memoryUsage) { console.log('内存使用:') console.log(` 堆使用: ${(metrics.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`) console.log(` 堆总计: ${(metrics.memoryUsage.heapTotal / 1024 / 1024).toFixed(2)}MB`) } console.log('================================\n') } } const tracker = new PerformanceTracker() // 增强的性能追踪中间件 const enhancedTraceMiddleware = async (req: Request, next: () => Promise) => { const requestId = tracker.startRequest(req) try { const response = await next() tracker.endRequest(requestId, response.status) return response } catch (error) { tracker.endRequest(requestId, 500) throw error } } // 中间件性能追踪 const createMiddlewareTracer = (name: string) => { return async (req: Request, next: () => Promise) => { const startTime = performance.now() try { const response = await next() const duration = performance.now() - startTime const requestId = req.headers.get('X-Request-ID') if (requestId) { tracker.recordMiddlewareTime(requestId, name, duration) } return response } catch (error) { const duration = performance.now() - startTime const requestId = req.headers.get('X-Request-ID') if (requestId) { tracker.recordMiddlewareTime(requestId, name, duration) } throw error } } } const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') } ]) const server = new Server(routes) // 应用增强的性能追踪 server.use(enhancedTraceMiddleware) server.use(createMiddlewareTracer('Global')) export default { fetch: server.fetch } ``` ## 实时性能监控 实现实时性能监控面板: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' class RealTimeMonitor { private metrics: Array<{ timestamp: number method: string path: string duration: number status: number }> = [] private maxMetrics = 1000 recordRequest(method: string, path: string, duration: number, status: number) { this.metrics.push({ timestamp: Date.now(), method, path, duration, status }) // 保持最新的指标 if (this.metrics.length > this.maxMetrics) { this.metrics = this.metrics.slice(-this.maxMetrics) } } getStats() { const now = Date.now() const lastMinute = this.metrics.filter(m => now - m.timestamp < 60000) const lastHour = this.metrics.filter(m => now - m.timestamp < 3600000) return { total: this.metrics.length, lastMinute: lastMinute.length, lastHour: lastHour.length, averageResponseTime: this.metrics.length > 0 ? this.metrics.reduce((sum, m) => sum + m.duration, 0) / this.metrics.length : 0, statusCodes: this.getStatusCodeDistribution(), topEndpoints: this.getTopEndpoints() } } private getStatusCodeDistribution() { const distribution: Record = {} this.metrics.forEach(m => { distribution[m.status] = (distribution[m.status] || 0) + 1 }) return distribution } private getTopEndpoints() { const endpointCounts: Record = {} this.metrics.forEach(m => { const key = `${m.method} ${m.path}` endpointCounts[key] = (endpointCounts[key] || 0) + 1 }) return Object.entries(endpointCounts) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([endpoint, count]) => ({ endpoint, count })) } } const monitor = new RealTimeMonitor() // 监控中间件 const monitoringMiddleware = async (req: Request, next: () => Promise) => { const startTime = performance.now() const url = new URL(req.url) try { const response = await next() const duration = performance.now() - startTime monitor.recordRequest(req.method, url.pathname, duration, response.status) return response } catch (error) { const duration = performance.now() - startTime monitor.recordRequest(req.method, url.pathname, duration, 500) throw error } } const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }, { method: 'GET', path: '/metrics', handler: createHandler(() => { const stats = monitor.getStats() return new Response(JSON.stringify(stats, null, 2), { headers: { 'Content-Type': 'application/json' } }) }) }, { method: 'GET', path: '/monitor', handler: createHandler(() => { const stats = monitor.getStats() return ` Vafast 性能监控

Vafast 性能监控

总请求数: ${stats.total}

最近1分钟: ${stats.lastMinute}

最近1小时: ${stats.lastHour}

平均响应时间: ${stats.averageResponseTime.toFixed(2)}ms

状态码分布:

${Object.entries(stats.statusCodes).map(([code, count]) => `

${code}: ${count}

` ).join('')}

热门端点:

${stats.topEndpoints.map(({ endpoint, count }) => `

${endpoint}: ${count}

` ).join('')}
` }) } ]) const server = new Server(routes) server.use(monitoringMiddleware) export default { fetch: server.fetch } ``` ## 性能基准测试 实现性能基准测试: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' class PerformanceBenchmark { private results: Array<{ name: string duration: number timestamp: number }> = [] async runBenchmark(name: string, testFn: () => Promise) { const startTime = performance.now() try { await testFn() const duration = performance.now() - startTime this.results.push({ name, duration, timestamp: Date.now() }) console.log(`[${name}] 基准测试完成: ${duration.toFixed(2)}ms`) return duration } catch (error) { console.error(`[${name}] 基准测试失败:`, error) throw error } } getResults() { return this.results } getAverageTime(name: string) { const relevantResults = this.results.filter(r => r.name === name) if (relevantResults.length === 0) return 0 return relevantResults.reduce((sum, r) => sum + r.duration, 0) / relevantResults.length } generateReport() { const uniqueNames = [...new Set(this.results.map(r => r.name))] console.log('\n=== 性能基准测试报告 ===') uniqueNames.forEach(name => { const avgTime = this.getAverageTime(name) const count = this.results.filter(r => r.name === name).length console.log(`${name}: 平均 ${avgTime.toFixed(2)}ms (${count} 次测试)`) }) console.log('==========================\n') } } const benchmark = new PerformanceBenchmark() // 基准测试路由 const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }, { method: 'POST', path: '/benchmark', handler: createHandler(async ({ body }) => { const { testName, iterations = 1000 } = body const testFn = async () => { for (let i = 0; i < iterations; i++) { // 模拟一些计算 Math.sqrt(i) * Math.PI } } const duration = await benchmark.runBenchmark(testName, testFn) return { testName, iterations, duration: duration.toFixed(2), averageTime: benchmark.getAverageTime(testName).toFixed(2) } }) }, { method: 'GET', path: '/benchmark/results', handler: createHandler(() => { return new Response(JSON.stringify(benchmark.getResults(), null, 2), { headers: { 'Content-Type': 'application/json' } }) }) } ]) const server = new Server(routes) // 运行基准测试 setTimeout(() => { benchmark.generateReport() }, 5000) export default { fetch: server.fetch } ``` ## 总结 Vafast 的性能追踪系统提供了: * ✅ 内置监控支持 * ✅ 自定义性能追踪中间件 * ✅ 详细的性能分析 * ✅ 实时性能监控 * ✅ 性能基准测试 * ✅ 内存使用监控 * ✅ 中间件执行时间追踪 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强功能 * 探索 [验证系统](/essential/validation) 了解类型安全 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/patterns/websocket.md' --- # WebSocket WebSocket 是一种用于客户端与服务器之间通信的实时协议。 与 HTTP 不同,客户端一次又一次地询问网站信息并等待每次的回复,WebSocket 建立了一条直接的通道,使我们的客户端和服务器可以直接来回发送消息,从而使对话更快、更流畅,而无需每条消息都重新开始。 Vafast 使用 Bun 的内置 WebSocket 支持,提供了高性能的实时通信能力。 ## 基本 WebSocket 实现 要使用 WebSocket,您可以使用 Bun 的内置 WebSocket 服务器: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 定义 HTTP 路由 const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') } ]) const server = new Server(routes) // 创建 WebSocket 服务器 const wsServer = Bun.serve({ port: 3001, fetch(req, server) { // 升级 HTTP 请求到 WebSocket if (server.upgrade(req)) { return // 返回 undefined 表示升级成功 } return new Response("Upgrade failed", { status: 500 }) }, websocket: { // 连接建立时 open(ws) { console.log("WebSocket 连接已建立") ws.send(JSON.stringify({ type: 'connected', message: 'Welcome to Vafast WebSocket!' })) }, // 接收消息时 message(ws, message) { console.log("收到消息:", message) // 回显消息 ws.send(JSON.stringify({ type: 'echo', message: message, timestamp: Date.now() })) }, // 连接关闭时 close(ws, code, reason) { console.log("WebSocket 连接已关闭:", code, reason) }, // 发生错误时 error(ws, error) { console.error("WebSocket 错误:", error) } } }) console.log(`WebSocket 服务器运行在端口 ${wsServer.port}`) export default { fetch: server.fetch } ``` ## 集成到 Vafast 应用 您可以将 WebSocket 功能集成到现有的 Vafast 应用中: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => ` Vafast WebSocket Demo

Vafast WebSocket 演示

`) } ]) const server = new Server(routes) // 启动 HTTP 服务器 const httpServer = Bun.serve({ port: 3000, fetch: server.fetch }) // 启动 WebSocket 服务器 const wsServer = Bun.serve({ port: 3001, fetch(req, server) { if (server.upgrade(req)) { return } return new Response("Upgrade failed", { status: 500 }) }, websocket: { open(ws) { console.log("WebSocket 连接已建立") }, message(ws, message) { console.log("收到消息:", message) ws.send(JSON.stringify({ type: 'response', message: \`服务器收到: \${message}\`, timestamp: Date.now() })) }, close(ws) { console.log("WebSocket 连接已关闭") } } }) console.log(`HTTP 服务器运行在端口 ${httpServer.port}`) console.log(`WebSocket 服务器运行在端口 ${wsServer.port}`) ``` ## 类型安全的 WebSocket 您可以使用 TypeScript 和 TypeBox 来确保 WebSocket 消息的类型安全: ```typescript import { Type } from '@sinclair/typebox' // 定义消息类型 const MessageSchema = Type.Object({ type: Type.Union([ Type.Literal('chat'), Type.Literal('notification'), Type.Literal('status') ]), content: Type.String(), userId: Type.Optional(Type.String()), timestamp: Type.Optional(Type.Number()) }) type Message = Static // WebSocket 服务器 const wsServer = Bun.serve({ port: 3001, fetch(req, server) { if (server.upgrade(req)) { return } return new Response("Upgrade failed", { status: 500 }) }, websocket: { open(ws) { console.log("WebSocket 连接已建立") }, message(ws, message) { try { // 解析和验证消息 const parsedMessage = JSON.parse(message as string) if (Type.Check(MessageSchema, parsedMessage)) { // 消息类型安全 const validatedMessage: Message = parsedMessage console.log("收到有效消息:", validatedMessage) // 处理不同类型的消息 switch (validatedMessage.type) { case 'chat': ws.send(JSON.stringify({ type: 'chat_response', content: `收到聊天消息: ${validatedMessage.content}`, timestamp: Date.now() })) break case 'notification': ws.send(JSON.stringify({ type: 'notification_response', content: '通知已收到', timestamp: Date.now() })) break case 'status': ws.send(JSON.stringify({ type: 'status_response', content: '状态已更新', timestamp: Date.now() })) break } } else { // 发送验证错误 ws.send(JSON.stringify({ type: 'error', message: '消息格式无效', timestamp: Date.now() })) } } catch (error) { ws.send(JSON.stringify({ type: 'error', message: '消息解析失败', timestamp: Date.now() })) } }, close(ws) { console.log("WebSocket 连接已关闭") } } }) ``` ## 房间和广播 实现聊天室功能: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 房间管理 class RoomManager { private rooms = new Map>() addToRoom(roomId: string, ws: WebSocket) { if (!this.rooms.has(roomId)) { this.rooms.set(roomId, new Set()) } this.rooms.get(roomId)!.add(ws) } removeFromRoom(roomId: string, ws: WebSocket) { const room = this.rooms.get(roomId) if (room) { room.delete(ws) if (room.size === 0) { this.rooms.delete(roomId) } } } broadcastToRoom(roomId: string, message: any, excludeWs?: WebSocket) { const room = this.rooms.get(roomId) if (room) { const messageStr = JSON.stringify(message) room.forEach(ws => { if (ws !== excludeWs && ws.readyState === WebSocket.OPEN) { ws.send(messageStr) } }) } } } const roomManager = new RoomManager() const wsServer = Bun.serve({ port: 3001, fetch(req, server) { if (server.upgrade(req)) { return } return new Response("Upgrade failed", { status: 500 }) }, websocket: { open(ws) { console.log("WebSocket 连接已建立") }, message(ws, message) { try { const data = JSON.parse(message as string) if (data.type === 'join_room') { const { roomId, userId } = data roomManager.addToRoom(roomId, ws) // 通知房间其他用户 roomManager.broadcastToRoom(roomId, { type: 'user_joined', userId, roomId, timestamp: Date.now() }, ws) // 确认加入房间 ws.send(JSON.stringify({ type: 'room_joined', roomId, userId, timestamp: Date.now() })) } else if (data.type === 'chat_message') { const { roomId, userId, content } = data // 广播消息到房间 roomManager.broadcastToRoom(roomId, { type: 'chat_message', userId, content, roomId, timestamp: Date.now() }) } else if (data.type === 'leave_room') { const { roomId, userId } = data roomManager.removeFromRoom(roomId, ws) // 通知房间其他用户 roomManager.broadcastToRoom(roomId, { type: 'user_left', userId, roomId, timestamp: Date.now() }) } } catch (error) { ws.send(JSON.stringify({ type: 'error', message: '消息处理失败', timestamp: Date.now() })) } }, close(ws) { console.log("WebSocket 连接已关闭") // 从所有房间中移除用户 // 这里需要维护 ws 到房间的映射 } } }) ``` ## 配置选项 Bun 的 WebSocket 服务器支持多种配置选项: ```typescript const wsServer = Bun.serve({ port: 3001, fetch(req, server) { if (server.upgrade(req)) { return } return new Response("Upgrade failed", { status: 500 }) }, websocket: { // 连接建立时 open(ws) { console.log("WebSocket 连接已建立") }, // 接收消息时 message(ws, message) { ws.send(message) }, // 连接关闭时 close(ws, code, reason) { console.log("WebSocket 连接已关闭:", code, reason) }, // 发生错误时 error(ws, error) { console.error("WebSocket 错误:", error) } }, // 其他配置选项 maxPayloadLength: 1024 * 1024, // 1MB idleTimeout: 30, // 30秒空闲超时 perMessageDeflate: false, // 禁用消息压缩 maxBackpressure: 1024 * 1024 // 1MB 背压限制 }) ``` ## 错误处理 实现健壮的错误处理: ```typescript const wsServer = Bun.serve({ port: 3001, fetch(req, server) { if (server.upgrade(req)) { return } return new Response("Upgrade failed", { status: 500 }) }, websocket: { open(ws) { try { console.log("WebSocket 连接已建立") ws.send(JSON.stringify({ type: 'connected' })) } catch (error) { console.error("连接建立失败:", error) ws.close(1011, "Internal error") } }, message(ws, message) { try { const data = JSON.parse(message as string) // 处理消息... ws.send(JSON.stringify({ type: 'ack', data })) } catch (error) { console.error("消息处理失败:", error) ws.send(JSON.stringify({ type: 'error', message: '消息处理失败', error: error instanceof Error ? error.message : 'Unknown error' })) } }, close(ws, code, reason) { console.log("WebSocket 连接已关闭:", code, reason) // 清理资源 }, error(ws, error) { console.error("WebSocket 错误:", error) // 尝试优雅地关闭连接 try { ws.close(1011, "Internal error") } catch (closeError) { console.error("关闭连接失败:", closeError) } } } }) ``` ## 性能优化 ### 1. 消息批处理 ```typescript class MessageBatcher { private batch: any[] = [] private batchTimeout: Timer | null = null private readonly batchSize = 10 private readonly batchDelay = 100 // 100ms addMessage(message: any) { this.batch.push(message) if (this.batch.length >= this.batchSize) { this.flush() } else if (!this.batchTimeout) { this.batchTimeout = setTimeout(() => this.flush(), this.batchDelay) } } private flush() { if (this.batchTimeout) { clearTimeout(this.batchTimeout) this.batchTimeout = null } if (this.batch.length > 0) { // 发送批量消息 console.log(`发送批量消息: ${this.batch.length} 条`) this.batch = [] } } } const batcher = new MessageBatcher() ``` ### 2. 连接池管理 ```typescript class ConnectionPool { private connections = new Set() private maxConnections = 1000 addConnection(ws: WebSocket): boolean { if (this.connections.size >= this.maxConnections) { return false } this.connections.add(ws) return true } removeConnection(ws: WebSocket) { this.connections.delete(ws) } broadcast(message: any) { const messageStr = JSON.stringify(message) this.connections.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { ws.send(messageStr) } }) } getConnectionCount(): number { return this.connections.size } } const connectionPool = new ConnectionPool() ``` ## 总结 Vafast 的 WebSocket 实现提供了: * ✅ 基于 Bun 的高性能 WebSocket 支持 * ✅ 类型安全的消息处理 * ✅ 房间和广播功能 * ✅ 完整的错误处理 * ✅ 性能优化选项 * ✅ 灵活的配置选项 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强功能 * 探索 [验证系统](/essential/validation) 了解类型安全 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/api-client/treaty/websocket.md' --- # WebSocket 支持 Vafast 类型安全客户端提供了完整的 WebSocket 支持,让您能够进行类型安全的实时通信。本章将详细介绍如何使用 WebSocket 功能。 ## 🌐 WebSocket 概述 WebSocket 是一种在客户端和服务器之间建立持久连接的协议,支持双向实时通信。Vafast 类型安全客户端基于 Bun 的原生 WebSocket 支持,提供了类型安全和易用的 API。 ### 主要特性 * **类型安全** - 完整的 TypeScript 类型支持 * **自动重连** - 智能的连接管理和重连机制 * **事件驱动** - 基于事件的 API 设计 * **消息验证** - 运行时消息类型验证 * **性能优化** - 高效的连接池和消息处理 ## 🚀 快速开始 ### 基本 WebSocket 连接 ```typescript import { createWebSocketClient } from '@vafast/api-client' // 创建 WebSocket 客户端 const wsClient = createWebSocketClient('ws://localhost:3000/ws', { autoReconnect: true, maxReconnectAttempts: 5 }) // 连接到服务器 await wsClient.connect() // 发送消息 wsClient.send({ type: 'chat', message: 'Hello, World!' }) // 监听消息 wsClient.on('message', (data) => { console.log('Received:', data) }) // 关闭连接 wsClient.close() ``` ### 类型安全的 WebSocket ```typescript // 定义消息类型 interface ChatMessage { type: 'chat' message: string userId: string timestamp: number } interface SystemMessage { type: 'system' action: 'user_joined' | 'user_left' | 'notification' data: any } type WebSocketMessage = ChatMessage | SystemMessage // 创建类型安全的 WebSocket 客户端 const wsClient = createWebSocketClient('ws://localhost:3000/ws', { autoReconnect: true }) // 连接后发送类型安全的消息 await wsClient.connect() // 发送聊天消息 wsClient.send({ type: 'chat', message: 'Hello, everyone!', userId: 'user123', timestamp: Date.now() }) // 监听消息(类型安全) wsClient.on('message', (data: WebSocketMessage) => { switch (data.type) { case 'chat': console.log(`${data.userId}: ${data.message}`) break case 'system': console.log(`System: ${data.action}`) break } }) ``` ## 🔌 连接管理 ### 连接配置 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws', { // 自动重连 autoReconnect: true, // 最大重连次数 maxReconnectAttempts: 10, // 重连延迟(毫秒) reconnectDelay: 1000, // 连接超时 connectionTimeout: 5000, // 心跳间隔 heartbeatInterval: 30000, // 心跳超时 heartbeatTimeout: 5000, // 协议 protocols: ['chat', 'v1'], // 请求头 headers: { 'Authorization': 'Bearer token123', 'User-Agent': 'Vafast-WebSocket-Client/1.0.0' } }) ``` ### 连接状态管理 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 监听连接状态变化 wsClient.on('open', () => { console.log('WebSocket connected') }) wsClient.on('close', (event) => { console.log('WebSocket closed:', event.code, event.reason) }) wsClient.on('error', (error) => { console.error('WebSocket error:', error) }) // 检查连接状态 if (wsClient.isConnected()) { console.log('WebSocket is connected') } else { console.log('WebSocket is disconnected') } // 获取连接信息 const connectionInfo = wsClient.getConnectionInfo() console.log('Connection URL:', connectionInfo.url) console.log('Protocol:', connectionInfo.protocol) console.log('Ready State:', connectionInfo.readyState) ``` ### 手动连接控制 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws', { autoReconnect: false // 禁用自动重连 }) // 手动连接 try { await wsClient.connect() console.log('Connected successfully') } catch (error) { console.error('Connection failed:', error) } // 手动重连 if (wsClient.isConnected()) { wsClient.reconnect() } // 关闭连接 wsClient.close(1000, 'Normal closure') // 强制关闭 wsClient.forceClose() ``` ## 📨 消息处理 ### 发送消息 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') await wsClient.connect() // 发送文本消息 wsClient.send('Hello, WebSocket!') // 发送 JSON 消息 wsClient.send({ type: 'user_action', action: 'click', target: 'button', timestamp: Date.now() }) // 发送二进制数据 const buffer = new ArrayBuffer(8) const view = new DataView(buffer) view.setFloat64(0, Math.PI) wsClient.send(buffer) // 发送 Blob const blob = new Blob(['Hello, Blob!'], { type: 'text/plain' }) wsClient.send(blob) ``` ### 接收消息 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 监听所有消息 wsClient.on('message', (data, event) => { console.log('Raw message:', data) console.log('Event:', event) }) // 监听特定类型的消息 wsClient.on('message', (data) => { if (typeof data === 'string') { console.log('Text message:', data) } else if (data instanceof ArrayBuffer) { console.log('Binary message:', data) } else if (data instanceof Blob) { console.log('Blob message:', data) } else { console.log('JSON message:', data) } }) // 类型安全的消息处理 interface GameMessage { type: 'game_update' | 'player_move' | 'game_over' data: any } wsClient.on('message', (data: GameMessage) => { switch (data.type) { case 'game_update': updateGameState(data.data) break case 'player_move': handlePlayerMove(data.data) break case 'game_over': showGameOver(data.data) break } }) ``` ### 消息验证 ```typescript import { Type } from '@sinclair/typebox' // 定义消息验证器 const messageValidator = Type.Union([ Type.Object({ type: Type.Literal('chat'), message: Type.String(), userId: Type.String() }), Type.Object({ type: Type.Literal('status'), online: Type.Boolean(), timestamp: Type.Number() }) ]) const wsClient = createWebSocketClient('ws://localhost:3000/ws', { // 启用消息验证 messageValidation: { enabled: true, validator: messageValidator } }) // 监听验证失败的消息 wsClient.on('validation_error', (error, message) => { console.error('Message validation failed:', error) console.error('Invalid message:', message) }) // 发送消息时会自动验证 wsClient.send({ type: 'chat', message: 'Hello!', userId: 'user123' }) // ✅ 有效 wsClient.send({ type: 'invalid', data: 'invalid' }) // ❌ 验证失败 ``` ## 🔄 事件系统 ### 内置事件 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 连接事件 wsClient.on('open', (event) => { console.log('Connection opened:', event) }) wsClient.on('close', (event) => { console.log('Connection closed:', event.code, event.reason) }) wsClient.on('error', (error) => { console.error('Connection error:', error) }) // 消息事件 wsClient.on('message', (data, event) => { console.log('Message received:', data) }) // 重连事件 wsClient.on('reconnect', (attempt) => { console.log(`Reconnecting... Attempt ${attempt}`) }) wsClient.on('reconnect_success', (attempt) => { console.log(`Reconnected successfully after ${attempt} attempts`) }) wsClient.on('reconnect_failed', (attempt) => { console.log(`Reconnection failed after ${attempt} attempts`) }) // 心跳事件 wsClient.on('heartbeat', () => { console.log('Heartbeat sent') }) wsClient.on('heartbeat_timeout', () => { console.log('Heartbeat timeout') }) ``` ### 自定义事件 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 创建自定义事件发射器 const eventEmitter = wsClient.createEventEmitter() // 监听自定义事件 eventEmitter.on('user_joined', (user) => { console.log('User joined:', user) updateUserList(user) }) eventEmitter.on('user_left', (user) => { console.log('User left:', user) removeUserFromList(user) }) eventEmitter.on('message_sent', (message) => { console.log('Message sent:', message) addMessageToChat(message) }) // 发射自定义事件 wsClient.on('message', (data) => { if (data.type === 'user_joined') { eventEmitter.emit('user_joined', data.user) } else if (data.type === 'user_left') { eventEmitter.emit('user_left', data.user) } else if (data.type === 'message_sent') { eventEmitter.emit('message_sent', data.message) } }) ``` ## 🏗️ 房间和广播 ### 房间管理 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 加入房间 wsClient.joinRoom('general') wsClient.joinRoom('support') wsClient.joinRoom('announcements') // 离开房间 wsClient.leaveRoom('support') // 获取当前房间 const currentRooms = wsClient.getCurrentRooms() console.log('Current rooms:', currentRooms) // 监听房间事件 wsClient.on('room_joined', (roomName) => { console.log(`Joined room: ${roomName}`) }) wsClient.on('room_left', (roomName) => { console.log(`Left room: ${roomName}`) }) wsClient.on('room_message', (roomName, message) => { console.log(`Message in ${roomName}:`, message) }) ``` ### 广播消息 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 向所有连接的客户端广播 wsClient.broadcast({ type: 'announcement', message: 'Server maintenance in 5 minutes', timestamp: Date.now() }) // 向特定房间广播 wsClient.broadcastToRoom('general', { type: 'chat', message: 'Hello, general room!', sender: 'system' }) // 向多个房间广播 wsClient.broadcastToRooms(['general', 'support'], { type: 'notification', message: 'New feature available!', feature: 'real-time-chat' }) // 向特定用户广播 wsClient.broadcastToUser('user123', { type: 'private_message', message: 'You have a new message', from: 'admin' }) ``` ## 🔒 安全特性 ### 认证和授权 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws', { // 认证配置 auth: { // 自动添加认证头 autoAuth: true, // 获取认证信息 getToken: () => localStorage.getItem('ws_token'), // 认证失败处理 onAuthFailure: (error) => { console.error('Authentication failed:', error) localStorage.removeItem('ws_token') window.location.href = '/login' } } }) // 发送认证消息 wsClient.send({ type: 'authenticate', token: localStorage.getItem('ws_token') }) // 监听认证响应 wsClient.on('message', (data) => { if (data.type === 'auth_success') { console.log('Authentication successful') wsClient.setAuthenticated(true) } else if (data.type === 'auth_failed') { console.error('Authentication failed:', data.reason) wsClient.setAuthenticated(false) } }) ``` ### 消息加密 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws', { // 加密配置 encryption: { enabled: true, algorithm: 'AES-256-GCM', key: 'your-secret-key' } }) // 发送加密消息 const encryptedMessage = wsClient.encrypt({ type: 'sensitive_data', data: 'confidential information' }) wsClient.send(encryptedMessage) // 接收加密消息 wsClient.on('message', (data) => { if (data.encrypted) { const decryptedMessage = wsClient.decrypt(data) console.log('Decrypted message:', decryptedMessage) } else { console.log('Plain message:', data) } }) ``` ## 📊 性能监控 ### 连接统计 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws') // 获取连接统计 const stats = wsClient.getStats() console.log('Total messages sent:', stats.messagesSent) console.log('Total messages received:', stats.messagesReceived) console.log('Connection uptime:', stats.uptime) console.log('Reconnection attempts:', stats.reconnectionAttempts) console.log('Average message size:', stats.averageMessageSize) // 监听性能事件 wsClient.on('performance_metric', (metric) => { console.log('Performance metric:', metric) // 发送到监控系统 sendToMonitoring({ type: 'websocket_performance', metric, timestamp: Date.now() }) }) ``` ### 消息队列 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws', { // 消息队列配置 messageQueue: { enabled: true, maxSize: 1000, flushInterval: 100 } }) // 批量发送消息 wsClient.queueMessage({ type: 'log', level: 'info', message: 'User action' }) wsClient.queueMessage({ type: 'log', level: 'info', message: 'Another action' }) wsClient.queueMessage({ type: 'log', level: 'info', message: 'Third action' }) // 手动刷新队列 wsClient.flushMessageQueue() // 获取队列状态 const queueStatus = wsClient.getMessageQueueStatus() console.log('Queue size:', queueStatus.size) console.log('Queue full:', queueStatus.isFull) ``` ## 🧪 测试和调试 ### WebSocket 测试 ```typescript // test/websocket.test.ts import { describe, expect, it, beforeEach, afterEach } from 'bun:test' import { createWebSocketClient } from '@vafast/api-client' describe('WebSocket Client', () => { let wsClient: any beforeEach(() => { wsClient = createWebSocketClient('ws://localhost:3000/ws', { autoReconnect: false }) }) afterEach(() => { wsClient.close() }) it('should connect successfully', async () => { await wsClient.connect() expect(wsClient.isConnected()).toBe(true) }) it('should send and receive messages', async () => { await wsClient.connect() const messagePromise = new Promise((resolve) => { wsClient.on('message', resolve) }) wsClient.send({ type: 'test', data: 'hello' }) const receivedMessage = await messagePromise expect(receivedMessage).toEqual({ type: 'test', data: 'hello' }) }) it('should handle connection errors', async () => { const errorPromise = new Promise((resolve) => { wsClient.on('error', resolve) }) // 尝试连接到无效的 URL wsClient = createWebSocketClient('ws://invalid-url:9999') try { await wsClient.connect() } catch (error) { // 预期会失败 } const error = await errorPromise expect(error).toBeDefined() }) }) ``` ### 调试工具 ```typescript const wsClient = createWebSocketClient('ws://localhost:3000/ws', { // 启用调试模式 debug: { enabled: true, logMessages: true, logEvents: true, logPerformance: true } }) // 在浏览器控制台中查看详细日志 wsClient.on('debug', (info) => { console.group('WebSocket Debug') console.log('Type:', info.type) console.log('Data:', info.data) console.log('Timestamp:', info.timestamp) console.groupEnd() }) // 获取调试信息 const debugInfo = wsClient.getDebugInfo() console.log('Debug info:', debugInfo) ``` ## 📱 实际应用示例 ### 实时聊天应用 ```typescript class ChatClient { private wsClient: any constructor(serverUrl: string, token: string) { this.wsClient = createWebSocketClient(`${serverUrl}/chat`, { autoReconnect: true, headers: { Authorization: `Bearer ${token}` } }) this.setupEventHandlers() } private setupEventHandlers() { this.wsClient.on('open', () => { console.log('Chat connected') this.joinRoom('general') }) this.wsClient.on('message', (data) => { this.handleMessage(data) }) this.wsClient.on('room_message', (roomName, message) => { this.displayMessage(roomName, message) }) } private handleMessage(data: any) { switch (data.type) { case 'user_joined': this.showUserJoined(data.user) break case 'user_left': this.showUserLeft(data.user) break case 'typing_start': this.showTypingIndicator(data.user) break case 'typing_stop': this.hideTypingIndicator(data.user) break } } public sendMessage(roomName: string, message: string) { this.wsClient.send({ type: 'chat_message', room: roomName, message, timestamp: Date.now() }) } public startTyping(roomName: string) { this.wsClient.send({ type: 'typing_start', room: roomName }) } public stopTyping(roomName: string) { this.wsClient.send({ type: 'typing_stop', room: roomName }) } private displayMessage(roomName: string, message: any) { // 在 UI 中显示消息 const messageElement = document.createElement('div') messageElement.textContent = `${message.user}: ${message.message}` document.getElementById(`room-${roomName}`)?.appendChild(messageElement) } private showUserJoined(user: any) { console.log(`${user.name} joined the chat`) } private showUserLeft(user: any) { console.log(`${user.name} left the chat`) } private showTypingIndicator(user: any) { console.log(`${user.name} is typing...`) } private hideTypingIndicator(user: any) { console.log(`${user.name} stopped typing`) } } // 使用聊天客户端 const chatClient = new ChatClient('ws://localhost:3000', 'user-token') // 发送消息 document.getElementById('send-button')?.addEventListener('click', () => { const messageInput = document.getElementById('message-input') as HTMLInputElement const message = messageInput.value if (message.trim()) { chatClient.sendMessage('general', message) messageInput.value = '' } }) // 输入指示器 let typingTimeout: NodeJS.Timeout document.getElementById('message-input')?.addEventListener('input', () => { chatClient.startTyping('general') clearTimeout(typingTimeout) typingTimeout = setTimeout(() => { chatClient.stopTyping('general') }, 1000) }) ``` ## 📝 WebSocket 最佳实践 ### 1. 连接管理 * 实现适当的重连策略 * 监控连接状态 * 处理连接错误 ### 2. 消息处理 * 使用类型安全的消息格式 * 实现消息验证 * 处理消息队列 ### 3. 性能优化 * 使用消息批处理 * 实现心跳机制 * 监控性能指标 ### 4. 安全性 * 实现适当的认证 * 验证消息来源 * 加密敏感数据 ### 5. 用户体验 * 显示连接状态 * 提供重连选项 * 实现优雅降级 ## 🔗 相关链接 * [类型安全客户端概述](/api-client/treaty/overview) - 了解基本概念 * [配置选项](/api-client/treaty/config) - 学习 WebSocket 配置 * [参数处理](/api-client/treaty/parameters) - 了解消息参数 * [响应处理](/api-client/treaty/response) - 处理 WebSocket 响应 * [单元测试](/api-client/treaty/unit-test) - 测试 WebSocket 功能 ## 📚 下一步 现在您已经了解了 Vafast 类型安全客户端的 WebSocket 支持,接下来可以: 1. **编写测试** - 验证 WebSocket 功能的正确性 2. **性能优化** - 优化 WebSocket 连接和消息处理 3. **安全加固** - 增强 WebSocket 安全性 4. **监控告警** - 实现 WebSocket 监控系统 5. **实际应用** - 在项目中使用 WebSocket 功能 如果您有任何问题或需要帮助,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/patterns/macro.md' --- # 中间件和装饰器 Vafast 提供了强大的中间件系统和请求装饰器,允许您为请求处理流程添加自定义逻辑和扩展功能。 虽然 Vafast 没有 VafastJS 的宏系统,但它提供了更灵活和标准的中间件模式,以及强大的请求装饰器功能。 ## 中间件系统 Vafast 的中间件系统基于标准的 Web 标准,每个中间件都是一个函数,接受 `Request` 和 `next` 函数: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 基本中间件 const loggingMiddleware = async (req: Request, next: () => Promise) => { const startTime = Date.now() console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`) const response = await next() const duration = Date.now() - startTime console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} - ${response.status} (${duration}ms)`) return response } // 认证中间件 const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } // 验证 token 并添加到请求中 try { const user = await validateToken(token) ;(req as any).user = user return await next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }, { method: 'GET', path: '/protected', handler: createHandler(({ req }) => { const user = (req as any).user return `Hello ${user.name}!` }), middleware: [authMiddleware] // 路由特定中间件 } ]) const server = new Server(routes) // 全局中间件 server.use(loggingMiddleware) export default { fetch: server.fetch } ``` ## 中间件工厂 创建可配置的中间件: ```typescript // 创建可配置的中间件 const createAuthMiddleware = (options: { required?: boolean roles?: string[] permissions?: string[] }) => { return async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token && options.required) { return new Response('Unauthorized', { status: 401 }) } if (!token && !options.required) { return await next() } try { const user = await validateToken(token) // 检查角色权限 if (options.roles && !options.roles.includes(user.role)) { return new Response('Insufficient permissions', { status: 403 }) } // 检查具体权限 if (options.permissions && !options.permissions.some(p => user.permissions.includes(p))) { return new Response('Insufficient permissions', { status: 403 }) } ;(req as any).user = user return await next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } } // 使用中间件工厂 const adminAuth = createAuthMiddleware({ required: true, roles: ['admin'], permissions: ['read:users', 'write:users'] }) const userAuth = createAuthMiddleware({ required: true, roles: ['user', 'admin'] }) const routes = defineRoutes([ { method: 'GET', path: '/admin/users', handler: createHandler(() => 'Admin users'), middleware: [adminAuth] }, { method: 'GET', path: '/profile', handler: createHandler(() => 'User profile'), middleware: [userAuth] } ]) ``` ## 请求装饰器 Vafast 允许您通过中间件扩展请求对象,添加自定义属性和方法: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 用户装饰器中间件 const userDecorator = async (req: Request, next: () => Promise) => { // 扩展请求对象 ;(req as any).getUser = () => { return (req as any).user } ;(req as any).isAuthenticated = () => { return !!(req as any).user } ;(req as any).hasRole = (role: string) => { const user = (req as any).user return user && user.role === role } ;(req as any).hasPermission = (permission: string) => { const user = (req as any).user return user && user.permissions.includes(permission) } return await next() } // 缓存装饰器中间件 const cacheDecorator = (cache: Map) => { return async (req: Request, next: () => Promise) => { ;(req as any).cache = { get: (key: string) => cache.get(key), set: (key: string, value: any) => cache.set(key, value), delete: (key: string) => cache.delete(key), clear: () => cache.clear() } return await next() } } // 数据库装饰器中间件 const databaseDecorator = (db: any) => { return async (req: Request, next: () => Promise) => { ;(req as any).db = db ;(req as any).query = async (sql: string, params: any[] = []) => { return await db.query(sql, params) } ;(req as any).transaction = async (callback: (req: Request) => Promise) => { return await db.transaction(async () => { return await callback(req) }) } return await next() } } const routes = defineRoutes([ { method: 'GET', path: '/user/:id', handler: createHandler(async ({ req, params }) => { // 使用装饰器添加的方法 if (!(req as any).isAuthenticated()) { return new Response('Unauthorized', { status: 401 }) } if (!(req as any).hasPermission('read:users')) { return new Response('Insufficient permissions', { status: 403 }) } // 使用数据库装饰器 const user = await (req as any).query('SELECT * FROM users WHERE id = ?', [params.id]) // 使用缓存装饰器 const cacheKey = `user:${params.id}` let cachedUser = (req as any).cache.get(cacheKey) if (!cachedUser) { cachedUser = user[0] (req as any).cache.set(cacheKey, cachedUser) } return cachedUser }) } ]) const server = new Server(routes) // 应用装饰器中间件 server.use(userDecorator) server.use(cacheDecorator(new Map())) server.use(databaseDecorator(/* 数据库连接 */)) export default { fetch: server.fetch } ``` ## 中间件组合 组合多个中间件创建复杂的处理流程: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 中间件组合器 const composeMiddleware = (...middlewares: any[]) => { return async (req: Request, next: () => Promise) => { let index = 0 const executeNext = async (): Promise => { if (index >= middlewares.length) { return await next() } const middleware = middlewares[index++] return await middleware(req, executeNext) } return await executeNext() } } // 条件中间件 const conditionalMiddleware = ( condition: (req: Request) => boolean, middleware: any ) => { return async (req: Request, next: () => Promise) => { if (condition(req)) { return await middleware(req, next) } return await next() } } // 重试中间件 const retryMiddleware = (maxRetries: number = 3) => { return async (req: Request, next: () => Promise) => { let lastError: Error for (let i = 0; i <= maxRetries; i++) { try { return await next() } catch (error) { lastError = error as Error if (i === maxRetries) { throw lastError } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000)) } } throw lastError! } } // 限流中间件 const rateLimitMiddleware = (options: { windowMs: number max: number keyGenerator?: (req: Request) => string }) => { const requests = new Map() return async (req: Request, next: () => Promise) => { const key = options.keyGenerator ? options.keyGenerator(req) : req.headers.get('x-forwarded-for') || 'unknown' const now = Date.now() const requestData = requests.get(key) if (!requestData || now > requestData.resetTime) { requests.set(key, { count: 1, resetTime: now + options.windowMs }) } else { requestData.count++ if (requestData.count > options.max) { return new Response('Too Many Requests', { status: 429 }) } } return await next() } } const routes = defineRoutes([ { method: 'GET', path: '/api/data', handler: createHandler(async ({ req }) => { // 使用数据库查询 const data = await (req as any).query('SELECT * FROM data') return data }), middleware: [ // 组合多个中间件 composeMiddleware( rateLimitMiddleware({ windowMs: 60000, max: 100 }), retryMiddleware(3), conditionalMiddleware( (req) => req.headers.get('x-debug') === 'true', async (req: Request, next: () => Promise) => { console.log('Debug mode enabled') return await next() } ) ) ] } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## 类型安全的装饰器 使用 TypeScript 确保装饰器的类型安全: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 扩展 Request 类型 interface ExtendedRequest extends Request { user?: { id: string name: string role: string permissions: string[] } cache?: { get: (key: string) => any set: (key: string, value: any) => void delete: (key: string) => void clear: () => void } db?: { query: (sql: string, params?: any[]) => Promise transaction: (callback: () => Promise) => Promise } isAuthenticated: () => boolean hasRole: (role: string) => boolean hasPermission: (permission: string) => boolean } // 类型安全的中间件 const typedUserDecorator = async (req: ExtendedRequest, next: () => Promise) => { req.isAuthenticated = () => !!req.user req.hasRole = (role: string) => req.user?.role === role req.hasPermission = (permission: string) => req.user?.permissions.includes(permission) || false return await next() } const routes = defineRoutes([ { method: 'GET', path: '/admin', handler: createHandler(({ req }: { req: ExtendedRequest }) => { if (!req.isAuthenticated()) { return new Response('Unauthorized', { status: 401 }) } if (!req.hasRole('admin')) { return new Response('Insufficient permissions', { status: 403 }) } return 'Admin panel' }) } ]) const server = new Server(routes) server.use(typedUserDecorator) export default { fetch: server.fetch } ``` ## 中间件测试 测试中间件和装饰器: ```typescript import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Middleware and Decorators', () => { it('should apply global middleware', async () => { const log: string[] = [] const loggingMiddleware = async (req: Request, next: () => Promise) => { log.push('before') const response = await next() log.push('after') return response } const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello') } ]) const server = new Server(routes) server.use(loggingMiddleware) const response = await server.fetch(new Request('http://localhost/')) expect(response.status).toBe(200) expect(await response.text()).toBe('Hello') expect(log).toEqual(['before', 'after']) }) it('should apply route-specific middleware', async () => { const log: string[] = [] const routeMiddleware = async (req: Request, next: () => Promise) => { log.push('route middleware') return await next() } const routes = defineRoutes([ { method: 'GET', path: '/protected', handler: createHandler(() => 'Protected'), middleware: [routeMiddleware] } ]) const server = new Server(routes) const response = await server.fetch(new Request('http://localhost/protected')) expect(response.status).toBe(200) expect(await response.text()).toBe('Protected') expect(log).toEqual(['route middleware']) }) it('should decorate request object', async () => { const userDecorator = async (req: Request, next: () => Promise) => { ;(req as any).user = { id: '1', name: 'Test User' } ;(req as any).isAuthenticated = () => true return await next() } const routes = defineRoutes([ { method: 'GET', path: '/profile', handler: createHandler(({ req }) => { const user = (req as any).user const isAuth = (req as any).isAuthenticated() return { user, isAuthenticated: isAuth } }) } ]) const server = new Server(routes) server.use(userDecorator) const response = await server.fetch(new Request('http://localhost/profile')) const data = await response.json() expect(data.user).toEqual({ id: '1', name: 'Test User' }) expect(data.isAuthenticated).toBe(true) }) }) ``` ## 总结 Vafast 的中间件和装饰器系统提供了: * ✅ 标准的中间件模式 * ✅ 可配置的中间件工厂 * ✅ 请求对象装饰器 * ✅ 中间件组合和条件执行 * ✅ 类型安全的装饰器 * ✅ 完整的测试支持 * ✅ 灵活的中间件链 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强功能 * 探索 [验证系统](/essential/validation) 了解类型安全 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/middleware/overview.md' --- # 概述 Vafast 旨在实现模块化和轻量化。 遵循与 Arch Linux 相同的理念(顺便说一句,我使用 Arch): > 设计决策通过开发者共识逐案作出 这确保了开发者最终得到他们所希望创建的高性能 Web 服务器。由此,Vafast 包含了预构建的常见模式中间件,以方便开发者使用: ## 官方中间件: * [API Client](/middleware/api-client) - 现代化、类型安全的 API 客户端 * [Bearer](/middleware/bearer) - 自动获取 [Bearer](https://swagger.io/docs/specification/authentication/bearer-authentication/) 令牌 * [Compress](/middleware/compress) - 支持 Brotli、GZIP 和 Deflate 压缩算法 * [Helmet](/middleware/helmet) - 通过添加各种 HTTP 安全头部来增强 Web 应用的安全性 * [IP](/middleware/ip) - 支持从各种代理头部中提取真实的客户端 IP 地址 * [Rate Limit](/middleware/rate-limit) - 提供灵活的速率限制功能,保护 API 免受滥用 * [CORS](/middleware/cors) - 设置 [跨域资源共享 (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) * [Cron](/middleware/cron) - 设置 [cron](https://en.wikipedia.org/wiki/Cron) 任务 * [GraphQL Apollo](/middleware/graphql-apollo) - 在 Vafast 上运行 [Apollo GraphQL](https://www.apollographql.com/) * [GraphQL Yoga](/middleware/graphql-yoga) - 在 Vafast 上运行 [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga) * [HTML](/middleware/html) - 处理 HTML 响应 * [JWT](/middleware/jwt) - 使用 [JWT](https://jwt.io/) 进行身份验证 * [OpenTelemetry](/middleware/opentelemetry) - 添加对 OpenTelemetry 的支持 * [Rate Limit](/middleware/rate-limit) - 简单轻量的速率限制器 * [Server Timing](/middleware/server-timing) - 使用 [Server-Timing API](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) 审计性能瓶颈 * [Static](/middleware/static) - 提供静态文件/文件夹服务 * [Stream](/middleware/stream) - 集成响应流和 [服务器发送事件 (SSEs)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) * [Swagger](/middleware/swagger) - 生成 [Swagger](https://swagger.io/) 文档 * [WebSocket](/patterns/websocket) - 支持 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) ## 社区中间件: * [Vafast Helmet](https://github.com/vafastjs/vafast-helmet) - 通过各种 HTTP 头增强 Vafast 应用安全 * [Vafast Compression](https://github.com/vafastjs/vafast-compression) - 压缩响应 * [Vafast IP](https://github.com/vafastjs/vafast-ip) - 获取客户端 IP 地址 * [Vafast Rate Limit](https://github.com/vafastjs/vafast-rate-limit) - 简单轻量的速率限制器 * [Vafast Server Timing](https://github.com/vafastjs/vafast-server-timing) - 使用 Server-Timing API 审计性能瓶颈 * [Vafast Static](https://github.com/vafastjs/vafast-static) - 提供静态文件/文件夹服务 * [Vafast Swagger](https://github.com/vafastjs/vafast-swagger) - 生成 Swagger/OpenAPI 文档 * [Vafast Bearer](https://github.com/vafastjs/vafast-bearer) - 自动获取 Bearer 令牌 * [Vafast CORS](https://github.com/vafastjs/vafast-cors) - 设置跨域资源共享 (CORS) * [Vafast Cron](https://github.com/vafastjs/vafast-cron) - 设置 cron 任务 * [Vafast JWT](https://github.com/vafastjs/vafast-jwt) - 使用 JWT 进行身份验证 * [Vafast OpenTelemetry](https://github.com/vafastjs/vafast-opentelemetry) - 添加对 OpenTelemetry 的支持 ## 开发中的中间件: * [Vafast GraphQL](https://github.com/vafastjs/vafast-graphql) - GraphQL 支持 * [Vafast WebSocket](https://github.com/vafastjs/vafast-websocket) - WebSocket 支持 * [Vafast Stream](https://github.com/vafastjs/vafast-stream) - 响应流和服务器发送事件支持 ## 相关项目: * [Vafast Ecosystem](https://github.com/vafastjs) - Vafast 官方中间件生态系统 *** 如果您为 Vafast 编写了一个中间件,请随时通过 **点击下面的 在 GitHub 上编辑此页面** 将您的中间件添加到列表中 👇 --- --- url: 'https://vafast.dev/middleware.md' --- # 中间件系统 Vafast 的中间件系统是框架的核心功能之一,它允许您在请求处理过程中执行自定义逻辑。中间件可以用于身份验证、日志记录、错误处理、数据转换等。 ## 什么是中间件? 中间件是一个函数,它在请求到达路由处理函数之前或之后执行。中间件可以: * 修改请求对象 * 验证请求数据 * 记录请求信息 * 处理错误 * 添加响应头 * 执行任何自定义逻辑 ## 中间件定义 ### 基本中间件 ```typescript const logMiddleware = async (req: Request, next: () => Promise) => { const start = Date.now() const response = await next() const duration = Date.now() - start console.log(`${req.method} ${req.url} - ${response.status} - ${duration}ms`) return response } ``` ### 中间件类型 ```typescript type Middleware = (req: Request, next: () => Promise) => Promise ``` ## 中间件执行顺序 中间件按照数组中的顺序执行,形成一个执行链: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/admin', middleware: [authMiddleware, logMiddleware, rateLimitMiddleware], handler: createHandler(() => 'Admin panel') } ]) ``` 执行顺序: 1. `authMiddleware` - 身份验证 2. `logMiddleware` - 日志记录 3. `rateLimitMiddleware` - 速率限制 4. 路由处理函数 - 实际业务逻辑 ## 常用中间件示例 ### 1. 日志中间件 ```typescript const logMiddleware = async (req: Request, next: () => Promise) => { const start = Date.now() const method = req.method const url = req.url const userAgent = req.headers.get('user-agent') console.log(`[${new Date().toISOString()}] ${method} ${url} - ${userAgent}`) const response = await next() const duration = Date.now() - start console.log(`[${new Date().toISOString()}] ${method} ${url} - ${response.status} - ${duration}ms`) return response } ``` ### 2. 身份验证中间件 ```typescript const authMiddleware = async (req: Request, next: () => Promise) => { const authHeader = req.headers.get('authorization') if (!authHeader) { return new Response('Unauthorized', { status: 401, headers: { 'WWW-Authenticate': 'Bearer' } }) } const token = authHeader.replace('Bearer ', '') try { // 验证 token const user = await validateToken(token) // 将用户信息添加到请求中 ;(req as any).user = user return await next() } catch (error) { return new Response('Invalid token', { status: 401 }) } } // 使用示例 const routes = defineRoutes([ { method: 'GET', path: '/profile', middleware: [authMiddleware], handler: createHandler(({ req }) => { const user = (req as any).user return `Hello ${user.name}` }) } ]) ``` ### 3. CORS 中间件 ```typescript const corsMiddleware = async (req: Request, next: () => Promise) => { const response = await next() // 添加 CORS 头 response.headers.set('Access-Control-Allow-Origin', '*') response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization') return response } ``` ### 4. 速率限制中间件 ```typescript const rateLimitMap = new Map() const rateLimitMiddleware = async (req: Request, next: () => Promise) => { const ip = req.headers.get('x-forwarded-for') || 'unknown' const now = Date.now() const windowMs = 15 * 60 * 1000 // 15 分钟 const maxRequests = 100 const key = `${ip}:${Math.floor(now / windowMs)}` const current = rateLimitMap.get(key) if (current && current.resetTime > now) { if (current.count >= maxRequests) { return new Response('Too many requests', { status: 429, headers: { 'Retry-After': '900' } }) } current.count++ } else { rateLimitMap.set(key, { count: 1, resetTime: now + windowMs }) } return next() } ``` ### 5. 错误处理中间件 ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { console.error('Error in route:', error) if (error instanceof Error) { return new Response(error.message, { status: 500 }) } return new Response('Internal Server Error', { status: 500 }) } } ``` ### 6. 数据验证中间件 ```typescript const validateBody = (schema: any) => { return async (req: Request, next: () => Promise) => { try { const body = await req.json() // 这里可以使用任何验证库,如 Zod、Joi 等 const validationResult = validateSchema(schema, body) if (!validationResult.valid) { return new Response(JSON.stringify({ error: 'Validation failed', details: validationResult.errors }), { status: 400, headers: { 'Content-Type': 'application/json' } }) } // 将验证后的数据添加到请求中 ;(req as any).validatedBody = validationResult.data return next() } catch (error) { return new Response('Invalid JSON', { status: 400 }) } } } // 使用示例 const userSchema = { name: { type: 'string', required: true, minLength: 2 }, email: { type: 'string', required: true, format: 'email' }, age: { type: 'number', min: 18 } } const routes = defineRoutes([ { method: 'POST', path: '/users', middleware: [validateBody(userSchema)], handler: createHandler(({ req }) => { const userData = (req as any).validatedBody // 处理验证后的数据... return new Response('User created', { status: 201 }) }) } ]) ``` ## 中间件组合 ### 创建中间件组合器 ```typescript const combineMiddleware = (...middlewares: any[]) => { return async (req: Request, next: () => Promise) => { let index = 0 const executeNext = async (): Promise => { if (index >= middlewares.length) { return next() } const middleware = middlewares[index++] return middleware(req, executeNext) } return executeNext() } } // 使用示例 const combinedMiddleware = combineMiddleware( logMiddleware, corsMiddleware, rateLimitMiddleware ) const routes = defineRoutes([ { method: 'GET', path: '/api/users', middleware: [combinedMiddleware], handler: createHandler(() => new Response('Users')) } ]) ``` ### 条件中间件 ```typescript const conditionalMiddleware = (condition: (req: Request) => boolean, middleware: any) => { return async (req: Request, next: () => Promise) => { if (condition(req)) { return middleware(req, next) } return next() } } // 使用示例 const adminOnly = conditionalMiddleware( (req) => req.url.includes('/admin'), authMiddleware ) const routes = defineRoutes([ { method: 'GET', path: '/admin/users', middleware: [adminOnly], handler: createHandler(() => new Response('Admin users')) } ]) ``` ## 全局中间件 您可以为整个应用或特定路径前缀应用中间件: ```typescript const routes = defineRoutes([ { path: '/api', middleware: [logMiddleware, corsMiddleware], // 应用到所有 /api 路由 children: [ { method: 'GET', path: '/users', handler: createHandler(() => new Response('Users')) }, { method: 'GET', path: '/posts', handler: createHandler(() => new Response('Posts')) } ] } ]) ``` ## 中间件最佳实践 ### 1. 保持中间件简单 ```typescript // 好的做法:每个中间件只做一件事 const logRequest = async (req: Request, next: () => Promise) => { console.log(`${req.method} ${req.url}`) return next() } const logResponse = async (req: Request, next: () => Promise) => { const response = await next() console.log(`Response: ${response.status}`) return response } // 不好的做法:一个中间件做太多事 const logEverything = async (req: Request, next: () => Promise) => { // 记录请求 console.log(`${req.method} ${req.url}`) // 验证 token const token = req.headers.get('authorization') if (!token) return new Response('Unauthorized', { status: 401 }) // 记录响应 const response = await next() console.log(`Response: ${response.status}`) return response } ``` ### 2. 错误处理 ```typescript const safeMiddleware = (middleware: any) => { return async (req: Request, next: () => Promise) => { try { return await middleware(req, next) } catch (error) { console.error('Middleware error:', error) return new Response('Middleware error', { status: 500 }) } } } // 使用安全中间件 const routes = defineRoutes([ { method: 'GET', path: '/api/users', middleware: [safeMiddleware(authMiddleware)], handler: createHandler(() => new Response('Users')) } ]) ``` ### 3. 中间件顺序 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/api/users', middleware: [ logMiddleware, // 1. 日志记录 corsMiddleware, // 2. CORS 处理 rateLimitMiddleware, // 3. 速率限制 authMiddleware, // 4. 身份验证 errorHandler // 5. 错误处理 ], handler: createHandler(() => new Response('Users')) } ]) ``` ### 4. 中间件测试 ```typescript // 测试中间件 const testMiddleware = async (middleware: any, req: Request) => { let executed = false const next = async () => { executed = true return new Response('Test response') } const result = await middleware(req, next) return { executed, result, status: result.status } } // 测试示例 const testReq = new Request('http://localhost:3000/test') const testResult = await testMiddleware(logMiddleware, testReq) console.log('Test result:', testResult) ``` ## 总结 Vafast 的中间件系统提供了: * ✅ 灵活的中间件定义 * ✅ 可预测的执行顺序 * ✅ 强大的错误处理 * ✅ 中间件组合和复用 * ✅ 全局和局部应用 * ✅ 类型安全的实现 ### 下一步 * 查看 [路由指南](/routing) 了解路由系统 * 学习 [组件路由](/component-routing) 了解声明式路由 * 探索 [最佳实践](/best-practices) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/migrate/from-express.md' --- # 从 Express 到 Vafast 本指南适用于希望了解 Express 与 Vafast 之间差异的 Express 用户,包括语法,以及如何通过示例将应用程序从 Express 迁移到 Vafast。 **Express** 是一个流行的 Node.js 网络框架,广泛用于构建 Web 应用程序和 API。因其简单性和灵活性而闻名。 **Vafast** 是一个专为 Bun 运行时设计的高性能 Web 框架,专注于类型安全、中间件系统和性能优化。设计时强调简单性和开发者友好,提供完整的 TypeScript 支持。 ## 性能 由于专为 Bun 运行时优化和智能路由匹配算法,Vafast 在性能上相比 Express 有显著提高。 ## 路由 Express 和 Vafast 有类似的路由语法,但 Vafast 使用配置对象的方式定义路由,提供更好的类型安全和中间件支持。 ::: code-group ```ts [Express] import express from 'express' const app = express() app.get('/', (req, res) => { res.send('Hello World') }) app.post('/id/:id', (req, res) => { res.status(201).send(req.params.id) }) app.listen(3000) ``` ::: > Express 使用 `req` 和 `res` 作为请求和响应对象 ::: code-group ```ts [Vafast] import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello World') }, { method: 'POST', path: '/id/:id', handler: createHandler(({ params }) => { return { id: params.id } }) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ::: > Vafast 使用配置对象定义路由,支持类型安全和中间件 ## 主要差异 ### 1. 路由定义方式 **Express** 使用链式方法调用: ```typescript app.get('/users', (req, res) => { ... }) app.post('/users', (req, res) => { ... }) ``` **Vafast** 使用配置对象数组: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { ... }) }, { method: 'POST', path: '/users', handler: createHandler(() => { ... }) } ]) ``` ### 2. 请求处理 **Express** 使用 `req` 和 `res` 对象: ```typescript app.get('/user/:id', (req, res) => { const id = req.params.id const query = req.query res.json({ id, query }) }) ``` **Vafast** 使用解构参数: ```typescript { method: 'GET', path: '/user/:id', handler: createHandler(({ params, query }) => { return { id: params.id, query } }) } ``` ### 3. 中间件系统 **Express** 使用 `app.use()` 和路由级中间件: ```typescript app.use(loggingMiddleware) app.get('/admin', authMiddleware, (req, res) => { res.send('Admin Panel') }) ``` **Vafast** 支持全局和路由级中间件: ```typescript const server = new Server(routes) server.use(loggingMiddleware) const routes = defineRoutes([ { method: 'GET', path: '/admin', handler: createHandler(() => 'Admin Panel'), middleware: [authMiddleware] } ]) ``` ### 4. 错误处理 **Express** 使用错误处理中间件: ```typescript app.use((err, req, res, next) => { console.error(err.stack) res.status(500).send('Something broke!') }) ``` **Vafast** 支持中间件链中的错误处理: ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } } ``` ## 迁移步骤 ### 步骤 1: 安装 Vafast ```bash bun add vafast ``` ### 步骤 2: 重构路由定义 将 Express 的路由定义转换为 Vafast 的配置对象格式: ```typescript // Express 风格 app.get('/api/users', (req, res) => { const users = getUsers() res.json(users) }) // Vafast 风格 { method: 'GET', path: '/api/users', handler: createHandler(() => { return getUsers() }) } ``` ### 步骤 3: 更新中间件 将 Express 中间件转换为 Vafast 中间件格式: ```typescript // Express 中间件 const authMiddleware = (req, res, next) => { const token = req.headers.authorization if (!token) { return res.status(401).send('Unauthorized') } next() } // Vafast 中间件 const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } return await next() } ``` ### 步骤 4: 更新错误处理 ```typescript // Express 错误处理 app.use((err, req, res, next) => { res.status(500).json({ error: err.message }) }) // Vafast 错误处理 const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } } ``` ## 完整迁移示例 ### Express 应用 ```typescript import express from 'express' import cors from 'cors' import helmet from 'helmet' const app = express() app.use(cors()) app.use(helmet()) app.use(express.json()) app.get('/users', (req, res) => { const users = getUsers() res.json(users) }) app.post('/users', (req, res) => { const user = createUser(req.body) res.status(201).json(user) }) app.get('/users/:id', (req, res) => { const user = getUserById(req.params.id) if (!user) { return res.status(404).json({ error: 'User not found' }) } res.json(user) }) app.listen(3000) ``` ### Vafast 应用 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { return getUsers() }) }, { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }) }, { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => { const user = getUserById(params.id) if (!user) { return new Response( JSON.stringify({ error: 'User not found' }), { status: 404 } ) } return user }) } ]) const server = new Server(routes) server.use(cors()) server.use(helmet()) export default { fetch: server.fetch } ``` ## 优势对比 | 特性 | Express | Vafast | |------|---------|---------| | 类型安全 | ❌ 需要额外配置 | ✅ 完整的 TypeScript 支持 | | 性能 | ⚠️ 中等 | 🚀 高性能 | | 中间件系统 | ✅ 成熟 | ✅ 灵活可扩展 | | 路由定义 | ⚠️ 链式调用 | ✅ 配置对象 | | 错误处理 | ✅ 中间件方式 | ✅ 中间件链 | | Bun 支持 | ❌ 需要适配 | ✅ 原生支持 | ## 下一步 现在您已经了解了如何从 Express 迁移到 Vafast,建议您: 1. 查看 [快速入门](/quick-start) 开始使用 Vafast 2. 阅读 [核心概念](/key-concept) 深入了解 Vafast 的工作原理 3. 探索 [中间件系统](/middleware) 了解如何扩展功能 4. 查看 [示例代码](/examples) 获取更多实践示例 如果您在迁移过程中遇到任何问题,欢迎在我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 社区寻求帮助。 --- --- url: 'https://vafast.dev/migrate/from-fastify.md' --- # 从 Fastify 到 Vafast 本指南适用于希望了解 Fastify 与 Vafast 之间差异的 Fastify 用户,包括语法,以及如何通过示例将应用程序从 Fastify 迁移到 Vafast。 **Fastify** 是一个专注于提供最大效率和速度的 Node.js Web 框架,具有低内存占用和优秀的性能。 **Vafast** 是一个专为 Bun 运行时设计的高性能 Web 框架,专注于类型安全、中间件系统和性能优化。设计时强调简单性和开发者友好,提供完整的 TypeScript 支持。 ## 性能 由于专为 Bun 运行时优化和智能路由匹配算法,Vafast 在性能上相比 Fastify 有显著提高。 ## 路由 Fastify 和 Vafast 都使用配置对象的方式定义路由,但 Vafast 提供了更简洁的 API 和更好的类型安全。 ::: code-group ```ts [Fastify] import Fastify from 'fastify' const fastify = Fastify() fastify.get('/', async (request, reply) => { return { hello: 'world' } }) fastify.post('/user/:id', async (request, reply) => { const { id } = request.params const { name } = request.body return { id, name } }) await fastify.listen({ port: 3000 }) ``` ::: > Fastify 使用 `request` 和 `reply` 作为请求和响应对象 ::: code-group ```ts [Vafast] import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => { return { hello: 'world' } }) }, { method: 'POST', path: '/user/:id', handler: createHandler(({ params, body }) => { return { id: params.id, name: body.name } }) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ::: > Vafast 使用配置对象定义路由,支持类型安全和中间件 ## 主要差异 ### 1. 路由定义方式 **Fastify** 使用链式方法调用: ```typescript fastify.get('/users', async (request, reply) => { ... }) fastify.post('/users', async (request, reply) => { ... }) ``` **Vafast** 使用配置对象数组: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { ... }) }, { method: 'POST', path: '/users', handler: createHandler(() => { ... }) } ]) ``` ### 2. 请求处理 **Fastify** 使用 `request` 和 `reply` 对象: ```typescript fastify.get('/user/:id', async (request, reply) => { const id = request.params.id const query = request.query return { id, query } }) ``` **Vafast** 使用解构参数: ```typescript { method: 'GET', path: '/user/:id', handler: createHandler(({ params, query }) => { return { id: params.id, query } }) } ``` ### 3. Schema 验证 **Fastify** 使用内置的 JSON Schema 验证: ```typescript const userSchema = { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' } }, required: ['name'] } fastify.post('/users', { schema: { body: userSchema } }, async (request, reply) => { return createUser(request.body) }) ``` **Vafast** 使用 TypeBox 进行验证: ```typescript import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String(), age: Type.Optional(Type.Number()) }) { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema } ``` ### 4. 中间件系统 **Fastify** 使用钩子(hooks)和插件系统: ```typescript fastify.addHook('preHandler', async (request, reply) => { console.log(`${request.method} ${request.url}`) }) fastify.register(async function (fastify) { fastify.get('/admin', async (request, reply) => { return 'Admin Panel' }) }) ``` **Vafast** 支持全局和路由级中间件: ```typescript const loggingMiddleware = async (req: Request, next: () => Promise) => { console.log(`${req.method} ${req.url}`) return await next() } const server = new Server(routes) server.use(loggingMiddleware) const routes = defineRoutes([ { method: 'GET', path: '/admin', handler: createHandler(() => 'Admin Panel'), middleware: [authMiddleware] } ]) ``` ### 5. 错误处理 **Fastify** 使用错误处理器: ```typescript fastify.setErrorHandler((error, request, reply) => { reply.status(500).send({ error: error.message }) }) ``` **Vafast** 支持中间件链中的错误处理: ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } } ``` ## 迁移步骤 ### 步骤 1: 安装 Vafast ```bash bun add vafast ``` ### 步骤 2: 重构路由定义 将 Fastify 的路由定义转换为 Vafast 的配置对象格式: ```typescript // Fastify 风格 fastify.get('/api/users', async (request, reply) => { const users = getUsers() return users }) // Vafast 风格 { method: 'GET', path: '/api/users', handler: createHandler(() => { return getUsers() }) } ``` ### 步骤 3: 更新 Schema 验证 将 Fastify 的 JSON Schema 转换为 TypeBox: ```typescript // Fastify Schema const userSchema = { type: 'object', properties: { name: { type: 'string', minLength: 1 }, email: { type: 'string', format: 'email' } }, required: ['name', 'email'] } // Vafast Schema import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) ``` ### 步骤 4: 更新中间件和钩子 ```typescript // Fastify 钩子 fastify.addHook('preHandler', async (request, reply) => { const token = request.headers.authorization if (!token) { reply.status(401).send('Unauthorized') } }) // Vafast 中间件 const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } return await next() } ``` ### 步骤 5: 更新错误处理 ```typescript // Fastify 错误处理 fastify.setErrorHandler((error, request, reply) => { reply.status(500).send({ error: error.message }) }) // Vafast 错误处理 const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } } ``` ## 完整迁移示例 ### Fastify 应用 ```typescript import Fastify from 'fastify' import cors from '@fastify/cors' import helmet from '@fastify/helmet' const fastify = Fastify() await fastify.register(cors) await fastify.register(helmet) fastify.get('/users', async (request, reply) => { const users = getUsers() return users }) fastify.post('/users', { schema: { body: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } }, required: ['name', 'email'] } } }, async (request, reply) => { const user = createUser(request.body) reply.status(201) return user }) fastify.get('/users/:id', async (request, reply) => { const user = getUserById(request.params.id) if (!user) { reply.status(404) return { error: 'User not found' } } return user }) await fastify.listen({ port: 3000 }) ``` ### Vafast 应用 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { helmet } from '@vafast/helmet' import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String(), email: Type.String() }) const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { return getUsers() }) }, { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema }, { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => { const user = getUserById(params.id) if (!user) { return new Response( JSON.stringify({ error: 'User not found' }), { status: 404 } ) } return user }) } ]) const server = new Server(routes) server.use(cors()) server.use(helmet()) export default { fetch: server.fetch } ``` ## 优势对比 | 特性 | Fastify | Vafast | |------|---------|---------| | 类型安全 | ⚠️ 需要额外配置 | ✅ 完整的 TypeScript 支持 | | 性能 | ✅ 高性能 | 🚀 超高性能 | | Schema 验证 | ✅ JSON Schema | ✅ TypeBox | | 中间件系统 | ✅ 钩子系统 | ✅ 灵活可扩展 | | 路由定义 | ⚠️ 链式调用 | ✅ 配置对象 | | 错误处理 | ✅ 错误处理器 | ✅ 中间件链 | | Bun 支持 | ❌ 需要适配 | ✅ 原生支持 | ## 下一步 现在您已经了解了如何从 Fastify 迁移到 Vafast,建议您: 1. 查看 [快速入门](/quick-start) 开始使用 Vafast 2. 阅读 [核心概念](/key-concept) 深入了解 Vafast 的工作原理 3. 探索 [中间件系统](/middleware) 了解如何扩展功能 4. 查看 [示例代码](/examples) 获取更多实践示例 如果您在迁移过程中遇到任何问题,欢迎在我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 社区寻求帮助。 --- --- url: 'https://vafast.dev/migrate/from-hono.md' --- # 从 Hono 到 Vafast 本指南适用于希望了解 Hono 与 Vafast 之间差异的 Hono 用户,包括语法,以及如何通过示例将应用程序从 Hono 迁移到 Vafast。 **Hono** 是一个轻量级、超快的 Web 框架,专为边缘运行时设计,支持多种平台。 **Vafast** 是一个专为 Bun 运行时设计的高性能 Web 框架,专注于类型安全、中间件系统和性能优化。设计时强调简单性和开发者友好,提供完整的 TypeScript 支持。 ## 性能 由于专为 Bun 运行时优化和智能路由匹配算法,Vafast 在性能上相比 Hono 有显著提高。 ## 路由 Hono 和 Vafast 都使用配置对象的方式定义路由,但 Vafast 提供了更结构化的 API 和更好的类型安全。 ::: code-group ```ts [Hono] import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => { return c.text('Hello World') }) app.post('/user/:id', async (c) => { const id = c.req.param('id') const body = await c.req.json() return c.json({ id, name: body.name }) }) export default app ``` ::: > Hono 使用 `c` (context) 作为请求和响应对象 ::: code-group ```ts [Vafast] import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello World') }, { method: 'POST', path: '/user/:id', handler: createHandler(async ({ params, req }) => { const body = await req.json() return { id: params.id, name: body.name } }) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ::: > Vafast 使用配置对象定义路由,支持类型安全和中间件 ## 主要差异 ### 1. 路由定义方式 **Hono** 使用链式方法调用: ```typescript app.get('/users', (c) => { ... }) app.post('/users', (c) => { ... }) ``` **Vafast** 使用配置对象数组: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { ... }) }, { method: 'POST', path: '/users', handler: createHandler(() => { ... }) } ]) ``` ### 2. 请求处理 **Hono** 使用 `c` (context) 对象: ```typescript app.get('/user/:id', (c) => { const id = c.req.param('id') const query = c.req.query() return c.json({ id, query }) }) ``` **Vafast** 使用解构参数: ```typescript { method: 'GET', path: '/user/:id', handler: createHandler(({ params, query }) => { return { id: params.id, query } }) } ``` ### 3. 中间件系统 **Hono** 使用 `app.use()` 和路由级中间件: ```typescript app.use('*', async (c, next) => { console.log(`${c.req.method} ${c.req.url}`) await next() }) app.get('/admin', authMiddleware, (c) => { return c.text('Admin Panel') }) ``` **Vafast** 支持全局和路由级中间件: ```typescript const loggingMiddleware = async (req: Request, next: () => Promise) => { console.log(`${req.method} ${req.url}`) return await next() } const server = new Server(routes) server.use(loggingMiddleware) const routes = defineRoutes([ { method: 'GET', path: '/admin', handler: createHandler(() => 'Admin Panel'), middleware: [authMiddleware] } ]) ``` ### 4. 验证系统 **Hono** 使用 Zod 进行验证: ```typescript import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const userSchema = z.object({ name: z.string().min(1), email: z.string().email() }) app.post('/users', zValidator('json', userSchema), (c) => { const body = c.req.valid('json') return c.json(createUser(body)) }) ``` **Vafast** 使用 TypeBox 进行验证: ```typescript import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema } ``` ### 5. 错误处理 **Hono** 使用错误处理器: ```typescript app.onError((err, c) => { console.error(`${err}`) return c.text('Custom Error Message', 500) }) ``` **Vafast** 支持中间件链中的错误处理: ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } } ``` ## 迁移步骤 ### 步骤 1: 安装 Vafast ```bash bun add vafast ``` ### 步骤 2: 重构路由定义 将 Hono 的路由定义转换为 Vafast 的配置对象格式: ```typescript // Hono 风格 app.get('/api/users', (c) => { const users = getUsers() return c.json(users) }) // Vafast 风格 { method: 'GET', path: '/api/users', handler: createHandler(() => { return getUsers() }) } ``` ### 步骤 3: 更新中间件 将 Hono 中间件转换为 Vafast 中间件格式: ```typescript // Hono 中间件 app.use('*', async (c, next) => { const token = c.req.header('authorization') if (!token) { return c.text('Unauthorized', 401) } await next() }) // Vafast 中间件 const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } return await next() } ``` ### 步骤 4: 更新验证系统 ```typescript // Hono 验证 import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const userSchema = z.object({ name: z.string().min(1), email: z.string().email() }) app.post('/users', zValidator('json', userSchema), (c) => { const body = c.req.valid('json') return c.json(createUser(body)) }) // Vafast 验证 import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema } ``` ### 步骤 5: 更新错误处理 ```typescript // Hono 错误处理 app.onError((err, c) => { return c.text('Something went wrong', 500) }) // Vafast 错误处理 const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } } ``` ## 完整迁移示例 ### Hono 应用 ```typescript import { Hono } from 'hono' import { cors } from 'hono/cors' import { logger } from 'hono/logger' import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const app = new Hono() app.use('*', cors()) app.use('*', logger()) const userSchema = z.object({ name: z.string().min(1), email: z.string().email() }) app.get('/users', (c) => { const users = getUsers() return c.json(users) }) app.post('/users', zValidator('json', userSchema), (c) => { const body = c.req.valid('json') const user = createUser(body) c.status(201) return c.json(user) }) app.get('/users/:id', (c) => { const id = c.req.param('id') const user = getUserById(id) if (!user) { c.status(404) return c.json({ error: 'User not found' }) } return c.json(user) }) export default app ``` ### Vafast 应用 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(() => { return getUsers() }) }, { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema }, { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => { const user = getUserById(params.id) if (!user) { return new Response( JSON.stringify({ error: 'User not found' }), { status: 404 } ) } return user }) } ]) const server = new Server(routes) server.use(cors()) export default { fetch: server.fetch } ``` ## 优势对比 | 特性 | Hono | Vafast | |------|------|---------| | 类型安全 | ⚠️ 需要额外配置 | ✅ 完整的 TypeScript 支持 | | 性能 | ✅ 高性能 | 🚀 超高性能 | | 验证系统 | ✅ Zod 支持 | ✅ TypeBox 支持 | | 中间件系统 | ✅ 灵活 | ✅ 灵活可扩展 | | 路由定义 | ⚠️ 链式调用 | ✅ 配置对象 | | 错误处理 | ✅ 错误处理器 | ✅ 中间件链 | | Bun 支持 | ⚠️ 需要适配 | ✅ 原生支持 | ## 下一步 现在您已经了解了如何从 Hono 迁移到 Vafast,建议您: 1. 查看 [快速入门](/quick-start) 开始使用 Vafast 2. 阅读 [核心概念](/key-concept) 深入了解 Vafast 的工作原理 3. 探索 [中间件系统](/middleware) 了解如何扩展功能 4. 查看 [示例代码](/examples) 获取更多实践示例 如果您在迁移过程中遇到任何问题,欢迎在我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 社区寻求帮助。 --- --- url: 'https://vafast.dev/key-concept.md' --- # 关键概念 Vafast 是一个专为 Bun 运行时设计的高性能 Web 框架。了解这些核心概念将帮助你更好地使用 Vafast 构建应用。 ## 🏗️ 架构概览 Vafast 采用模块化架构设计,主要包含以下核心组件: * **Server**: 主要的服务器类,负责处理请求和响应 * **Router**: 路由匹配和分发系统 * **Middleware**: 中间件系统,用于扩展功能 * **Types**: 完整的类型定义系统 * **Utils**: 工具函数和辅助类 ## 🚀 Server 类 `Server` 类是 Vafast 的核心,继承自 `BaseServer`,提供了完整的 HTTP 服务器功能。 ### 主要特性 * **路由管理**: 自动扁平化嵌套路由 * **中间件支持**: 全局和路由级中间件 * **错误处理**: 内置的错误处理机制 * **性能优化**: 智能路由排序和冲突检测 ### 基本用法 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello World') } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## 🛣️ 路由系统 Vafast 的路由系统基于配置对象,支持静态路径、动态参数和嵌套路由。 ### 路由配置 ```typescript interface Route { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD' path: string handler: Handler middleware?: Middleware[] body?: any query?: any params?: any headers?: any cookies?: any } ``` ### 路径匹配 Vafast 使用智能路径匹配算法,支持: * **静态路径**: `/users` * **动态参数**: `/users/:id` * **嵌套路由**: 支持父子路由结构 ### 路由优先级 路由按特异性自动排序: 1. 静态路径(最高优先级) 2. 动态参数(`:param`) 3. 通配符(`*`) ## 🔧 中间件系统 中间件是 Vafast 中扩展功能的核心机制,支持全局和路由级中间件。 ### 中间件类型 ```typescript type Middleware = ( req: Request, next: () => Promise ) => Promise ``` ### 中间件链 中间件按以下顺序执行: 1. 全局中间件 2. 路由级中间件 3. 路由处理器 ### 中间件示例 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' // 日志中间件 const loggingMiddleware = async (req: Request, next: () => Promise) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`) const response = await next() console.log(`Response: ${response.status}`) return response } // 认证中间件 const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } return await next() } const routes = defineRoutes([ { method: 'GET', path: '/protected', handler: createHandler(() => 'Protected content'), middleware: [authMiddleware] } ]) const server = new Server(routes) server.use(loggingMiddleware) // 全局中间件 ``` ## 📝 类型系统 Vafast 提供完整的 TypeScript 支持,包括类型安全的处理器和验证器。 ### 处理器类型 ```typescript type Handler = (context: HandlerContext) => Response | Promise interface HandlerContext { req: Request params?: Record body?: any query?: any headers?: any cookies?: any } ``` ### Schema 验证 Vafast 集成了 TypeBox 进行运行时类型验证: ```typescript import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ pattern: '^[^@]+@[^@]+\\.[^@]+$' }), age: Type.Optional(Type.Number({ minimum: 0 })) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { // body 已经通过验证,类型安全 return { success: true, user: body } }), body: userSchema } ]) ``` ## 🎯 路由处理器工厂 `createHandler` 函数用于创建类型安全的路由处理器,自动处理参数解构和类型推断。 ### 基本用法 ```typescript // 简单处理器 const simpleHandler = createHandler(() => 'Hello') // 带参数的处理器 const paramHandler = createHandler(({ params }) => `ID: ${params.id}`) // 带请求体的处理器 const bodyHandler = createHandler(async ({ req, body }) => { const data = await req.json() return { received: data, validated: body } }) ``` ### 高级用法 ```typescript // 带多个验证的处理器 const fullHandler = createHandler( ({ params, body, query, headers }) => { return { params, body, query, headers } }, { body: userSchema, query: querySchema, params: paramsSchema } ) ``` ## 🔄 请求处理流程 Vafast 的请求处理流程如下: 1. **请求接收**: 接收 HTTP 请求 2. **路由匹配**: 根据路径和方法匹配路由 3. **中间件执行**: 按顺序执行全局和路由中间件 4. **参数解析**: 解析路径参数、查询参数等 5. **Schema 验证**: 验证请求数据(如果配置了) 6. **处理器执行**: 执行路由处理器 7. **响应返回**: 返回 HTTP 响应 ## 🚀 性能优化 Vafast 内置多项性能优化技术,无需额外配置即可获得高性能: ### JIT 编译验证器 Schema 验证器在首次使用时编译并缓存,后续验证直接使用编译后的代码: ```typescript import { createValidator, validateFast, precompileSchemas } from 'vafast' import { Type } from '@sinclair/typebox' const UserSchema = Type.Object({ name: Type.String(), age: Type.Number() }) // 方式一:自动缓存(推荐) const isValid = validateFast(UserSchema, data) // 方式二:预编译验证器(最高性能) const validateUser = createValidator(UserSchema) const result = validateUser(data) // 启动时预编译(避免首次请求开销) precompileSchemas([UserSchema, PostSchema]) ``` **性能效果:10000 次验证仅需 ~5ms** ### 中间件链预编译 路由注册时自动预编译完整的中间件链,运行时零开销: ```typescript const server = new Server(routes) // 添加全局中间件后手动触发预编译 server.use(authMiddleware) server.use(logMiddleware) server.compile() // 预编译所有路由 // 每次请求直接执行编译好的处理链 ``` **性能效果:1000 次请求仅需 ~4ms,平均每次 0.004ms** ### 快速请求解析 提供优化的解析函数,比标准方法快约 2x: ```typescript import { parseQueryFast, getCookie, getHeader } from 'vafast' // 快速解析查询参数(简单场景) const query = parseQueryFast(req) // 获取单个 Cookie(避免解析全部) const sessionId = getCookie(req, 'sessionId') // 获取单个请求头 const token = getHeader(req, 'Authorization') ``` ### Radix Tree 路由 基于 Radix Tree 的高效路由匹配,时间复杂度 O(k)(k 为路径段数): * **路由预排序**: 构造时按特异性排序 * **智能路径匹配**: 静态路径 > 动态参数 > 通配符 * **冲突检测**: 自动检测并警告路由冲突 ## 📚 下一步 现在你已经了解了 Vafast 的核心概念,建议你: 1. 查看 [路由指南](/routing) 学习如何定义复杂路由 2. 阅读 [中间件系统](/middleware) 了解如何扩展功能 3. 探索 [API 参考](/api) 查看完整的 API 文档 4. 查看 [示例代码](/examples) 获取更多实践示例 如果你有任何问题,欢迎在我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 社区询问。 --- --- url: 'https://vafast.dev/best-practices.md' --- # 最佳实践 本文档提供了使用 Vafast 框架开发应用的最佳实践和设计模式。遵循这些建议将帮助您构建更高质量、更易维护的应用程序。 ## 项目结构 ### 推荐的目录结构 ``` src/ ├── routes/ # 路由定义 │ ├── index.ts # 主路由文件 │ ├── user.ts # 用户相关路由 │ ├── admin.ts # 管理相关路由 │ └── api.ts # API 路由 ├── middleware/ # 中间件 │ ├── auth.ts # 身份验证中间件 │ ├── cors.ts # CORS 中间件 │ ├── validation.ts # 验证中间件 │ └── index.ts # 中间件导出 ├── services/ # 业务逻辑服务 │ ├── userService.ts │ ├── authService.ts │ └── emailService.ts ├── models/ # 数据模型 │ ├── User.ts │ ├── Post.ts │ └── Comment.ts ├── utils/ # 工具函数 │ ├── validation.ts │ ├── encryption.ts │ └── helpers.ts ├── config/ # 配置文件 │ ├── database.ts │ ├── redis.ts │ └── app.ts └── index.ts # 应用入口 ``` ### 路由组织 ```typescript // routes/index.ts import { userRoutes } from './user' import { adminRoutes } from './admin' import { apiRoutes } from './api' export const routes: any[] = [ { path: '/', component: () => import('../components/pages/Home.vue') }, ...userRoutes, ...adminRoutes, ...apiRoutes ] ``` ```typescript // routes/user.ts export const userRoutes: any[] = [ { path: '/user', middleware: [authMiddleware], children: [ { method: 'GET', path: '/profile', handler: userController.getProfile }, { method: 'PUT', path: '/profile', handler: userController.updateProfile } ] } ] ``` ## 代码组织 ### 控制器模式 将路由处理逻辑分离到控制器中: ```typescript // controllers/userController.ts export class UserController { async getProfile(req: Request, params?: Record) { try { const userId = (req as any).user.id const user = await userService.findById(userId) return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } }) } catch (error) { return new Response('Failed to get profile', { status: 500 }) } } async updateProfile(req: Request, params?: Record) { try { const userId = (req as any).user.id const body = await req.json() // 验证数据 const validatedData = await validateUserUpdate(body) const updatedUser = await userService.update(userId, validatedData) return new Response(JSON.stringify(updatedUser), { status: 200, headers: { 'Content-Type': 'application/json' } }) } catch (error) { if (error instanceof ValidationError) { return new Response(JSON.stringify({ error: error.message }), { status: 400, headers: { 'Content-Type': 'application/json' } }) } return new Response('Failed to update profile', { status: 500 }) } } } export const userController = new UserController() ``` ### 服务层模式 将业务逻辑封装在服务中: ```typescript // services/userService.ts export class UserService { async findById(id: string) { // 数据库查询逻辑 const user = await db.user.findUnique({ where: { id } }) if (!user) { throw new Error('User not found') } return user } async update(id: string, data: Partial) { // 数据更新逻辑 const updatedUser = await db.user.update({ where: { id }, data }) return updatedUser } async delete(id: string) { // 数据删除逻辑 await db.user.delete({ where: { id } }) } } export const userService = new UserService() ``` ### 中间件组织 将中间件按功能分组: ```typescript // middleware/index.ts export { authMiddleware } from './auth' export { corsMiddleware } from './cors' export { validationMiddleware } from './validation' export { rateLimitMiddleware } from './rateLimit' export { logMiddleware } from './log' // 组合中间件 export const commonMiddleware = [ logMiddleware, corsMiddleware, rateLimitMiddleware ] export const protectedMiddleware = [ ...commonMiddleware, authMiddleware ] ``` ## 错误处理 ### 统一错误处理 ```typescript // utils/errors.ts export class AppError extends Error { constructor( message: string, public statusCode: number = 500, public code?: string ) { super(message) this.name = 'AppError' } } export class ValidationError extends AppError { constructor(message: string) { super(message, 400, 'VALIDATION_ERROR') } } export class AuthenticationError extends AppError { constructor(message: string = 'Authentication required') { super(message, 401, 'AUTHENTICATION_ERROR') } } export class AuthorizationError extends AppError { constructor(message: string = 'Insufficient permissions') { super(message, 403, 'AUTHORIZATION_ERROR') } } export class NotFoundError extends AppError { constructor(resource: string) { super(`${resource} not found`, 404, 'NOT_FOUND') } } ``` ### 错误处理中间件 ```typescript // middleware/errorHandler.ts import { AppError } from '../utils/errors' export const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { console.error('Error:', error) if (error instanceof AppError) { return new Response(JSON.stringify({ error: error.message, code: error.code, statusCode: error.statusCode }), { status: error.statusCode, headers: { 'Content-Type': 'application/json' } }) } // 未知错误 return new Response(JSON.stringify({ error: 'Internal server error', code: 'INTERNAL_ERROR', statusCode: 500 }), { status: 500, headers: { 'Content-Type': 'application/json' } }) } } ``` ## 数据验证 ### 使用 Zod 进行验证 ```typescript // utils/validation.ts import { z } from 'zod' export const userCreateSchema = z.object({ name: z.string().min(2).max(100), email: z.string().email(), age: z.number().min(18).max(120).optional(), password: z.string().min(8) }) export const userUpdateSchema = userCreateSchema.partial().omit({ password: true }) export type UserCreate = z.infer export type UserUpdate = z.infer ``` ### 验证中间件 ```typescript // middleware/validation.ts import { z } from 'zod' export const validateBody = (schema: z.ZodSchema) => { return async (req: Request, next: () => Promise) => { try { const body = await req.json() const validatedData = schema.parse(body) // 将验证后的数据添加到请求中 ;(req as any).validatedBody = validatedData return next() } catch (error) { if (error instanceof z.ZodError) { return new Response(JSON.stringify({ error: 'Validation failed', details: error.errors }), { status: 400, headers: { 'Content-Type': 'application/json' } }) } return new Response('Invalid JSON', { status: 400 }) } } } export const validateQuery = (schema: z.ZodSchema) => { return async (req: Request, next: () => Promise) => { try { const url = new URL(req.url) const queryParams = Object.fromEntries(url.searchParams.entries()) const validatedData = schema.parse(queryParams) ;(req as any).validatedQuery = validatedData return next() } catch (error) { if (error instanceof z.ZodError) { return new Response(JSON.stringify({ error: 'Query validation failed', details: error.errors }), { status: 400, headers: { 'Content-Type': 'application/json' } }) } return next() } } } ``` ## 安全性最佳实践 ### 身份验证 ```typescript // middleware/auth.ts import jwt from 'jsonwebtoken' export const authMiddleware = async (req: Request, next: () => Promise) => { try { const authHeader = req.headers.get('authorization') if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new AuthenticationError('Invalid authorization header') } const token = authHeader.substring(7) const decoded = jwt.verify(token, process.env.JWT_SECRET!) ;(req as any).user = decoded return next() } catch (error) { if (error instanceof jwt.JsonWebTokenError) { throw new AuthenticationError('Invalid token') } if (error instanceof jwt.TokenExpiredError) { throw new AuthenticationError('Token expired') } throw error } } ``` ### 速率限制 ```typescript // middleware/rateLimit.ts const rateLimitMap = new Map() export const rateLimitMiddleware = (maxRequests: number = 100, windowMs: number = 15 * 60 * 1000) => { return async (req: Request, next: () => Promise) => { const ip = req.headers.get('x-forwarded-for') || 'unknown' const now = Date.now() const key = `${ip}:${Math.floor(now / windowMs)}` const current = rateLimitMap.get(key) if (current && current.resetTime > now) { if (current.count >= maxRequests) { return new Response(JSON.stringify({ error: 'Too many requests', retryAfter: Math.ceil((current.resetTime - now) / 1000) }), { status: 429, headers: { 'Content-Type': 'application/json', 'Retry-After': Math.ceil((current.resetTime - now) / 1000).toString() } }) } current.count++ } else { rateLimitMap.set(key, { count: 1, resetTime: now + windowMs }) } return next() } } ``` ### 输入清理 ```typescript // utils/sanitization.ts import DOMPurify from 'isomorphic-dompurify' export const sanitizeHtml = (html: string): string => { return DOMPurify.sanitize(html) } export const sanitizeObject = >(obj: T): T => { const sanitized: any = {} for (const [key, value] of Object.entries(obj)) { if (typeof value === 'string') { sanitized[key] = sanitizeHtml(value) } else if (typeof value === 'object' && value !== null) { sanitized[key] = sanitizeObject(value) } else { sanitized[key] = value } } return sanitized } ``` ## 性能优化 ### 路由缓存 ```typescript // utils/cache.ts const routeCache = new Map() export const cacheMiddleware = (ttl: number = 300) => { return async (req: Request, next: () => Promise) => { const cacheKey = `${req.method}:${req.url}` const cached = routeCache.get(cacheKey) if (cached && Date.now() - cached.timestamp < cached.ttl * 1000) { return new Response(cached.data, { headers: { 'Content-Type': 'application/json', 'X-Cache': 'HIT', 'Cache-Control': `public, max-age=${ttl}` } }) } const response = await next() if (response.status === 200) { const data = await response.clone().text() routeCache.set(cacheKey, { data, timestamp: Date.now(), ttl }) } return response } } ``` ### 数据库查询优化 ```typescript // services/userService.ts export class UserService { async findByIdWithPosts(id: string) { // 使用 include 避免 N+1 查询 const user = await db.user.findUnique({ where: { id }, include: { posts: { select: { id: true, title: true, createdAt: true }, orderBy: { createdAt: 'desc' }, take: 10 } } }) return user } async findManyWithPagination(page: number = 1, limit: number = 20) { const skip = (page - 1) * limit const [users, total] = await Promise.all([ db.user.findMany({ skip, take: limit, orderBy: { createdAt: 'desc' } }), db.user.count() ]) return { users, pagination: { page, limit, total, pages: Math.ceil(total / limit) } } } } ``` ## 测试策略 ### 单元测试 ```typescript // tests/services/userService.test.ts import { test, expect, describe, beforeEach } from 'bun:test' import { UserService } from '../../src/services/userService' describe('UserService', () => { let userService: UserService beforeEach(() => { userService = new UserService() }) test('should create user with valid data', async () => { const userData = { name: 'John Doe', email: 'john@example.com', password: 'password123' } const user = await userService.create(userData) expect(user.name).toBe(userData.name) expect(user.email).toBe(userData.email) expect(user.password).not.toBe(userData.password) // 应该被哈希 }) test('should throw error for duplicate email', async () => { const userData = { name: 'John Doe', email: 'existing@example.com', password: 'password123' } await expect(userService.create(userData)).rejects.toThrow('Email already exists') }) }) ``` ### 集成测试 ```typescript // tests/integration/user.test.ts import { test, expect, describe, beforeAll, afterAll } from 'bun:test' import { Server } from 'vafast' import { userRoutes } from '../../src/routes/user' describe('User API Integration', () => { let server: Server beforeAll(() => { server = new Server(userRoutes) }) test('GET /user/profile should return user profile', async () => { const req = new Request('http://localhost:3000/user/profile', { headers: { 'Authorization': 'Bearer valid-token' } }) const response = await server.fetch(req) const data = await response.json() expect(response.status).toBe(200) expect(data).toHaveProperty('id') expect(data).toHaveProperty('name') expect(data).toHaveProperty('email') }) test('PUT /user/profile should update user profile', async () => { const updateData = { name: 'Updated Name', email: 'updated@example.com' } const req = new Request('http://localhost:3000/user/profile', { method: 'PUT', headers: { 'Authorization': 'Bearer valid-token', 'Content-Type': 'application/json' }, body: JSON.stringify(updateData) }) const response = await server.fetch(req) const data = await response.json() expect(response.status).toBe(200) expect(data.name).toBe(updateData.name) expect(data.email).toBe(updateData.email) }) }) ``` ## 环境配置 ### 配置管理 ```typescript // config/app.ts import { z } from 'zod' const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), PORT: z.string().transform(Number).default('3000'), DATABASE_URL: z.string(), JWT_SECRET: z.string(), REDIS_URL: z.string().optional(), CORS_ORIGIN: z.string().default('*'), LOG_LEVEL: z.enum(['error', 'warn', 'info', 'debug']).default('info') }) export const config = envSchema.parse(process.env) export const isDevelopment = config.NODE_ENV === 'development' export const isProduction = config.NODE_ENV === 'production' export const isTest = config.NODE_ENV === 'test' ``` ### 环境特定配置 ```typescript // config/database.ts import { config } from './app' export const databaseConfig = { url: config.DATABASE_URL, ssl: isProduction ? { rejectUnauthorized: false } : false, logging: isDevelopment ? ['query', 'error', 'warn'] : ['error'], pool: { min: isProduction ? 5 : 1, max: isProduction ? 20 : 5 } } ``` ## 日志记录 ### 结构化日志 ```typescript // utils/logger.ts export class Logger { private formatMessage(level: string, message: string, meta?: any) { return JSON.stringify({ timestamp: new Date().toISOString(), level, message, ...meta }) } info(message: string, meta?: any) { console.log(this.formatMessage('info', message, meta)) } warn(message: string, meta?: any) { console.warn(this.formatMessage('warn', message, meta)) } error(message: string, meta?: any) { console.error(this.formatMessage('error', message, meta)) } debug(message: string, meta?: any) { if (isDevelopment) { console.log(this.formatMessage('debug', message, meta)) } } } export const logger = new Logger() ``` ### 请求日志中间件 ```typescript // middleware/log.ts import { logger } from '../utils/logger' export const logMiddleware = async (req: Request, next: () => Promise) => { const start = Date.now() const method = req.method const url = req.url const userAgent = req.headers.get('user-agent') const ip = req.headers.get('x-forwarded-for') || 'unknown' logger.info('Request started', { method, url, userAgent, ip, timestamp: new Date().toISOString() }) try { const response = await next() const duration = Date.now() - start logger.info('Request completed', { method, url, status: response.status, duration, timestamp: new Date().toISOString() }) return response } catch (error) { const duration = Date.now() - start logger.error('Request failed', { method, url, error: error.message, duration, timestamp: new Date().toISOString() }) throw error } } ``` ## 总结 遵循这些最佳实践将帮助您: * ✅ 构建可维护的代码结构 * ✅ 实现安全的应用程序 * ✅ 优化性能和用户体验 * ✅ 建立可靠的测试策略 * ✅ 管理环境配置 * ✅ 实现有效的日志记录 ### 下一步 * 查看 [路由指南](/routing) 了解路由系统 * 学习 [中间件系统](/middleware) 了解中间件用法 * 探索 [组件路由](/component-routing) 了解组件路由功能 * 查看 [API 参考](/api) 获取完整的 API 文档 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/essential/best-practice.md' --- # 最佳实践 Vafast 是一个与模式无关的框架,选择何种编码模式由您和您的团队决定。 然而,在尝试将 MVC 模式 [(Model-View-Controller)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) 适配到 Vafast 时,我们发现很难解耦和处理类型。 本页面是关于如何结合 MVC 模式遵循 Vafast 结构最佳实践的指南,但也可以适用于任何您喜欢的编码模式。 ## 文件夹结构 Vafast 对文件夹结构没有固定看法,留给您 **自行决定** 如何组织代码。 然而,**如果您没有具体结构的想法**,我们推荐基于功能的文件夹结构。每个功能模块拥有自己的文件夹,里面包含控制器、服务和模型。 ``` | src | modules | auth | index.ts (Vafast 控制器) | service.ts (服务) | model.ts (模型) | user | index.ts (Vafast 控制器) | service.ts (服务) | model.ts (模型) | utils | a | index.ts | b | index.ts ``` 这种结构使您更容易找到和管理代码,并将相关代码集中在一起。 下面是一个如何将代码分布到基于功能文件夹结构的示例: ::: code-group ```typescript [auth/index.ts] // 控制器处理 HTTP 相关,如路由、请求验证 import { Vafast } from 'vafast' import { Auth } from './service' import { AuthModel } from './model' export const auth = new Vafast({ prefix: '/auth' }) .get( '/sign-in', async ({ body, cookie: { session } }) => { const response = await Auth.signIn(body) // 设置 session cookie session.value = response.token return response }, { body: AuthModel.signInBody, response: { 200: AuthModel.signInResponse, 400: AuthModel.signInInvalid } } ) ``` ```typescript [auth/service.ts] // 服务处理业务逻辑,解耦于 Vafast 控制器 import { status } from 'vafast' import type { AuthModel } from './model' // If the class doesn't need to store a property, // you may use `abstract class` to avoid class allocation export abstract class Auth { static async signIn({ username, password }: AuthModel.signInBody) { const user = await sql` SELECT password FROM users WHERE username = ${username} LIMIT 1` if (!await Bun.password.verify(password, user.password)) // 你可以直接抛出 HTTP 错误 throw status( 400, 'Invalid username or password' satisfies AuthModel.signInInvalid ) return { username, token: await generateAndSaveTokenToDB(user.id) } } } ``` ```typescript [auth/model.ts] // 模型定义请求和响应的数据结构和验证 import { t } from 'vafast' export namespace AuthModel { // 定义用于 Vafast 验证的数据传输对象 export const signInBody = t.Object({ username: t.String(), password: t.String(), }) // 以 TypeScript 类型定义 export type signInBody = typeof signInBodyBody.static // 其它模型同理 export const signInResponse = t.Object({ username: t.String(), token: t.String(), }) export type signInResponse = typeof signInResponse.static export const signInInvalid = t.Literal('Invalid username or password') export type signInInvalid = typeof signInInvalid.static } ``` ::: 每个文件的职责如下: * **控制器(Controller)**:处理 HTTP 路由、请求验证和 Cookie。 * **服务(Service)**:处理业务逻辑,尽可能解耦于 Vafast 控制器。 * **模型(Model)**:定义请求和响应的数据结构及验证。 您可以随意调整此结构以满足自己的需求,使用任何您喜欢的编码模式。 ## 方法链 Vafast 代码应始终使用 **方法链**。 由于 Vafast 的类型系统较复杂,Vafast 的每个方法都会返回一个新的类型引用。 **这非常重要**,以确保类型的完整性和推断。 ```typescript import { Vafast } from 'vafast' new Vafast() .state('build', 1) // 存储是强类型的 // [!code ++] .get('/', ({ store: { build } }) => build) .listen(3000) ``` 在上述代码中,**state** 返回了一个新的 **VafastInstance** 类型,并添加了 `build` 类型。 ### ❌ 不要:不要不使用方法链来使用 Vafast 如果不使用方法链,Vafast 无法保存新增类型,导致类型推断丢失。 ```typescript import { Vafast } from 'vafast' const app = new Vafast() app.state('build', 1) app.get('/', ({ store: { build } }) => build) app.listen(3000) ``` 我们建议 **始终使用方法链** 以确保准确的类型推断。 ## 控制器 > 1 个 Vafast 实例 = 1 个控制器 Vafast 在多个方面确保类型完整性,如果您直接把整个 `Context` 类型传递给控制器,可能会遇到以下问题: 1. Vafast 类型复杂,严重依赖插件和多级链。 2. 类型难以准确化,且可能因装饰器和状态变化而随时改变。 3. 类型转换容易导致类型完整性丢失,无法确保类型与运行时代码匹配。 4. 这会使得 [Sucrose](/blog/vafast-10#sucrose) *(Vafast 的“编译器”)* 更难对代码做静态分析。 ### ❌ 不要:创建一个单独的控制器类 不要创建单独的控制器类,而是直接使用 Vafast 实例作为控制器: ```typescript import { Vafast, t, type Context } from 'vafast' abstract class Controller { static root(context: Context) { return Service.doStuff(context.stuff) } } // ❌ 不要这样用 new Vafast() .get('/', Controller.root) ``` 将整个 `Controller.method` 传给 Vafast 等同于传递了两层控制器,这违背框架设计原则和 MVC 模式本质。 ### ✅ 做法:将 Vafast 本身作为控制器使用 代替上面做法,直接将 Vafast 实例本身视为控制器。 ```typescript import { Vafast } from 'vafast' import { Service } from './service' new Vafast() .get('/', ({ stuff }) => { Service.doStuff(stuff) }) ``` ### 测试 您可以使用 `handle` 方法直接调用控制器函数以进行测试(包括其生命周期): ```typescript import { Vafast } from 'vafast' import { Service } from './service' import { describe, it, expect } from 'bun:test' const app = new Vafast() .get('/', ({ stuff }) => { Service.doStuff(stuff) return 'ok' }) describe('控制器', () => { it('应该工作', async () => { const response = await app .handle(new Request('http://localhost/')) .then((x) => x.text()) expect(response).toBe('ok') }) }) ``` 您可以在 [单元测试](/patterns/unit-test.html) 中了解更多相关信息。 ## 服务 服务是独立的工具/辅助函数集合,作为业务逻辑被解耦出来,供模块或控制器使用,在此处即 Vafast 实例。 任何可以从控制器中解耦的技术逻辑都可以放在 **服务** 中。 Vafast 中有两种类型的服务: 1. 不依赖请求的服务 2. 依赖请求的服务 ### ✅ 做:抽象不依赖请求的服务 建议将服务类或函数与 Vafast 解耦。 如果服务或函数不依赖 HTTP 请求或 `Context`,推荐将其抽象为静态类或函数。 ```typescript import { Vafast, t } from 'vafast' abstract class Service { static fibo(number: number): number { if(number < 2) return number return Service.fibo(number - 1) + Service.fibo(number - 2) } } new Vafast() .get('/fibo', ({ body }) => { return Service.fibo(body) }, { body: t.Numeric() }) ``` 如果服务不需要存储属性,可以使用 `abstract class` 和 `static`,避免创建类实例。 ### ✅ 做:请求依赖的服务作为 Vafast 实例 **如果服务依赖请求**或需要处理 HTTP 请求,建议将其抽象为 Vafast 实例,以确保类型完整性和推断: ```typescript import { Vafast } from 'vafast' // ✅ 推荐做法 const AuthService = new Vafast({ name: 'Auth.Service' }) .macro({ isSignIn: { resolve({ cookie, status }) { if (!cookie.session.value) return status(401) return { session: cookie.session.value, } } } }) const UserController = new Vafast() .use(AuthService) .get('/profile', ({ Auth: { session } }) => session, { isSignIn: true }) ``` ::: tip Vafast 默认自动处理[插件去重](/essential/plugin.html#plugin-deduplication),所以您无需担心性能问题,只要指定了 **"name"** 属性,它就会是单例。 ::: ### ✅ 做:只装饰请求依赖属性 建议 `decorate`(装饰) 仅针对请求依赖的属性,如 `requestIP`、`requestTime` 或 `session`。 过度使用装饰器可能导致代码与 Vafast 紧耦合,增加测试和重用难度。 ```typescript import { Vafast } from 'vafast' new Vafast() .decorate('requestIP', ({ request }) => request.headers.get('x-forwarded-for') || request.ip) .decorate('requestTime', () => Date.now()) .decorate('session', ({ cookie }) => cookie.session.value) .get('/', ({ requestIP, requestTime, session }) => { return { requestIP, requestTime, session } }) ``` ### ❌ 不要:将整个 `Context` 传递给服务 **Context 是一个高度动态的类型**,可以从 Vafast 实例推断得到。 不要直接将整个 `Context` 传递给服务,而应对象解构只提取所需字段再传入服务: ```typescript import type { Context } from 'vafast' class AuthService { constructor() {} // ❌ 不建议这样写 isSignIn({ status, cookie: { session } }: Context) { if (session.value) return status(401) } } ``` 由于 Vafast 类型复杂,且强依赖插件和多层链式调用,手动准确类型化很有挑战。 ### ⚠️ 从 Vafast 实例推断 Context 在 **非常必要** 的情况下,可以从 Vafast 实例推断 `Context` 类型: ```typescript import { Vafast, type InferContext } from 'vafast' const setup = new Vafast() .state('a', 'a') .decorate('b', 'b') class AuthService { constructor() {} // ✅ 推荐写法 isSignIn({ status, cookie: { session } }: InferContext) { if (session.value) return status(401) } } ``` 不过建议尽量避免这样,并优先使用 [Vafast 作为服务实例](#✅-做-请求依赖的服务作为-vafast-实例)。 更多关于 [InferContext](/essential/handler#infercontext) 的信息,详见 [基础:处理程序](/essential/handler)。 ## 模型 模型或 [DTO(数据传输对象)](https://en.wikipedia.org/wiki/Data_transfer_object) 使用 [Vafast.t(验证系统)](/essential/validation.html#vafast-type) 处理。 Vafast 内置验证系统能从代码推断类型并进行运行时校验。 ### ❌ 不要:将类实例作为模型声明 不要将类实例用于模型声明: ```typescript // ❌ 不建议 class CustomBody { username: string password: string constructor(username: string, password: string) { this.username = username this.password = password } } // ❌ 不建议 interface ICustomBody { username: string password: string } ``` ### ✅ 做:使用 Vafast 验证系统定义模型 使用 Vafast 验证系统而非类或接口声明模型: ```typescript // ✅ 推荐做法 import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) // 可选:获取模型对应类型 // 通常无须专门使用该类型,因为 Vafast 已推断 type CustomBody = typeof customBody.static export { customBody } ``` 我们可以用 `typeof` 和 `.static` 来获取类型。 这样可以通过 `CustomBody` 类型正确推断请求体。 ```typescript import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) // ✅ 推荐写法 new Vafast() .post('/login', ({ body }) => { return body }, { body: customBody }) ``` ### ❌ 不要:把类型和模型分开声明 不要把模型和类型分开声明,应通过模型的 `typeof` 和 `.static` 获取类型。 ```typescript // ❌ 不推荐 import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) type CustomBody = { username: string password: string } // ✅ 推荐写法 const customBody = t.Object({ username: t.String(), password: t.String() }) type CustomBody = typeof customBody.static ``` ### 分组 您可以将多个模型归组到一个对象中,便于管理: ```typescript import { Vafast, t } from 'vafast' export const AuthModel = { sign: t.Object({ username: t.String(), password: t.String() }) } const models = AuthModel.models ``` ### 模型注入 虽然可选,但如严格遵循 MVC 模式,您可能希望像使用服务一样,将模型注入控制器中。 推荐使用 [Vafast 引用模型](/essential/validation#reference-model)。 使用 Vafast 的模型引用示例: ```typescript import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) const AuthModel = new Vafast() .model({ 'auth.sign': customBody }) const models = AuthModel.models const UserController = new Vafast({ prefix: '/auth' }) .use(AuthModel) .post('/sign-in', async ({ body, cookie: { session } }) => { return true }, { body: 'auth.sign' }) ``` 这种方法带来若干优势: 1. 允许模型命名并获得自动补全。 2. 可以修改架构用于后续用途,或执行 [重映射](/essential/handler.html#remap)。 3. 在 OpenAPI 兼容客户端中作为“模型”,例如 Swagger。 4. 加快 TypeScript 推断速度,因为模型类型注册时已缓存。 ## 重用插件 多次重用插件以支持类型推断是可行的。 Vafast 默认自动处理插件去重,性能影响极小。 要创建唯一插件,您可以给 Vafast 实例指定一个 **name** 或可选的 **seed**。 ```typescript import { Vafast } from 'vafast' const plugin = new Vafast({ name: 'my-plugin' }) .decorate("type", "plugin") const app = new Vafast() .use(plugin) .use(plugin) .use(plugin) .use(plugin) .listen(3000) ``` 这样 Vafast 会复用已注册插件提升性能,而不是重复加载插件。 --- --- url: 'https://vafast.dev/api-client/treaty/unit-test.md' --- # 单元测试 单元测试是确保代码质量的关键部分。Vafast 类型安全客户端提供了完整的测试支持,让您能够轻松地测试各种功能和场景。本章将详细介绍如何编写有效的单元测试。 ## 🧪 测试环境设置 ### 安装测试依赖 ```bash # 使用 Bun(推荐) bun add -d bun @types/node # 使用 npm npm install -D jest @types/jest ts-jest # 使用 yarn yarn add -D jest @types/jest ts-jest # 使用 pnpm pnpm add -D jest @types/jest ts-jest ``` ### 测试配置文件 ```typescript // test/setup.ts import { beforeAll, afterAll } from 'bun:test' // 设置测试环境变量 process.env.NODE_ENV = 'test' process.env.API_BASE_URL = 'http://localhost:3000' // 全局测试设置 beforeAll(() => { console.log('Setting up test environment...') }) afterAll(() => { console.log('Cleaning up test environment...') }) ``` ```javascript // jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts' ], coverageDirectory: 'coverage', coverageReporters: ['text', 'lcov', 'html'] } ``` ## 🔧 基础测试 ### 客户端实例测试 ```typescript // test/client.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Typed Client', () => { let client: any beforeEach(() => { client = createTypedClient('http://localhost:3000', { timeout: 5000, retries: 1 }) }) it('should create client instance', () => { expect(client).toBeDefined() expect(typeof client.get).toBe('function') expect(typeof client.post).toBe('function') expect(typeof client.put).toBe('function') expect(typeof client.delete).toBe('function') }) it('should have correct configuration', () => { const config = client.getConfig() expect(config.baseURL).toBe('http://localhost:3000') expect(config.timeout).toBe(5000) expect(config.retries).toBe(1) }) it('should update configuration', () => { client.updateConfig({ timeout: 10000 }) const config = client.getConfig() expect(config.timeout).toBe(10000) }) }) ``` ### HTTP 方法测试 ```typescript // test/http-methods.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('HTTP Methods', () => { let client: any beforeEach(() => { client = createTypedClient('http://localhost:3000') }) describe('GET', () => { it('should make GET request successfully', async () => { const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.status).toBe(200) }) it('should handle query parameters', async () => { const response = await client.get('/users', { page: 1, limit: 5 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) it('should handle path parameters', async () => { const response = await client.get('/users/:id', { id: '123' }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) }) describe('POST', () => { it('should make POST request successfully', async () => { const userData = { name: 'John Doe', email: 'john@example.com' } const response = await client.post('/users', userData) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) it('should validate request body', async () => { const invalidData = { name: '', // 空字符串,应该验证失败 email: 'invalid-email' // 无效邮箱格式 } const response = await client.post('/users', invalidData) expect(response.error).toBeDefined() expect(response.error?.type).toBe('validation') }) }) describe('PUT', () => { it('should make PUT request successfully', async () => { const updateData = { name: 'John Updated' } const response = await client.put('/users/:id', updateData, { id: '123' }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) }) describe('DELETE', () => { it('should make DELETE request successfully', async () => { const response = await client.delete('/users/:id', { id: '123' }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) }) }) ``` ## 🚨 错误处理测试 ### 网络错误测试 ```typescript // test/error-handling.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Error Handling', () => { let client: any beforeEach(() => { client = createTypedClient('http://invalid-domain-that-does-not-exist.com', { timeout: 1000 }) }) it('should handle network errors', async () => { const response = await client.get('/test') expect(response.error).toBeDefined() expect(response.error?.type).toBe('network') expect(response.data).toBeUndefined() }) it('should handle timeout errors', async () => { const response = await client.get('/test') expect(response.error).toBeDefined() expect(response.error?.type).toBe('timeout') }) it('should handle 404 errors', async () => { // 使用有效的域名但无效的路径 client.updateConfig({ baseURL: 'http://localhost:3000' }) const response = await client.get('/nonexistent') expect(response.error).toBeDefined() expect(response.error?.status).toBe(404) }) it('should handle 500 errors', async () => { // 模拟服务器错误 const mockServer = createTypedClient('https://httpstat.us') const response = await mockServer.get('/500') expect(response.error).toBeDefined() expect(response.error?.status).toBe(500) }) }) ``` ### 验证错误测试 ```typescript // test/validation.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Validation', () => { let client: any beforeEach(() => { client = createTypedClient('http://localhost:3000') }) it('should validate required fields', async () => { const response = await client.post('/users', { // 缺少必需的 name 字段 email: 'john@example.com' }) expect(response.error).toBeDefined() expect(response.error?.type).toBe('validation') expect(response.error?.details?.name).toBeDefined() }) it('should validate field types', async () => { const response = await client.post('/users', { name: 123, // 应该是字符串 email: 'john@example.com' }) expect(response.error).toBeDefined() expect(response.error?.type).toBe('validation') expect(response.error?.details?.name).toBeDefined() }) it('should validate field formats', async () => { const response = await client.post('/users', { name: 'John Doe', email: 'invalid-email' // 无效邮箱格式 }) expect(response.error).toBeDefined() expect(response.error?.type).toBe('validation') expect(response.error?.details?.email).toBeDefined() }) it('should validate field constraints', async () => { const response = await client.post('/users', { name: '', // 太短 email: 'john@example.com' }) expect(response.error).toBeDefined() expect(response.error?.type).toBe('validation') expect(response.error?.details?.name).toBeDefined() }) }) ``` ## 🔄 中间件测试 ### 请求中间件测试 ```typescript // test/middleware.test.ts import { describe, expect, it, beforeEach, jest } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Middleware', () => { let client: any let mockRequestInterceptor: jest.Mock let mockResponseInterceptor: jest.Mock beforeEach(() => { mockRequestInterceptor = jest.fn() mockResponseInterceptor = jest.fn() client = createTypedClient('http://localhost:3000', { requestInterceptors: [ async (config, next) => { mockRequestInterceptor(config) return await next(config) } ], responseInterceptors: [ async (response, next) => { mockResponseInterceptor(response) return await next(response) } ] }) }) it('should execute request middleware', async () => { await client.get('/users') expect(mockRequestInterceptor).toHaveBeenCalled() expect(mockRequestInterceptor.mock.calls[0][0]).toHaveProperty('url') expect(mockRequestInterceptor.mock.calls[0][0]).toHaveProperty('method') }) it('should execute response middleware', async () => { await client.get('/users') expect(mockResponseInterceptor).toHaveBeenCalled() expect(mockResponseInterceptor.mock.calls[0][0]).toHaveProperty('data') expect(mockResponseInterceptor.mock.calls[0][0]).toHaveProperty('status') }) it('should modify request in middleware', async () => { const customClient = createTypedClient('http://localhost:3000', { requestInterceptors: [ async (config, next) => { // 添加自定义头 config.headers['X-Custom-Header'] = 'test-value' return await next(config) } ] }) const response = await customClient.get('/users') expect(response.error).toBeUndefined() // 注意:这里我们无法直接验证请求头,但可以验证请求成功 }) it('should handle middleware errors', async () => { const errorClient = createTypedClient('http://localhost:3000', { requestInterceptors: [ async (config, next) => { throw new Error('Middleware error') } ] }) const response = await errorClient.get('/users') expect(response.error).toBeDefined() expect(response.error?.message).toBe('Middleware error') }) }) ``` ## 💾 缓存测试 ### 缓存功能测试 ```typescript // test/cache.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Cache', () => { let client: any beforeEach(() => { client = createTypedClient('http://localhost:3000', { enableCache: true, cacheExpiry: 1000 // 1秒 }) }) it('should cache responses', async () => { const startTime = Date.now() // 第一次请求 const response1 = await client.get('/users') const firstRequestTime = Date.now() - startTime expect(response1.error).toBeUndefined() expect(response1.data).toBeDefined() // 第二次请求(应该使用缓存) const response2 = await client.get('/users') const secondRequestTime = Date.now() - startTime expect(response2.error).toBeUndefined() expect(response2.data).toEqual(response1.data) // 第二次请求应该更快(使用缓存) expect(secondRequestTime).toBeLessThan(firstRequestTime + 100) }) it('should respect cache expiry', async () => { // 第一次请求 await client.get('/users') // 等待缓存过期 await new Promise(resolve => setTimeout(resolve, 1100)) // 第二次请求(缓存已过期) const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) it('should clear cache', async () => { // 第一次请求 await client.get('/users') // 清除缓存 client.clearCache('/users') // 第二次请求(缓存已清除) const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) it('should handle cache control headers', async () => { // 强制不使用缓存 const response1 = await client.get('/users', {}, { cache: 'no-cache' }) expect(response1.error).toBeUndefined() // 强制使用缓存 const response2 = await client.get('/users', {}, { cache: 'force-cache' }) expect(response2.error).toBeUndefined() }) }) ``` ## 🔄 重试机制测试 ### 重试功能测试 ```typescript // test/retry.test.ts import { describe, expect, it, beforeEach, jest } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Retry Mechanism', () => { let client: any let mockFetch: jest.Mock beforeEach(() => { mockFetch = jest.fn() global.fetch = mockFetch client = createTypedClient('http://localhost:3000', { retries: 3, retryDelay: 100 }) }) it('should retry failed requests', async () => { // 模拟前两次失败,第三次成功 mockFetch .mockRejectedValueOnce(new Error('Network error')) .mockRejectedValueOnce(new Error('Network error')) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ success: true }) }) const response = await client.get('/test') expect(mockFetch).toHaveBeenCalledTimes(3) expect(response.error).toBeUndefined() expect(response.data).toEqual({ success: true }) }) it('should respect retry limit', async () => { // 模拟所有请求都失败 mockFetch.mockRejectedValue(new Error('Network error')) const response = await client.get('/test') expect(mockFetch).toHaveBeenCalledTimes(4) // 初始请求 + 3次重试 expect(response.error).toBeDefined() expect(response.error?.type).toBe('network') }) it('should use custom retry condition', async () => { client = createTypedClient('http://localhost:3000', { retries: 2, retryCondition: (error) => { // 只在特定错误时重试 return error.status === 429 } }) // 模拟 400 错误(不应该重试) mockFetch.mockResolvedValue({ ok: false, status: 400, json: async () => ({ error: 'Bad Request' }) }) const response = await client.get('/test') expect(mockFetch).toHaveBeenCalledTimes(1) // 不应该重试 expect(response.error?.status).toBe(400) }) }) ``` ## 📊 统计和监控测试 ### 统计功能测试 ```typescript // test/stats.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Statistics', () => { let client: any beforeEach(() => { client = createTypedClient('http://localhost:3000', { enableStats: true }) }) it('should track request count', async () => { const initialStats = client.getStats() await client.get('/users') await client.get('/posts') const finalStats = client.getStats() expect(finalStats.totalRequests).toBe(initialStats.totalRequests + 2) expect(finalStats.successfulRequests).toBe(initialStats.successfulRequests + 2) }) it('should track response times', async () => { await client.get('/users') const stats = client.getStats() expect(stats.averageResponseTime).toBeGreaterThan(0) expect(stats.totalRequests).toBeGreaterThan(0) }) it('should track error count', async () => { const initialStats = client.getStats() // 触发一个错误 client.updateConfig({ baseURL: 'http://invalid-domain.com' }) await client.get('/test') const finalStats = client.getStats() expect(finalStats.failedRequests).toBe(initialStats.failedRequests + 1) }) it('should reset statistics', async () => { await client.get('/users') const statsBeforeReset = client.getStats() expect(statsBeforeReset.totalRequests).toBeGreaterThan(0) client.resetStats() const statsAfterReset = client.getStats() expect(statsAfterReset.totalRequests).toBe(0) expect(statsAfterReset.successfulRequests).toBe(0) expect(statsAfterReset.failedRequests).toBe(0) }) }) ``` ## 🔗 集成测试 ### 与真实 API 的集成测试 ```typescript // test/integration.test.ts import { describe, expect, it, beforeAll, afterAll } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Integration Tests', () => { let client: any beforeAll(() => { client = createTypedClient('http://localhost:3000', { timeout: 10000 }) }) it('should perform full CRUD operations', async () => { // Create const createResponse = await client.post('/users', { name: 'Test User', email: 'test@example.com' }) expect(createResponse.error).toBeUndefined() expect(createResponse.data).toBeDefined() expect(createResponse.data.name).toBe('Test User') const userId = createResponse.data.id // Read const readResponse = await client.get('/users/:id', { id: userId }) expect(readResponse.error).toBeUndefined() expect(readResponse.data).toBeDefined() expect(readResponse.data.id).toBe(userId) // Update const updateResponse = await client.put('/users/:id', { name: 'Updated User' }, { id: userId }) expect(updateResponse.error).toBeUndefined() expect(updateResponse.data).toBeDefined() expect(updateResponse.data.name).toBe('Updated User') // Delete const deleteResponse = await client.delete('/users/:id', { id: userId }) expect(deleteResponse.error).toBeUndefined() }) it('should handle pagination', async () => { const response = await client.get('/users', { page: 1, limit: 5 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeLessThanOrEqual(5) }) it('should handle search and filtering', async () => { const response = await client.get('/users', { search: 'john' }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeGreaterThan(0) // 验证搜索结果 const hasMatchingName = response.data.some((user: any) => user.name.toLowerCase().includes('john') ) expect(hasMatchingName).toBe(true) }) }) ``` ## 🎯 性能测试 ### 性能基准测试 ```typescript // test/performance.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' describe('Performance Tests', () => { let client: any beforeEach(() => { client = createTypedClient('http://localhost:3000', { enableCache: true }) }) it('should handle concurrent requests', async () => { const startTime = Date.now() const promises = Array.from({ length: 10 }, () => client.get('/users') ) const responses = await Promise.all(promises) const endTime = Date.now() const totalTime = endTime - startTime expect(responses).toHaveLength(10) expect(responses.every(r => r.error === undefined)).toBe(true) // 验证并发性能(10个请求应该在合理时间内完成) expect(totalTime).toBeLessThan(5000) // 5秒内 }) it('should handle large responses efficiently', async () => { const startTime = Date.now() const response = await client.get('/users') const endTime = Date.now() const responseTime = endTime - startTime expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeGreaterThan(0) // 验证响应时间 expect(responseTime).toBeLessThan(3000) // 3秒内 }) it('should cache responses efficiently', async () => { // 第一次请求 const startTime1 = Date.now() await client.get('/users') const firstRequestTime = Date.now() - startTime1 // 第二次请求(使用缓存) const startTime2 = Date.now() await client.get('/users') const secondRequestTime = Date.now() - startTime2 // 缓存请求应该显著更快 expect(secondRequestTime).toBeLessThan(firstRequestTime * 0.5) }) }) ``` ## 🔍 测试工具和辅助函数 ### 测试辅助函数 ```typescript // test/helpers.ts import { createTypedClient } from '@vafast/api-client' import type { App } from '../server' // 创建测试客户端 export function createTestClient(config = {}) { return createTypedClient('http://localhost:3000', { timeout: 5000, retries: 1, enableCache: false, ...config }) } // 等待指定时间 export function wait(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } // 模拟网络延迟 export function simulateNetworkDelay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)) } // 创建模拟响应 export function createMockResponse(data: any, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => data, text: async () => JSON.stringify(data), headers: new Headers() } } // 创建模拟错误 export function createMockError(message: string, status = 500) { return { ok: false, status, statusText: message, json: async () => ({ error: message }), text: async () => JSON.stringify({ error: message }), headers: new Headers() } } // 验证响应结构 export function validateResponseStructure(response: any) { expect(response).toHaveProperty('data') expect(response).toHaveProperty('error') expect(response).toHaveProperty('status') expect(response).toHaveProperty('headers') } // 验证错误响应 export function validateErrorResponse(response: any, expectedStatus?: number) { expect(response.error).toBeDefined() expect(response.data).toBeUndefined() if (expectedStatus) { expect(response.error.status).toBe(expectedStatus) } expect(response.error.message).toBeDefined() expect(typeof response.error.message).toBe('string') } // 验证成功响应 export function validateSuccessResponse(response: any) { expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.status).toBeGreaterThanOrEqual(200) expect(response.status).toBeLessThan(300) } ``` ### 测试数据工厂 ```typescript // test/factories.ts // 用户数据工厂 export function createUserData(overrides = {}) { return { name: 'John Doe', email: 'john@example.com', age: 30, ...overrides } } // 用户列表工厂 export function createUserList(count: number, overrides = {}) { return Array.from({ length: count }, (_, index) => createUserData({ id: `user-${index + 1}`, name: `User ${index + 1}`, email: `user${index + 1}@example.com`, ...overrides }) ) } // 分页响应工厂 export function createPaginatedResponse(data: T[], page = 1, limit = 10) { return { data, total: data.length, page, limit, totalPages: Math.ceil(data.length / limit) } } // 错误响应工厂 export function createErrorResponse(message: string, status = 400, type = 'client') { return { error: { message, status, type, details: null }, data: undefined, status, headers: {} } } ``` ## 📝 测试最佳实践 ### 1. 测试组织 * 按功能模块组织测试文件 * 使用描述性的测试名称 * 保持测试的独立性 ### 2. 测试覆盖 * 测试所有主要功能 * 测试边界条件 * 测试错误情况 ### 3. 测试数据 * 使用工厂函数创建测试数据 * 避免硬编码的测试数据 * 清理测试后的数据 ### 4. 异步测试 * 正确处理异步操作 * 使用适当的等待机制 * 避免测试超时 ### 5. 测试性能 * 监控测试执行时间 * 避免不必要的网络请求 * 使用适当的模拟 ## 🚀 运行测试 ### 使用 Bun ```bash # 运行所有测试 bun test # 运行特定测试文件 bun test test/client.test.ts # 运行测试并显示覆盖率 bun test --coverage # 监听模式 bun test --watch ``` ### 使用 npm ```bash # 运行所有测试 npm test # 运行特定测试文件 npm test -- test/client.test.ts # 运行测试并显示覆盖率 npm run test:coverage # 监听模式 npm run test:watch ``` ### 测试覆盖率 ```bash # 生成覆盖率报告 bun test --coverage # 查看覆盖率报告 open coverage/lcov-report/index.html ``` ## 🔗 相关链接 * [类型安全客户端概述](/api-client/treaty/overview) - 了解基本概念 * [配置选项](/api-client/treaty/config) - 学习测试配置 * [参数处理](/api-client/treaty/parameters) - 测试参数处理 * [响应处理](/api-client/treaty/response) - 测试响应处理 * [WebSocket 支持](/api-client/treaty/websocket) - 测试 WebSocket 功能 ## 📚 下一步 现在您已经了解了如何为 Vafast 类型安全客户端编写单元测试,接下来可以: 1. **编写更多测试用例** - 覆盖更多功能和场景 2. **设置持续集成** - 自动化测试流程 3. **性能测试** - 验证在高负载下的表现 4. **安全测试** - 测试各种安全场景 5. **文档测试** - 确保示例代码的正确性 如果您在测试过程中遇到任何问题,请查看我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/api-client/treaty/parameters.md' --- # 参数处理 Vafast 类型安全客户端提供了强大的参数处理能力,支持路径参数、查询参数、请求体等多种参数类型。本章将详细介绍如何正确使用这些参数。 ## 🎯 参数类型概述 Vafast 类型安全客户端支持以下参数类型: * **路径参数** - URL 路径中的动态部分 * **查询参数** - URL 查询字符串中的参数 * **请求体** - HTTP 请求的主体内容 * **请求头** - HTTP 请求头信息 * **Cookie** - 请求中的 Cookie 信息 ## 🛣️ 路径参数 ### 基本用法 路径参数是 URL 路径中的动态部分,用冒号 `:` 表示。 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => `User ${params.id}`), params: Type.Object({ id: Type.String() }) } ]) // 客户端使用 const response = await client.get('/users/:id', { id: '123' }) console.log(response.data) // "User 123" ``` ### 多个路径参数 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/posts/:postId/comments/:commentId', handler: createHandler(({ params }) => `Post ${params.postId}, Comment ${params.commentId}` ), params: Type.Object({ postId: Type.String(), commentId: Type.String() }) } ]) // 客户端使用 const response = await client.get('/posts/:postId/comments/:commentId', { postId: '456', commentId: '789' }) console.log(response.data) // "Post 456, Comment 789" ``` ### 嵌套路径参数 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/organizations/:orgId/departments/:deptId/employees/:empId', handler: createHandler(({ params }) => `Employee ${params.empId} in ${params.deptId} of ${params.orgId}` ), params: Type.Object({ orgId: Type.String(), deptId: Type.String(), empId: Type.String() }) } ]) // 客户端使用 const response = await client.get('/organizations/:orgId/departments/:deptId/employees/:empId', { orgId: 'org123', deptId: 'dept456', empId: 'emp789' }) console.log(response.data) // "Employee emp789 in dept456 of org123" ``` ### 路径参数类型验证 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => `User ${params.id}`), params: Type.Object({ id: Type.Union([ Type.String({ pattern: '^[0-9]+$' }), // 数字字符串 Type.String({ pattern: '^[a-f0-9]{24}$' }) // MongoDB ObjectId ]) }) } ]) // 客户端使用 - 类型安全 const response = await client.get('/users/:id', { id: '123' }) // ✅ 有效 const response2 = await client.get('/users/:id', { id: '507f1f77bcf86cd799439011' }) // ✅ 有效 const response3 = await client.get('/users/:id', { id: 'invalid' }) // ❌ 类型错误 ``` ## 🔍 查询参数 ### 基本查询参数 查询参数是 URL 查询字符串中的键值对。 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(({ query }) => { const { page = 1, limit = 10, search = '' } = query return `Page ${page}, Limit ${limit}, Search: ${search}` }), query: Type.Object({ page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), search: Type.Optional(Type.String()) }) } ]) // 客户端使用 const response = await client.get('/users', { page: 1, limit: 20, search: 'john' }) console.log(response.data) // "Page 1, Limit 20, Search: john" ``` ### 数组查询参数 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/products', handler: createHandler(({ query }) => { const { categories = [], tags = [] } = query return `Categories: ${categories.join(', ')}, Tags: ${tags.join(', ')}` }), query: Type.Object({ categories: Type.Optional(Type.Array(Type.String())), tags: Type.Optional(Type.Array(Type.String())) }) } ]) // 客户端使用 const response = await client.get('/products', { categories: ['electronics', 'books'], tags: ['featured', 'new'] }) console.log(response.data) // "Categories: electronics, books, Tags: featured, new" ``` ### 复杂查询参数 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/search', handler: createHandler(({ query }) => { const { filters, sort, pagination } = query return `Filters: ${JSON.stringify(filters)}, Sort: ${JSON.stringify(sort)}, Page: ${pagination.page}` }), query: Type.Object({ filters: Type.Object({ price: Type.Object({ min: Type.Optional(Type.Number()), max: Type.Optional(Type.Number()) }), brand: Type.Optional(Type.Array(Type.String())), inStock: Type.Optional(Type.Boolean()) }), sort: Type.Object({ field: Type.String(), order: Type.Union([Type.Literal('asc'), Type.Literal('desc')]) }), pagination: Type.Object({ page: Type.Number({ minimum: 1 }), limit: Type.Number({ minimum: 1, maximum: 100 }) }) }) } ]) // 客户端使用 const response = await client.get('/search', { filters: { price: { min: 100, max: 1000 }, brand: ['apple', 'dell', 'hp'], inStock: true }, sort: { field: 'price', order: 'asc' }, pagination: { page: 1, limit: 20 } }) ``` ### 查询参数验证 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/analytics', handler: createHandler(({ query }) => { const { startDate, endDate, metrics } = query return `Analytics from ${startDate} to ${endDate} for ${metrics.join(', ')}` }), query: Type.Object({ startDate: Type.String({ format: 'date' }), endDate: Type.String({ format: 'date' }), metrics: Type.Array(Type.Union([ Type.Literal('views'), Type.Literal('clicks'), Type.Literal('conversions') ])) }) } ]) // 客户端使用 - 类型安全 const response = await client.get('/analytics', { startDate: '2024-01-01', endDate: '2024-01-31', metrics: ['views', 'clicks'] // ✅ 有效 }) const response2 = await client.get('/analytics', { startDate: '2024-01-01', endDate: '2024-01-31', metrics: ['invalid'] // ❌ 类型错误 }) ``` ## 📦 请求体 ### 基本请求体 请求体是 HTTP 请求的主体内容,通常用于 POST、PUT、PATCH 等请求。 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => `Created user: ${body.name}`), body: Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }), age: Type.Number({ minimum: 0, maximum: 150 }) }) } ]) // 客户端使用 const response = await client.post('/users', { name: 'John Doe', email: 'john@example.com', age: 30 }) console.log(response.data) // "Created user: John Doe" ``` ### 复杂请求体 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/orders', handler: createHandler(({ body }) => { const { customer, items, shipping } = body return `Order for ${customer.name} with ${items.length} items` }), body: Type.Object({ customer: Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }), phone: Type.Optional(Type.String()) }), items: Type.Array(Type.Object({ productId: Type.String(), quantity: Type.Number({ minimum: 1 }), price: Type.Number({ minimum: 0 }) })), shipping: Type.Object({ address: Type.String(), city: Type.String(), country: Type.String(), postalCode: Type.String() }) }) } ]) // 客户端使用 const response = await client.post('/orders', { customer: { name: 'John Doe', email: 'john@example.com', phone: '+1234567890' }, items: [ { productId: 'prod123', quantity: 2, price: 29.99 }, { productId: 'prod456', quantity: 1, price: 49.99 } ], shipping: { address: '123 Main St', city: 'New York', country: 'USA', postalCode: '10001' } }) ``` ### 文件上传 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/upload', handler: createHandler(async ({ body }) => { const { file, metadata } = body const fileName = file.name return `Uploaded file: ${fileName}` }), body: Type.Object({ file: Type.Any(), // 文件对象 metadata: Type.Object({ description: Type.Optional(Type.String()), tags: Type.Optional(Type.Array(Type.String())) }) }) } ]) // 客户端使用 const formData = new FormData() formData.append('file', fileInput.files[0]) formData.append('metadata', JSON.stringify({ description: 'Profile picture', tags: ['avatar', 'profile'] })) const response = await client.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) ``` ### 请求体验证 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/validation', handler: createHandler(({ body }) => `Valid data: ${body.value}`), body: Type.Object({ value: Type.String({ minLength: 5, maxLength: 100, pattern: '^[a-zA-Z0-9\\s]+$' }), number: Type.Number({ minimum: 1, maximum: 1000, multipleOf: 5 }), email: Type.String({ format: 'email' }), url: Type.String({ format: 'uri' }), date: Type.String({ format: 'date' }) }) } ]) // 客户端使用 - 类型安全 const response = await client.post('/validation', { value: 'Hello World', // ✅ 有效 number: 25, // ✅ 有效 email: 'test@example.com', // ✅ 有效 url: 'https://example.com', // ✅ 有效 date: '2024-01-01' // ✅ 有效 }) // 类型错误会在编译时被捕获 const response2 = await client.post('/validation', { value: 'Hi', // ❌ 太短 number: 23, // ❌ 不是5的倍数 email: 'invalid-email', // ❌ 格式错误 url: 'not-a-url', // ❌ 格式错误 date: 'invalid-date' // ❌ 格式错误 }) ``` ## 🎛️ 请求头 ### 基本请求头 请求头用于传递额外的 HTTP 请求信息。 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/protected', handler: createHandler(({ headers }) => { const authHeader = headers.get('authorization') return `Authenticated with: ${authHeader}` }), headers: Type.Object({ authorization: Type.String({ pattern: '^Bearer .+' }) }) } ]) // 客户端使用 const response = await client.get('/protected', {}, { headers: { 'Authorization': 'Bearer token123', 'X-Request-ID': 'req-456', 'User-Agent': 'Vafast-Client/1.0.0' } }) ``` ### 动态请求头 ```typescript // 客户端使用 - 动态请求头 const getAuthHeaders = () => { const token = localStorage.getItem('token') return token ? { 'Authorization': `Bearer ${token}` } : {} } const response = await client.get('/protected', {}, { headers: { ...getAuthHeaders(), 'X-Request-ID': `req-${Date.now()}` } }) ``` ### 请求头验证 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/secure', handler: createHandler(({ headers }) => 'Secure endpoint accessed'), headers: Type.Object({ 'x-api-key': Type.String({ minLength: 32, maxLength: 32 }), 'x-timestamp': Type.String({ pattern: '^\\d{10}$' }), 'x-signature': Type.String({ minLength: 64, maxLength: 64 }) }) } ]) // 客户端使用 const timestamp = Math.floor(Date.now() / 1000).toString() const signature = generateSignature(timestamp) const response = await client.post('/secure', { data: 'secret' }, { headers: { 'X-API-Key': '1234567890abcdef1234567890abcdef', 'X-Timestamp': timestamp, 'X-Signature': signature } }) ``` ## 🍪 Cookie 参数 ### 基本 Cookie 处理 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/profile', handler: createHandler(({ cookies }) => { const sessionId = cookies.get('sessionId') return `Profile for session: ${sessionId}` }), cookies: Type.Object({ sessionId: Type.String() }) } ]) // 客户端使用 const response = await client.get('/profile', {}, { credentials: 'include' // 包含 Cookie }) ``` ### Cookie 验证 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/login', handler: createHandler(({ body, cookies }) => { // 设置认证 Cookie cookies.set('sessionId', 'new-session-123', { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600 }) return 'Login successful' }), body: Type.Object({ username: Type.String(), password: Type.String() }) } ]) // 客户端使用 const response = await client.post('/login', { username: 'john', password: 'password123' }, { credentials: 'include' }) ``` ## 🔧 参数组合 ### 混合参数类型 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'PUT', path: '/users/:id', handler: createHandler(({ params, query, body, headers }) => { const { id } = params const { version } = query const { name, email } = body const { 'x-request-id': requestId } = headers return `Updated user ${id} to version ${version} with request ${requestId}` }), params: Type.Object({ id: Type.String() }), query: Type.Object({ version: Type.String() }), body: Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) }), headers: Type.Object({ 'x-request-id': Type.String() }) } ]) // 客户端使用 const response = await client.put('/users/:id', { name: 'John Updated', email: 'john.updated@example.com' }, { id: '123', query: { version: '2.0' }, headers: { 'X-Request-ID': 'req-789' } }) ``` ### 可选参数 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/search', handler: createHandler(({ query }) => { const { q, page = 1, limit = 10, sort = 'relevance' } = query return `Search: ${q}, Page: ${page}, Limit: ${limit}, Sort: ${sort}` }), query: Type.Object({ q: Type.String(), page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), sort: Type.Optional(Type.Union([ Type.Literal('relevance'), Type.Literal('date'), Type.Literal('price') ])) }) } ]) // 客户端使用 - 部分参数 const response = await client.get('/search', { q: 'laptop', page: 2 // limit 和 sort 使用默认值 }) ``` ## 📝 参数最佳实践 ### 1. 参数命名 * 使用清晰的描述性名称 * 遵循一致的命名约定 * 避免缩写和模糊名称 ### 2. 参数验证 * 为所有参数定义验证规则 * 使用适当的类型约束 * 提供有意义的错误消息 ### 3. 参数文档 * 为每个参数提供清晰的描述 * 说明参数的格式和约束 * 提供使用示例 ### 4. 参数安全 * 验证和清理所有输入 * 使用适当的类型检查 * 防止参数注入攻击 ### 5. 参数性能 * 避免过大的请求体 * 使用适当的缓存策略 * 优化参数处理逻辑 ## 🔍 参数调试 ### 启用参数日志 ```typescript const client = createTypedClient('http://localhost:3000', { logging: { enabled: true, level: 'debug' } }) // 参数会被自动记录 const response = await client.get('/users/:id', { id: '123' }, { query: { page: 1, limit: 10 } }) ``` ### 参数验证错误 ```typescript try { const response = await client.get('/users/:id', { id: 'invalid' }) } catch (error) { if (error.type === 'validation') { console.error('Parameter validation failed:', error.details) } } ``` ## 🔗 相关链接 * [类型安全客户端概述](/api-client/treaty/overview) - 了解基本概念 * [配置选项](/api-client/treaty/config) - 学习配置参数 * [响应处理](/api-client/treaty/response) - 了解响应参数 * [WebSocket 支持](/api-client/treaty/websocket) - 处理实时参数 * [单元测试](/api-client/treaty/unit-test) - 测试参数处理 ## 📚 下一步 现在您已经了解了 Vafast 类型安全客户端的参数处理,接下来可以: 1. **学习响应处理** - 了解如何处理响应参数 2. **配置 WebSocket** - 处理实时通信参数 3. **编写测试** - 验证参数处理的正确性 4. **性能优化** - 优化参数处理性能 5. **安全加固** - 增强参数安全性 如果您有任何问题或需要帮助,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/api-client/treaty/response.md' --- # 响应处理 Vafast 类型安全客户端提供了强大的响应处理能力,支持类型安全的响应数据、错误处理、状态码处理等多种功能。本章将详细介绍如何正确处理各种类型的响应。 ## 📋 响应结构概述 Vafast 类型安全客户端的每个请求都会返回一个标准化的响应对象,包含以下属性: ```typescript interface ApiResponse { // 响应数据(成功时) data?: T // 错误信息(失败时) error?: ApiError // HTTP 状态码 status: number // 响应头 headers: Record // 响应元数据 metadata?: ResponseMetadata } interface ApiError { // 错误消息 message: string // HTTP 状态码 status: number // 错误类型 type: 'network' | 'timeout' | 'validation' | 'server' | 'client' // 错误详情 details?: any // 原始错误 originalError?: Error } interface ResponseMetadata { // 请求开始时间 startTime?: number // 响应时间 duration?: number // 重试次数 retryCount?: number // 缓存信息 cache?: { hit: boolean key: string age: number } } ``` ## ✅ 成功响应处理 ### 基本成功响应 ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => ({ id: params.id, name: 'John Doe', email: 'john@example.com' })), params: Type.Object({ id: Type.String() }) } ]) // 客户端处理成功响应 const response = await client.get('/users/:id', { id: '123' }) if (response.error) { // 处理错误 console.error('Error:', response.error.message) } else { // 处理成功响应 const user = response.data console.log('User ID:', user.id) console.log('User Name:', user.name) console.log('User Email:', user.email) // 类型安全:TypeScript 知道 user 的类型 // user.invalid // ❌ 编译时错误 } ``` ### 类型化响应数据 ```typescript // 定义响应数据类型 interface User { id: string name: string email: string profile: { age: number location: string } } interface PaginatedResponse { data: T[] total: number page: number limit: number } // 服务器端定义 const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(({ query }) => { const { page = 1, limit = 10 } = query return { data: [ { id: '1', name: 'John', email: 'john@example.com', profile: { age: 30, location: 'NY' } }, { id: '2', name: 'Jane', email: 'jane@example.com', profile: { age: 25, location: 'CA' } } ], total: 2, page, limit } }), query: Type.Object({ page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })) }) } ]) // 客户端使用类型化响应 const response = await client.get('/users', { page: 1, limit: 10 }) if (response.error) { console.error('Error:', response.error.message) } else { // response.data 的类型自动推断为 PaginatedResponse const users = response.data console.log('Total users:', users.total) console.log('Current page:', users.page) users.data.forEach(user => { console.log(`${user.name} (${user.email}) - ${user.profile.age} years old`) }) } ``` ### 响应状态码处理 ```typescript const response = await client.get('/users/:id', { id: '123' }) // 检查 HTTP 状态码 switch (response.status) { case 200: console.log('Success:', response.data) break case 201: console.log('Created:', response.data) break case 204: console.log('No content') break default: console.log('Other status:', response.status) } ``` ## ❌ 错误响应处理 ### 基本错误处理 ```typescript try { const response = await client.get('/users/:id', { id: 'invalid' }) if (response.error) { // 处理 API 错误 switch (response.error.type) { case 'validation': console.error('Validation error:', response.error.details) break case 'server': console.error('Server error:', response.error.message) break case 'client': console.error('Client error:', response.error.message) break case 'network': console.error('Network error:', response.error.message) break case 'timeout': console.error('Request timeout') break default: console.error('Unknown error:', response.error.message) } } } catch (error) { // 处理网络或其他异常 console.error('Unexpected error:', error) } ``` ### 错误类型详解 #### 验证错误 (Validation Error) ```typescript // 服务器端定义 const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => `Created user: ${body.name}`), body: Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) } ]) // 客户端发送无效数据 const response = await client.post('/users', { name: '', // ❌ 太短 email: 'invalid-email' // ❌ 格式错误 }) if (response.error && response.error.type === 'validation') { console.error('Validation failed:') console.error('Details:', response.error.details) // 显示用户友好的错误消息 if (response.error.details?.name) { showFieldError('name', response.error.details.name) } if (response.error.details?.email) { showFieldError('email', response.error.details.email) } } ``` #### 服务器错误 (Server Error) ```typescript const response = await client.get('/users/:id', { id: '123' }) if (response.error && response.error.type === 'server') { switch (response.error.status) { case 500: console.error('Internal server error') showErrorMessage('服务器内部错误,请稍后重试') break case 502: console.error('Bad gateway') showErrorMessage('网关错误,请稍后重试') break case 503: console.error('Service unavailable') showErrorMessage('服务暂时不可用,请稍后重试') break case 504: console.error('Gateway timeout') showErrorMessage('网关超时,请稍后重试') break default: console.error('Server error:', response.error.message) showErrorMessage('服务器错误,请稍后重试') } } ``` #### 客户端错误 (Client Error) ```typescript const response = await client.get('/users/:id', { id: '123' }) if (response.error && response.error.type === 'client') { switch (response.error.status) { case 400: console.error('Bad request:', response.error.details) showErrorMessage('请求参数错误,请检查输入') break case 401: console.error('Unauthorized') // 重定向到登录页面 window.location.href = '/login' break case 403: console.error('Forbidden') showErrorMessage('权限不足,无法访问此资源') break case 404: console.error('Not found') showErrorMessage('请求的资源不存在') break case 409: console.error('Conflict:', response.error.details) showErrorMessage('资源冲突,请检查数据') break case 422: console.error('Unprocessable entity:', response.error.details) showErrorMessage('无法处理的请求,请检查数据') break case 429: console.error('Too many requests') showErrorMessage('请求过于频繁,请稍后重试') break default: console.error('Client error:', response.error.message) showErrorMessage('客户端错误,请检查请求') } } ``` #### 网络错误 (Network Error) ```typescript const response = await client.get('/users/:id', { id: '123' }) if (response.error && response.error.type === 'network') { console.error('Network error:', response.error.message) // 检查网络连接 if (!navigator.onLine) { showErrorMessage('网络连接已断开,请检查网络设置') } else { showErrorMessage('网络请求失败,请检查网络连接') } // 尝试重新连接 setTimeout(() => { retryRequest() }, 5000) } ``` #### 超时错误 (Timeout Error) ```typescript const response = await client.get('/users/:id', { id: '123' }) if (response.error && response.error.type === 'timeout') { console.error('Request timeout') showErrorMessage('请求超时,请稍后重试') // 自动重试 if (response.metadata?.retryCount < 3) { console.log('Retrying request...') setTimeout(() => { retryRequest() }, 1000) } } ``` ## 🔄 响应拦截器 ### 响应中间件 ```typescript import { createTypedClient } from '@vafast/api-client' import type { App } from './server' const client = createTypedClient('http://localhost:3000', { // 响应拦截器 responseInterceptors: [ async (response, next) => { // 记录响应时间 if (response.metadata?.startTime) { const duration = Date.now() - response.metadata.startTime console.log(`Request completed in ${duration}ms`) } // 处理特定状态码 if (response.status === 401) { // 清除无效的认证信息 localStorage.removeItem('token') window.location.href = '/login' return response } // 处理 5xx 错误 if (response.status >= 500) { // 记录服务器错误 logServerError(response) // 显示用户友好的错误消息 showServerErrorMessage() } return await next(response) }, async (response, next) => { // 数据转换 if (response.data && typeof response.data === 'object') { // 转换日期字符串为 Date 对象 response.data = transformDates(response.data) } return await next(response) } ] }) ``` ### 自定义响应处理 ```typescript // 自定义响应处理器 const customResponseHandler = async (response: ApiResponse) => { // 处理成功响应 if (response.data) { // 数据标准化 const normalizedData = normalizeResponseData(response.data) // 缓存响应数据 if (response.metadata?.cache?.hit === false) { cacheResponseData(response.url, normalizedData) } return normalizedData } // 处理错误响应 if (response.error) { // 错误分类 const errorCategory = categorizeError(response.error) // 错误报告 reportError(errorCategory, response.error) // 错误恢复 const recoveredData = await attemptErrorRecovery(response.error) if (recoveredData) { return recoveredData } // 抛出错误 throw new ApiError(response.error.message, response.error.status) } return null } // 使用自定义响应处理器 const response = await client.get('/users/:id', { id: '123' }) const result = await customResponseHandler(response) ``` ## 📊 响应数据转换 ### 数据标准化 ```typescript // 响应数据标准化 const normalizeResponseData = (data: any): any => { if (Array.isArray(data)) { return data.map(normalizeResponseData) } if (data && typeof data === 'object') { const normalized: any = {} for (const [key, value] of Object.entries(data)) { // 转换蛇形命名法为驼峰命名法 const camelKey = snakeToCamel(key) // 递归处理嵌套对象 normalized[camelKey] = normalizeResponseData(value) } return normalized } return data } // 使用示例 const response = await client.get('/users/:id', { id: '123' }) if (response.data) { // 原始数据:{ user_name: "John Doe", created_at: "2024-01-01" } const normalizedData = normalizeResponseData(response.data) // 标准化后:{ userName: "John Doe", createdAt: "2024-01-01" } console.log(normalizedData.userName) console.log(normalizedData.createdAt) } ``` ### 日期转换 ```typescript // 日期转换器 const transformDates = (data: any): any => { if (Array.isArray(data)) { return data.map(transformDates) } if (data && typeof data === 'object') { const transformed: any = {} for (const [key, value] of Object.entries(data)) { if (typeof value === 'string' && isDateString(value)) { // 转换日期字符串为 Date 对象 transformed[key] = new Date(value) } else if (typeof value === 'object') { // 递归处理嵌套对象 transformed[key] = transformDates(value) } else { transformed[key] = value } } return transformed } return data } // 使用示例 const response = await client.get('/users/:id', { id: '123' }) if (response.data) { // 原始数据:{ createdAt: "2024-01-01T00:00:00.000Z" } const transformedData = transformDates(response.data) // 转换后:{ createdAt: Date object } console.log(transformedData.createdAt instanceof Date) // true console.log(transformedData.createdAt.toLocaleDateString()) // "1/1/2024" } ``` ## 🎛️ 响应配置 ### 响应类型配置 ```typescript const client = createTypedClient('http://localhost:3000', { // 响应配置 response: { // 自动解析 JSON autoParseJson: true, // 响应超时 timeout: 30000, // 响应大小限制 maxSize: 10 * 1024 * 1024, // 10MB // 响应验证 validate: true, // 响应缓存 cache: { enabled: true, maxAge: 300000, // 5分钟 maxSize: 100 // 最多缓存100个响应 } } }) ``` ### 响应头处理 ```typescript const response = await client.get('/users/:id', { id: '123' }) // 获取响应头 const contentType = response.headers['content-type'] const contentLength = response.headers['content-length'] const cacheControl = response.headers['cache-control'] // 检查响应类型 if (contentType?.includes('application/json')) { // 处理 JSON 响应 const data = response.data } else if (contentType?.includes('text/')) { // 处理文本响应 const text = response.data } else if (contentType?.includes('image/')) { // 处理图片响应 const imageBlob = response.data } // 检查缓存控制 if (cacheControl?.includes('no-cache')) { // 不使用缓存 console.log('Response should not be cached') } else if (cacheControl?.includes('max-age=')) { // 解析缓存时间 const maxAge = parseInt(cacheControl.match(/max-age=(\d+)/)?.[1] || '0') console.log(`Response can be cached for ${maxAge} seconds`) } ``` ## 🔍 响应调试 ### 启用响应日志 ```typescript const client = createTypedClient('http://localhost:3000', { logging: { enabled: true, level: 'debug', // 响应日志配置 response: { enabled: true, includeHeaders: true, includeBody: true, includeMetadata: true } } }) // 响应会被自动记录 const response = await client.get('/users/:id', { id: '123' }) ``` ### 响应性能分析 ```typescript const client = createTypedClient('http://localhost:3000', { monitoring: { enabled: true, // 响应性能指标 metrics: { responseTime: true, responseSize: true, statusCodeDistribution: true } } }) // 获取响应性能统计 const stats = client.getResponseStats() console.log('Average response time:', stats.averageResponseTime) console.log('Response size distribution:', stats.responseSizeDistribution) console.log('Status code distribution:', stats.statusCodeDistribution) ``` ## 📝 响应最佳实践 ### 1. 错误处理 * 始终检查 `response.error` 的存在 * 根据错误类型采取不同的处理策略 * 提供用户友好的错误消息 ### 2. 类型安全 * 使用 TypeScript 类型定义响应数据 * 避免使用 `any` 类型 * 利用类型推断减少类型错误 ### 3. 性能优化 * 使用响应缓存减少重复请求 * 监控响应时间识别性能问题 * 实现适当的错误重试机制 ### 4. 用户体验 * 显示加载状态 * 提供错误恢复选项 * 实现优雅的降级策略 ### 5. 安全性 * 验证响应数据的完整性 * 防止 XSS 攻击 * 安全地处理敏感信息 ## 🔗 相关链接 * [类型安全客户端概述](/api-client/treaty/overview) - 了解基本概念 * [配置选项](/api-client/treaty/config) - 学习响应配置 * [参数处理](/api-client/treaty/parameters) - 了解请求参数 * [WebSocket 支持](/api-client/treaty/websocket) - 处理实时响应 * [单元测试](/api-client/treaty/unit-test) - 测试响应处理 ## 📚 下一步 现在您已经了解了 Vafast 类型安全客户端的响应处理,接下来可以: 1. **配置 WebSocket** - 处理实时通信响应 2. **编写测试** - 验证响应处理的正确性 3. **性能优化** - 优化响应处理性能 4. **错误处理** - 完善错误处理策略 5. **监控告警** - 实现响应监控系统 如果您有任何问题或需要帮助,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/api-client/fetch.md' --- # 基础用法 Vafast API 客户端提供了简单而强大的 API 来发送 HTTP 请求。本章将介绍基本的用法和常见的操作模式。 ## 🚀 创建客户端 首先,创建一个 API 客户端实例: ```typescript import { VafastApiClient } from '@vafast/api-client' const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 10000, retries: 3 }) ``` ## 📡 HTTP 请求方法 ### GET 请求 ```typescript // 基本 GET 请求 const response = await client.get('/users') // 带查询参数的 GET 请求 const response = await client.get('/users', { page: 1, limit: 10, search: 'john' }) // 带路径参数的 GET 请求 const response = await client.get('/users/:id', { id: 123 }) // 带自定义头的 GET 请求 const response = await client.get('/users', {}, { headers: { 'Authorization': 'Bearer token123', 'X-Custom-Header': 'value' } }) ``` ### POST 请求 ```typescript // 基本 POST 请求 const response = await client.post('/users', { name: 'John Doe', email: 'john@example.com', age: 30 }) // 带查询参数的 POST 请求 const response = await client.post('/users', { name: 'John Doe', email: 'john@example.com' }, { query: { role: 'admin' } }) // 带自定义头的 POST 请求 const response = await client.post('/users', { name: 'John Doe', email: 'john@example.com' }, { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token123' } }) ``` ### PUT 请求 ```typescript // 更新用户信息 const response = await client.put('/users/:id', { name: 'John Updated', email: 'john.updated@example.com' }, { id: 123 }) // 带查询参数的 PUT 请求 const response = await client.put('/users/:id', { name: 'John Updated' }, { id: 123, query: { version: '2.0' } }) ``` ### PATCH 请求 ```typescript // 部分更新用户信息 const response = await client.patch('/users/:id', { name: 'John Updated' }, { id: 123 }) ``` ### DELETE 请求 ```typescript // 删除用户 const response = await client.delete('/users/:id', { id: 123 }) // 带查询参数的 DELETE 请求 const response = await client.delete('/users/:id', { id: 123, query: { permanent: true } }) ``` ## 🔧 参数处理 ### 路径参数 ```typescript // 单个路径参数 const response = await client.get('/users/:id', { id: 123 }) // 多个路径参数 const response = await client.get('/posts/:postId/comments/:commentId', { postId: 456, commentId: 789 }) // 嵌套路径参数 const response = await client.get('/organizations/:orgId/departments/:deptId/employees/:empId', { orgId: 'org123', deptId: 'dept456', empId: 'emp789' }) ``` ### 查询参数 ```typescript // 基本查询参数 const response = await client.get('/users', { page: 1, limit: 10, sort: 'name', order: 'asc' }) // 数组查询参数 const response = await client.get('/products', { categories: ['electronics', 'books'], tags: ['featured', 'new'] }) // 复杂查询参数 const response = await client.get('/search', { query: 'laptop', filters: { price: { min: 100, max: 1000 }, brand: ['apple', 'dell', 'hp'], inStock: true }, sort: { field: 'price', order: 'asc' } }) ``` ### 请求体 ```typescript // JSON 请求体 const response = await client.post('/users', { name: 'John Doe', email: 'john@example.com', profile: { age: 30, location: 'New York', interests: ['programming', 'music'] } }) // FormData 请求体 const formData = new FormData() formData.append('name', 'John Doe') formData.append('avatar', fileInput.files[0]) const response = await client.post('/users', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) // 文本请求体 const response = await client.post('/logs', 'User logged in at 2024-01-01', { headers: { 'Content-Type': 'text/plain' } }) ``` ## 📋 响应处理 ### 基本响应结构 ```typescript const response = await client.get('/users') if (response.error) { // 处理错误 console.error('Error:', response.error.message) console.error('Status:', response.error.status) console.error('Details:', response.error.details) } else { // 处理成功响应 console.log('Data:', response.data) console.log('Status:', response.status) console.log('Headers:', response.headers) } ``` ### 响应类型 ```typescript interface ApiResponse { data?: T error?: { message: string status: number details?: any } status: number headers: Record } // 使用类型化的响应 interface User { id: number name: string email: string } const response: ApiResponse = await client.get('/users') ``` ### 错误处理 ```typescript try { const response = await client.get('/users') if (response.error) { switch (response.error.status) { case 400: console.error('Bad Request:', response.error.message) break case 401: console.error('Unauthorized:', response.error.message) // 重定向到登录页面 break case 403: console.error('Forbidden:', response.error.message) break case 404: console.error('Not Found:', response.error.message) break case 500: console.error('Server Error:', response.error.message) break default: console.error('Unknown Error:', response.error.message) } } else { console.log('Success:', response.data) } } catch (error) { console.error('Network Error:', error) } ``` ## 🎛️ 请求配置 ### 全局配置 ```typescript const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 10000, retries: 3, defaultHeaders: { 'Content-Type': 'application/json', 'User-Agent': 'Vafast-API-Client/1.0.0' } }) ``` ### 请求级配置 ```typescript // 单个请求的配置 const response = await client.get('/users', {}, { timeout: 5000, retries: 1, headers: { 'Authorization': 'Bearer token123', 'X-Request-ID': 'req-123' } }) ``` ### 中间件配置 ```typescript // 添加请求中间件 client.use(async (config, next) => { // 添加认证头 if (localStorage.getItem('token')) { config.headers.Authorization = `Bearer ${localStorage.getItem('token')}` } // 添加请求 ID config.headers['X-Request-ID'] = `req-${Date.now()}` return await next(config) }) // 添加响应中间件 client.use(async (response, next) => { // 记录响应时间 console.log(`Request completed in ${Date.now() - response.startTime}ms`) return await next(response) }) ``` ## 🔄 重试机制 ### 自动重试 ```typescript const client = new VafastApiClient({ baseURL: 'https://api.example.com', retries: 3, retryDelay: 1000, retryCondition: (error) => { // 只在网络错误或 5xx 错误时重试 return error.status >= 500 || error.type === 'network' } }) ``` ### 自定义重试策略 ```typescript const client = new VafastApiClient({ baseURL: 'https://api.example.com', retries: 5, retryDelay: (attempt) => { // 指数退避策略 return Math.min(1000 * Math.pow(2, attempt), 10000) }, retryCondition: (error, attempt) => { // 最多重试 5 次,只在特定错误时重试 if (attempt >= 5) return false return error.status === 429 || error.status >= 500 } }) ``` ## 💾 缓存机制 ### 启用缓存 ```typescript const client = new VafastApiClient({ baseURL: 'https://api.example.com', enableCache: true, cacheExpiry: 300000, // 5分钟 cacheStrategy: 'memory' // 'memory' | 'localStorage' | 'sessionStorage' }) ``` ### 缓存控制 ```typescript // 强制刷新缓存 const response = await client.get('/users', {}, { cache: 'no-cache' }) // 使用缓存 const response = await client.get('/users', {}, { cache: 'force-cache' }) // 清除特定路径的缓存 client.clearCache('/users') // 清除所有缓存 client.clearCache() ``` ## 📊 请求统计 ### 启用统计 ```typescript const client = new VafastApiClient({ baseURL: 'https://api.example.com', enableStats: true }) // 获取统计信息 const stats = client.getStats() console.log('Total requests:', stats.totalRequests) console.log('Successful requests:', stats.successfulRequests) console.log('Failed requests:', stats.failedRequests) console.log('Average response time:', stats.averageResponseTime) ``` ### 自定义统计 ```typescript client.on('request', (config) => { console.log('Request started:', config.url) }) client.on('response', (response) => { console.log('Response received:', response.status) }) client.on('error', (error) => { console.error('Request failed:', error.message) }) ``` ## 🔗 完整示例 ```typescript import { VafastApiClient } from '@vafast/api-client' // 创建客户端 const client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', timeout: 10000, retries: 3, enableCache: true, cacheExpiry: 300000 }) // 用户管理 API class UserAPI { // 获取用户列表 static async getUsers(page = 1, limit = 10) { const response = await client.get('/users', { _page: page, _limit: limit }) if (response.error) { throw new Error(`Failed to fetch users: ${response.error.message}`) } return response.data } // 获取单个用户 static async getUser(id: number) { const response = await client.get('/users/:id', { id }) if (response.error) { throw new Error(`Failed to fetch user: ${response.error.message}`) } return response.data } // 创建用户 static async createUser(userData: { name: string; email: string }) { const response = await client.post('/users', userData) if (response.error) { throw new Error(`Failed to create user: ${response.error.message}`) } return response.data } // 更新用户 static async updateUser(id: number, userData: Partial<{ name: string; email: string }>) { const response = await client.put('/users/:id', userData, { id }) if (response.error) { throw new Error(`Failed to update user: ${response.error.message}`) } return response.data } // 删除用户 static async deleteUser(id: number) { const response = await client.delete('/users/:id', { id }) if (response.error) { throw new Error(`Failed to delete user: ${response.error.message}`) } return response.data } } // 使用示例 async function main() { try { // 获取用户列表 const users = await UserAPI.getUsers(1, 5) console.log('Users:', users) // 获取单个用户 const user = await UserAPI.getUser(1) console.log('User:', user) // 创建新用户 const newUser = await UserAPI.createUser({ name: 'John Doe', email: 'john@example.com' }) console.log('New user:', newUser) // 更新用户 const updatedUser = await UserAPI.updateUser(1, { name: 'John Updated' }) console.log('Updated user:', updatedUser) // 删除用户 await UserAPI.deleteUser(1) console.log('User deleted successfully') } catch (error) { console.error('API Error:', error.message) } } main() ``` ## 📚 下一步 现在您已经掌握了 Vafast API 客户端的基础用法,接下来可以: 1. **探索类型安全** - 学习如何创建类型安全的客户端 2. **学习 WebSocket** - 掌握实时通信功能 3. **配置中间件** - 自定义请求和响应处理 4. **高级配置** - 了解更复杂的配置选项 5. **错误处理** - 学习更完善的错误处理策略 如果您有任何问题,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/essential/handler.md' --- # 处理程序 处理程序是响应每个路由请求的函数。 接受请求信息并返回响应给客户端。 在其他框架中,处理程序也被称为 **控制器**。 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'hello world') } ]) ``` ## 基本用法 ### 简单响应 最简单的处理程序直接返回数据: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello World') }, { method: 'GET', path: '/json', handler: createHandler(() => ({ message: 'Hello World' })) }, { method: 'GET', path: '/html', handler: createHandler(() => '

Hello World

') } ]) ``` ### 访问请求信息 处理程序可以访问请求的各种信息: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/info', handler: createHandler(({ req, headers, query }) => { return { url: req.url, method: req.method, userAgent: headers['user-agent'], query: query.search || 'default' } }) } ]) ``` ### 异步处理 处理程序支持异步操作: ```typescript const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { // 模拟数据库操作 const user = await createUser(body) return user }) } ]) ``` ## 参数解构 Vafast 使用参数解构来提供类型安全的访问: ### 基本参数 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/user/:id', handler: createHandler(({ params, query, headers }) => { const userId = params.id const page = query.page || '1' const auth = headers.authorization return `User ${userId}, Page ${page}, Auth: ${auth}` }) } ]) ``` ### 请求体 ```typescript const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { const { name, email, age } = body if (!name || !email) { return new Response('Name and email are required', { status: 400 }) } return { name, email, age: age || 18 } }) } ]) ``` ### 查询参数 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/search', handler: createHandler(({ query }) => { const { q, page = '1', limit = '10', sort = 'name' } = query return { query: q, page: parseInt(page), limit: parseInt(limit), sort, results: [] } }) } ]) ``` ## 响应处理 ### 自动响应类型 Vafast 会自动处理不同类型的返回值: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/string', handler: createHandler(() => 'Plain text') // 返回 text/plain }, { method: 'GET', path: '/json', handler: createHandler(() => ({ data: 'JSON' })) // 返回 application/json }, { method: 'GET', path: '/html', handler: createHandler(() => '

HTML

') // 返回 text/html }, { method: 'GET', path: '/number', handler: createHandler(() => 42) // 返回 text/plain } ]) ``` ### 手动响应控制 如果需要更精细的控制,可以返回 Response 对象: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/custom', handler: createHandler(() => { return new Response('Custom response', { status: 200, headers: { 'Content-Type': 'text/plain', 'X-Custom-Header': 'value' } }) }) }, { method: 'GET', path: '/redirect', handler: createHandler(() => { return new Response(null, { status: 302, headers: { 'Location': '/new-page' } }) }) } ]) ``` ### 错误响应 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/user/:id', handler: createHandler(({ params }) => { const userId = params.id if (!userId || isNaN(Number(userId))) { return new Response('Invalid user ID', { status: 400 }) } if (userId === '999') { return new Response('User not found', { status: 404 }) } return { id: userId, name: 'John Doe' } }) } ]) ``` ## 中间件集成 处理程序可以与中间件配合使用: ```typescript const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } return await next() } const routes = defineRoutes([ { method: 'GET', path: '/protected', handler: createHandler(() => 'Protected content'), middleware: [authMiddleware] } ]) ``` ## Schema 验证 处理程序可以与 TypeBox 验证集成,使用两参数形式: ```typescript import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }), age: Type.Optional(Type.Number({ minimum: 0 })) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler( { body: userSchema }, ({ body }) => { // body 已经通过验证,类型安全 const { name, email, age } = body return { name, email, age: age || 18 } } ) } ]) ``` ## 最佳实践 ### 1. 保持处理程序简洁 ```typescript // ✅ 好的做法 const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { const user = await createUser(body) return user }) } ]) // ❌ 避免的做法 const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { // 不要在这里放太多业务逻辑 const { name, email, age, address, phone, preferences, ... } = body // 复杂的验证逻辑 // 数据库操作 // 邮件发送 // 日志记录 // 等等... }) } ]) ``` ### 2. 使用适当的错误处理 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/user/:id', handler: createHandler(async ({ params }) => { try { const user = await getUserById(params.id) if (!user) { return new Response('User not found', { status: 404 }) } return user } catch (error) { console.error('Error fetching user:', error) return new Response('Internal server error', { status: 500 }) } }) } ]) ``` ### 3. 利用类型安全 ```typescript interface User { id: string name: string email: string } const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }): Promise => { const user = await createUser(body) return user }) } ]) ``` ## 总结 Vafast 的处理程序系统提供了: * ✅ 类型安全的参数访问 * ✅ 自动响应类型推断 * ✅ 中间件集成支持 * ✅ 验证系统集成 * ✅ 异步操作支持 * ✅ 灵活的响应控制 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强处理程序功能 * 探索 [验证系统](/essential/validation) 了解如何验证请求数据 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/api-client/installation.md' --- # 安装指南 Vafast API 客户端提供了多种安装方式,您可以根据项目需求选择最适合的方法。 ## 📦 包管理器安装 ### 使用 Bun(推荐) ```bash bun add @vafast/api-client ``` ### 使用 npm ```bash npm install @vafast/api-client ``` ### 使用 yarn ```bash yarn add @vafast/api-client ``` ### 使用 pnpm ```bash pnpm add @vafast/api-client ``` ## 🔧 开发依赖安装 如果您需要在开发环境中使用类型定义或测试工具: ```bash # Bun bun add -d @vafast/api-client # npm npm install -D @vafast/api-client # yarn yarn add -D @vafast/api-client # pnpm pnpm add -D @vafast/api-client ``` ## 📋 系统要求 ### Node.js 版本 * **Node.js**: 18.0.0 或更高版本 * **Bun**: 1.0.0 或更高版本(推荐) ### TypeScript 支持 * **TypeScript**: 5.0.0 或更高版本 * **ES2020** 或更高版本支持 ### 浏览器支持 * **现代浏览器**: Chrome 88+, Firefox 85+, Safari 14+, Edge 88+ * **不支持**: Internet Explorer ## 🚀 快速开始 ### 1. 创建项目 ```bash # 使用 Bun 创建新项目 bun create vafast my-api-client cd my-api-client # 或使用 npm npm create vafast@latest my-api-client cd my-api-client ``` ### 2. 安装依赖 ```bash bun add @vafast/api-client ``` ### 3. 创建客户端 ```typescript // src/client.ts import { VafastApiClient } from '@vafast/api-client' const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 10000, retries: 3 }) export default client ``` ### 4. 使用客户端 ```typescript // src/index.ts import client from './client' async function main() { try { // 发送 GET 请求 const response = await client.get('/users') if (response.error) { console.error('Error:', response.error) } else { console.log('Users:', response.data) } } catch (error) { console.error('Request failed:', error) } } main() ``` ### 5. 运行项目 ```bash bun run src/index.ts ``` ## ⚙️ 配置选项 ### 基础配置 ```typescript import { VafastApiClient } from '@vafast/api-client' const client = new VafastApiClient({ // 基础 URL baseURL: 'https://api.example.com', // 默认请求头 defaultHeaders: { 'Content-Type': 'application/json', 'User-Agent': 'Vafast-API-Client/1.0.0' }, // 请求超时时间(毫秒) timeout: 10000, // 重试次数 retries: 3, // 重试延迟(毫秒) retryDelay: 1000, // 是否启用缓存 enableCache: true, // 缓存过期时间(毫秒) cacheExpiry: 300000, // 5分钟 }) ``` ### 高级配置 ```typescript import { VafastApiClient } from '@vafast/api-client' const client = new VafastApiClient({ baseURL: 'https://api.example.com', // 请求拦截器 requestInterceptors: [ async (config) => { // 添加认证头 if (localStorage.getItem('token')) { config.headers.Authorization = `Bearer ${localStorage.getItem('token')}` } return config } ], // 响应拦截器 responseInterceptors: [ async (response) => { // 处理响应数据 if (response.status === 401) { // 处理未授权错误 localStorage.removeItem('token') window.location.href = '/login' } return response } ], // 错误处理器 errorHandler: (error) => { console.error('API Error:', error) // 可以在这里添加全局错误处理逻辑 }, // 日志配置 logging: { enabled: true, level: 'info', // 'debug' | 'info' | 'warn' | 'error' format: 'json' // 'json' | 'text' } }) ``` ## 🔐 环境变量配置 ### 创建环境配置文件 ```bash # .env API_BASE_URL=https://api.example.com API_TIMEOUT=10000 API_RETRIES=3 API_ENABLE_CACHE=true API_CACHE_EXPIRY=300000 # .env.development API_BASE_URL=http://localhost:3000 API_TIMEOUT=5000 API_RETRIES=1 # .env.production API_BASE_URL=https://api.production.com API_TIMEOUT=15000 API_RETRIES=5 ``` ### 使用环境变量 ```typescript import { VafastApiClient } from '@vafast/api-client' const client = new VafastApiClient({ baseURL: process.env.API_BASE_URL || 'http://localhost:3000', timeout: parseInt(process.env.API_TIMEOUT || '10000'), retries: parseInt(process.env.API_RETRIES || '3'), enableCache: process.env.API_ENABLE_CACHE === 'true', cacheExpiry: parseInt(process.env.API_CACHE_EXPIRY || '300000') }) ``` ## 📱 浏览器环境 ### 使用 CDN ```html Vafast API Client Demo

Vafast API Client

``` ## 🧪 测试环境配置 ### 安装测试依赖 ```bash # Bun bun add -d bun @types/node # npm npm install -D jest @types/jest ts-jest # yarn yarn add -D jest @types/jest ts-jest # pnpm pnpm add -D jest @types/jest ts-jest ``` ### 测试配置 ```typescript // test/client.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('VafastApiClient', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', timeout: 5000 }) }) it('should fetch users successfully', async () => { const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(Array.isArray(response.data)).toBe(true) }) it('should handle errors gracefully', async () => { const response = await client.get('/nonexistent') expect(response.error).toBeDefined() expect(response.data).toBeUndefined() }) }) ``` ### 运行测试 ```bash # Bun bun test # npm npm test # yarn yarn test # pnpm pnpm test ``` ## 🔍 故障排除 ### 常见问题 #### 1. 类型错误 ```bash # 确保安装了正确的类型定义 bun add -d @types/node # 检查 TypeScript 配置 npx tsc --noEmit ``` #### 2. 网络错误 ```typescript // 检查网络连接 const client = new VafastApiClient({ baseURL: 'https://api.example.com', timeout: 5000, // 减少超时时间 retries: 1 // 减少重试次数 }) ``` #### 3. CORS 问题 ```typescript // 在服务器端配置 CORS // 或使用代理服务器 const client = new VafastApiClient({ baseURL: '/api', // 使用相对路径 proxy: 'http://localhost:3001' // 配置代理 }) ``` ### 调试模式 ```typescript const client = new VafastApiClient({ baseURL: 'https://api.example.com', logging: { enabled: true, level: 'debug' } }) // 启用详细日志 client.setLogLevel('debug') ``` ## 📚 下一步 安装完成后,您可以: 1. **阅读基础用法** - 学习如何发送 HTTP 请求 2. **探索类型安全** - 了解如何创建类型安全的客户端 3. **学习 WebSocket** - 掌握实时通信功能 4. **配置中间件** - 自定义请求和响应处理 5. **运行测试** - 确保代码质量 如果您在安装过程中遇到任何问题,请查看我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/quick-start.md' --- # 快速入门 Vafast 是一个高性能、类型安全的 TypeScript Web 框架。内置 JIT 编译验证器、中间件预编译等优化技术,比 Express/Hono 快约 **1.8x**。 \ Vafast 针对 Bun 进行了优化,Bun 是一种旨在作为 Node.js 的直接替代品的 JavaScript 运行时。 你可以使用下面的命令安装 Bun: ::: code-group ```bash [MacOS/Linux] curl -fsSL https://bun.sh/install | bash ``` ```bash [Windows] powershell -c "irm bun.sh/install.ps1 | iex" ``` ::: \ 我们建议使用 `bun create vafast` 启动一个新的 Vafast 服务器,该命令会自动设置所有内容。 ```bash bun create vafast my-app ``` 完成后,你应该会在目录中看到名为 `my-app` 的文件夹。 ```bash cd my-app ``` 通过以下命令启动开发服务器: ```bash bun dev ``` 访问 [localhost:3000](http://localhost:3000) 应该会显示 "Hello Vafast"。 ::: tip Vafast 提供了 `dev` 命令,能够在文件更改时自动重新加载你的服务器。 ::: 要手动创建一个新的 Vafast 应用,请将 Vafast 作为一个包安装: ```bash bun add vafast bun add -d @types/bun ``` 这将安装 Vafast 和 Bun 的类型定义。 创建一个新文件 `src/index.ts`,并添加以下代码: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` 保存文件后,运行以下命令启动开发服务器: ```bash bun run --hot src/index.ts ``` Vafast 也支持 Node.js 环境。 首先安装 Node.js(推荐版本 18+),然后安装 Vafast: ```bash npm install vafast ``` 创建一个新文件 `src/index.ts`: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` 使用 Node.js 启动: ```bash node --loader ts-node/esm src/index.ts ``` Vafast 基于 Web 标准构建,可以在任何支持 Web 标准的运行时中运行。 ```bash npm install vafast ``` 创建应用文件: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## 基础示例 ### 简单路由 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }, { method: 'GET', path: '/users', handler: createHandler(() => ['user1', 'user2', 'user3']) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### 带参数的路由 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => { return `User ID: ${params.id}` }) }, { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { return { success: true, user: body } }) } ]) ``` ### 使用 Schema 验证 ```typescript import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ pattern: '^[^@]+@[^@]+\\.[^@]+$' }), age: Type.Optional(Type.Number({ minimum: 0 })) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler( { body: userSchema }, ({ body }) => { // body 已验证并自动推导类型 return { success: true, user: body } } ) } ]) ``` ## 下一步 现在你已经成功创建了一个 Vafast 应用!接下来你可以: * 查看 [核心概念](/key-concept) 了解 Vafast 的基本原理 * 阅读 [路由指南](/routing) 学习如何定义路由 * 探索 [中间件系统](/middleware) 了解如何扩展功能 * 查看 [示例项目](/examples) 获取更多灵感 如果你遇到任何问题,请查看 [故障排除](/troubleshooting) 页面或加入我们的社区。 --- --- url: 'https://vafast.dev/tutorial.md' --- # Vafast 教程 我们将构建一个简单的 CRUD 笔记 API 服务器。 这里没有数据库,也没有其他"生产就绪"功能。本教程将重点介绍 Vafast 的功能以及如何仅使用 Vafast。 如果你跟着做,我们预计大约需要 15-20 分钟。 *** ### 来自其他框架? 如果您使用过其他流行框架,如 Express、Fastify 或 Hono,您会发现 Vafast 非常熟悉,只是有一些小差异。 ### 不喜欢教程? 如果您更倾向于自己动手的方式,可以跳过这个教程,直接访问 [关键概念](/key-concept) 页面,深入了解 Vafast 的工作原理。 ### llms.txt 或者,您可以下载 llms.txt 或 llms-full.txt,并将其输入您最喜欢的 LLM,如 ChatGPT、Claude 或 Gemini,以获得更互动的体验。 ## 设置 Vafast 的设计是运行在 [Bun](https://bun.sh) 上,这是一个替代 Node.js 的运行时,但它也可以运行在 Node.js 或任何支持 Web 标准 API 的运行时上。 然而,在本教程中,我们将使用 Bun。 如果您还没有安装 Bun,请先安装。 ::: code-group ```bash [MacOS/Linux] curl -fsSL https://bun.sh/install | bash ``` ```bash [Windows] powershell -c "irm bun.sh/install.ps1 | iex" ``` ::: ### 创建一个新项目 ```bash # 创建一个新目录 mkdir hi-vafast cd hi-vafast # 初始化项目 bun init # 安装 Vafast bun add vafast bun add -d @types/bun ``` ### 项目结构 创建项目后,您应该看到以下结构: ``` hi-vafast/ ├── src/ │ └── index.ts ├── package.json ├── tsconfig.json └── README.md ``` ### 启动开发服务器 ```bash bun run --hot src/index.ts ``` 现在您应该能够在 看到 "Hello Vafast!" 消息。 ## 构建笔记 API 现在让我们开始构建我们的笔记 API。我们将创建一个简单的内存存储系统来管理笔记。 ### 1. 定义笔记类型 首先,让我们在 `src/index.ts` 中定义我们的笔记类型: ```typescript interface Note { id: string title: string content: string createdAt: Date updatedAt: Date } // 内存存储 const notes: Note[] = [] ``` ### 2. 创建路由 现在让我们创建我们的 API 路由: ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ // 获取所有笔记 { method: 'GET', path: '/notes', handler: createHandler(() => { return notes }) }, // 获取单个笔记 { method: 'GET', path: '/notes/:id', handler: createHandler(({ params }) => { const id = params.id const note = notes.find(n => n.id === id) if (!note) { return new Response('Note not found', { status: 404 }) } return note }) }, // 创建笔记 { method: 'POST', path: '/notes', handler: createHandler(async ({ req }) => { const body = await req.json() const { title, content } = body if (!title || !content) { return new Response('Title and content are required', { status: 400 }) } const note: Note = { id: Date.now().toString(), title, content, createdAt: new Date(), updatedAt: new Date() } notes.push(note) return note }) }, // 更新笔记 { method: 'PUT', path: '/notes/:id', handler: createHandler(async ({ req, params }) => { const id = params.id const noteIndex = notes.findIndex(n => n.id === id) if (noteIndex === -1) { return new Response('Note not found', { status: 404 }) } const body = await req.json() const { title, content } = body if (!title || !content) { return new Response('Title and content are required', { status: 400 }) } notes[noteIndex] = { ...notes[noteIndex], title, content, updatedAt: new Date() } return notes[noteIndex] }) }, // 删除笔记 { method: 'DELETE', path: '/notes/:id', handler: createHandler(({ params }) => { const id = params.id const noteIndex = notes.findIndex(n => n.id === id) if (noteIndex === -1) { return new Response('Note not found', { status: 404 }) } const deletedNote = notes.splice(noteIndex, 1)[0] return deletedNote }) } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### 3. 测试 API 现在让我们测试我们的 API。重启开发服务器: ```bash bun run --hot src/index.ts ``` #### 创建笔记 ```bash curl -X POST http://localhost:3000/notes \ -H "Content-Type: application/json" \ -d '{"title": "我的第一个笔记", "content": "这是笔记的内容"}' ``` #### 获取所有笔记 ```bash curl http://localhost:3000/notes ``` #### 获取单个笔记 ```bash curl http://localhost:3000/notes/1234567890 ``` #### 更新笔记 ```bash curl -X PUT http://localhost:3000/notes/1234567890 \ -H "Content-Type: application/json" \ -d '{"title": "更新的标题", "content": "更新的内容"}' ``` #### 删除笔记 ```bash curl -X DELETE http://localhost:3000/notes/1234567890 ``` ## 添加中间件 让我们为我们的 API 添加一些中间件来增强功能: ### 1. 日志中间件 ```typescript const logMiddleware = async (req: Request, next: () => Promise) => { const start = Date.now() console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`) const response = await next() const duration = Date.now() - start console.log(`Response: ${response.status} (${duration}ms)`) return response } ``` ### 2. 错误处理中间件 ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { console.error('Error:', error) return new Response( JSON.stringify({ error: 'Internal Server Error', message: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' } } ) } } ``` ### 3. 使用中间件 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/notes', middleware: [logMiddleware, errorHandler], handler: createHandler(() => { return notes }) } // ... 其他路由 ]) ``` ## 添加验证 让我们为我们的 API 添加一些基本的验证。Vafast 集成了 TypeBox 进行 Schema 验证: ```typescript import { Type } from '@sinclair/typebox' const noteSchema = Type.Object({ title: Type.String({ minLength: 1, maxLength: 100 }), content: Type.String({ minLength: 1, maxLength: 1000 }) }) const routes = defineRoutes([ { method: 'POST', path: '/notes', handler: createHandler(async ({ req, body }) => { // body 已经通过验证,类型安全 const { title, content } = body const note: Note = { id: Date.now().toString(), title, content, createdAt: new Date(), updatedAt: new Date() } notes.push(note) return note }), body: noteSchema } ]) ``` ## 总结 恭喜!您已经成功构建了一个完整的 CRUD API 服务器,包括: * ✅ 创建、读取、更新和删除笔记 * ✅ 中间件支持(日志记录、错误处理) * ✅ Schema 验证 * ✅ 类型安全的 TypeScript 代码 * ✅ 内存数据存储 ### 下一步 现在您可以: 1. **添加更多功能** - 如搜索、分页、排序等 2. **集成数据库** - 如 SQLite、PostgreSQL 或 MongoDB 3. **添加身份验证** - 用户登录和权限控制 4. **部署到生产环境** - 如 Vercel、Netlify 或自己的服务器 ### 相关资源 * [核心概念](/key-concept) - 深入了解 Vafast 的工作原理 * [路由指南](/routing) - 学习更多路由技巧 * [中间件系统](/middleware) - 探索中间件的强大功能 * [API 参考](/api) - 完整的 API 文档 如果您有任何问题或需要帮助,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/middleware/stream.md' --- # 流中间件 ::: warning 此中间件处于维护模式,将不再接收新功能。我们建议使用 [生成器流](/essential/handler#stream) 代替。 ::: 此中间件添加对流响应或向客户端发送服务器推送事件的支持。 安装命令: ```bash bun add @vafastjs/stream ``` 然后使用它: ```typescript import { Vafast } from 'vafast' import { Stream } from '@vafastjs/stream' new Vafast() .get('/', () => new Stream(async (stream) => { stream.send('hello') await stream.wait(1000) stream.send('world') stream.close() })) .listen(3000) ``` 默认情况下,`Stream` 将返回 `Response`,其 `content-type` 为 `text/event-stream; charset=utf8`。 ## 构造函数 以下是 `Stream` 接受的构造参数: 1. 流: * 自动:自动从提供的值流响应 * Iterable * AsyncIterable * ReadableStream * Response * 手动:`(stream: this) => unknown` 或 `undefined` 的回调 2. 选项:`StreamOptions` * [event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event):标识事件类型的字符串 * [retry](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#retry):重连时间(毫秒) ## 方法 以下是 `Stream` 提供的方法: ### send 将数据加入队列以发送回客户端 ### close 关闭流 ### wait 返回在提供的毫秒数后解析的 promise ### value `ReadableStream` 的内部值 ## 模式 以下是使用该中间件的常见模式。 * [OpenAI](#openai) * [获取流](#fetch-stream) * [服务器推送事件](#server-sent-event) ## OpenAI 当参数为 `Iterable` 或 `AsyncIterable` 时,自动模式将被触发,自动将响应流返回给客户端。 以下是集成 ChatGPT 到 Vafast 的示例。 ```ts new Vafast() .get( '/ai', ({ query: { prompt } }) => new Stream( openai.chat.completions.create({ model: 'gpt-3.5-turbo', stream: true, messages: [{ role: 'user', content: prompt }] }) ) ) ``` 默认情况下 [openai](https://npmjs.com/package/openai) 的 chatGPT 完成返回 `AsyncIterable`,因此您应该能够将 OpenAI 包裹在 `Stream` 中。 ## 获取流 您可以传递一个从返回流的端点获取的 fetch 来代理一个流。 这对于那些使用 AI 文本生成的端点非常有用,因为您可以直接代理,例如 [Cloudflare AI](https://developers.cloudflare.com/workers-ai/models/llm/#examples---chat-style-with-system-prompt-preferred)。 ```ts const model = '@cf/meta/llama-2-7b-chat-int8' const endpoint = `https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/ai/run/${model}` new Vafast() .get('/ai', ({ query: { prompt } }) => fetch(endpoint, { method: 'POST', headers: { authorization: `Bearer ${API_TOKEN}`, 'content-type': 'application/json' }, body: JSON.stringify({ messages: [ { role: 'system', content: '你是一个友好的助手' }, { role: 'user', content: prompt } ] }) }) ) ``` ## 服务器推送事件 当参数为 `callback` 或 `undefined` 时,手动模式将被触发,允许您控制流。 ### 基于回调 以下是使用构造函数回调创建服务器推送事件端点的示例 ```ts new Vafast() .get('/source', () => new Stream((stream) => { const interval = setInterval(() => { stream.send('hello world') }, 500) setTimeout(() => { clearInterval(interval) stream.close() }, 3000) }) ) ``` ### 基于值 以下是使用基于值创建服务器推送事件端点的示例 ```ts new Vafast() .get('/source', () => { const stream = new Stream() const interval = setInterval(() => { stream.send('hello world') }, 500) setTimeout(() => { clearInterval(interval) stream.close() }, 3000) return stream }) ``` 基于回调和基于值的流在工作原理上相同,但语法不同以满足您的偏好。 --- --- url: 'https://vafast.dev/patterns/unit-test.md' --- # 单元测试 作为 WinterCG 的合规实现,我们可以使用 Request/Response 类来测试 Vafast 服务器。 Vafast 提供了 **Server.fetch** 方法,该方法接受 Web 标准 [Request](https://developer.mozilla.org/zh-CN/docs/Web/API/Request) 并返回 [Response](https://developer.mozilla.org/zh-CN/docs/Web/API/Response),模拟 HTTP 请求。 Bun 包含一个内置的 [测试运行器](https://bun.sh/docs/cli/test),通过 `bun:test` 模块提供类似 Jest 的 API,便于创建单元测试。 在项目根目录下创建 **test/index.test.ts**,内容如下: ```typescript // test/index.test.ts import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Vafast', () => { it('returns a response', async () => { const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'hi') } ]) const server = new Server(routes) const response = await server .fetch(new Request('http://localhost/')) .then((res) => res.text()) expect(response).toBe('hi') }) }) ``` 然后我们可以通过运行 **bun test** 来进行测试。 ```bash bun test ``` 对 Vafast 服务器的新请求必须是一个完全有效的 URL,**不能**是 URL 的一部分。 请求必须提供如下格式的 URL: | URL | 有效 | | --------------------- | ----- | | http://localhost/user | ✅ | | /user | ❌ | 我们还可以使用其他测试库,如 Jest 或其他测试库来创建 Vafast 单元测试。 ## 路由测试 我们可以测试不同的路由和 HTTP 方法: ```typescript // test/routes.test.ts import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Vafast Routes', () => { const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => `User ${params.id}`) }, { method: 'POST', path: '/users', handler: createHandler(({ body }) => ({ id: 1, ...body })) } ]) const server = new Server(routes) it('handles GET request with params', async () => { const response = await server .fetch(new Request('http://localhost/users/123')) .then((res) => res.text()) expect(response).toBe('User 123') }) it('handles POST request with body', async () => { const response = await server .fetch(new Request('http://localhost/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John', email: 'john@example.com' }) })) .then((res) => res.json()) expect(response).toEqual({ id: 1, name: 'John', email: 'john@example.com' }) }) }) ``` ## 中间件测试 测试中间件的功能: ```typescript // test/middleware.test.ts import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Vafast Middleware', () => { const loggingMiddleware = async (req: Request, next: () => Promise) => { console.log(`${req.method} ${req.url}`) const response = await next() console.log(`Response: ${response.status}`) return response } const routes = defineRoutes([ { method: 'GET', path: '/test', handler: createHandler(() => 'Hello from middleware') } ]) const server = new Server(routes) server.use(loggingMiddleware) it('executes middleware correctly', async () => { const response = await server .fetch(new Request('http://localhost/test')) .then((res) => res.text()) expect(response).toBe('Hello from middleware') }) }) ``` ## 验证测试 测试 TypeBox 验证功能: ```typescript // test/validation.test.ts import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' describe('Vafast Validation', () => { const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }), age: Type.Number({ minimum: 0 }) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => ({ success: true, user: body })), body: userSchema } ]) const server = new Server(routes) it('validates valid request body', async () => { const response = await server .fetch(new Request('http://localhost/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John', email: 'john@example.com', age: 25 }) })) .then((res) => res.json()) expect(response.success).toBe(true) expect(response.user.name).toBe('John') }) it('rejects invalid request body', async () => { const response = await server .fetch(new Request('http://localhost/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: '', // 违反 minLength: 1 email: 'invalid-email', // 违反 format: 'email' age: -5 // 违反 minimum: 0 }) })) expect(response.status).toBe(400) }) }) ``` ## 错误处理测试 测试错误处理逻辑: ```typescript // test/error-handling.test.ts import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Vafast Error Handling', () => { const routes = defineRoutes([ { method: 'GET', path: '/error', handler: createHandler(() => { throw new Error('Something went wrong') }) }, { method: 'GET', path: '/not-found', handler: createHandler(() => 'This should not be reached') } ]) const server = new Server(routes) it('handles internal server errors', async () => { const response = await server .fetch(new Request('http://localhost/error')) expect(response.status).toBe(500) }) it('returns 404 for non-existent routes', async () => { const response = await server .fetch(new Request('http://localhost/non-existent')) expect(response.status).toBe(404) }) it('returns 405 for method not allowed', async () => { const response = await server .fetch(new Request('http://localhost/not-found', { method: 'POST' })) expect(response.status).toBe(405) }) }) ``` ## 集成测试 测试完整的应用流程: ```typescript // test/integration.test.ts import { describe, expect, it } from 'bun:test' import { Server, defineRoutes, createHandler } from 'vafast' describe('Vafast Integration', () => { let server: Server beforeAll(() => { const routes = defineRoutes([ { method: 'GET', path: '/health', handler: createHandler(() => ({ status: 'ok' })) }, { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => ({ id: params.id, name: 'Test User', email: 'test@example.com' })) }, { method: 'POST', path: '/users', handler: createHandler(({ body }) => ({ id: Date.now(), ...body, createdAt: new Date().toISOString() })) } ]) server = new Server(routes) }) it('performs complete user CRUD flow', async () => { // 1. 检查健康状态 const healthResponse = await server .fetch(new Request('http://localhost/health')) .then((res) => res.json()) expect(healthResponse.status).toBe('ok') // 2. 创建用户 const createResponse = await server .fetch(new Request('http://localhost/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Integration Test User', email: 'integration@example.com' }) })) .then((res) => res.json()) expect(createResponse.name).toBe('Integration Test User') expect(createResponse.id).toBeDefined() expect(createResponse.createdAt).toBeDefined() // 3. 获取用户 const userId = createResponse.id const getUserResponse = await server .fetch(new Request(`http://localhost/users/${userId}`)) .then((res) => res.json()) expect(getUserResponse.id).toBe(userId) expect(getUserResponse.name).toBe('Integration Test User') }) }) ``` ## 测试配置 在 `package.json` 中配置测试脚本: ```json { "scripts": { "test": "bun test", "test:watch": "bun test --watch", "test:coverage": "bun test --coverage" } } ``` ## 测试最佳实践 1. **隔离测试**:每个测试应该独立运行,不依赖其他测试的状态 2. **清理资源**:在测试后清理任何创建的资源 3. **模拟外部依赖**:使用 mock 来隔离外部服务 4. **测试边界情况**:测试正常流程和异常情况 5. **保持测试简单**:每个测试只测试一个功能点 ## 总结 Vafast 的测试系统提供了: * ✅ 基于 WinterCG 标准的 Request/Response 测试 * ✅ 完整的路由测试支持 * ✅ 中间件测试能力 * ✅ 验证系统测试 * ✅ 错误处理测试 * ✅ 集成测试支持 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强功能 * 探索 [验证系统](/essential/validation) 了解类型安全 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/api-client/test.md' --- # 测试指南 测试是确保代码质量的关键部分。Vafast API 客户端提供了完整的测试支持,让您能够轻松地测试各种场景。 ## 🧪 测试环境设置 ### 安装测试依赖 ```bash # 使用 Bun(推荐) bun add -d bun @types/node # 使用 npm npm install -D jest @types/jest ts-jest # 使用 yarn yarn add -D jest @types/jest ts-jest # 使用 pnpm pnpm add -D jest @types/jest ts-jest ``` ### 配置测试环境 ```typescript // test/setup.ts import { beforeAll, afterAll } from 'bun:test' // 设置测试环境变量 process.env.NODE_ENV = 'test' process.env.API_BASE_URL = 'http://localhost:3000' // 全局测试设置 beforeAll(() => { console.log('Setting up test environment...') }) afterAll(() => { console.log('Cleaning up test environment...') }) ``` ## 🔧 基础测试 ### 客户端实例测试 ```typescript // test/client.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('VafastApiClient', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', timeout: 5000 }) }) it('should create client instance', () => { expect(client).toBeInstanceOf(VafastApiClient) expect(client.config.baseURL).toBe('https://jsonplaceholder.typicode.com') expect(client.config.timeout).toBe(5000) }) it('should have default configuration', () => { expect(client.config.retries).toBe(3) expect(client.config.enableCache).toBe(false) }) it('should update configuration', () => { client.updateConfig({ timeout: 10000 }) expect(client.config.timeout).toBe(10000) }) }) ``` ### HTTP 方法测试 ```typescript // test/http-methods.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('HTTP Methods', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com' }) }) describe('GET', () => { it('should fetch users successfully', async () => { const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(Array.isArray(response.data)).toBe(true) expect(response.data.length).toBeGreaterThan(0) }) it('should handle query parameters', async () => { const response = await client.get('/users', { _page: 1, _limit: 5 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeLessThanOrEqual(5) }) it('should handle path parameters', async () => { const response = await client.get('/users/:id', { id: 1 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.id).toBe(1) }) }) describe('POST', () => { it('should create user successfully', async () => { const userData = { name: 'John Doe', email: 'john@example.com' } const response = await client.post('/users', userData) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.name).toBe(userData.name) expect(response.data.email).toBe(userData.email) }) }) describe('PUT', () => { it('should update user successfully', async () => { const updateData = { name: 'John Updated' } const response = await client.put('/users/:id', updateData, { id: 1 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.name).toBe(updateData.name) }) }) describe('DELETE', () => { it('should delete user successfully', async () => { const response = await client.delete('/users/:id', { id: 1 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) }) }) ``` ## 🚨 错误处理测试 ### 网络错误测试 ```typescript // test/error-handling.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Error Handling', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://invalid-domain-that-does-not-exist.com', timeout: 1000 }) }) it('should handle network errors', async () => { const response = await client.get('/test') expect(response.error).toBeDefined() expect(response.error?.type).toBe('network') expect(response.data).toBeUndefined() }) it('should handle timeout errors', async () => { const response = await client.get('/test') expect(response.error).toBeDefined() expect(response.error?.type).toBe('timeout') }) it('should handle 404 errors', async () => { // 使用有效的域名但无效的路径 client.updateConfig({ baseURL: 'https://jsonplaceholder.typicode.com' }) const response = await client.get('/nonexistent') expect(response.error).toBeDefined() expect(response.error?.status).toBe(404) }) it('should handle 500 errors', async () => { // 模拟服务器错误 const mockServer = new VafastApiClient({ baseURL: 'https://httpstat.us' }) const response = await mockServer.get('/500') expect(response.error).toBeDefined() expect(response.error?.status).toBe(500) }) }) ``` ### 重试机制测试 ```typescript // test/retry.test.ts import { describe, expect, it, beforeEach, jest } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Retry Mechanism', () => { let client: VafastApiClient let mockFetch: jest.Mock beforeEach(() => { mockFetch = jest.fn() global.fetch = mockFetch client = new VafastApiClient({ baseURL: 'https://api.example.com', retries: 3, retryDelay: 100 }) }) it('should retry failed requests', async () => { // 模拟前两次失败,第三次成功 mockFetch .mockRejectedValueOnce(new Error('Network error')) .mockRejectedValueOnce(new Error('Network error')) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ success: true }) }) const response = await client.get('/test') expect(mockFetch).toHaveBeenCalledTimes(3) expect(response.error).toBeUndefined() expect(response.data).toEqual({ success: true }) }) it('should respect retry limit', async () => { // 模拟所有请求都失败 mockFetch.mockRejectedValue(new Error('Network error')) const response = await client.get('/test') expect(mockFetch).toHaveBeenCalledTimes(4) // 初始请求 + 3次重试 expect(response.error).toBeDefined() expect(response.error?.type).toBe('network') }) it('should use custom retry condition', async () => { client = new VafastApiClient({ baseURL: 'https://api.example.com', retries: 2, retryCondition: (error) => { // 只在特定错误时重试 return error.status === 429 } }) // 模拟 400 错误(不应该重试) mockFetch.mockResolvedValue({ ok: false, status: 400, json: async () => ({ error: 'Bad Request' }) }) const response = await client.get('/test') expect(mockFetch).toHaveBeenCalledTimes(1) // 不应该重试 expect(response.error?.status).toBe(400) }) }) ``` ## 🔄 中间件测试 ### 请求中间件测试 ```typescript // test/middleware.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Middleware', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com' }) }) it('should execute request middleware', async () => { const requestMiddleware = jest.fn() client.use(async (config, next) => { requestMiddleware(config) return await next(config) }) await client.get('/users') expect(requestMiddleware).toHaveBeenCalled() expect(requestMiddleware.mock.calls[0][0]).toHaveProperty('url') expect(requestMiddleware.mock.calls[0][0]).toHaveProperty('method') }) it('should modify request in middleware', async () => { client.use(async (config, next) => { // 添加自定义头 config.headers['X-Custom-Header'] = 'test-value' return await next(config) }) const response = await client.get('/users') expect(response.error).toBeUndefined() // 注意:这里我们无法直接验证请求头,但可以验证请求成功 }) it('should execute response middleware', async () => { const responseMiddleware = jest.fn() client.use(async (response, next) => { responseMiddleware(response) return await next(response) }) await client.get('/users') expect(responseMiddleware).toHaveBeenCalled() expect(responseMiddleware.mock.calls[0][0]).toHaveProperty('data') expect(responseMiddleware.mock.calls[0][0]).toHaveProperty('status') }) it('should handle middleware errors', async () => { client.use(async (config, next) => { throw new Error('Middleware error') }) const response = await client.get('/users') expect(response.error).toBeDefined() expect(response.error?.message).toBe('Middleware error') }) }) ``` ## 💾 缓存测试 ### 缓存功能测试 ```typescript // test/cache.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Cache', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', enableCache: true, cacheExpiry: 1000 // 1秒 }) }) it('should cache responses', async () => { const startTime = Date.now() // 第一次请求 const response1 = await client.get('/users') const firstRequestTime = Date.now() - startTime expect(response1.error).toBeUndefined() expect(response1.data).toBeDefined() // 第二次请求(应该使用缓存) const response2 = await client.get('/users') const secondRequestTime = Date.now() - startTime expect(response2.error).toBeUndefined() expect(response2.data).toEqual(response1.data) // 第二次请求应该更快(使用缓存) expect(secondRequestTime).toBeLessThan(firstRequestTime + 100) }) it('should respect cache expiry', async () => { // 第一次请求 await client.get('/users') // 等待缓存过期 await new Promise(resolve => setTimeout(resolve, 1100)) // 第二次请求(缓存已过期) const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) it('should clear cache', async () => { // 第一次请求 await client.get('/users') // 清除缓存 client.clearCache('/users') // 第二次请求(缓存已清除) const response = await client.get('/users') expect(response.error).toBeUndefined() expect(response.data).toBeDefined() }) it('should handle cache control headers', async () => { // 强制不使用缓存 const response1 = await client.get('/users', {}, { cache: 'no-cache' }) expect(response1.error).toBeUndefined() // 强制使用缓存 const response2 = await client.get('/users', {}, { cache: 'force-cache' }) expect(response2.error).toBeUndefined() }) }) ``` ## 📊 统计和监控测试 ### 统计功能测试 ```typescript // test/stats.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Statistics', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', enableStats: true }) }) it('should track request count', async () => { const initialStats = client.getStats() await client.get('/users') await client.get('/posts') const finalStats = client.getStats() expect(finalStats.totalRequests).toBe(initialStats.totalRequests + 2) expect(finalStats.successfulRequests).toBe(initialStats.successfulRequests + 2) }) it('should track response times', async () => { await client.get('/users') const stats = client.getStats() expect(stats.averageResponseTime).toBeGreaterThan(0) expect(stats.totalRequests).toBeGreaterThan(0) }) it('should track error count', async () => { const initialStats = client.getStats() // 触发一个错误 client.updateConfig({ baseURL: 'https://invalid-domain.com' }) await client.get('/test') const finalStats = client.getStats() expect(finalStats.failedRequests).toBe(initialStats.failedRequests + 1) }) it('should reset statistics', async () => { await client.get('/users') const statsBeforeReset = client.getStats() expect(statsBeforeReset.totalRequests).toBeGreaterThan(0) client.resetStats() const statsAfterReset = client.getStats() expect(statsAfterReset.totalRequests).toBe(0) expect(statsAfterReset.successfulRequests).toBe(0) expect(statsAfterReset.failedRequests).toBe(0) }) }) ``` ## 🔗 集成测试 ### 与真实 API 的集成测试 ```typescript // test/integration.test.ts import { describe, expect, it, beforeAll, afterAll } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Integration Tests', () => { let client: VafastApiClient beforeAll(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', timeout: 10000 }) }) it('should perform full CRUD operations', async () => { // Create const createResponse = await client.post('/posts', { title: 'Test Post', body: 'Test Body', userId: 1 }) expect(createResponse.error).toBeUndefined() expect(createResponse.data).toBeDefined() expect(createResponse.data.title).toBe('Test Post') const postId = createResponse.data.id // Read const readResponse = await client.get('/posts/:id', { id: postId }) expect(readResponse.error).toBeUndefined() expect(readResponse.data).toBeDefined() expect(readResponse.data.id).toBe(postId) // Update const updateResponse = await client.put('/posts/:id', { title: 'Updated Post', body: 'Updated Body', userId: 1 }, { id: postId }) expect(updateResponse.error).toBeUndefined() expect(updateResponse.data).toBeDefined() expect(updateResponse.data.title).toBe('Updated Post') // Delete const deleteResponse = await client.delete('/posts/:id', { id: postId }) expect(deleteResponse.error).toBeUndefined() }) it('should handle pagination', async () => { const response = await client.get('/posts', { _page: 1, _limit: 5 }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeLessThanOrEqual(5) }) it('should handle search and filtering', async () => { const response = await client.get('/posts', { title_like: 'qui est esse' }) expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeGreaterThan(0) // 验证搜索结果 const hasMatchingTitle = response.data.some((post: any) => post.title.includes('qui est esse') ) expect(hasMatchingTitle).toBe(true) }) }) ``` ## 🎯 性能测试 ### 性能基准测试 ```typescript // test/performance.test.ts import { describe, expect, it, beforeEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Performance Tests', () => { let client: VafastApiClient beforeEach(() => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', enableCache: true }) }) it('should handle concurrent requests', async () => { const startTime = Date.now() const promises = Array.from({ length: 10 }, () => client.get('/users') ) const responses = await Promise.all(promises) const endTime = Date.now() const totalTime = endTime - startTime expect(responses).toHaveLength(10) expect(responses.every(r => r.error === undefined)).toBe(true) // 验证并发性能(10个请求应该在合理时间内完成) expect(totalTime).toBeLessThan(5000) // 5秒内 }) it('should handle large responses efficiently', async () => { const startTime = Date.now() const response = await client.get('/users') const endTime = Date.now() const responseTime = endTime - startTime expect(response.error).toBeUndefined() expect(response.data).toBeDefined() expect(response.data.length).toBeGreaterThan(0) // 验证响应时间 expect(responseTime).toBeLessThan(3000) // 3秒内 }) it('should cache responses efficiently', async () => { // 第一次请求 const startTime1 = Date.now() await client.get('/users') const firstRequestTime = Date.now() - startTime1 // 第二次请求(使用缓存) const startTime2 = Date.now() await client.get('/users') const secondRequestTime = Date.now() - startTime2 // 缓存请求应该显著更快 expect(secondRequestTime).toBeLessThan(firstRequestTime * 0.5) }) }) ``` ## 🧹 测试清理 ### 测试后清理 ```typescript // test/cleanup.test.ts import { describe, expect, it, afterEach } from 'bun:test' import { VafastApiClient } from '@vafast/api-client' describe('Test Cleanup', () => { let client: VafastApiClient afterEach(() => { // 清理缓存 if (client) { client.clearCache() client.resetStats() } }) it('should clean up after tests', () => { client = new VafastApiClient({ baseURL: 'https://jsonplaceholder.typicode.com', enableCache: true, enableStats: true }) // 执行一些操作 expect(client).toBeDefined() }) }) ``` ## 📚 测试最佳实践 ### 1. 测试隔离 * 每个测试用例都应该是独立的 * 使用 `beforeEach` 和 `afterEach` 清理状态 * 避免测试之间的依赖关系 ### 2. 模拟外部依赖 * 使用 mock 函数模拟网络请求 * 避免依赖外部服务的可用性 * 使用测试数据而不是真实 API ### 3. 错误场景测试 * 测试各种错误情况 * 验证错误处理的正确性 * 测试边界条件和异常情况 ### 4. 性能测试 * 测试并发请求处理 * 验证缓存机制的有效性 * 监控响应时间和资源使用 ### 5. 集成测试 * 测试与真实 API 的集成 * 验证端到端功能 * 测试各种配置组合 ## 🚀 运行测试 ### 使用 Bun ```bash # 运行所有测试 bun test # 运行特定测试文件 bun test test/client.test.ts # 运行测试并显示覆盖率 bun test --coverage # 监听模式 bun test --watch ``` ### 使用 npm ```bash # 运行所有测试 npm test # 运行特定测试文件 npm test -- test/client.test.ts # 运行测试并显示覆盖率 npm run test:coverage # 监听模式 npm run test:watch ``` ## 📚 下一步 现在您已经了解了如何测试 Vafast API 客户端,接下来可以: 1. **编写更多测试用例** - 覆盖更多功能和场景 2. **设置持续集成** - 自动化测试流程 3. **性能测试** - 验证在高负载下的表现 4. **安全测试** - 测试各种安全场景 5. **文档测试** - 确保示例代码的正确性 如果您在测试过程中遇到任何问题,请查看我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/community.md' --- # 社区 欢迎加入 Vafast 社区!我们是一个开放、友好的开发者社区,致力于构建高性能的 TypeScript Web 框架。 ## 获取帮助 ### 常见问题 (FAQ) 在提问之前,请先查看我们的常见问题: **Q: Vafast 支持哪些运行时?** A: Vafast 主要针对 Bun 优化,但也支持 Node.js 和任何支持 Web 标准的运行时。 **Q: 如何迁移现有的 Express/Fastify 应用?** A: 查看我们的 [迁移指南](/migrate) 了解详细的迁移步骤。 **Q: 支持 TypeScript 吗?** A: 是的!Vafast 完全使用 TypeScript 编写,提供完整的类型安全。 **Q: 性能如何?** A: Vafast 经过精心优化,在 Bun 运行时上表现优异,支持高并发请求。 ### 提问指南 当您需要帮助时,请遵循以下指南: 1. **搜索现有问题** - 在提问前先搜索是否已有类似问题 2. **提供详细信息** - 包括错误信息、代码示例、环境信息等 3. **使用清晰的标题** - 简洁描述问题 4. **包含最小复现示例** - 帮助快速定位问题 ### 提问模板 ```` ## 问题描述 [简要描述您遇到的问题] ## 环境信息 - 运行时: [Bun/Node.js/其他] - 版本: [具体版本号] - 操作系统: [OS 信息] ## 代码示例 ```typescript // 您的代码 ```` ## 错误信息 \[完整的错误堆栈] ## 期望行为 \[描述您期望的结果] ## 实际行为 \[描述实际发生的情况] ```` ## 参与讨论 ### 社区渠道 #### GitHub Discussions - **主要讨论平台**: [GitHub Discussions](https://github.com/vafast/vafast/discussions) - **功能请求**: 分享您的想法和建议 - **使用讨论**: 讨论最佳实践和解决方案 - **公告**: 获取最新更新和重要通知 #### Discord 服务器 - **实时交流**: [Vafast Discord](https://discord.gg/vafast) - **技术讨论**: 实时技术问答 - **项目展示**: 分享您的项目 - **社区活动**: 参与线上活动 #### 微信群 - **中文社区**: 扫描二维码加入微信群 - **本地化支持**: 中文技术讨论 - **项目合作**: 寻找合作伙伴 ### 讨论主题 我们欢迎讨论以下主题: - **技术问题** - 框架使用、最佳实践、性能优化 - **功能建议** - 新功能提案、改进建议 - **项目展示** - 使用 Vafast 构建的项目 - **教程分享** - 技术文章、视频教程 - **社区活动** - 线下聚会、技术分享会 ## 贡献代码 ### 贡献指南 我们欢迎所有形式的贡献!无论您是经验丰富的开发者还是初学者,都可以为项目做出贡献。 #### 贡献类型 1. **代码贡献** - Bug 修复 - 新功能开发 - 性能优化 - 代码重构 2. **文档贡献** - 文档改进 - 示例代码 - 翻译工作 - 教程编写 3. **测试贡献** - 单元测试 - 集成测试 - 性能测试 - 测试覆盖率提升 4. **社区贡献** - 回答问题 - 代码审查 - 项目管理 - 社区建设 ### 开发环境设置 #### 前置要求 ```bash # 安装 Bun curl -fsSL https://bun.sh/install | bash # 克隆仓库 git clone https://github.com/vafast/vafast.git cd vafast # 安装依赖 bun install ```` #### 运行测试 ```bash # 运行所有测试 bun test # 运行特定测试 bun test --grep "user" # 运行性能测试 bun run test:perf ``` #### 代码规范 我们使用以下工具确保代码质量: * **ESLint** - 代码风格检查 * **Prettier** - 代码格式化 * **TypeScript** - 类型检查 * **Husky** - Git hooks ```bash # 代码检查 bun run lint # 代码格式化 bun run format # 类型检查 bun run type-check ``` ### 提交规范 我们使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: ``` feat: 添加新功能 fix: 修复 Bug docs: 文档更新 style: 代码格式调整 refactor: 代码重构 test: 测试相关 chore: 构建过程或辅助工具的变动 ``` ### 拉取请求流程 1. **Fork 仓库** - 创建您的 Fork 2. **创建分支** - 为您的功能创建新分支 3. **开发功能** - 实现您的功能或修复 4. **运行测试** - 确保所有测试通过 5. **提交代码** - 使用规范的提交信息 6. **创建 PR** - 提交拉取请求 7. **代码审查** - 等待维护者审查 8. **合并代码** - 审查通过后合并 ## 项目展示 ### 优秀项目 展示使用 Vafast 构建的优秀项目: #### 个人博客系统 * **项目**: [Vafast Blog](https://github.com/example/vafast-blog) * **描述**: 基于 Vafast 的现代化博客系统 * **特性**: 支持 Markdown、评论系统、SEO 优化 #### 电商 API * **项目**: [Vafast Shop](https://github.com/example/vafast-shop) * **描述**: 完整的电商后端 API * **特性**: 用户管理、商品管理、订单系统、支付集成 #### 实时聊天应用 * **项目**: [Vafast Chat](https://github.com/example/vafast-chat) * **描述**: 支持 WebSocket 的实时聊天应用 * **特性**: 实时消息、用户在线状态、群组聊天 ### 提交您的项目 如果您有使用 Vafast 构建的项目,欢迎提交展示: 1. 在 [GitHub Discussions](https://github.com/vafast/vafast/discussions) 中创建新帖子 2. 使用 "Show and Tell" 标签 3. 包含项目描述、截图、技术栈等信息 4. 提供项目链接和源代码 ## 学习资源 ### 官方资源 * **文档**: [docs.vafast.dev](https://docs.vafast.dev) * **API 参考**: [api.vafast.dev](https://api.vafast.dev) * **示例代码**: [examples.vafast.dev](https://examples.vafast.dev) * **性能基准**: [benchmarks.vafast.dev](https://benchmarks.vafast.dev) ### 第三方资源 #### 视频教程 * **Bilibili**: Vafast 入门教程系列 * **YouTube**: Vafast Framework Tutorials * **腾讯视频**: Vafast 实战开发 #### 博客文章 * **掘金**: Vafast 相关技术文章 * **CSDN**: Vafast 开发经验分享 * **知乎**: Vafast 技术讨论 #### 开源项目 * **GitHub**: 搜索 "vafast" 标签 * **GitLab**: Vafast 相关项目 * **Gitee**: 国内镜像项目 ## 社区活动 ### 线上活动 #### 技术分享会 * **时间**: 每月最后一个周六 * **主题**: 轮换的技术主题 * **形式**: 线上直播 + 互动讨论 * **报名**: \[活动报名链接] #### 代码审查日 * **时间**: 每周三晚上 * **内容**: 开源代码审查 * **参与**: 提交代码或参与审查 * **奖励**: 优秀贡献者奖励 #### 问答时间 * **时间**: 每周五下午 * **形式**: 在线问答 * **专家**: 框架维护者和社区专家 * **记录**: 问答内容会整理成文档 ### 线下活动 #### 技术聚会 * **北京**: 每月一次技术分享 * **上海**: 开发者交流聚会 * **深圳**: 创业公司技术分享 * **其他城市**: 根据需求组织 #### 技术大会 * **Vafast Conf**: 年度技术大会 * **JSConf China**: JavaScript 大会 * **Node.js 开发者大会**: Node.js 生态大会 ## 社区规范 ### 行为准则 我们致力于创建一个友好、包容的社区环境: 1. **尊重他人** - 尊重所有社区成员 2. **建设性讨论** - 保持积极、建设性的讨论氛围 3. **包容性** - 欢迎不同背景和经验的开发者 4. **专业态度** - 保持专业的技术讨论态度 ### 禁止行为 以下行为在社区中是不被允许的: * 人身攻击或侮辱性言论 * 垃圾信息或广告 * 恶意代码或安全漏洞利用 * 违反法律法规的内容 ### 举报机制 如果您遇到不当行为: 1. **私信管理员** - 通过私信联系社区管理员 2. **举报内容** - 使用平台提供的举报功能 3. **邮件举报** - 发送邮件到 ## 联系信息 ### 官方联系方式 * **邮箱**: * **Twitter**: [@vafast\_dev](https://twitter.com/vafast_dev) * **GitHub**: [github.com/vafast](https://github.com/vafast) * **Discord**: [discord.gg/vafast](https://discord.gg/vafast) ### 社区管理员 * **技术负责人**: [@tech-lead](https://github.com/tech-lead) * **社区经理**: [@community-manager](https://github.com/community-manager) * **文档维护者**: [@docs-maintainer](https://github.com/docs-maintainer) ### 反馈渠道 * **功能建议**: [GitHub Issues](https://github.com/vafast/vafast/issues) * **Bug 报告**: [Bug Report Template](https://github.com/vafast/vafast/issues/new?template=bug_report.md) * **文档反馈**: [Documentation Issues](https://github.com/vafast/vafast/issues?q=label%3Adocumentation) ## 总结 Vafast 社区是一个充满活力的开发者社区,我们致力于: * ✅ 提供技术支持和帮助 * ✅ 促进知识分享和交流 * ✅ 鼓励代码贡献和参与 * ✅ 组织技术活动和聚会 * ✅ 维护友好的社区环境 ### 加入我们 无论您是经验丰富的开发者还是初学者,我们都欢迎您加入 Vafast 社区! * **立即加入**: [GitHub Discussions](https://github.com/vafast/vafast/discussions) * **实时交流**: [Discord 服务器](https://discord.gg/vafast) * **贡献代码**: [贡献指南](/contributing) * **学习资源**: [文档中心](/docs) 让我们一起构建更好的 Web 开发体验!🚀 --- --- url: 'https://vafast.dev/at-glance.md' --- # 简介 Vafast 不只是一个框架,更是一种 **结构、清晰、可控** 的开发哲学。 ## 🎯 Vafast 哲学 ### 结构即真相 Structure is Truth API 用代码定义,而非行为。没有装饰器,没有魔法。 ```typescript // 所见即所得 const routes = [ { method: 'GET', path: '/users/:id', handler: getUser } ] ``` ### 错误即数据 Errors are Data 错误包含状态、类型和可见性。不是混乱,而是契约。 ```typescript return { data: { error: 'Not Found' }, status: 404 } ``` ### 组合优于约定 Composition Matters 中间件显式组合,执行顺序清晰可控,无全局污染。 ```typescript { path: '/admin', middleware: [auth, log], handler } ``` ### 边缘原生 Edge-Native 原生运行于 Bun、Workers、Deno —— 冷启动无忧。 ```typescript export default { port: 3000, fetch: server.fetch } ``` ### 零样板代码 No Boilerplate 无 CLI,无配置文件夹。一个文件,即刻运行。 ## ✨ 核心特性 * ✅ **结构优先的路由** — 用声明式对象定义整个 API,所见即所得 * ✅ **可组合的中间件** — 显式组合,无装饰器,无全局污染 * ✅ **结构化响应** — `{ data, status }` 统一格式,错误也是数据 * ✅ **内置响应工具** — `json()`、`html()`、`text()` 等,简洁统一 * ✅ **边缘原生** — Bun、Workers、Deno 即时冷启动,亚毫秒响应 * ✅ **零样板代码** — 无 CLI,无配置文件,一个文件即可运行 * ✅ **类型安全** — 路由、处理器、响应,全部 TypeScript 类型推断 ## 🚀 技术特性 * **超高性能**: 比 Express/Hono 快约 **1.8x**,达到 Elysia 86% 性能 * **JIT 编译验证器**: Schema 验证器编译缓存,10000 次验证仅需 ~5ms * **中间件链预编译**: 路由注册时预编译处理链,运行时零开销 * **快速请求解析**: 优化的 Query/Cookie 解析,比标准方法快 2x * **类型安全**: 完整的 TypeScript 支持和自动类型推断 * **灵活中间件**: 可组合的中间件架构,支持全局和路由级 * **零配置**: 开箱即用,无需复杂配置 以下是在 Vafast 中的简单 hello world 示例。 ```typescript import { Server, defineRoutes } from 'vafast' interface TypedRequest extends Request { params: Record } const routes = defineRoutes([ { method: 'GET', path: '/', handler: () => 'Hello Vafast' }, { method: 'GET', path: '/user/:id', handler: (req) => { const { id } = (req as TypedRequest).params return `User ID: ${id}` } }, { method: 'POST', path: '/form', handler: async (req) => { const body = await req.json() return { success: true, data: body } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` 打开 [localhost:3000](http://localhost:3000/),结果应该显示 'Hello Vafast'。 ::: tip 这是一个简单的示例,展示了 Vafast 的基本用法。在实际项目中,你可以根据需要添加更多的路由和中间件。 ::: ## 性能 基于多项核心优化,Vafast 提供卓越的性能表现: | 框架 | RPS | 相对性能 | |------|-----|----------| | Elysia | ~118K | 100% | | **Vafast** | **~101K** | **86%** | | Express | ~56K | 48% | | Hono | ~56K | 47% | > 测试环境:Bun 1.2.20, macOS, wrk 基准测试 (4线程, 100连接, 30s) ### 性能优化技术 * **JIT 编译验证器**: TypeBox Schema 编译后缓存,避免重复编译开销 * **中间件链预编译**: 路由注册时预编译完整处理链,每次请求仅需 0.004ms * **快速请求解析**: `parseQueryFast`、`getCookie` 等优化函数,比标准方法快 2x * **Radix Tree 路由**: O(k) 时间复杂度的高效路由匹配 ## TypeScript Vafast 旨在帮助你编写更少的 TypeScript。 通过提供完整的类型定义和类型推断,Vafast 让你能够: * 获得完整的类型安全 * 减少类型注解的需求 * 享受更好的开发体验 * 避免运行时类型错误 ## 架构特点 Vafast 采用现代化的架构设计: ### 路由驱动 * 清晰的路由配置 * 支持嵌套路由 * 灵活的参数处理 * 自动路由冲突检测 ### 中间件系统 * 可组合的中间件 * 支持异步操作 * 错误处理机制 * 全局和路由级中间件 ### 类型安全 * 完整的 TypeScript 支持 * 自动类型推断 * 编译时错误检查 * Schema 验证支持 ### 高性能路由 * 智能路径匹配算法 * 路由特异性排序 * 扁平化嵌套路由 * 优化的中间件链 ## 下一步 现在您已经了解了 Vafast 的基本概念,建议您: 1. 查看 [快速入门](/quick-start) 开始构建您的第一个应用 2. 阅读 [关键概念](/key-concept) 深入了解 Vafast 的核心特性 3. 探索 [教程](/tutorial) 学习更多高级功能 如果您有任何问题,欢迎在我们的 [GitHub Issues](https://github.com/vafast/vafast/issues) 社区询问。 --- --- url: 'https://vafast.dev/patterns/type.md' --- # 类型 这是在 Vafast 中编写验证类型的常见模式。 ## 基本类型 TypeBox API 是围绕 TypeScript 类型设计的,并与之类似。 有许多熟悉的名称和行为与 TypeScript 对应项交叉,例如 **String**、**Number**、**Boolean** 和 **Object**,以及更高级的功能,如 **Intersect**、**KeyOf** 和 **Tuple**,以增强灵活性。 如果你熟悉 TypeScript,创建 TypeBox 模式的行为就像编写 TypeScript 类型一样,只是它在运行时提供实际的类型验证。 要创建第一个模式,从 TypeBox 导入 **Type**,并从最基本的类型开始: ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' const BodySchema = Type.String() const validator = TypeCompiler.Compile(BodySchema) const routes = defineRoutes([ { method: 'POST', path: '/', handler: async (req) => { const body = await req.text() if (!validator.Check(body)) { return new Response('Invalid body', { status: 400 }) } return `Hello ${body}` } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` 这段代码告诉 Vafast 验证传入的 HTTP 主体,确保主体是一个字符串。如果它是字符串,则可以在请求管道和处理程序中流动。 如果形状不匹配,将抛出验证错误。 ### 基本类型 TypeBox 提供具有与 TypeScript 类型相同行为的基本原始类型。 以下表格列出了最常见的基本类型: ```typescript Type.String() ``` ```typescript string ``` ```typescript Type.Number() ``` ```typescript number ``` ```typescript Type.Boolean() ``` ```typescript boolean ``` ```typescript Type.Null() ``` ```typescript null ``` ```typescript Type.Undefined() ``` ```typescript undefined ``` ```typescript Type.Any() ``` ```typescript any ``` ```typescript Type.Unknown() ``` ```typescript unknown ``` ```typescript Type.Never() ``` ```typescript never ``` ```typescript Type.Void() ``` ```typescript void ``` ```typescript Type.Symbol() ``` ```typescript symbol ``` ```typescript Type.BigInt() ``` ```typescript bigint ``` ### 字符串类型 TypeBox 提供了多种字符串验证选项: ```typescript import { Type } from '@sinclair/typebox' // 基本字符串 const basicString = Type.String() // 带长度限制的字符串 const limitedString = Type.String({ minLength: 1, maxLength: 100 }) // 带正则表达式的字符串 const emailString = Type.String({ format: 'email' }) // 带枚举值的字符串 const statusString = Type.Union([ Type.Literal('active'), Type.Literal('inactive'), Type.Literal('pending') ]) ``` ### 数字类型 ```typescript import { Type } from '@sinclair/typebox' // 基本数字 const basicNumber = Type.Number() // 带范围限制的数字 const ageNumber = Type.Number({ minimum: 0, maximum: 150 }) // 整数 const integerNumber = Type.Integer() // 正数 const positiveNumber = Type.Number({ minimum: 0, exclusiveMinimum: true }) ``` ### 对象类型 ```typescript import { Type } from '@sinclair/typebox' // 基本对象 const userObject = Type.Object({ id: Type.Number(), name: Type.String(), email: Type.String({ format: 'email' }), age: Type.Optional(Type.Number()) }) // 嵌套对象 const addressObject = Type.Object({ street: Type.String(), city: Type.String(), country: Type.String() }) const userWithAddress = Type.Object({ ...userObject.properties, address: addressObject }) ``` ### 数组类型 ```typescript import { Type } from '@sinclair/typebox' // 基本数组 const stringArray = Type.Array(Type.String()) // 带长度限制的数组 const limitedArray = Type.Array(Type.Number(), { minItems: 1, maxItems: 10 }) // 元组类型 const tuple = Type.Tuple([ Type.String(), Type.Number(), Type.Boolean() ]) ``` ## Vafast 类型 Vafast 使用 TypeBox 进行类型验证,提供了完整的类型安全。 ### 请求体验证 ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }), age: Type.Optional(Type.Number({ minimum: 0 })) }) const userValidator = TypeCompiler.Compile(userSchema) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: async (req) => { const body = await req.json() if (!userValidator.Check(body)) { return new Response(JSON.stringify({ error: 'Invalid body' }), { status: 400, headers: { 'Content-Type': 'application/json' } }) } // body 已经通过验证 return { success: true, data: body } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### 查询参数验证 ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' const querySchema = Type.Object({ page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), search: Type.Optional(Type.String()) }) const queryValidator = TypeCompiler.Compile(querySchema) const routes = defineRoutes([ { method: 'GET', path: '/users', handler: (req) => { const url = new URL(req.url) const query = { page: url.searchParams.get('page') ? Number(url.searchParams.get('page')) : 1, limit: url.searchParams.get('limit') ? Number(url.searchParams.get('limit')) : 10, search: url.searchParams.get('search') || '' } if (!queryValidator.Check(query)) { return new Response('Invalid query', { status: 400 }) } return { page: query.page, limit: query.limit, search: query.search } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### 路径参数验证 ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' interface TypedRequest extends Request { params: Record } const paramsSchema = Type.Object({ id: Type.String({ minLength: 1 }) }) const paramsValidator = TypeCompiler.Compile(paramsSchema) const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: (req) => { const params = (req as TypedRequest).params if (!paramsValidator.Check(params)) { return new Response('Invalid params', { status: 400 }) } return { userId: params.id } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### 头部验证 ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' const headersSchema = Type.Object({ authorization: Type.String({ pattern: '^Bearer .+' }) }) const headersValidator = TypeCompiler.Compile(headersSchema) const routes = defineRoutes([ { method: 'POST', path: '/secure', handler: (req) => { const headers = { authorization: req.headers.get('authorization') || '' } if (!headersValidator.Check(headers)) { return new Response('Unauthorized', { status: 401 }) } const token = headers.authorization.replace('Bearer ', '') return { token } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## Vafast 行为 Vafast 与 TypeBox 的集成提供了以下特性: ### 自动类型推断 ```typescript import { Type, type Static } from '@sinclair/typebox' // 定义模式 const userSchema = Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }), age: Type.Number({ minimum: 0 }) }) // TypeScript 自动推断类型 type User = Static // 等同于: { name: string; email: string; age: number } ``` ### 验证错误处理 ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) const userValidator = TypeCompiler.Compile(userSchema) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: async (req) => { const body = await req.json() const errors = [...userValidator.Errors(body)] if (errors.length > 0) { return new Response( JSON.stringify({ error: 'Validation failed', details: errors.map(e => ({ path: e.path, message: e.message })) }), { status: 400, headers: { 'Content-Type': 'application/json' } } ) } return { success: true, data: body } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### 高级类型模式 #### 联合类型 ```typescript const statusSchema = Type.Union([ Type.Literal('active'), Type.Literal('inactive'), Type.Literal('pending') ]) const userStatusSchema = Type.Object({ id: Type.Number(), status: statusSchema, updatedAt: Type.String({ format: 'date-time' }) }) ``` #### 交叉类型 ```typescript const baseUserSchema = Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) }) const adminUserSchema = Type.Object({ role: Type.Literal('admin'), permissions: Type.Array(Type.String()) }) const adminUser = Type.Intersect([baseUserSchema, adminUserSchema]) ``` #### 递归类型 ```typescript import { Type } from '@sinclair/typebox' const commentSchema = Type.Recursive(This => Type.Object({ id: Type.Number(), content: Type.String(), author: Type.String(), replies: Type.Array(This) })) ``` ### 性能优化 #### 预编译验证器 ```typescript import { TypeCompiler } from '@sinclair/typebox/compiler' import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String(), email: Type.String() }) // 预编译验证器以提高性能 const userValidator = TypeCompiler.Compile(userSchema) // 使用预编译的验证器 const isValid = userValidator.Check({ name: 'John', email: 'john@example.com' }) ``` ## 最佳实践 ### 1. 使用描述性的错误消息 ```typescript const userSchema = Type.Object({ name: Type.String({ minLength: 1, error: 'Name is required' }), email: Type.String({ format: 'email', error: 'Please provide a valid email address' }), age: Type.Number({ minimum: 0, maximum: 150, error: 'Age must be between 0 and 150' }) }) ``` ### 2. 重用验证模式 ```typescript import { Type } from '@sinclair/typebox' // 基础模式 const baseUserSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) // 创建用户模式 const createUserSchema = baseUserSchema // 更新用户模式 const updateUserSchema = Type.Partial(baseUserSchema) // 用户列表查询模式 const userQuerySchema = Type.Object({ page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), search: Type.Optional(Type.String()) }) ``` ### 3. 类型安全的路由定义 ```typescript import { Server, defineRoutes, Type } from 'vafast' import { TypeCompiler } from '@sinclair/typebox/compiler' import type { Static } from '@sinclair/typebox' // 定义模式 const userSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) type User = Static const userValidator = TypeCompiler.Compile(userSchema) interface TypedRequest extends Request { params: { id: string } } // 定义路由 const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: (req) => { const { id } = (req as TypedRequest).params return { userId: id } } }, { method: 'POST', path: '/users', handler: async (req) => { const body = await req.json() if (!userValidator.Check(body)) { return new Response('Invalid body', { status: 400 }) } // body 完全类型安全 const user = body as User return { success: true, user } } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ## 总结 Vafast 的类型系统提供了: * ✅ 基于 TypeBox 的完整类型安全 * ✅ 运行时数据验证 * ✅ 自动类型推断 * ✅ 灵活的验证规则 * ✅ 性能优化选项 * ✅ 错误处理支持 ### 下一步 * 查看 [验证系统](/essential/validation) 了解完整的验证功能 * 学习 [路由系统](/essential/route) 了解如何组织路由 * 探索 [中间件系统](/middleware) 了解如何增强功能 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/api-client/treaty/overview.md' --- # 类型安全客户端概述 Vafast 类型安全客户端是一个强大的工具,它提供了完整的端到端类型安全,让您能够以类型安全的方式与 Vafast 服务器进行交互。通过利用 TypeScript 的类型系统,您可以在编译时捕获类型错误,提高开发效率和代码质量。 ## ✨ 核心特性 ### 🔒 端到端类型安全 * 服务器和客户端之间的完全类型同步 * 编译时类型检查,避免运行时错误 * 自动类型推断和智能提示 ### 🎯 零代码生成 * 无需额外的代码生成步骤 * 直接使用 TypeScript 类型系统 * 实时类型同步 ### 🚀 高性能 * 轻量级实现,体积小 * 快速的类型推断 * 优化的运行时性能 ### 🧩 易于集成 * 与现有 Vafast 项目无缝集成 * 支持所有 Vafast 功能 * 灵活的配置选项 ## 🏗️ 架构设计 Vafast 类型安全客户端采用智能的类型推断机制,通过分析 Vafast 服务器的类型定义,自动生成对应的客户端类型。这种设计确保了: ``` Vafast 服务器类型定义 ↓ 类型安全客户端 ↓ 类型化的 API 调用 ↓ 编译时类型检查 ``` ### 类型推断流程 1. **服务器类型导出** - 从 Vafast 服务器导出类型定义 2. **客户端类型生成** - 自动推断客户端可用的类型 3. **API 调用验证** - 在编译时验证 API 调用的正确性 4. **运行时类型安全** - 确保运行时数据的类型一致性 ## 🔧 核心概念 ### 类型安全 类型安全是指在编译时能够检测到类型错误,避免在运行时出现类型不匹配的问题。Vafast 类型安全客户端通过以下方式实现类型安全: * **请求参数类型检查** - 验证请求参数的类型和结构 * **响应数据类型推断** - 自动推断响应的数据类型 * **路径参数类型验证** - 确保路径参数的类型正确性 * **查询参数类型检查** - 验证查询参数的类型 ### 端到端类型同步 端到端类型同步意味着服务器端的类型定义会自动同步到客户端,当服务器端的类型发生变化时,客户端会立即获得相应的类型更新。这确保了: * **类型一致性** - 服务器和客户端的类型始终保持一致 * **自动更新** - 无需手动同步类型定义 * **错误预防** - 在编译时捕获类型不匹配的问题 ### 零代码生成 零代码生成意味着您不需要运行额外的工具来生成类型定义,所有的类型推断都在 TypeScript 编译时完成。这带来了: * **开发效率** - 无需等待代码生成 * **类型准确性** - 类型定义始终是最新的 * **维护简单** - 减少了额外的构建步骤 ## 📱 使用场景 ### 前端应用 * **React 应用** - 类型安全的 API 调用 * **Vue 应用** - 完整的类型支持 * **Angular 应用** - 强类型的数据绑定 * **Svelte 应用** - 类型安全的组件通信 ### 后端服务 * **微服务通信** - 类型安全的服务间调用 * **API 网关** - 统一的类型定义 * **数据同步** - 类型一致的数据传输 ### 移动应用 * **React Native** - 跨平台的类型安全 * **Flutter** - 通过 FFI 实现类型安全 * **原生应用** - 类型安全的网络层 ### 桌面应用 * **Electron** - 类型安全的 IPC 通信 * **Tauri** - 类型安全的后端调用 * **原生应用** - 类型安全的网络请求 ## 🚀 快速开始 ### 1. 导出服务器类型 ```typescript // server.ts import { Server, defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello Vafast!') }, { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => `User ${params.id}`), params: Type.Object({ id: Type.String() }) }, { method: 'POST', path: '/users', handler: createHandler(({ body }) => `Created user: ${body.name}`), body: Type.Object({ name: Type.String(), email: Type.String() }) } ]) const server = new Server(routes) // 导出服务器类型 export type App = typeof server export default { fetch: server.fetch } ``` ### 2. 创建类型安全客户端 ```typescript // client.ts import { createTypedClient } from '@vafast/api-client' import type { App } from './server' // 创建类型安全客户端 const client = createTypedClient('http://localhost:3000') export default client ``` ### 3. 使用类型安全客户端 ```typescript // index.ts import client from './client' async function main() { try { // 类型安全的 GET 请求 const homeResponse = await client.get('/') console.log('Home:', homeResponse.data) // 类型: string // 类型安全的路径参数 const userResponse = await client.get('/users/:id', { id: '123' }) console.log('User:', userResponse.data) // 类型: string // 类型安全的 POST 请求 const createResponse = await client.post('/users', { name: 'John Doe', email: 'john@example.com' }) console.log('Created:', createResponse.data) // 类型: string } catch (error) { console.error('Error:', error) } } main() ``` ## 🔍 类型推断示例 ### 自动类型推断 ```typescript // 服务器端类型定义 interface User { id: string name: string email: string profile: { age: number location: string } } // 客户端自动获得类型推断 const user = await client.get('/users/:id', { id: '123' }) // user.data 的类型自动推断为 User // 完整的类型支持 console.log(user.data.name) // ✅ 类型安全 console.log(user.data.profile.age) // ✅ 类型安全 console.log(user.data.invalid) // ❌ 编译时错误 ``` ### 参数类型验证 ```typescript // 服务器端验证器 const userValidator = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }), age: Type.Number({ minimum: 0, maximum: 150 }) }) // 客户端自动获得类型信息 const response = await client.post('/users', { name: 'John', // ✅ 类型正确 email: 'john@example.com', // ✅ 类型正确 age: 30 // ✅ 类型正确 }) // 类型错误会在编译时被捕获 const invalidResponse = await client.post('/users', { name: 123, // ❌ 编译时错误:应该是 string email: 'invalid-email', // ❌ 编译时错误:格式不正确 age: 'thirty' // ❌ 编译时错误:应该是 number }) ``` ## 🎛️ 高级功能 ### 中间件类型支持 ```typescript // 服务器端中间件 const authMiddleware = createMiddleware(async (request, next) => { const token = request.headers.get('Authorization') if (!token) { throw new Error('Unauthorized') } return await next(request) }) // 客户端自动获得中间件类型 const response = await client.get('/protected', {}, { headers: { 'Authorization': 'Bearer token123' // ✅ 类型安全 } }) ``` ### 错误类型推断 ```typescript // 服务器端错误类型 interface ApiError { code: string message: string details?: any } // 客户端自动获得错误类型 const response = await client.get('/users/:id', { id: 'invalid' }) if (response.error) { // response.error 的类型自动推断为 ApiError console.log(response.error.code) // ✅ 类型安全 console.log(response.error.message) // ✅ 类型安全 console.log(response.error.details) // ✅ 类型安全 } ``` ### 响应类型推断 ```typescript // 服务器端响应类型 interface PaginatedResponse { data: T[] total: number page: number limit: number } // 客户端自动获得响应类型 const response = await client.get('/users', { page: 1, limit: 10 }) // response.data 的类型自动推断为 PaginatedResponse console.log(response.data.total) // ✅ 类型安全 console.log(response.data.data[0].name) // ✅ 类型安全 ``` ## 🔗 与其他工具集成 ### TypeScript 配置 ```json // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "exactOptionalPropertyTypes": true } } ``` ### ESLint 配置 ```javascript // .eslintrc.js module.exports = { extends: [ '@typescript-eslint/recommended', '@typescript-eslint/recommended-requiring-type-checking' ], parserOptions: { project: './tsconfig.json' } } ``` ### VSCode 配置 ```json // .vscode/settings.json { "typescript.preferences.includePackageJsonAutoImports": "on", "typescript.suggest.autoImports": true, "typescript.preferences.importModuleSpecifier": "relative" } ``` ## 📚 最佳实践 ### 1. 类型定义组织 * 将类型定义放在单独的文件中 * 使用命名空间组织相关类型 * 导出所有必要的类型 ### 2. 验证器设计 * 使用 TypeBox 创建严格的验证器 * 为所有输入参数定义验证规则 * 提供清晰的错误消息 ### 3. 错误处理 * 定义统一的错误类型 * 在客户端处理所有可能的错误 * 提供用户友好的错误消息 ### 4. 性能优化 * 避免不必要的类型检查 * 使用类型缓存减少重复计算 * 优化大型类型定义的性能 ## 🔍 故障排除 ### 常见问题 #### 1. 类型推断失败 ```typescript // 确保服务器类型正确导出 export type App = typeof server // 确保客户端正确导入类型 import type { App } from './server' ``` #### 2. 类型不匹配 ```typescript // 检查服务器和客户端的类型定义 // 确保它们使用相同的类型库版本 ``` #### 3. 编译性能问题 ```typescript // 使用类型缓存 // 避免过大的类型定义 // 使用类型别名简化复杂类型 ``` ## 📚 下一步 现在您已经了解了 Vafast 类型安全客户端的基本概念,接下来可以: 1. **学习具体用法** - 了解如何创建和使用类型安全客户端 2. **探索高级特性** - 学习中间件、错误处理等高级功能 3. **集成到项目** - 将类型安全客户端集成到您的项目中 4. **性能优化** - 学习如何优化类型推断的性能 5. **最佳实践** - 了解更多的使用技巧和最佳实践 如果您有任何问题或需要帮助,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/component-routing.md' --- # 组件路由 Vafast 的组件路由系统是一个强大的功能,它允许您将 Vue 组件与路由关联,创建声明式的组件路由。这对于构建单页应用(SPA)和混合应用非常有用。 ## 什么是组件路由? 组件路由允许您: * 将 Vue 组件与特定路径关联 * 实现客户端路由功能 * 创建单页应用体验 * 支持嵌套组件路由 * 应用中间件到组件路由 ## 基本组件路由 ### 创建组件路由 ```typescript import { ComponentServer } from 'vafast' const routes: any[] = [ { path: '/', component: () => import('./components/Home.vue') }, { path: '/about', component: () => import('./components/About.vue') } ] const server = new ComponentServer(routes) export default { fetch: server.fetch } ``` ### 组件路由结构 ```typescript interface ComponentRoute { path: string component: () => Promise middleware?: Middleware[] } ``` ## 组件定义 ### 基本组件 ```vue ``` ### 动态组件 ```vue ``` ## 嵌套组件路由 Vafast 支持嵌套组件路由,允许您创建复杂的组件层次结构。 ### 基本嵌套 ```typescript const routes: any[] = [ { path: '/dashboard', component: () => import('./components/Dashboard.vue'), children: [ { path: '/overview', component: () => import('./components/dashboard/Overview.vue') }, { path: '/users', component: () => import('./components/dashboard/Users.vue') }, { path: '/settings', component: () => import('./components/dashboard/Settings.vue') } ] } ] ``` ### 嵌套组件实现 ```vue ``` ### 深层嵌套 ```typescript const routes: any[] = [ { path: '/admin', component: () => import('./components/Admin.vue'), children: [ { path: '/users', component: () => import('./components/admin/Users.vue'), children: [ { path: '/list', component: () => import('./components/admin/users/UserList.vue') }, { path: '/create', component: () => import('./components/admin/users/CreateUser.vue') }, { path: '/:id', component: () => import('./components/admin/users/UserDetail.vue') } ] } ] } ] ``` ## 中间件支持 组件路由支持中间件,允许您在组件渲染前后执行自定义逻辑。 ### 组件级中间件 ```typescript const authMiddleware = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { return new Response('Unauthorized', { status: 401 }) } return next() } const routes: any[] = [ { path: '/admin', component: () => import('./components/Admin.vue'), middleware: [authMiddleware], children: [ { path: '/users', component: () => import('./components/admin/Users.vue') } ] } ] ``` ### 全局中间件 ```typescript const logMiddleware = async (req: Request, next: () => Promise) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`) const response = await next() console.log(`Response: ${response.status}`) return response } const routes: any[] = [ { path: '/', middleware: [logMiddleware], // 应用到所有子路由 children: [ { path: '/home', component: () => import('./components/Home.vue') }, { path: '/about', component: () => import('./components/About.vue') } ] } ] ``` ## 动态路由参数 组件路由支持动态参数,允许您根据 URL 参数动态渲染组件。 ### 参数传递 ```typescript const routes: any[] = [ { path: '/user/:id', component: () => import('./components/UserDetail.vue') }, { path: '/post/:postId/comment/:commentId', component: () => import('./components/CommentDetail.vue') } ] ``` ### 组件中获取参数 ```vue ``` ## 路由守卫 Vafast 的组件路由系统支持路由守卫,允许您在路由切换时执行自定义逻辑。 ### 前置守卫 ```typescript const authGuard = async (req: Request, next: () => Promise) => { const token = req.headers.get('authorization') if (!token) { // 重定向到登录页面 return new Response(null, { status: 302, headers: { 'Location': '/login' } }) } // 验证 token try { const user = await validateToken(token) ;(req as any).user = user return next() } catch (error) { return new Response(null, { status: 302, headers: { 'Location': '/login' } }) } } const routes: any[] = [ { path: '/profile', component: () => import('./components/Profile.vue'), middleware: [authGuard] } ] ``` ### 后置守卫 ```typescript const logGuard = async (req: Request, next: () => Promise) => { const start = Date.now() const response = await next() const duration = Date.now() - start console.log(`Route ${req.url} took ${duration}ms`) return response } ``` ## 最佳实践 ### 1. 组件组织 按功能模块组织组件: ``` components/ ├── common/ │ ├── Header.vue │ ├── Footer.vue │ └── Sidebar.vue ├── pages/ │ ├── Home.vue │ ├── About.vue │ └── Contact.vue ├── dashboard/ │ ├── Dashboard.vue │ ├── Overview.vue │ └── Settings.vue └── admin/ ├── Admin.vue ├── Users.vue └── Reports.vue ``` ### 2. 路由配置 将路由配置分离到不同文件: ```typescript // routes/index.ts import { userRoutes } from './user' import { adminRoutes } from './admin' import { dashboardRoutes } from './dashboard' export const routes: any[] = [ { path: '/', component: () => import('../components/pages/Home.vue') }, { path: '/about', component: () => import('../components/pages/About.vue') }, ...userRoutes, ...adminRoutes, ...dashboardRoutes ] ``` ```typescript // routes/user.ts export const userRoutes: any[] = [ { path: '/user', component: () => import('../components/user/UserLayout.vue'), children: [ { path: '/profile', component: () => import('../components/user/Profile.vue') }, { path: '/settings', component: () => import('../components/user/Settings.vue') } ] } ] ``` ### 3. 懒加载 使用动态导入实现组件懒加载: ```typescript const routes: any[] = [ { path: '/dashboard', component: () => import('./components/Dashboard.vue'), children: [ { path: '/analytics', component: () => import(/* webpackChunkName: "analytics" */ './components/Analytics.vue') } ] } ] ``` ### 4. 错误处理 为组件路由添加错误处理: ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { console.error('Component routing error:', error) // 返回错误页面组件 return new Response(` Error

页面加载失败

请稍后重试

`, { status: 500, headers: { 'Content-Type': 'text/html' } }) } } const routes: any[] = [ { path: '/', middleware: [errorHandler], children: [ // 子路由 ] } ] ``` ## 实际应用示例 ### 博客应用 ```typescript const blogRoutes: any[] = [ { path: '/blog', component: () => import('./components/blog/BlogLayout.vue'), children: [ { path: '/', component: () => import('./components/blog/BlogList.vue') }, { path: '/:slug', component: () => import('./components/blog/BlogPost.vue') }, { path: '/category/:category', component: () => import('./components/blog/CategoryPosts.vue') } ] } ] ``` ### 管理面板 ```typescript const adminRoutes: any[] = [ { path: '/admin', component: () => import('./components/admin/AdminLayout.vue'), middleware: [authMiddleware, adminMiddleware], children: [ { path: '/dashboard', component: () => import('./components/admin/Dashboard.vue') }, { path: '/users', component: () => import('./components/admin/Users.vue'), children: [ { path: '/', component: () => import('./components/admin/users/UserList.vue') }, { path: '/create', component: () => import('./components/admin/users/CreateUser.vue') }, { path: '/:id', component: () => import('./components/admin/users/UserDetail.vue') } ] } ] } ] ``` ## 总结 Vafast 的组件路由系统提供了: * ✅ 声明式组件路由 * ✅ 嵌套路由支持 * ✅ 中间件集成 * ✅ 动态参数支持 * ✅ 路由守卫 * ✅ 懒加载支持 * ✅ 类型安全 ### 下一步 * 查看 [路由指南](/routing) 了解基本路由系统 * 学习 [中间件系统](/middleware) 了解如何增强组件路由 * 探索 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/essential/structure.md' --- #### 此页面已移至 [最佳实践](/essential/best-practice) # 结构 Vafast 是一个无模式框架,决定使用哪种编码模式由您和您的团队决定。 然而,尝试将 MVC 模式 [(模型-视图-控制器)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) 与 Vafast 适配时,有几点需要关注,发现很难解耦和处理类型。 此页面是关于如何遵循 Vafast 结构最佳实践与 MVC 模式结合的指南,但可以适应您喜欢的任何编码模式。 ## 方法链 Vafast 代码应始终使用 **方法链**。 由于 Vafast 的类型系统复杂,Vafast 中的每个方法都返回一个新的类型引用。 **这点很重要**,以确保类型的完整性和推断。 ```typescript import { Vafast } from 'vafast' new Vafast() .state('build', 1) // Store 是严格类型的 // [!code ++] .get('/', ({ store: { build } }) => build) .listen(3000) ``` 在上面的代码中 **state** 返回一个新的 **VafastInstance** 类型,添加了一个 `build` 类型。 ### ❌ 不要:不使用方法链 如果不使用方法链,Vafast 就无法保存这些新类型,从而导致没有类型推断。 ```typescript import { Vafast } from 'vafast' const app = new Vafast() app.state('build', 1) app.get('/', ({ store: { build } }) => build) app.listen(3000) ``` 我们建议 **始终使用方法链** 以提供准确的类型推断。 ## 控制器 > 1 个 Vafast 实例 = 1 个控制器 Vafast 确保类型完整性做了很多工作,如果您将整个 `Context` 类型传递给控制器,这可能会产生以下问题: 1. Vafast 类型复杂且严重依赖插件和多级链式调用。 2. 难以类型化,Vafast 类型可能随时改变,特别是在使用装饰器和存储时。 3. 类型转换可能导致类型完整性的丧失或无法确保类型与运行时代码之间的一致性。 4. 这使得 [Sucrose](/blog/vafast-10#sucrose) *(Vafast的“类似”编译器)* 更难以对您的代码进行静态分析。 ### ❌ 不要:创建单独的控制器 不要创建单独的控制器,而是使用 Vafast 自身作为控制器: ```typescript import { Vafast, t, type Context } from 'vafast' abstract class Controller { static root(context: Context) { return Service.doStuff(context.stuff) } } // ❌ 不要 new Vafast() .get('/', Controller.hi) ``` 将整个 `Controller.method` 传递给 Vafast 相当于拥有两个控制器在数据之间来回传递。这违背了框架和 MVC 模式本身的设计。 ### ✅ 要:将 Vafast 作为控制器使用 相反,将 Vafast 实例视为控制器本身。 ```typescript import { Vafast } from 'vafast' import { Service } from './service' new Vafast() .get('/', ({ stuff }) => { Service.doStuff(stuff) }) ``` ## 服务 服务是一组实用程序/辅助函数,解耦为在模块/控制器中使用的业务逻辑,在我们的例子中就是 Vafast 实例。 任何可以从控制器中解耦的技术逻辑都可以放在 **Service** 中。 Vafast 中有两种类型的服务: 1. 非请求依赖服务 2. 请求依赖服务 ### ✅ 要:非请求依赖服务 这种服务不需要访问请求或 `Context` 的任何属性,可以像通常的 MVC 服务模式一样作为静态类进行初始化。 ```typescript import { Vafast, t } from 'vafast' abstract class Service { static fibo(number: number): number { if (number < 2) return number return Service.fibo(number - 1) + Service.fibo(number - 2) } } new Vafast() .get('/fibo', ({ body }) => { return Service.fibo(body) }, { body: t.Numeric() }) ``` 如果您的服务不需要存储属性,您可以使用 `abstract class` 和 `static` 来避免分配类实例。 ### 请求依赖服务 这种服务可能需要请求中的一些属性,应该 **作为 Vafast 实例进行初始化**。 ### ❌ 不要:将整个 `Context` 传递给服务 **Context 是高度动态的类型**,可以从 Vafast 实例推断。 不要将整个 `Context` 传递给服务,而是使用对象解构提取所需内容并将其传递给服务。 ```typescript import type { Context } from 'vafast' class AuthService { constructor() {} // ❌ 不要这样做 isSignIn({ status, cookie: { session } }: Context) { if (session.value) return status(401) } } ``` 由于 Vafast 类型复杂且严重依赖插件和多级链式调用,手动进行类型化可能会很具挑战性,因为它高度动态。 ### ✅ 建议做法:将依赖服务请求抽象为Vafast实例 我们建议将服务类抽象化,远离 Vafast。 然而,**如果服务是请求依赖服务**或需要处理 HTTP 请求,我们建议将其抽象为 Vafast 实例,以确保类型的完整性和推断: ```typescript import { Vafast } from 'vafast' // ✅ 要 const AuthService = new Vafast({ name: 'Service.Auth' }) .derive({ as: 'scoped' }, ({ cookie: { session } }) => ({ // 这相当于依赖注入 Auth: { user: session.value } })) .macro(({ onBeforeHandle }) => ({ // 这声明了一个服务方法 isSignIn(value: boolean) { onBeforeHandle(({ Auth, status }) => { if (!Auth?.user || !Auth.user) return status(401) }) } })) const UserController = new Vafast() .use(AuthService) .get('/profile', ({ Auth: { user } }) => user, { isSignIn: true }) ``` ::: tip Vafast 默认处理 [插件去重](/essential/plugin.html#plugin-deduplication),您无需担心性能,因为如果您指定了 **"name"** 属性,它将成为单例。 ::: ### ⚠️ 从 Vafast 实例中推断 Context 如果**绝对必要**,您可以从 Vafast 实例本身推断 `Context` 类型: ```typescript import { Vafast, type InferContext } from 'vafast' const setup = new Vafast() .state('a', 'a') .decorate('b', 'b') class AuthService { constructor() {} // ✅ 要 isSignIn({ status, cookie: { session } }: InferContext) { if (session.value) return status(401) } } ``` 然而,我们建议尽可能避免这样做,而是使用 [Vafast 作为服务](✅-do-use-vafast-instance-as-a-service)。 您可以在 [Essential: Handler](/essential/handler) 中了解更多关于 [InferContext](/essential/handler#infercontext) 的信息。 ## 模型 模型或 [DTO (数据传输对象)](https://en.wikipedia.org/wiki/Data_transfer_object) 通过 [Vafast.t (验证)](/validation/overview.html#data-validation) 来处理。 Vafast 内置了验证系统,可以从您的代码中推断类型并在运行时进行验证。 ### ❌ 不要:将类实例声明为模型 不要将类实例声明为模型: ```typescript // ❌ 不要 class CustomBody { username: string password: string constructor(username: string, password: string) { this.username = username this.password = password } } // ❌ 不要 interface ICustomBody { username: string password: string } ``` ### ✅ 要:使用 Vafast 的验证系统 不要声明类或接口,而是使用 Vafast 的验证系统来定义模型: ```typescript // ✅ 要 import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) // 如果您想获取模型的类型可以选择性地 // 通常如果我们不使用类型,因为它已由 Vafast 推断 type CustomBody = typeof customBody.static export { customBody } ``` 我们可以通过与 `.static` 属性结合使用 `typeof` 来获取模型的类型。 然后您可以使用 `CustomBody` 类型来推断请求体的类型。 ```typescript import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) // ✅ 要 new Vafast() .post('/login', ({ body }) => { return body }, { body: customBody }) ``` ### ❌ 不要:将类型与模型分开声明 不要将类型与模型分开声明,而是使用 `typeof` 和 `.static` 属性来获取模型的类型。 ```typescript // ❌ 不要 import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) type CustomBody = { username: string password: string } // ✅ 要 const customBody = t.Object({ username: t.String(), password: t.String() }) type customBody = typeof customBody.static ``` ### 分组 您可以将多个模型分组为一个单独的对象,以使其更加有序。 ```typescript import { Vafast, t } from 'vafast' export const AuthModel = { sign: t.Object({ username: t.String(), password: t.String() }) } ``` ### 模型注入 虽然这不是必需的,如果您严格遵循 MVC 模式,您可能想像服务一样将模型注入到控制器中。我们推荐使用 [Vafast 参考模型](/essential/validation.html#reference-model) 使用 Vafast 的模型引用 ```typescript import { Vafast, t } from 'vafast' const customBody = t.Object({ username: t.String(), password: t.String() }) const AuthModel = new Vafast() .model({ 'auth.sign': customBody }) const UserController = new Vafast({ prefix: '/auth' }) .use(AuthModel) .post('/sign-in', async ({ body, cookie: { session } }) => { return true }, { body: 'auth.sign' }) ``` 这个方法提供了几个好处: 1. 允许我们命名模型并提供自动完成。 2. 为以后的使用修改模式,或执行 [重新映射](/patterns/remapping.html#remapping)。 3. 在 OpenAPI 合规客户端中显示为“模型”,例如 Swagger。 4. 提升 TypeScript 推断速度,因为模型类型在注册期间会被缓存。 *** 如前所述,Vafast 是一个无模式框架,我们仅提供关于如何将 Vafast 与 MVC 模式结合的推荐指南。 是否遵循此推荐完全取决于您和您的团队的偏好和共识。 --- --- url: 'https://vafast.dev/essential/route.md' --- # 路由 Web 服务器使用请求的 **路径和 HTTP 方法** 来查找正确的资源,这被称为 **"路由"**。 在 Vafast 中,我们通过定义路由配置对象来定义路由,包括 HTTP 方法、路径和处理函数。 ## 基本路由 ### 定义路由 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => 'Hello World') } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### HTTP 方法 Vafast 支持所有标准的 HTTP 方法: ```typescript const routes = defineRoutes([ { method: 'GET', // 获取资源 path: '/users', handler: createHandler(() => 'Get users') }, { method: 'POST', // 创建资源 path: '/users', handler: createHandler(() => 'Create user') }, { method: 'PUT', // 更新资源 path: '/users/:id', handler: createHandler(() => 'Update user') }, { method: 'DELETE', // 删除资源 path: '/users/:id', handler: createHandler(() => 'Delete user') }, { method: 'PATCH', // 部分更新资源 path: '/users/:id', handler: createHandler(() => 'Patch user') } ]) ``` ### 路径参数 路径参数允许您捕获 URL 中的动态值: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(({ params }) => { return `User ID: ${params.id}` }) }, { method: 'GET', path: '/posts/:postId/comments/:commentId', handler: createHandler(({ params }) => { return `Post: ${params.postId}, Comment: ${params.commentId}` }) } ]) ``` ### 查询参数 查询参数通过 `query` 对象访问: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/search', handler: createHandler(({ query }) => { const { q, page = '1', limit = '10' } = query return `Search: ${q}, Page: ${page}, Limit: ${limit}` }) } ]) ``` ### 请求体 POST、PUT、PATCH 请求的请求体通过 `body` 对象访问: ```typescript const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { return `Created user: ${body.name}` }) } ]) ``` ## 路由优先级 Vafast 使用智能路由匹配算法,静态路径优先于动态路径: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/users/123', // 静态路径 - 优先级高 handler: createHandler(() => 'Specific user') }, { method: 'GET', path: '/users/:id', // 动态路径 - 优先级低 handler: createHandler(({ params }) => `User ${params.id}`) } ]) ``` ## 嵌套路由 Vafast 支持嵌套路由结构,使用 `children` 属性: ```typescript const routes = defineRoutes([ { path: '/api', children: [ { path: '/v1', children: [ { method: 'GET', path: '/users', handler: createHandler(() => 'API v1 users') } ] }, { path: '/v2', children: [ { method: 'GET', path: '/users', handler: createHandler(() => 'API v2 users') } ] } ] } ]) ``` ## 路由配置选项 每个路由可以配置以下选项: ```typescript const routes = defineRoutes([ { method: 'GET', path: '/protected', handler: createHandler(() => 'Protected content'), middleware: [authMiddleware], // 路由级中间件 body: userSchema, // 请求体验证 query: querySchema, // 查询参数验证 params: paramsSchema // 路径参数验证 } ]) ``` ## 最佳实践 ### 1. 使用描述性路径 ```typescript // ✅ 好的 path: '/users/:id/profile' path: '/posts/:postId/comments' // ❌ 不好的 path: '/u/:i' path: '/p/:p/c' ``` ### 2. 保持路由结构清晰 ```typescript const routes = defineRoutes([ // 用户相关路由 { path: '/users', children: [ { method: 'GET', path: '/', handler: createHandler(() => 'List users') }, { method: 'POST', path: '/', handler: createHandler(() => 'Create user') }, { method: 'GET', path: '/:id', handler: createHandler(({ params }) => `User ${params.id}`) } ] }, // 文章相关路由 { path: '/posts', children: [ { method: 'GET', path: '/', handler: createHandler(() => 'List posts') }, { method: 'POST', path: '/', handler: createHandler(() => 'Create post') } ] } ]) ``` ### 3. 使用适当的 HTTP 方法 ```typescript const routes = defineRoutes([ { method: 'GET', // 获取数据 path: '/users', handler: createHandler(() => 'Get users') }, { method: 'POST', // 创建数据 path: '/users', handler: createHandler(() => 'Create user') }, { method: 'PUT', // 完全更新 path: '/users/:id', handler: createHandler(() => 'Update user') }, { method: 'PATCH', // 部分更新 path: '/users/:id', handler: createHandler(() => 'Patch user') }, { method: 'DELETE', // 删除数据 path: '/users/:id', handler: createHandler(() => 'Delete user') } ]) ``` --- --- url: 'https://vafast.dev/routing.md' --- # 路由指南 Vafast 的路由系统是框架的核心,它提供了强大而灵活的方式来定义 API 端点。本指南将详细介绍 Vafast 的路由功能。 ## 基本路由 路由是 Vafast 应用的基础构建块。每个路由都定义了 HTTP 方法、路径和处理函数。 ### 路由结构 ```typescript import { Server } from 'vafast' const routes: any[] = [ { method: 'GET', path: '/', handler: () => new Response('Hello Vafast!') } ] const server = new Server(routes) export default { fetch: server.fetch } ``` ### 支持的 HTTP 方法 Vafast 支持所有标准的 HTTP 方法: ```typescript const routes: any[] = [ { method: 'GET', // 获取资源 path: '/users', handler: () => new Response('Get users') }, { method: 'POST', // 创建资源 path: '/users', handler: async (req: Request) => { const body = await req.json() return new Response('Create user') } }, { method: 'PUT', // 更新资源 path: '/users/:id', handler: async (req: Request) => new Response('Update user') }, { method: 'DELETE', // 删除资源 path: '/users/:id', handler: () => new Response('Delete user') }, { method: 'PATCH', // 部分更新 path: '/users/:id', handler: () => new Response('Patch user') }, { method: 'OPTIONS', // 预检请求 path: '/users', handler: () => new Response('Options') }, { method: 'HEAD', // 获取响应头 path: '/users', handler: () => new Response('Head') } ] ``` ## 动态路由 Vafast 支持动态路由参数,允许您捕获 URL 中的变量值。 ### 基本参数 ```typescript { method: 'GET', path: '/users/:id', handler: (req: Request, params?: Record) => { const userId = params?.id return new Response(`User ID: ${userId}`) } } ``` ### 多个参数 ```typescript { method: 'GET', path: '/users/:userId/posts/:postId', handler: (req: Request, params?: Record) => { const { userId, postId } = params || {} return new Response(`User ${userId}, Post ${postId}`) } } ``` ### 可选参数 ```typescript { method: 'GET', path: '/users/:id?', handler: (req: Request, params?: Record) => { if (params?.id) { return new Response(`User ID: ${params.id}`) } return new Response('All users') } } ``` ## 嵌套路由 Vafast 支持嵌套路由结构,允许您组织复杂的路由层次。 ### 基本嵌套 ```typescript const routes: any[] = [ { path: '/api', children: [ { method: 'GET', path: '/users', handler: () => new Response('Users API') }, { method: 'GET', path: '/posts', handler: () => new Response('Posts API') } ] } ] ``` ### 深层嵌套 ```typescript const routes: any[] = [ { path: '/api', children: [ { path: '/v1', children: [ { path: '/users', children: [ { method: 'GET', path: '/', handler: () => new Response('Users v1') }, { method: 'POST', path: '/', handler: async (req: Request) => new Response('Create user v1') } ] } ] } ] } ] ``` ## 中间件 中间件是 Vafast 路由系统的强大功能,允许您在请求处理前后执行自定义逻辑。 ### 中间件定义 ```typescript const authMiddleware = async (req: Request, next: () => Promise) => { const auth = req.headers.get('authorization') if (!auth) { return new Response('Unauthorized', { status: 401 }) } return next() } const logMiddleware = async (req: Request, next: () => Promise) => { const start = Date.now() const response = await next() const duration = Date.now() - start console.log(`${req.method} ${req.url} - ${duration}ms`) return response } ``` ### 应用中间件 ```typescript const routes: any[] = [ { method: 'GET', path: '/admin', middleware: [authMiddleware, logMiddleware], handler: () => new Response('Admin panel') } ] ``` ### 全局中间件 ```typescript const routes: any[] = [ { path: '/api', middleware: [logMiddleware], // 应用到所有子路由 children: [ { method: 'GET', path: '/users', handler: () => new Response('Users') } ] } ] ``` ## 路由处理函数 处理函数是路由的核心,负责处理请求并返回响应。 ### 基本处理函数 ```typescript { method: 'GET', path: '/hello', handler: () => new Response('Hello World') } ``` ### 异步处理函数 ```typescript { method: 'POST', path: '/users', handler: async (req: Request) => { const body = await req.json() // 处理数据... return new Response('User created', { status: 201 }) } } ``` ### 生成器处理函数 ```typescript { method: 'GET', path: '/stream', handler: function* () { yield 'Hello' yield 'World' yield '!' } } ``` ### 带参数的处理函数 ```typescript { method: 'GET', path: '/users/:id', handler: (req: Request, params?: Record) => { const userId = params?.id const query = new URL(req.url).searchParams return new Response(`User ${userId}, Query: ${query.get('sort')}`) } } ``` ## 响应处理 Vafast 使用标准的 Web API Response 对象,提供了灵活的响应方式。 ### 基本响应 ```typescript handler: () => new Response('Hello World') ``` ### JSON 响应 ```typescript handler: () => new Response( JSON.stringify({ message: 'Success' }), { headers: { 'Content-Type': 'application/json' } } ) ``` ### 状态码和头部 ```typescript handler: () => new Response('Created', { status: 201, headers: { 'Content-Type': 'text/plain', 'X-Custom-Header': 'value' } }) ``` ### 重定向 ```typescript handler: () => new Response(null, { status: 302, headers: { 'Location': '/new-page' } }) ``` ## 错误处理 Vafast 提供了多种错误处理方式。 ### 抛出错误 ```typescript handler: () => { throw new Error('Something went wrong') } ``` ### 返回错误响应 ```typescript handler: () => { return new Response('Not found', { status: 404 }) } ``` ### 错误处理中间件 ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { console.error('Error:', error) return new Response('Internal Server Error', { status: 500 }) } } ``` ## 最佳实践 ### 1. 路由组织 ```typescript // 按功能组织路由 const userRoutes = [ { method: 'GET', path: '/users', handler: () => new Response('Get users') }, { method: 'POST', path: '/users', handler: async (req: Request) => new Response('Create user') } ] const postRoutes = [ { method: 'GET', path: '/posts', handler: () => new Response('Get posts') } ] const routes: any[] = [ { path: '/api', children: [...userRoutes, ...postRoutes] } ] ``` ### 2. 中间件复用 ```typescript const commonMiddleware = [logMiddleware, corsMiddleware] const routes: any[] = [ { path: '/api', middleware: commonMiddleware, children: [ // 所有子路由都会应用 commonMiddleware ] } ] ``` ### 3. 类型安全 ```typescript interface RouteParams { id: string category?: string } const routes: any[] = [ { method: 'GET', path: '/posts/:id/:category?', handler: (req: Request, params?: RouteParams) => { // 类型安全的参数访问 const { id, category } = params || {} return new Response(`Post ${id} in ${category || 'default'}`) } } ] ``` ## 总结 Vafast 的路由系统提供了: * ✅ 完整的 HTTP 方法支持 * ✅ 动态路由参数 * ✅ 嵌套路由结构 * ✅ 灵活的中间件系统 * ✅ 类型安全的处理函数 * ✅ 标准的 Web API 响应 ### 下一步 * 查看 [中间件系统](/middleware) 了解更高级的中间件用法 * 学习 [组件路由](/component-routing) 了解声明式路由 * 探索 [最佳实践](/best-practices) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/patterns/deploy.md' --- # 部署到生产环境 本页面是关于如何将 Vafast 部署到生产环境的指南。 ## 编译为二进制 我们建议在部署到生产环境之前运行构建命令,因为这可能会显著减少内存使用和文件大小。 我们推荐使用以下命令将 Vafast 编译成单个二进制文件: ```bash bun build \ --compile \ --minify-whitespace \ --minify-syntax \ --target bun \ --outfile server \ ./src/index.ts ``` 这将生成一个可移植的二进制文件 `server`,我们可以运行它来启动我们的服务器。 将服务器编译为二进制文件通常会将内存使用量显著减少 2-3 倍,相较于开发环境。 这个命令有点长,所以让我们分解一下: 1. `--compile` - 将 TypeScript 编译为二进制 2. `--minify-whitespace` - 删除不必要的空白 3. `--minify-syntax` - 压缩 JavaScript 语法以减少文件大小 4. `--target bun` - 针对 `bun` 平台,可以为目标平台优化二进制文件 5. `--outfile server` - 输出二进制文件为 `server` 6. `./src/index.ts` - 我们服务器的入口文件(代码库) 要启动我们的服务器,只需运行二进制文件。 ```bash ./server ``` 一旦二进制文件编译完成,您就不需要在机器上安装 `Bun` 以运行服务器。 这很好,因为部署服务器不需要安装额外的运行时,使得二进制文件便于移植。 ### 为什么不使用 --minify Bun 确实有 `--minify` 标志,用于压缩二进制文件。 然而,如果我们正在使用 [OpenTelemetry](/middleware/opentelemetry),它会将函数名缩减为单个字符。 这使得跟踪变得比应该的更加困难,因为 OpenTelemetry 依赖于函数名。 但是,如果您不使用 OpenTelemetry,您可以选择使用 `--minify`: ```bash bun build \ --compile \ --minify \ --target bun \ --outfile server \ ./src/index.ts ``` ### 权限 一些 Linux 发行版可能无法运行二进制文件,如果您使用的是 Linux,建议为二进制文件启用可执行权限: ```bash chmod +x ./server ./server ``` ### 未知的随机中文错误 如果您尝试将二进制文件部署到服务器但无法运行,并出现随机中文字符错误。 这意味着您运行的机器 **不支持 AVX2**。 不幸的是,Bun 要求机器具有 `AVX2` 硬件支持。 据我们所知没有替代方案。 ## 编译为 JavaScript 如果您无法编译为二进制文件或您正在 Windows 服务器上进行部署。 您可以将服务器打包为一个 JavaScript 文件。 ```bash bun build \ --minify-whitespace \ --minify-syntax \ --target bun \ --outfile ./dist/index.js \ ./src/index.ts ``` 这将生成一个压缩的 JavaScript 文件,您可以使用 Node.js 或 Bun 运行它。 ```bash # 使用 Bun 运行 bun ./dist/index.js # 或使用 Node.js 运行 node ./dist/index.js ``` ## Docker 部署 ### 创建 Dockerfile ```dockerfile # 使用 Bun 官方镜像 FROM oven/bun:1 as base WORKDIR /usr/src/app # 安装依赖 COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile # 复制源代码 COPY . . # 构建应用 RUN bun run build # 生产镜像 FROM oven/bun:1-slim WORKDIR /usr/src/app # 复制构建产物和依赖 COPY --from=base /usr/src/app/dist ./dist COPY --from=base /usr/src/app/node_modules ./node_modules COPY --from=base /usr/src/app/package.json ./ # 暴露端口 EXPOSE 3000 # 启动应用 CMD ["bun", "dist/index.js"] ``` ### 构建和运行 Docker 镜像 ```bash # 构建镜像 docker build -t vafast-app . # 运行容器 docker run -p 3000:3000 vafast-app ``` ## 环境变量配置 在生产环境中,建议使用环境变量来配置应用: ```bash # .env.production NODE_ENV=production PORT=3000 DATABASE_URL=postgresql://user:password@localhost:5432/db JWT_SECRET=your-secret-key ``` 在代码中使用: ```typescript // src/config.ts export const config = { port: process.env.PORT || 3000, databaseUrl: process.env.DATABASE_URL, jwtSecret: process.env.JWT_SECRET, nodeEnv: process.env.NODE_ENV || 'development' } ``` ## 性能优化 ### 1. 启用压缩 ```typescript // src/index.ts import { Server, defineRoutes, createHandler } from 'vafast' import { compress } from '@vafast/compress' const routes = defineRoutes([ // ... 你的路由 ]) const server = new Server(routes) // 应用压缩中间件 server.use(compress()) export default { fetch: server.fetch } ``` ### 2. 缓存策略 ```typescript // 添加缓存头 const routes = defineRoutes([ { method: 'GET', path: '/static/:file', handler: createHandler(({ params }) => { const file = getStaticFile(params.file) return new Response(file, { headers: { 'Cache-Control': 'public, max-age=31536000', // 1年 'ETag': generateETag(file) } }) }) } ]) ``` ### 3. 负载均衡 如果您的应用需要处理高流量,可以考虑使用负载均衡器: ```bash # 使用 Nginx 作为反向代理 upstream vafast_backend { server 127.0.0.1:3000; server 127.0.0.1:3001; server 127.0.0.1:3002; } server { listen 80; server_name yourdomain.com; location / { proxy_pass http://vafast_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ## 监控和日志 ### 1. 健康检查端点 ```typescript const routes = defineRoutes([ { method: 'GET', path: '/health', handler: createHandler(() => ({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage() })) } ]) ``` ### 2. 结构化日志 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' const loggingMiddleware = async (req: Request, next: () => Promise) => { const startTime = Date.now() const response = await next() const duration = Date.now() - startTime console.log(JSON.stringify({ timestamp: new Date().toISOString(), method: req.method, url: req.url, status: response.status, duration: `${duration}ms`, userAgent: req.headers.get('user-agent') })) return response } const server = new Server(routes) server.use(loggingMiddleware) ``` ### 3. 性能监控 ```typescript import { withMonitoring } from 'vafast/monitoring' const server = new Server(routes) const monitoredServer = withMonitoring(server, { enableMetrics: true, enableLogging: true }) export default { fetch: monitoredServer.fetch } ``` ## 安全配置 ### 1. HTTPS 配置 在生产环境中,强烈建议使用 HTTPS: ```typescript // 使用 Bun 的内置 HTTPS 支持 const server = Bun.serve({ port: 3000, fetch: server.fetch, tls: { cert: Bun.file('path/to/cert.pem'), key: Bun.file('path/to/key.pem') } }) ``` ### 2. 安全头 ```typescript import { helmet } from '@vafast/helmet' const server = new Server(routes) server.use(helmet()) ``` ### 3. 速率限制 ```typescript import { rateLimit } from '@vafast/rate-limit' const server = new Server(routes) server.use(rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 限制每个IP 15分钟内最多100个请求 })) ``` ## 部署检查清单 在部署到生产环境之前,请确保: * \[ ] 所有环境变量已正确配置 * \[ ] 数据库连接已测试 * \[ ] 日志记录已启用 * \[ ] 监控已配置 * \[ ] 健康检查端点已实现 * \[ ] 错误处理已完善 * \[ ] 安全头已配置 * \[ ] 速率限制已启用 * \[ ] HTTPS 已配置(如果适用) * \[ ] 备份策略已制定 ## 总结 Vafast 的生产部署提供了: * ✅ 二进制编译支持 * ✅ Docker 容器化 * ✅ 环境变量配置 * ✅ 性能优化选项 * ✅ 监控和日志 * ✅ 安全配置 * ✅ 负载均衡支持 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [中间件系统](/middleware) 了解如何增强功能 * 探索 [验证系统](/essential/validation) 了解类型安全 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。 --- --- url: 'https://vafast.dev/api-client/treaty/config.md' --- # 配置选项 Vafast 类型安全客户端提供了丰富的配置选项,让您能够根据项目需求进行精确的定制。本章将详细介绍所有可用的配置选项。 ## 🔧 基础配置 ### 创建客户端时的配置 ```typescript import { createTypedClient } from '@vafast/api-client' import type { App } from './server' const client = createTypedClient('http://localhost:3000', { // 基础配置 baseURL: 'https://api.example.com', timeout: 10000, retries: 3, // 请求配置 defaultHeaders: { 'Content-Type': 'application/json', 'User-Agent': 'Vafast-API-Client/1.0.0' }, // 缓存配置 enableCache: true, cacheExpiry: 300000, // 重试配置 retryDelay: 1000, retryCondition: (error) => error.status >= 500 }) ``` ### 配置选项详解 #### 基础选项 ```typescript interface BaseConfig { // 基础 URL,所有请求都会基于此 URL baseURL?: string // 请求超时时间(毫秒) timeout?: number // 最大重试次数 retries?: number // 重试延迟时间(毫秒) retryDelay?: number // 是否启用缓存 enableCache?: boolean // 缓存过期时间(毫秒) cacheExpiry?: number // 缓存策略 cacheStrategy?: 'memory' | 'localStorage' | 'sessionStorage' } ``` #### 请求选项 ```typescript interface RequestConfig { // 默认请求头 defaultHeaders?: Record // 请求拦截器 requestInterceptors?: RequestInterceptor[] // 响应拦截器 responseInterceptors?: ResponseInterceptor[] // 错误处理器 errorHandler?: ErrorHandler // 日志配置 logging?: LoggingConfig } ``` #### 重试选项 ```typescript interface RetryConfig { // 重试条件函数 retryCondition?: (error: ApiError, attempt: number) => boolean // 自定义重试延迟函数 retryDelay?: (attempt: number) => number // 最大重试次数 maxRetries?: number // 重试状态码 retryStatusCodes?: number[] } ``` ## 🎛️ 高级配置 ### 中间件配置 ```typescript import { createTypedClient } from '@vafast/api-client' import type { App } from './server' const client = createTypedClient('http://localhost:3000', { // 请求中间件 requestInterceptors: [ async (config, next) => { // 添加认证头 if (localStorage.getItem('token')) { config.headers.Authorization = `Bearer ${localStorage.getItem('token')}` } // 添加请求 ID config.headers['X-Request-ID'] = `req-${Date.now()}` return await next(config) }, async (config, next) => { // 记录请求开始时间 config.metadata = { startTime: Date.now() } return await next(config) } ], // 响应中间件 responseInterceptors: [ async (response, next) => { // 记录响应时间 if (response.metadata?.startTime) { const duration = Date.now() - response.metadata.startTime console.log(`Request completed in ${duration}ms`) } return await next(response) }, async (response, next) => { // 处理特定状态码 if (response.status === 401) { // 清除无效的认证信息 localStorage.removeItem('token') window.location.href = '/login' } return await next(response) } ] }) ``` ### 缓存配置 ```typescript const client = createTypedClient('http://localhost:3000', { // 启用缓存 enableCache: true, // 缓存过期时间(5分钟) cacheExpiry: 300000, // 缓存策略 cacheStrategy: 'memory', // 'memory' | 'localStorage' | 'sessionStorage' // 自定义缓存键生成器 cacheKeyGenerator: (config) => { const { method, url, params, query } = config return `${method}:${url}:${JSON.stringify(params)}:${JSON.stringify(query)}` }, // 缓存验证器 cacheValidator: (cachedData) => { // 检查缓存数据是否仍然有效 return cachedData && Date.now() - cachedData.timestamp < 300000 } }) ``` ### 重试配置 ```typescript const client = createTypedClient('http://localhost:3000', { // 重试配置 retries: 5, retryDelay: 1000, // 自定义重试条件 retryCondition: (error, attempt) => { // 只在特定错误时重试 if (attempt >= 5) return false // 重试 5xx 错误和网络错误 if (error.status >= 500) return true // 重试 429 错误(限流) if (error.status === 429) return true // 重试网络错误 if (error.type === 'network') return true return false }, // 指数退避重试延迟 retryDelay: (attempt) => { const baseDelay = 1000 const maxDelay = 10000 const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay) // 添加随机抖动 const jitter = Math.random() * 0.1 * delay return delay + jitter } }) ``` ### 日志配置 ```typescript const client = createTypedClient('http://localhost:3000', { // 日志配置 logging: { enabled: true, level: 'info', // 'debug' | 'info' | 'warn' | 'error' format: 'json', // 'json' | 'text' // 自定义日志记录器 logger: { debug: (message, data) => console.debug(`[DEBUG] ${message}`, data), info: (message, data) => console.info(`[INFO] ${message}`, data), warn: (message, data) => console.warn(`[WARN] ${message}`, data), error: (message, data) => console.error(`[ERROR] ${message}`, data) }, // 日志过滤器 filter: (level, message, data) => { // 在生产环境中过滤敏感信息 if (process.env.NODE_ENV === 'production') { if (data?.headers?.Authorization) { data.headers.Authorization = '[REDACTED]' } } return true } } }) ``` ## 🔐 安全配置 ### 认证配置 ```typescript const client = createTypedClient('http://localhost:3000', { // 认证配置 auth: { // 自动添加认证头 autoAuth: true, // 认证头格式 authHeader: 'Authorization', authFormat: 'Bearer {token}', // 获取认证信息 getToken: () => localStorage.getItem('token'), // 刷新认证信息 refreshToken: async () => { const refreshToken = localStorage.getItem('refreshToken') if (!refreshToken) throw new Error('No refresh token') const response = await fetch('/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) }) if (!response.ok) throw new Error('Failed to refresh token') const { token, refreshToken: newRefreshToken } = await response.json() localStorage.setItem('token', token) localStorage.setItem('refreshToken', newRefreshToken) return token }, // 认证失败处理 onAuthFailure: (error) => { if (error.status === 401) { localStorage.removeItem('token') localStorage.removeItem('refreshToken') window.location.href = '/login' } } } }) ``` ### CORS 配置 ```typescript const client = createTypedClient('http://localhost:3000', { // CORS 配置 cors: { // 允许的源 allowedOrigins: ['http://localhost:3000', 'https://example.com'], // 允许的请求方法 allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // 允许的请求头 allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], // 是否允许携带认证信息 allowCredentials: true, // 预检请求缓存时间 maxAge: 86400 } }) ``` ## 🌍 环境配置 ### 多环境配置 ```typescript // config/environments.ts interface EnvironmentConfig { development: ClientConfig staging: ClientConfig production: ClientConfig } const environments: EnvironmentConfig = { development: { baseURL: 'http://localhost:3000', timeout: 5000, retries: 1, enableCache: false, logging: { enabled: true, level: 'debug' } }, staging: { baseURL: 'https://staging-api.example.com', timeout: 10000, retries: 3, enableCache: true, cacheExpiry: 300000, logging: { enabled: true, level: 'info' } }, production: { baseURL: 'https://api.example.com', timeout: 15000, retries: 5, enableCache: true, cacheExpiry: 600000, logging: { enabled: false } } } // 根据环境变量选择配置 const env = process.env.NODE_ENV || 'development' const config = environments[env as keyof EnvironmentConfig] // 创建客户端 const client = createTypedClient('', config) ``` ### 动态配置更新 ```typescript const client = createTypedClient('http://localhost:3000', { baseURL: 'https://api.example.com', timeout: 10000 }) // 动态更新配置 client.updateConfig({ timeout: 15000, retries: 5 }) // 更新基础 URL client.updateConfig({ baseURL: 'https://new-api.example.com' }) // 启用缓存 client.updateConfig({ enableCache: true, cacheExpiry: 300000 }) ``` ## 🔧 自定义配置 ### 自定义拦截器 ```typescript // 自定义请求拦截器 const customRequestInterceptor: RequestInterceptor = async (config, next) => { // 添加时间戳 config.headers['X-Timestamp'] = Date.now().toString() // 添加请求签名 const signature = generateSignature(config) config.headers['X-Signature'] = signature return await next(config) } // 自定义响应拦截器 const customResponseInterceptor: ResponseInterceptor = async (response, next) => { // 验证响应签名 const expectedSignature = generateSignature(response) const actualSignature = response.headers.get('X-Signature') if (expectedSignature !== actualSignature) { throw new Error('Invalid response signature') } return await next(response) } // 使用自定义拦截器 const client = createTypedClient('http://localhost:3000', { requestInterceptors: [customRequestInterceptor], responseInterceptors: [customResponseInterceptor] }) ``` ### 自定义错误处理器 ```typescript // 自定义错误处理器 const customErrorHandler: ErrorHandler = (error, request) => { // 记录错误 console.error('API Error:', { message: error.message, status: error.status, url: request.url, method: request.method, timestamp: new Date().toISOString() }) // 发送错误报告 if (process.env.NODE_ENV === 'production') { sendErrorReport(error, request) } // 根据错误类型采取不同措施 switch (error.status) { case 401: // 重定向到登录页面 window.location.href = '/login' break case 403: // 显示权限不足提示 showPermissionDeniedMessage() break case 429: // 显示限流提示 showRateLimitMessage() break case 500: // 显示服务器错误提示 showServerErrorMessage() break default: // 显示通用错误提示 showErrorMessage(error.message) } } // 使用自定义错误处理器 const client = createTypedClient('http://localhost:3000', { errorHandler: customErrorHandler }) ``` ## 📊 监控配置 ### 性能监控 ```typescript const client = createTypedClient('http://localhost:3000', { // 启用性能监控 monitoring: { enabled: true, // 性能指标收集 metrics: { responseTime: true, requestSize: true, responseSize: true, errorRate: true, throughput: true }, // 性能阈值 thresholds: { responseTime: 5000, // 5秒 errorRate: 0.05, // 5% throughput: 100 // 100 req/s }, // 性能报告 reporting: { interval: 60000, // 每分钟报告一次 destination: 'https://metrics.example.com/api/metrics' } } }) ``` ### 健康检查 ```typescript const client = createTypedClient('http://localhost:3000', { // 健康检查配置 healthCheck: { enabled: true, endpoint: '/health', interval: 30000, // 30秒检查一次 // 健康检查条件 conditions: { responseTime: (time) => time < 1000, statusCode: (status) => status === 200, responseBody: (body) => body.status === 'healthy' }, // 健康检查失败处理 onFailure: (error) => { console.warn('Health check failed:', error) // 可以在这里实现故障转移逻辑 } } }) ``` ## 🔍 调试配置 ### 开发环境调试 ```typescript const client = createTypedClient('http://localhost:3000', { // 开发环境调试配置 debug: { enabled: process.env.NODE_ENV === 'development', // 请求日志 logRequests: true, logResponses: true, logErrors: true, // 性能分析 profile: true, // 类型检查 typeCheck: true, // 调试工具 devTools: { enabled: true, port: 9229 } } }) ``` ### 调试工具集成 ```typescript // 启用调试工具 if (process.env.NODE_ENV === 'development') { // 在浏览器中打开调试工具 client.debug.openDevTools() // 监听所有请求 client.debug.on('request', (config) => { console.group('🚀 API Request') console.log('URL:', config.url) console.log('Method:', config.method) console.log('Headers:', config.headers) console.log('Body:', config.body) console.groupEnd() }) // 监听所有响应 client.debug.on('response', (response) => { console.group('✅ API Response') console.log('Status:', response.status) console.log('Headers:', response.headers) console.log('Data:', response.data) console.log('Time:', response.metadata?.duration) console.groupEnd() }) // 监听所有错误 client.debug.on('error', (error) => { console.group('❌ API Error') console.error('Message:', error.message) console.error('Status:', error.status) console.error('Details:', error.details) console.groupEnd() }) } ``` ## 📚 配置最佳实践 ### 1. 环境分离 * 为不同环境创建不同的配置文件 * 使用环境变量控制配置 * 避免在代码中硬编码配置值 ### 2. 配置验证 * 验证配置选项的有效性 * 提供配置选项的默认值 * 在运行时检查必需的配置 ### 3. 性能优化 * 根据使用场景调整缓存策略 * 优化重试策略减少不必要的重试 * 监控性能指标并调整配置 ### 4. 安全考虑 * 在生产环境中禁用详细日志 * 使用 HTTPS 进行安全通信 * 实现适当的认证和授权 ### 5. 错误处理 * 提供有意义的错误消息 * 实现优雅的降级策略 * 记录和监控错误情况 ## 🔗 相关链接 * [类型安全客户端概述](/api-client/treaty/overview) - 了解基本概念 * [参数处理](/api-client/treaty/parameters) - 学习参数配置 * [响应处理](/api-client/treaty/response) - 了解响应配置 * [WebSocket 支持](/api-client/treaty/websocket) - 配置实时通信 * [单元测试](/api-client/treaty/unit-test) - 测试配置选项 ## 📚 下一步 现在您已经了解了 Vafast 类型安全客户端的所有配置选项,接下来可以: 1. **学习参数处理** - 了解如何配置请求参数 2. **探索响应处理** - 学习响应配置选项 3. **配置 WebSocket** - 设置实时通信 4. **编写测试** - 验证配置的正确性 5. **性能调优** - 根据实际需求优化配置 如果您有任何问题或需要帮助,请查看我们的 [GitHub 仓库](https://github.com/vafast/vafast) 或 [社区页面](/community)。 --- --- url: 'https://vafast.dev/integrations/cheat-sheet.md' --- # 集成速查表 这是一个快速参考指南,展示如何在 Vafast 中集成常用的库和工具。 ## 数据库 ### Prisma ```typescript import { defineRoutes, createHandler } from 'vafast' import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(async () => { const users = await prisma.user.findMany() return { users } }) }, { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { const user = await prisma.user.create({ data: body }) return { user } }), body: Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) }) } ]) ``` ### Drizzle ```typescript import { defineRoutes, createHandler } from 'vafast' import { drizzle } from 'drizzle-orm/bun-sqlite' import { Database } from 'bun:sqlite3' import { users } from './schema' const db = drizzle(new Database('sqlite.db')) const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(async () => { const allUsers = await db.select().from(users) return { users: allUsers } }) } ]) ``` ### MongoDB ```typescript import { defineRoutes, createHandler } from 'vafast' import { MongoClient } from 'mongodb' const client = new MongoClient('mongodb://localhost:27017') const db = client.db('myapp') const routes = defineRoutes([ { method: 'GET', path: '/users', handler: createHandler(async () => { const users = await db.collection('users').find({}).toArray() return { users } }) } ]) ``` ## 认证 ### JWT ```typescript import { defineRoutes, createHandler } from 'vafast' import { jwt } from '@vafast/jwt' const routes = defineRoutes([ { method: 'POST', path: '/login', handler: createHandler(async ({ body }) => { const token = await jwt.sign(body, { expiresIn: '1h' }) return { token } }), body: Type.Object({ username: Type.String(), password: Type.String() }) } ]) const app = createHandler(routes) .use(jwt({ secret: process.env.JWT_SECRET })) ``` ### Better Auth ```typescript import { defineRoutes, createHandler } from 'vafast' import { BetterAuth } from 'better-auth' import { VafastAdapter } from 'better-auth/adapters/vafast' const auth = new BetterAuth({ adapter: VafastAdapter({ // 配置选项 }) }) const routes = defineRoutes([ { method: 'GET', path: '/profile', handler: createHandler(async ({ request }) => { const session = await auth.api.getSession(request) return { user: session?.user } }) } ]) ``` ## 中间件 ### CORS ```typescript import { defineRoutes, createHandler } from 'vafast' import { cors } from '@vafast/cors' const routes = defineRoutes([ // 路由定义 ]) const app = createHandler(routes) .use(cors({ origin: ['http://localhost:3000'], credentials: true })) ``` ### Helmet ```typescript import { defineRoutes, createHandler } from 'vafast' import { helmet } from '@vafast/helmet' const app = createHandler(routes) .use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"] } } })) ``` ### Rate Limiting ```typescript import { defineRoutes, createHandler } from 'vafast' import { rateLimit } from '@vafast/rate-limit' const app = createHandler(routes) .use(rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 限制每个IP 15分钟内最多100个请求 })) ``` ## 监控和日志 ### OpenTelemetry ```typescript import { defineRoutes, createHandler } from 'vafast' import { opentelemetry } from '@vafast/opentelemetry' const app = createHandler(routes) .use(opentelemetry({ serviceName: 'my-vafast-app', tracing: { enabled: true, exporter: { type: 'otlp', endpoint: 'http://localhost:4317' } } })) ``` ### Server Timing ```typescript import { defineRoutes, createHandler } from 'vafast' import { serverTiming } from '@vafast/server-timing' const app = createHandler(routes) .use(serverTiming()) ``` ## 文件处理 ### 文件上传 ```typescript import { defineRoutes, createHandler } from 'vafast' const routes = defineRoutes([ { method: 'POST', path: '/upload', handler: createHandler(async ({ request }) => { const formData = await request.formData() const file = formData.get('file') as File if (file) { const bytes = await file.arrayBuffer() const buffer = Buffer.from(bytes) // 保存文件 await Bun.write(`./uploads/${file.name}`, buffer) return { success: true, filename: file.name } } return { error: 'No file uploaded' }, { status: 400 } }) } ]) ``` ### 静态文件服务 ```typescript import { defineRoutes, createHandler } from 'vafast' import { staticFiles } from '@vafast/static' const app = createHandler(routes) .use(staticFiles({ root: './public', prefix: '/static' })) ``` ## 缓存 ### Redis ```typescript import { defineRoutes, createHandler } from 'vafast' import { Redis } from 'ioredis' const redis = new Redis() const routes = defineRoutes([ { method: 'GET', path: '/users/:id', handler: createHandler(async ({ params }) => { // 尝试从缓存获取 const cached = await redis.get(`user:${params.id}`) if (cached) { return JSON.parse(cached) } // 从数据库获取 const user = await getUserFromDB(params.id) // 缓存结果 await redis.setex(`user:${params.id}`, 3600, JSON.stringify(user)) return { user } }) } ]) ``` ### 内存缓存 ```typescript import { defineRoutes, createHandler } from 'vafast' const cache = new Map() const routes = defineRoutes([ { method: 'GET', path: '/data/:key', handler: createHandler(async ({ params }) => { if (cache.has(params.key)) { return { data: cache.get(params.key), cached: true } } const data = await fetchData(params.key) cache.set(params.key, data) return { data, cached: false } }) } ]) ``` ## 任务调度 ### Cron Jobs ```typescript import { defineRoutes, createHandler } from 'vafast' import { cron } from '@vafast/cron' const app = createHandler(routes) .use(cron({ jobs: [ { name: 'cleanup', schedule: '0 2 * * *', // 每天凌晨2点 task: async () => { console.log('Running cleanup task...') // 执行清理任务 } } ] })) ``` ## 压缩 ### Gzip/Brotli ```typescript import { defineRoutes, createHandler } from 'vafast' import { compress } from '@vafast/compress' const app = createHandler(routes) .use(compress({ algorithms: ['gzip', 'brotli'], threshold: 1024 })) ``` ## 模板引擎 ### HTML 渲染 ```typescript import { defineRoutes, createHandler } from 'vafast' import { html } from '@vafast/html' const routes = defineRoutes([ { method: 'GET', path: '/', handler: createHandler(() => { return html` Vafast App

Welcome to Vafast!

` }) } ]) ``` ## 测试 ### 单元测试 ```typescript import { describe, expect, it } from 'bun:test' import { createHandler } from 'vafast' describe('User Routes', () => { it('should create a user', async () => { const handler = createHandler(({ body }) => { return { id: '123', ...body } }) const request = new Request('http://localhost/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John', email: 'john@example.com' }) }) const response = await handler(request) const data = await response.json() expect(data.id).toBe('123') expect(data.name).toBe('John') }) }) ``` ## 部署 ### Docker ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lock ./ RUN bun install --production COPY . . RUN bun run build EXPOSE 3000 CMD ["bun", "run", "start"] ``` ### 环境变量 ```env # 数据库 DATABASE_URL="postgresql://user:password@localhost:5432/myapp" # JWT JWT_SECRET="your-secret-key" # Redis REDIS_URL="redis://localhost:6379" # 监控 OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317" ``` ## 最佳实践 1. **错误处理**:始终使用 try-catch 包装异步操作 2. **类型安全**:使用 TypeBox 进行运行时类型验证 3. **中间件顺序**:注意中间件的执行顺序 4. **性能监控**:使用 OpenTelemetry 监控应用性能 5. **安全**:使用 Helmet 和其他安全中间件 6. **测试**:为所有路由编写测试用例 ## 相关链接 * [中间件系统](/middleware) - 探索可用的中间件 * [路由定义](/essential/route) - 学习如何定义路由 * [类型验证](/patterns/type) - 了解类型验证系统 * [部署指南](/patterns/deploy) - 生产环境部署建议 --- --- url: 'https://vafast.dev/essential/validation.md' --- # 验证 创建 API 服务器的目的在于接收输入并对其进行处理。 JavaScript 允许任何数据成为任何类型。Vafast 提供了一个工具,可以对数据进行验证,以确保数据的格式正确。 ```typescript import { Server, defineRoutes, createHandler } from 'vafast' import { Type } from '@sinclair/typebox' import { TypeCompiler } from '@sinclair/typebox/compiler' interface TypedRequest extends Request { params: { id: string } } const paramsSchema = Type.Object({ id: Type.String() }) const paramsValidator = TypeCompiler.Compile(paramsSchema) const routes = defineRoutes([ { method: 'GET', path: '/id/:id', handler: (req) => { const params = (req as TypedRequest).params if (!paramsValidator.Check(params)) { return new Response('Invalid params', { status: 400 }) } return params.id } } ]) const server = new Server(routes) export default { fetch: server.fetch } ``` ### TypeBox **TypeBox** 是一个用于运行时、编译时和 OpenAPI 模式的类型安全模式构建器,用于生成 OpenAPI/Swagger 文档。 TypeBox 是一个非常快速、轻量且类型安全的 TypeScript 运行时验证库。Vafast 直接使用 TypeBox 进行验证,提供完整的类型安全。 我们认为验证应该由框架原生处理,而不是依赖用户为每个项目设置自定义类型。 ### TypeScript 我们可以通过访问 TypeBox 的类型定义来获得完整的类型安全: ```typescript import { Type } from '@sinclair/typebox' const UserSchema = Type.Object({ id: Type.Number(), name: Type.String(), email: Type.String({ format: 'email' }), age: Type.Optional(Type.Number({ minimum: 0 })) }) // 自动推断类型 type User = Static ``` ## 基本验证 ### 请求体验证 ```typescript import { Type } from '@sinclair/typebox' const userSchema = Type.Object({ name: Type.String({ minLength: 1, maxLength: 100 }), email: Type.String({ format: 'email' }), age: Type.Optional(Type.Number({ minimum: 0, maximum: 150 })) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { // body 已经通过验证,类型安全 const { name, email, age } = body return { name, email, age: age || 18 } }), body: userSchema } ]) ``` ### 查询参数验证 ```typescript const searchSchema = Type.Object({ q: Type.String({ minLength: 1 }), page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), sort: Type.Optional(Type.Union([ Type.Literal('name'), Type.Literal('email'), Type.Literal('created_at') ])) }) const routes = defineRoutes([ { method: 'GET', path: '/search', handler: createHandler(({ query }) => { const { q, page = 1, limit = 10, sort = 'name' } = query return { query: q, page, limit, sort, results: [] } }), query: searchSchema } ]) ``` ### 路径参数验证 ```typescript const userParamsSchema = Type.Object({ id: Type.Number({ minimum: 1 }), action: Type.Optional(Type.Union([ Type.Literal('profile'), Type.Literal('settings'), Type.Literal('posts') ])) }) const routes = defineRoutes([ { method: 'GET', path: '/users/:id/:action?', handler: createHandler(({ params }) => { const { id, action = 'profile' } = params return `User ${id} ${action}` }), params: userParamsSchema } ]) ``` ## 高级验证 ### 嵌套对象 ```typescript const addressSchema = Type.Object({ street: Type.String(), city: Type.String(), country: Type.String(), postalCode: Type.String() }) const userSchema = Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }), address: addressSchema, phones: Type.Array(Type.String({ pattern: '^\\+?[1-9]\\d{1,14}$' })) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema } ]) ``` ### 联合类型 ```typescript const userInputSchema = Type.Union([ Type.Object({ type: Type.Literal('create'), data: Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) }) }), Type.Object({ type: Type.Literal('update'), id: Type.Number(), data: Type.Partial(Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) })) }) ]) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { if (body.type === 'create') { return createUser(body.data) } else { return updateUser(body.id, body.data) } }), body: userInputSchema } ]) ``` ### 条件验证 ```typescript const conditionalSchema = Type.Object({ type: Type.Union([ Type.Literal('individual'), Type.Literal('company') ]), name: Type.String(), email: Type.String({ format: 'email' }), companyName: Type.Conditional( Type.Ref('type'), Type.Literal('company'), Type.String({ minLength: 1 }), Type.Never() ), ssn: Type.Conditional( Type.Ref('type'), Type.Literal('individual'), Type.String({ pattern: '^\\d{3}-\\d{2}-\\d{4}$' }), Type.Never() ) }) const routes = defineRoutes([ { method: 'POST', path: '/accounts', handler: createHandler(({ body }) => { return createAccount(body) }), body: conditionalSchema } ]) ``` ## 自定义验证 ### 自定义验证器 ```typescript const customStringSchema = Type.String({ minLength: 1, maxLength: 100, pattern: '^[a-zA-Z0-9_]+$', errorMessage: 'Username must contain only letters, numbers, and underscores' }) const customNumberSchema = Type.Number({ minimum: 0, maximum: 100, multipleOf: 5, errorMessage: 'Score must be a multiple of 5 between 0 and 100' }) const routes = defineRoutes([ { method: 'POST', path: '/scores', handler: createHandler(({ body }) => { return saveScore(body) }), body: Type.Object({ username: customStringSchema, score: customNumberSchema }) } ]) ``` ### 异步验证 ```typescript const asyncValidationSchema = Type.Object({ email: Type.String({ format: 'email' }), username: Type.String({ minLength: 3, maxLength: 20 }) }) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(async ({ body }) => { // 异步验证 const emailExists = await checkEmailExists(body.email) if (emailExists) { return new Response('Email already exists', { status: 400 }) } const usernameExists = await checkUsernameExists(body.username) if (usernameExists) { return new Response('Username already exists', { status: 400 }) } return createUser(body) }), body: asyncValidationSchema } ]) ``` ## 错误处理 ### 验证错误 当验证失败时,Vafast 会自动返回 400 错误: ```typescript const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { // 如果验证失败,这里不会执行 return createUser(body) }), body: Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) } ]) ``` ### 自定义错误处理 ```typescript const errorHandler = async (req: Request, next: () => Promise) => { try { return await next() } catch (error) { if (error.name === 'ValidationError') { return new Response( JSON.stringify({ error: 'Validation failed', details: error.details }), { status: 400, headers: { 'Content-Type': 'application/json' } } ) } return new Response('Internal server error', { status: 500 }) } } const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), body: userSchema, middleware: [errorHandler] } ]) ``` ## 性能优化 ### 预编译验证器 ```typescript import { TypeCompiler } from '@sinclair/typebox/compiler' // 预编译验证器以提高性能 const userValidator = TypeCompiler.Compile(userSchema) const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { // 使用预编译的验证器 const isValid = userValidator.Check(body) if (!isValid) { return new Response('Invalid data', { status: 400 }) } return createUser(body) }) } ]) ``` ### 缓存验证结果 ```typescript const validationCache = new Map() const cachedValidation = (schema: any) => { return async (req: Request, next: () => Promise) => { const body = await req.json() const cacheKey = JSON.stringify(body) if (validationCache.has(cacheKey)) { return await next() } const isValid = Type.Is(schema, body) if (isValid) { validationCache.set(cacheKey, true) return await next() } else { return new Response('Invalid data', { status: 400 }) } } } const routes = defineRoutes([ { method: 'POST', path: '/users', handler: createHandler(({ body }) => { return createUser(body) }), middleware: [cachedValidation(userSchema)] } ]) ``` ## 最佳实践 ### 1. 使用描述性的错误消息 ```typescript const userSchema = Type.Object({ name: Type.String({ minLength: 1, errorMessage: 'Name is required' }), email: Type.String({ format: 'email', errorMessage: 'Please provide a valid email address' }), age: Type.Number({ minimum: 0, maximum: 150, errorMessage: 'Age must be between 0 and 150' }) }) ``` ### 2. 重用验证模式 ```typescript // 基础模式 const baseUserSchema = Type.Object({ name: Type.String({ minLength: 1 }), email: Type.String({ format: 'email' }) }) // 创建用户模式 const createUserSchema = baseUserSchema // 更新用户模式 const updateUserSchema = Type.Partial(baseUserSchema) // 用户列表查询模式 const userQuerySchema = Type.Object({ page: Type.Optional(Type.Number({ minimum: 1 })), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), search: Type.Optional(Type.String()) }) ``` ### 3. 验证中间件 ```typescript const validateBody = (schema: any) => { return async (req: Request, next: () => Promise) => { try { const body = await req.json() const isValid = Type.Is(schema, body) if (!isValid) { return new Response('Invalid request body', { status: 400 }) } // 将验证后的数据添加到请求中 ;(req as any).validatedBody = body return await next() } catch (error) { return new Response('Invalid JSON', { status: 400 }) } } } const routes = defineRoutes([ { method: 'POST', path: '/users', middleware: [validateBody(userSchema)], handler: createHandler(({ req }) => { const userData = (req as any).validatedBody return createUser(userData) }) } ]) ``` ## 总结 Vafast 的验证系统提供了: * ✅ 基于 TypeBox 的完整类型安全 * ✅ 运行时数据验证 * ✅ 自动错误处理 * ✅ 性能优化选项 * ✅ 灵活的验证规则 * ✅ 中间件集成支持 ### 下一步 * 查看 [路由系统](/essential/route) 了解如何组织路由 * 学习 [处理程序](/essential/handler) 了解如何处理请求 * 探索 [中间件系统](/middleware) 了解如何增强功能 * 查看 [最佳实践](/essential/best-practice) 获取更多开发建议 如果您有任何问题,请查看我们的 [社区页面](/community) 或 [GitHub 仓库](https://github.com/vafast/vafast)。