# ElysiaAPI开发流程 ## 0. 概览 这是一个基于 **Bun + Elysia** 的现代化后端API项目,采用TypeScript开发,集成了MySQL、Redis、JWT认证、Swagger文档等功能。 - **运行时**:Bun - **框架**:Elysia - **语言**:TypeScript - **数据库**:MySQL + Drizzle ORM - **缓存**:Redis - **认证**:JWT - **测试**:Vitest - **文档**:Swagger - **日志**:Winston - **代码规范**:ESLint + Prettier 使用的技术和库 - **数据库**:`"mysql2": "^3.14.1"`, `"drizzle-orm": "^0.44.2"` - **token验证**:`"jsonwebtoken": "^9.0.2"` - **密码加密**:`"bcrypt": "^6.0.0"` ## 1. 相关目录结构和描述 ### 1.0 整体目录结构 ``` project/ ├── 📋 配置文件(config/) ├── 📁 源代码 (src/) ├───└───config ├───├───eneities ├───├───modules │ ├───├───auth │ ├───├───captcha │ ├───├───health │ ├───├───test │ ├───└───user ├───├───plugins │ ├───├───drizzle │ ├───├───email │ ├───├───errorHandle │ ├───├───jwt │ ├───├───logger │ ├───├───redis │ ├───└───swagger ├───└───tests │ └───demo ├───type ├───utils ├── 📁 文档 (docs/) ├── 📁 需求文档 (prd/) ├── 📁 任务管理 (tasks/) ├── 📁 AI对话记录 (aiChat/) ├── 📁 数据库迁移 (drizzle/) └── 📁 静态资源 (public/) | 文件 | 说明 | |------|------| | `package.json` | 项目依赖和脚本配置 | | `tsconfig.json` | TypeScript编译配置 | | `tsconfig.test.json` | 测试环境TypeScript配置 | | `vitest.config.ts` | Vitest测试框架配置 | | `eslint.config.js` | ESLint代码规范配置 | | `bunfig.toml` | Bun运行时配置 | | `drizzle.config.ts` | Drizzle ORM配置 | | `README.md` | 项目说明文档 | ``` ### 1.1 接口目录结构 ``` src/modules/ ├── index.ts # API总入口:所有的模块文件都由此导出给主程序 ├── [moduleName]/ # 模块目录:每个模块的名称做目录名 │ ├── [moduleName].docs.md # 接口逻辑规则 │ ├── [moduleName].schema.ts # Schema定义 │ ├── [moduleName].response.ts # 响应格式 │ ├── [moduleName].service.ts # 业务逻辑 │ ├── [moduleName].controller.ts # 路由控制器 │ └── [moduleName].test.doc # 测试用例文档 ``` ### 1.2 接口目录功能 - 每个模块目录下的文件,都需要有对应的功能,不能只有docs.md,其他文件缺一不可 - [moduleName].docs.md:需要包含接口开发的业务逻辑,包括schema的注意点,response格式的注意点,接口的业务流程,分析相关的数据库,性能问题,安全问题等等需要考虑的地方和注意点 - [moduleName].schema.ts:定义接口的输入参数,包括参数的类型、参数的必填、参数的默认值、参数的描述、参数的示例 - [moduleName].response.ts:接口返回数据的格式定义文件 - [moduleName].service.ts:接口的具体业务逻辑方法 - [moduleName].controller.ts:接口路由定义文件,在这里组合接口的请求方式、参数类型、响应格式 ### 1.3 插件目录结构 ``` src/plugins/ ├── index.ts # 插件总入口 ├── drizzle/ # 数据库ORM插件 │ ├── drizzle.plugins.ts │ ├── drizzle.service.ts │ └── README.md ├── email/ # 邮件插件 │ ├── email.plugins.ts │ ├── email.service.ts │ └── README.md ├── errorHandle/ # 错误处理插件 │ └── errorHandler.plugins.ts ├── jwt/ # JWT认证插件 │ ├── jwt.plugins.ts │ └── jwt.service.ts ├── logger/ # 日志插件 │ ├── logger.plugins.ts │ └── logger.service.ts ├── redis/ # Redis插件 │ ├── redis.plugins.ts │ └── redis.service.ts └── swagger/ # API文档插件 └── swagger.plugins.ts ``` ### 1.4 类型定义 (type/) ``` src/type/ ├── config.type.ts # 配置相关类型 ├── drizzle.type.ts # 数据库相关类型 ├── error.type.ts # 错误相关类型 ├── jwt.type.ts # JWT相关类型 ├── logger.type.ts # 日志相关类型 ├── redis.type.ts # Redis相关类型 └── email.type.ts # 邮件相关类型 ``` ### 1.5 工具函数 (utils/) ``` src/utils/ ├── deviceInfo.ts # 设备信息工具 ├── distributedLock.ts # 分布式锁工具 ├── formatFileSize.ts # 文件大小格式化 ├── formatRoute.ts # 路由格式化 ├── jwt.helper.ts # JWT工具函数 ├── mysql.ts # MySQL工具 ├── pagination.ts # 分页请求参数工厂函数工具 ├── randomChalk.ts # 随机颜色工具 ├── redis.ts # Redis工具 ├── responseFormate.ts # 响应统一格式化处理工具 ├── snowflake.ts # 雪花ID生成器 └── text.ts # 文本处理工具 ``` ### 1.6 常量定义 (constants/) ``` src/constants/ ├── swaggerTags.ts # Swagger标签定义:所有模块的tag应该集中在此定义 └── 其他常量 ``` ### 1.7 数据库实体 ``` src/eneities/ ├── index.ts # 实体总入口 ├── customType.ts # 自定义类型 └── [table].ts # 数据表定义 ``` ### 1.8 文件命名约定 - 模块名使用 **单数形式**:`auth`、`user`、`product`、`order` - 文件名格式:`[module].[type].ts` - 导出名格式:`[module][类型名]` ## 2. Schema(请求参数类型) **主要指请求参数验证工具,不包括响应** ### 2.1 Schema定义规范 - 一个模块的所有schema都在一个文件中 - 使用elysia自带的类型共据`t` - 参数一定要明确是必填还是选填,是否有默认值 - 对必要的数据进行转化,如用户名全小写并且去除两端空格 - 必须有描述和示例参数 - 导出类型 - 注意已经存在的工具函数,比如分页有现成的简化工具,且能够统一请求格式 ### 2.2 特别注意点 **必须遵循的命名模式:** - Request类型:`[动作][模块]Request` → `RegisterRequest` - Schema名:`[动作][模块]Schema` → `RegisterSchema` ### 2.3 代码示例 ```ts /** * @file 用户模块Schema定义 * @author AI Assistant * @date 2024-12-19 * @lastEditor AI Assistant * @lastEditTime 2025-01-07 * @description 定义用户模块的Schema,包括获取当前用户信息、用户列表查询等 */ import { t, type Static } from 'elysia'; import { createQuerySchema } from '@/utils/pagination'; /** * 用户列表查询参数Schema * @description 用户列表查询的请求参数验证规则 */ export const UserListQuerySchema = createQuerySchema( t.Object({ // 用户特有参数 keyword: t.Optional( t.String({ minLength: 1, maxLength: 100, description: '搜索关键词,支持用户名、邮箱模糊搜索', examples: ['admin', 'test@example.com'], }), ), status: t.Optional( t.Union([t.Literal('active'), t.Literal('inactive'), t.Literal('pending')], { description: '用户状态筛选', examples: ['active', 'inactive', 'pending'], }), ), gender: t.Optional( t.Union([t.Literal(0), t.Literal(1), t.Literal(2), t.Literal('0'), t.Literal('1'), t.Literal('2')], { description: '性别筛选:0-未知,1-男,2-女', examples: [0, 1, 2], }), ), isRoot: t.Optional( t.Boolean({ description: '是否超级管理员筛选', examples: [true, false], }), ), }), ); /** 用户列表查询参数类型 */ export type UserListQueryRequest = Static; ``` ## 3. Response(响应数据类型) ### 3.1 响应格式定义规范 - 使用统一的相应格式工具`responseWrapperSchema` - 错误相应只提供示例和描述 - 导出响应成功类型 ### 3.2 特别注意点 **必须遵循的命名模式:** - Response格式定义:`[动作][模块]Response` → `RegisterResponse` - Response成功类型:`[动作][模块]SuccessType ` → `RegisterSuccessType ` ### 3.3 代码示例 ```ts /** * @file 用户模块响应格式定义 * @author AI Assistant * @date 2024-12-19 * @lastEditor AI Assistant * @lastEditTime 2025-01-07 * @description 定义用户模块的响应格式,包括获取当前用户信息、用户列表查询等 */ import { t, type Static } from 'elysia'; import { responseWrapperSchema } from '@/utils/responseFormate'; import { createPaginationResponseSchema } from '@/utils/pagination'; /** * 用户列表项Schema * @description 用户列表中单个用户的数据结构 */ export const UserListItemSchema = t.Object({ /** 用户ID */ id: t.String({ description: '用户ID(bigint类型以字符串形式返回防止精度丢失)', examples: ['1', '2', '3'], }), /** 用户名 */ username: t.String({ description: '用户名', examples: ['admin', 'testuser'], }), /** 更多字段... */ /** 更新时间 */ updatedAt: t.String({ description: '更新时间', examples: ['2024-12-19T10:30:00Z'], }), }); /** * 获取用户列表接口响应组合 * @description 用于Controller中定义所有可能的响应格式 */ export const GetUserListResponsesSchema = { 200: responseWrapperSchema(createPaginationResponseSchema(UserListItemSchema)), 401: responseWrapperSchema( t.Object({ error: t.String({ description: '认证失败', examples: ['未提供有效的认证令牌', '令牌已过期'], }), }), ), 400: responseWrapperSchema( t.Object({ error: t.String({ description: '参数错误', examples: ['分页参数无效', '搜索关键词格式错误'], }), }), ), 500: responseWrapperSchema( t.Object({ error: t.String({ description: '服务器错误', examples: ['内部服务器错误'], }), }), ), }; /** 获取用户列表成功响应数据类型 */ export type GetUserListSuccessType = Static<(typeof GetUserListResponsesSchema)[200]>; ``` ## 4. Service(业务逻辑层) ### 4.1 Service层要求 - ✅ 所有方法必须有完整的类型注解 - ✅ 所有方法必须有详细的JSDoc注释 - ✅ 必须使用统一的响应格式工具 - ✅ 必须使用统一的错误码 - ✅ 必须有详细的日志记录,接口有请求响应记录,无需记录每次请求 - ✅ 需要有完整的错误处理,服务错误有拦截器处理可以不管,只判断逻辑上的错误 - ✅ 导出单例实例供controller使用 ### 4.2 注意点 - 开发逻辑时,注意数据库实体,必须严格按照数据库字段进行开发,不能出错 - 更新时间数据库一般会自动天写,不用手动设置或更新 - 数据库id一般为bigint,注意类型和精度问题 - 在写入数据时,必要的增加分布式锁 - 逻辑要清晰,代码要简洁 ### 4.3 代码示例 ```ts /** * @file 用户模块Service层实现 * @author AI Assistant * @date 2024-12-19 * @lastEditor AI Assistant * @lastEditTime 2025-01-07 * @description 用户模块的业务逻辑实现,包括获取当前用户信息、用户列表查询等 */ import { Logger } from '@/plugins/logger/logger.service'; import { db } from '@/plugins/drizzle/drizzle.service'; import { sysUsers } from '@/eneities'; import { eq, like, and, desc, asc, sql } from 'drizzle-orm'; import { successResponse, errorResponse, BusinessError } from '@/utils/responseFormate'; import { calculatePagination, normalizePaginationParams } from '@/utils/pagination'; import type { GetCurrentUserSuccessType, GetUserListSuccessType } from './user.response'; import type { UserListQueryRequest, UserListItem } from './user.schema'; /** * 用户服务类 * @description 处理用户相关的业务逻辑 */ export class UserService { /** * 获取当前用户信息 * @param userId 用户ID * @returns Promise * @throws BusinessError 业务逻辑错误 * @type API ===================================================================== */ public async getCurrentUser(userId: string): Promise { Logger.info(`获取用户信息:${userId}`); // 查询用户信息 const user = await db() .select({ id: sysUsers.id, username: sysUsers.username, email: sysUsers.email, nickname: sysUsers.nickname, avatar: sysUsers.avatar, mobile: sysUsers.mobile, status: sysUsers.status, lastLoginAt: sysUsers.lastLoginAt, createdAt: sysUsers.createdAt, updatedAt: sysUsers.updatedAt, }) .from(sysUsers) .where(eq(sysUsers.id, BigInt(userId))) .limit(1); if (!user || user.length === 0) { Logger.warn(`用户不存在:${userId}`); throw new BusinessError(`用户不存在:${userId}`, 404); } const userData = user[0]!; Logger.info(`获取用户信息成功:${userId} - ${userData.username}`); return successResponse( { id: userId, // 使用传入的字符串ID,避免精度丢失 username: userData.username, email: userData.email, nickname: userData.nickname, avatar: userData.avatar, phone: userData.mobile, status: userData.status, lastLoginAt: userData.lastLoginAt || null, createdAt: userData.createdAt, updatedAt: userData.updatedAt, }, '获取用户信息成功', ); } } // 导出单例实例 export const userService = new UserService(); ``` ## 5. Controllers(接口名称接入) ### 5.1 Controllers层要求 - ✅ 路由方法必须有完整的JSDoc注释 - ✅ 必须定义完整的response schema - ✅ 必须有适当的tags分类 - ✅ 必须有operationId用于API文档 - ✅ 错误处理由Service层统一处理 ### 5.2 Controllers层注意点 - 使用service时要简洁,如`({ body }) => authService.register(body),` ### 5.3 Controllers层代码示例 ```ts /** * 认证控制器 * @description 处理用户认证相关的HTTP请求 */ export const authController = new Elysia() /** * 用户注册接口 * @route POST /api/auth/register * @description 用户注册,包含验证码验证、用户名邮箱唯一性检查等 */ .post('/register', ({ body }) => authService.register(body), { body: RegisterSchema, detail: { summary: '用户注册', description: '用户注册接口,需要提供用户名、邮箱、密码和验证码', tags: [tags.auth], operationId: 'registerUser', }, response: RegisterResponsesSchema, }); ``` ## 6. 错误处理 ### 6.1 错误处理规范 - 统一`import { BusinessError } from '@/utils/responseFormate';`引入错误工具抛出错误 - 错误处理在errorHandle.plugins中统一处理, ### 6.2 主动抛出异常示例 ```typescript // ✅ Service层错误处理 import { BusinessError } from '@/utils/responseFormate'; // 抛出业务错误 throw new BusinessError('消息说明...', 409); ``` ## 7. 测试用例文档 1. 分模块 2. 分接口 3. 测试名称 4. 场景 5. 方法