cursor-init/.trae/rules/project_rules.md

16 KiB
Raw Blame History

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 文件命名约定

  • 模块名使用 单数形式authuserproductorder
  • 文件名格式:[module].[type].ts
  • 导出名格式:[module][类型名]

2. Schema请求参数类型

主要指请求参数验证工具,不包括响应

2.1 Schema定义规范

  • 一个模块的所有schema都在一个文件中
  • 使用elysia自带的类型共据t
  • 参数一定要明确是必填还是选填,是否有默认值
  • 对必要的数据进行转化,如用户名全小写并且去除两端空格
  • 必须有描述和示例参数
  • 导出类型
  • 注意已经存在的工具函数,比如分页有现成的简化工具,且能够统一请求格式

2.2 特别注意点

必须遵循的命名模式:

  • Request类型[动作][模块]RequestRegisterRequest
  • Schema名[动作][模块]SchemaRegisterSchema

2.3 代码示例

/**
 * @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<typeof UserListQuerySchema>;

3. Response响应数据类型

3.1 响应格式定义规范

  • 使用统一的相应格式工具responseWrapperSchema
  • 错误相应只提供示例和描述
  • 导出响应成功类型

3.2 特别注意点

必须遵循的命名模式:

  • Response格式定义[动作][模块]ResponseRegisterResponse
  • Response成功类型[动作][模块]SuccessType RegisterSuccessType

3.3 代码示例

/**
 * @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: '用户IDbigint类型以字符串形式返回防止精度丢失',
        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 代码示例

/**
 * @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<GetCurrentUserSuccessType>
     * @throws BusinessError 业务逻辑错误
     * @type API =====================================================================
     */
    public async getCurrentUser(userId: string): Promise<GetCurrentUserSuccessType> {
        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层代码示例

/**
 * 认证控制器
 * @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 主动抛出异常示例

// ✅ Service层错误处理
import { BusinessError } from '@/utils/responseFormate';

// 抛出业务错误
throw new BusinessError('消息说明...', 409);

7. 测试用例文档

  1. 分模块
  2. 分接口
  3. 测试名称
  4. 场景
  5. 方法