From 5b0b37ef78cca6962f37d041ac8be5f975ebcfdd Mon Sep 17 00:00:00 2001 From: expressgy Date: Sat, 28 Jun 2025 02:03:40 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E9=A1=B9=E7=9B=AE=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE=E4=B8=8E=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=BD=92=E6=A1=A3=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Prettier 配置迁移为 .prettierrc.cjs,解决 ESM/CJS 兼容问题 - 优化 package.json,补全元信息、整理依赖、完善 Bun 热更新脚本 - 归档项目初始化 PRD 与任务清单到 tasks/archive,并加日期前缀 - 同步代码风格与格式化配置,提升团队协作一致性 归档文件:tasks/archive/20240610-prd-项目初始化.md, tasks/archive/20240610-tasks-prd-项目初始化.md --- .cursor/rules/cursor-comment-rules.mdc | 112 ++++++++++++++ .cursor/rules/elysia-api-rules.mdc | 110 ++++++++++++++ .cursor/rules/elysia-backend-rules.mdc | 141 ++++++++++++++++++ .cursor/rules/elysia-test-rules.mdc | 108 ++++++++++++++ .cursor/rules/git-commit-rules.mdc | 55 +++++++ .cursor/rules/global-rules/create-prd.mdc | 54 +++++++ .cursor/rules/global-rules/generate-task.mdc | 61 ++++++++ .cursor/rules/global-rules/global.mdc | 10 ++ .../rules/global-rules/process-list-task.mdc | 66 ++++++++ .../use-bun-instead-of-node-vite-npm-pnpm.mdc | 111 ++++++++++++++ .gitignore | 4 + .prettierignore | 3 + .prettierrc.cjs | 25 ++++ .test-rules.md | 99 ++++++++++++ README.md | 17 +++ bunfig.toml | 2 + eslint.config.js | 70 +++++++++ package.json | 41 +++++ src/app.test.ts | 96 ++++++++++++ src/app.ts | 40 +++++ src/config/db.config.ts | 29 ++++ src/config/jwt.config.ts | 20 +++ src/controllers/try/auth.controller.ts | 34 +++++ src/controllers/try/protected.controller.ts | 33 ++++ src/plugins/jwt-auth.ts | 49 ++++++ src/plugins/jwt.ts | 17 +++ src/plugins/swagger.ts | 35 +++++ src/server.ts | 13 ++ src/services/try/auth.service.ts | 33 ++++ src/services/try/protected.service.ts | 25 ++++ src/utils/mysql.test.ts | 28 ++++ src/utils/mysql.ts | 22 +++ src/validators/try/auth.response.ts | 24 +++ src/validators/try/auth.validator.ts | 23 +++ src/validators/try/protected.response.ts | 42 ++++++ tasks/archive/20240610-prd-项目初始化.md | 107 +++++++++++++ .../archive/20240610-tasks-prd-项目初始化.md | 64 ++++++++ tasks/prd-架构优化.md | 105 +++++++++++++ tasks/tasks-prd-架构优化.md | 44 ++++++ tsconfig.json | 41 +++++ tsconfig.test.json | 9 ++ vitest.config.ts | 16 ++ 42 files changed, 2038 insertions(+) create mode 100644 .cursor/rules/cursor-comment-rules.mdc create mode 100644 .cursor/rules/elysia-api-rules.mdc create mode 100644 .cursor/rules/elysia-backend-rules.mdc create mode 100644 .cursor/rules/elysia-test-rules.mdc create mode 100644 .cursor/rules/git-commit-rules.mdc create mode 100644 .cursor/rules/global-rules/create-prd.mdc create mode 100644 .cursor/rules/global-rules/generate-task.mdc create mode 100644 .cursor/rules/global-rules/global.mdc create mode 100644 .cursor/rules/global-rules/process-list-task.mdc create mode 100644 .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.cjs create mode 100644 .test-rules.md create mode 100644 README.md create mode 100644 bunfig.toml create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 src/app.test.ts create mode 100644 src/app.ts create mode 100644 src/config/db.config.ts create mode 100644 src/config/jwt.config.ts create mode 100644 src/controllers/try/auth.controller.ts create mode 100644 src/controllers/try/protected.controller.ts create mode 100644 src/plugins/jwt-auth.ts create mode 100644 src/plugins/jwt.ts create mode 100644 src/plugins/swagger.ts create mode 100644 src/server.ts create mode 100644 src/services/try/auth.service.ts create mode 100644 src/services/try/protected.service.ts create mode 100644 src/utils/mysql.test.ts create mode 100644 src/utils/mysql.ts create mode 100644 src/validators/try/auth.response.ts create mode 100644 src/validators/try/auth.validator.ts create mode 100644 src/validators/try/protected.response.ts create mode 100644 tasks/archive/20240610-prd-项目初始化.md create mode 100644 tasks/archive/20240610-tasks-prd-项目初始化.md create mode 100644 tasks/prd-架构优化.md create mode 100644 tasks/tasks-prd-架构优化.md create mode 100644 tsconfig.json create mode 100644 tsconfig.test.json create mode 100644 vitest.config.ts diff --git a/.cursor/rules/cursor-comment-rules.mdc b/.cursor/rules/cursor-comment-rules.mdc new file mode 100644 index 0000000..4d4d976 --- /dev/null +++ b/.cursor/rules/cursor-comment-rules.mdc @@ -0,0 +1,112 @@ +--- +description: +globs: +alwaysApply: true +--- +## Cursor 代码注释规范(Code Comment Rules) + +### 1. 文件头部注释 +每个源文件开头应包含如下信息: +```javascript +/** + * @file 文件简要说明 + * @author 创建者姓名(如:张三 ) + * @date 创建时间(如:2024-06-01) + * @lastEditor 最后修改人 + * @lastEditTime最后修改时间 + * @description 文件详细描述(可选) + */ +``` + +### 2. 函数/方法注释(JSDoc) +- 每个公开函数、类、接口都应有 JSDoc 注释 +- 增加修改记录,包含修改人、修改时间、修改描述 + +**推荐标签:** +- `@param` 参数说明 +- `@returns` 返回值说明 +- `@throws` 可能抛出的异常 +- `@deprecated` 弃用说明 +- `@example` 使用示例 +- `@modification` 修改记录(格式:修改人 修改时间 修改描述) + +**示例:** +```typescript +/** + * 计算两个数的和 + * @param a 第一个加数 + * @param b 第二个加数 + * @returns 两数之和 + * @example + * add(1, 2) // 3 + * @modification 李四 2024-06-05 优化了参数校验 + */ +function add(a: number, b: number): number { + return a + b; +} +``` + +### 3. 注释类型与风格 +- 单行注释:`//`,用于简短说明 +- 多行注释:`/* ... */`,用于较长描述 +- 文档注释(JSDoc):`/** ... */`,用于结构化说明 +- 注释应简洁明了,避免废话和重复代码内容 +- 注释内容使用中文或英文均可,但需统一 +- 代码变更时同步更新相关注释,避免注释与代码不符 +- 不要注释掉无用代码,直接删除,必要时可通过版本管理找回 + +### 4. 特殊标记 +- `TODO:` 需要补充或优化的内容 +- `FIXME:` 需要修复的问题 +- `HACK:` 临时解决方案,需后续优化 + +**示例:** +```javascript +// TODO: 优化此处的性能 +// FIXME: 这里有边界条件未处理 +// HACK: 临时绕过接口校验 +``` + +### 5. 变量注释 + +- 每一个变量遵照JSDoc添加注释,携带描述、用途 + +参照 +```ts +/** + * MySQL数据库连接配置 + * @property {string} host - 数据库主机地址 + * @property {number} port - 数据库端口号 + * @property {string} user - 数据库用户名 + * @property {string} password - 数据库密码 + * @property {string} database - 数据库名称 + */ +export const dbConfig = { + /** 数据库主机地址 */ + host: process.env.DB_HOST || 'localhost', + /** 数据库端口号 */ + port: Number(process.env.DB_PORT) || 3306, + /** 数据库用户名 */ + user: process.env.DB_USER || 'root', + /** 数据库密码 */ + password: process.env.DB_PASSWORD || '', + /** 数据库名称 */ + database: process.env.DB_NAME || 'test', +}; +``` + +### 6. 规范补充建议 +- 注释应随代码同步更新,避免"注释失效"或误导他人。 +- 代码评审时,建议同时检查注释的准确性和完整性。 +- 复杂算法、业务逻辑、边界处理、特殊依赖等务必详细注释。 +- 简单、易懂的代码无需过度注释,避免注释冗余。 +- 团队应约定注释统一使用中文或英文,避免混杂,提升协作效率。 +- 推荐使用 ESLint、TSLint 等工具结合注释相关插件(如 eslint-plugin-jsdoc)进行注释规范自动校验。 +- 可使用 IDE 插件(如 VSCode 的 JSDoc Generator)自动生成注释模板,提升效率。 +- 建议在项目根目录下提供注释模板(如 `.comment-templates`),便于新成员快速上手。 +- 注释中严禁出现密码、密钥、用户隐私等敏感信息。 +- 重要模块、核心业务建议将注释内容同步到项目文档,便于知识传承和查阅。 + +--- + +**请所有开发者严格遵守以上注释规范,提升代码可读性与可维护性。** \ No newline at end of file diff --git a/.cursor/rules/elysia-api-rules.mdc b/.cursor/rules/elysia-api-rules.mdc new file mode 100644 index 0000000..548ae80 --- /dev/null +++ b/.cursor/rules/elysia-api-rules.mdc @@ -0,0 +1,110 @@ +--- +description: +globs: +alwaysApply: true +--- +# Elysia 后台接口 API 设计与校验规范 + +## 1. 接口文档自动生成(Swagger/OpenAPI) +- 每个接口必须通过 `@elysiajs/swagger` 插件自动生成 Swagger 文档,便于前后端协作与接口管理。 +- 路由、参数、返回类型、错误码等需在 Swagger 中完整描述。 +- 推荐在开发环境默认启用 Swagger UI。 + +## 2. 参数与返回类型校验 +- 所有接口参数(body、query、params、headers)必须定义类型并进行严格校验。 +- 推荐使用Elysia原生t类型校验库。 +- 校验规则需明确字段必填、类型、长度、格式、范围等。 +- 校验失败时,接口需返回标准 JSON 错误响应,便于用户理解。 +- 返回类型也需定义并校验,保证接口契约。 + +## 3. 错误响应规范 +- 所有参数校验失败、类型不匹配等错误,需统一返回如下结构: + ```json + { + "code": 400, + "message": "error message", + "data": null + } + ``` +- 错误码、错误信息需文档化,便于前后端协作。 +- 常见的公共的错误和正常相应,统一放在validators中的global.response.ts + + +**注意,其他的请求参数和响应验证,在同一批接口下,放在同一个validators文件中** + + +## 4. 示例代码 + +### 4.1 安装依赖 +```sh +bun add elysia @elysiajs/swagger valibot +``` + +### 4.2 接口定义与参数校验 + +```ts +.get( + '/note/:index', + ({ note, params: { index } }) => { + return note.data[index] + }, + { + params: t.Object({ + index: t.Number() + }) + } + ) + ``` + +### 4.3 调用接口示例 +- 请求: + ```json + POST /api/user + { + "name": "A" + } + ``` +- 响应: + ```json + { + "code": 400, + "message": "姓名长度不能少于2个字符", + "data": null + } + ``` + +- 请求: + ```json + POST /api/user + { + "name": "张三丰丰丰丰丰丰丰丰" + } + ``` +- 响应: + ```json + { + "code": 400, + "message": "姓名长度不能超过8个字符", + "data": null + } + ``` + +- 请求: + ```json + POST /api/user + { + "name": "张三" + } + ``` +- 响应: + ```json + { + "code": 0, + "message": "创建成功", + "data": { "name": "张三" } + } + ``` + +--- + +**请所有开发者严格遵守以上规范,确保接口文档完整、参数校验严格、错误提示友好(中文),提升用户体验与协作效率。** \ No newline at end of file diff --git a/.cursor/rules/elysia-backend-rules.mdc b/.cursor/rules/elysia-backend-rules.mdc new file mode 100644 index 0000000..f9f0cad --- /dev/null +++ b/.cursor/rules/elysia-backend-rules.mdc @@ -0,0 +1,141 @@ +--- +description: +globs: +alwaysApply: true +--- +# Elysia(Bun.js)后端开发规范 + +## 1. 项目结构 + +- 推荐目录结构如下,分层清晰,便于维护和扩展: + ``` + ├── src/ + │ ├── controllers/ # 路由与业务入口 + │ ├── services/ # 业务逻辑 + │ ├── models/ # 数据模型与类型定义 + │ ├── middlewares/ # 中间件 + │ ├── plugins/ # Elysia 插件 + │ ├── utils/ # 工具函数 + │ ├── validators/ # 参数校验,注意!!!所有的参数校验必须挡在此目录中,目录结构遵照路由结构 + │ ├── config/ # 配置文件 + │ ├── type/ # 类型定义文件 + │ └── app.ts # 应用入口 + ├── tests/ # 测试用例 + ├── public/ # 静态资源 + ├── .env # 环境变量 + ├── bun.lockb # Bun 依赖锁 + ├── package.json + └── README.md + └── .git/ # Git版本控制目录,跟踪项目历史和协作 + ``` + +## 2. 代码风格 + +- 强制使用 ESLint + Prettier 统一代码风格,TypeScript 项目建议使用 `@typescript-eslint`。 +- 文件、变量、函数、类命名采用小驼峰(camelCase),类名用大驼峰(PascalCase)。 +- 严格类型检查,禁止使用 `any`,优先使用类型推断和类型声明。 +- 每个文件、类、方法、复杂逻辑必须有规范注释(参考注释规范 rules)。 +- 代码提交前必须通过 lint 检查和格式化。 + +## 3. 路由与接口设计 + +- 路由统一注册在 `controllers` 目录下,按业务模块拆分。 +- 遵循 RESTful API 设计原则,接口资源命名清晰、语义化。 +- 路由统一使用小写、短横线分隔(如 `/api/v1/user-info`)。 +- GET 用于获取资源,POST 用于创建,PUT/PATCH 用于更新,DELETE 用于删除。 +- 所有接口返回统一的数据结构,例如: + ```json + { + "code": 0, + "message": "success", + "data": {} + } + ``` +- 错误码、错误信息需文档化,便于前后端协作。 + +## 4. 参数校验 + +- 所有接口参数必须进行校验。 +- 校验逻辑统一放在 `validators` 目录,便于复用和维护。 +- 校验失败时返回标准错误响应,禁止直接抛出异常。 + +## 5. 中间件与插件 + +- 公共逻辑(如鉴权、日志、限流、CORS、错误处理等)必须通过中间件实现,统一注册在 `middlewares` 或 `plugins` 目录。 +- 推荐使用 Elysia 官方和社区插件(如 `@elysiajs/cors`、`@elysiajs/jwt`、`@elysiajs/swagger` 等)。 +- 自定义插件需有详细注释和文档。 + +## 6. 安全规范 + +- 禁止 SQL 注入、XSS、CSRF 等常见安全漏洞,使用 ORM/参数化查询。 +- 重要接口需鉴权(如 JWT、Session),敏感操作需权限校验。 +- 密码等敏感信息必须加密存储,严禁明文。 +- 配置信息(如密钥、数据库连接)使用环境变量管理,严禁写死在代码中。 +- 日志中不得记录敏感信息。 + +## 7. 错误处理 + +- 所有异常必须统一捕获和处理,返回标准错误响应。 +- 推荐全局错误处理中间件,记录错误日志,便于排查问题。 +- 错误响应结构示例: + ```json + { + "code": 1001, + "message": "参数校验失败", + "data": null + } + ``` + +## 8. 日志与监控 + +- 统一使用日志库(如 pino、winston),区分 info、warn、error 等级。 +- 关键操作、异常、接口请求需有日志记录,便于排查问题。 +- 推荐接入监控系统(如 Prometheus、ELK)。 + +## 9. 测试 + +- 必须编写单元测试和集成测试,覆盖核心业务逻辑。 +- 推荐使用 Vitest(Bun 原生支持)、Jest 等测试框架。 +- 新增功能需同步补充测试用例,CI 阶段自动跑测试。 +- 测试代码与业务代码分离,统一放在 `tests` 目录。 + +## 10. 依赖与版本管理 + +- 依赖包需定期升级,避免使用过时或有安全漏洞的库。 +- 使用 `bun.lockb` 锁定依赖版本。 +- 不得将未使用的依赖、临时文件提交到仓库。 + +## 11. 持续集成与部署 + +- 推荐使用 CI 工具(如 GitHub Actions、GitLab CI)自动化测试、构建、部署。 +- 生产环境部署需有回滚机制,避免单点故障。 +- 环境变量、密钥等敏感信息通过 CI/CD 平台安全注入。 + +## 12. 文档与协作 + +- 所有接口、核心模块需有详细文档,推荐使用 OpenAPI/Swagger 自动生成接口文档(可用 `@elysiajs/swagger`)。 +- 重要变更需在 README/CHANGELOG 中记录。 +- 团队成员需遵守代码评审流程,确保代码质量。 +- 项目根目录应有完整的 README,包含启动、开发、测试、部署等说明。 + +## 13. 其他最佳实践 + +- 代码与注释同步更新,避免注释失效。 +- 复杂算法、业务逻辑、边界处理、特殊依赖等务必详细注释。 +- 严禁在注释、日志、代码中出现密码、密钥、用户隐私等敏感信息。 +- 推荐在项目根目录下提供注释模板和接口模板,便于新成员快速上手。 +- 重要模块、核心业务建议将注释内容同步到项目文档,便于知识传承和查阅。 + +## 14. 模块引入 + +- 全部使用路径别名,如`@/app` `@/config/db.config` +- 注意更新 tsconfig.json bunfig.toml 等配置中关于路径别名的配置 + +## 15. 配置文件 + +- 配置请全部存放在config中,不要再其他文件使用process.env +- 除了密码,所有配置都需要有默认值 + +--- + +**请所有开发者严格遵守以上规范,保障 Elysia 后端服务的健壮性、安全性与可维护性。** \ No newline at end of file diff --git a/.cursor/rules/elysia-test-rules.mdc b/.cursor/rules/elysia-test-rules.mdc new file mode 100644 index 0000000..f0b1655 --- /dev/null +++ b/.cursor/rules/elysia-test-rules.mdc @@ -0,0 +1,108 @@ +--- +description: +globs: +alwaysApply: true +--- +# Elysia(Bun.js)接口开发测试规范 + +## 1. 测试类型与原则 + +- **单元测试**:聚焦于最小功能单元(如函数、service),保证核心逻辑正确。 +- **接口测试**:验证 HTTP API 的输入输出、边界条件、异常处理,确保接口契约。 +- **性能测试**:评估接口在高并发、大数据量下的响应速度与稳定性。 +- **基本原则**: + - 测试应小而快、独立、可重复、可读。 + - 测试覆盖正常流程、边界条件、异常分支。 + - 测试代码与业务代码同等重要,需持续维护。 + +## 2. 目录结构建议 + +``` +├── src/ +├── tests/ +│ ├── unit/ # 单元测试 +│ ├── api/ # 接口测试 +│ └── performance/ # 性能测试 +``` + +- 测试文件与被测模块一一对应,命名规范如 `xxx.spec.ts` 或 `xxx.test.ts`。 + +## 3. 工具推荐 + +- **单元/接口测试**:推荐 [Vitest](mdc:https:/vitest.dev)(Bun 原生支持,兼容 Jest 语法)。 +- **接口请求模拟**:可用 [undici](mdc:https:/github.com/nodejs/undici)、[supertest](mdc:https:/github.com/ladjs/supertest) 等。 +- **Mock/Stub**:推荐 [sinon](mdc:https:/sinonjs.org)、[msw](mdc:https:/mswjs.io) 等。 +- **性能测试**:推荐 [autocannon](mdc:https:/github.com/mcollina/autocannon)、[wrk](mdc:https:/github.com/wg/wrk)。 +- **覆盖率统计**:集成 c8、nyc 或 Vitest 内置覆盖率。 + +## 4. 单元测试规范 + +- 每个 service、util、核心函数都应有单元测试。 +- 用 Arrange-Act-Assert(AAA)模式编写:准备数据、执行逻辑、断言结果。 +- 对外部依赖(如数据库、第三方服务)使用 mock,保证测试纯粹。 +- 用例命名清晰,表达测试目的。 +- 示例: + ```typescript + import { describe, it, expect } from 'vitest' + import { sum } from '../../src/utils/sum' + + describe('sum', () => { + it('should return 3 when 1 + 2', () => { + expect(sum(1, 2)).toBe(3) + }) + }) + ``` + +## 5. 接口测试规范 + +- 覆盖所有 API 路由的正常、异常、边界场景。 +- 启动 Elysia 实例,使用 supertest/undici 发起 HTTP 请求,断言响应。 +- 可用 mock 数据库、mock token 等方式隔离外部依赖。 +- 推荐自动化生成接口文档与测试用例(如结合 Swagger/OpenAPI)。 +- 示例: + ```typescript + import { app } from '../../src/app' + import request from 'supertest' + + describe('GET /api/v1/hello', () => { + it('should return hello world', async () => { + const res = await request(app.listen()).get('/api/v1/hello') + expect(res.status).toBe(200) + expect(res.body.message).toBe('hello world') + }) + }) + ``` + +## 6. 性能测试规范 + +- 关键接口需定期进行性能压测,评估 QPS、延迟、并发等指标。 +- 使用 autocannon/wrk 等工具模拟高并发请求,记录响应时间、错误率。 +- 性能基线与目标需文档化,便于回归对比。 +- 示例命令: + ```sh + autocannon -c 100 -d 30 http://localhost:3000/api/v1/hello + ``` +- 性能测试脚本与结果建议归档在 `tests/performance/` 目录。 + +## 7. Mock 与数据隔离 + +- 测试用例中对数据库、缓存、外部 API 等依赖统一 mock/stub,避免真实调用。 +- 测试数据应自动生成或在每次测试前清理,保证测试独立。 +- 推荐使用内存数据库或 mock server 进行隔离。 + +## 8. 持续集成与覆盖率 + +- 测试必须集成到 CI 流程,提交/合并前自动运行。 +- 要求单元测试覆盖率不低于 80%,核心业务代码 100%。 +- 覆盖率报告需归档,便于追踪。 + +## 9. 其他建议 + +- 测试代码应有规范注释,便于理解和维护。 +- 重要 bug 修复需补充回归测试。 +- 推荐采用 TDD(测试驱动开发)提升设计质量。 +- 测试失败时应优先修复,保证主干分支始终通过所有测试。 + +--- + +**请所有开发者严格遵守以上测试规范,保障 Elysia 项目的高质量交付。** \ No newline at end of file diff --git a/.cursor/rules/git-commit-rules.mdc b/.cursor/rules/git-commit-rules.mdc new file mode 100644 index 0000000..f01abf3 --- /dev/null +++ b/.cursor/rules/git-commit-rules.mdc @@ -0,0 +1,55 @@ +--- +description: +globs: +alwaysApply: true +--- +## Git 提交规范(Commit Message Rules) + +1. **提交格式** + ``` + (): + ``` + - `type`:提交类型(必填) + - `scope`:影响范围(可选) + - `subject`:简要描述(必填) + +2. **type 类型** + - feat:新功能 + - fix:修复 bug + - docs:文档变更 + - style:代码格式(不影响功能,例如空格、分号等) + - refactor:代码重构(既不是新增功能,也不是修复 bug) + - perf:性能优化 + - test:增加或修改测试 + - chore:构建过程或辅助工具的变动 + - revert:回滚某个提交 + +3. **scope 范围** + - 用于说明 commit 影响的模块或文件(如 user、api、core 等),可省略。 + +4. **subject 描述** + - 简明扼要,建议不超过 50 字符 + - 以动词开头,首字母小写 + - 结尾不加句号 + +5. **正文(body)** + - 可选,详细描述本次提交的内容、动机、对比信息等 + - 建议每行不超过 72 字符 + +6. **Footer** + - 可选,用于关联 issue 或进行破坏性变更说明 + - 例如:`BREAKING CHANGE: xxx` 或 `Closes #123` + +### 示例 + +``` +feat(user): 新增用户登录功能 + +fix(api): 修复获取数据时的空指针异常 + +docs(readme): 更新安装说明 + +refactor(core): 优化数据处理逻辑 + +chore: 升级依赖包 +``` \ No newline at end of file diff --git a/.cursor/rules/global-rules/create-prd.mdc b/.cursor/rules/global-rules/create-prd.mdc new file mode 100644 index 0000000..72a64fe --- /dev/null +++ b/.cursor/rules/global-rules/create-prd.mdc @@ -0,0 +1,54 @@ +--- +description: +globs: +alwaysApply: true +--- +# 规则:从PRD生成任务清单 + +## 目标 (Goal) + +指导AI助手基于现有的产品需求文档(PRD)创建一个详细的、逐步执行的任务清单(Markdown格式)。该任务清单应指导开发人员进行实现。 + +## 输出 (Output) + +* **格式:** Markdown (`.md`) +* **位置:** `/tasks/` +* **文件名:** `tasks-[prd文件名].md` (例如:`tasks-prd-user-profile-editing.md`) + +## 流程 (Process) + +1. **接收PRD引用:** 用户指定一个特定的PRD文件。 +2. **分析PRD:** AI阅读并分析指定PRD中的功能需求、用户故事和其他部分。 +3. **阶段1:生成父任务:** 基于PRD分析,创建文件并生成实现该功能所需的主要、高级别任务。根据你的判断决定使用多少高级别任务(大约5个)。以指定格式(尚未包含子任务)将这些任务呈现给用户。通知用户:“我已根据PRD生成了高级别任务。准备好生成子任务了吗?请回复 'Go' 以继续。” +4. **等待确认:** 暂停并等待用户回复“Go”。 +5. **阶段2:生成子任务:** 一旦用户确认,将每个父任务分解为完成父任务所需的更小、可操作的子任务。确保子任务逻辑上源自父任务,并涵盖PRD中隐含的实现细节。 +6. **识别相关文件:** 基于任务和PRD,识别需要创建或修改的潜在文件。在 `Relevant Files` 部分列出这些文件,如果适用,包括对应的测试文件。 +7. **生成最终输出:** 将父任务、子任务、相关文件和备注合并到最终的Markdown结构中。 +8. **保存任务清单:** 将生成的文档保存在 `/tasks/` 目录中,文件名为 `tasks-[prd文件名].md`,其中 `[prd文件名]` 与输入PRD文件的基本名称匹配(例如,如果输入是 `prd-user-profile-editing.md`,则输出为 `tasks-prd-user-profile-editing.md`)。 + +## 输出格式 (Output Format) + +生成的任务清单*必须*遵循以下结构: + +```markdown +## 相关文件 (Relevant Files) + +- `path/to/potential/file1.ts` - 该文件相关性的简要说明(例如:包含此功能的主要组件)。 +- `path/to/file1.test.ts` - `file1.ts` 的单元测试。 +- `path/to/another/file.tsx` - 简要说明(例如:用于数据提交的API路由处理器)。 +- `path/to/another/file.test.tsx` - `another/file.tsx` 的单元测试。 +- `lib/utils/helpers.ts` - 简要说明(例如:计算所需的工具函数)。 +- `lib/utils/helpers.test.ts` - `helpers.ts` 的单元测试。 + +### 备注 (Notes) + +- 单元测试通常应放置在与它们测试的代码文件相同的目录中(例如:在同一目录下的 `MyComponent.tsx` 和 `MyComponent.test.tsx`)。 + +## 任务 (Tasks) + +- [ ] 1.0 父任务标题 + - [ ] 1.1 [子任务描述 1.1] + - [ ] 1.2 [子任务描述 1.2] +- [ ] 2.0 父任务标题 + - [ ] 2.1 [子任务描述 2.1] +- [ ] 3.0 父任务标题 (如果纯粹是结构性的或配置性的,则可能不需要子任务) \ No newline at end of file diff --git a/.cursor/rules/global-rules/generate-task.mdc b/.cursor/rules/global-rules/generate-task.mdc new file mode 100644 index 0000000..9b9d4dd --- /dev/null +++ b/.cursor/rules/global-rules/generate-task.mdc @@ -0,0 +1,61 @@ +--- +description: +globs: +alwaysApply: true +--- +# 规则:生成产品需求文档 (PRD) + +## 目标 (Goal) + +指导 AI 助手基于用户的初始提示创建详细的产品需求文档 (PRD)(Markdown 格式)。PRD 应清晰、可操作,并适合初级开发人员理解和实施该功能。 + +## 流程 (Process) + +1. **接收初始提示:** 用户提供新功能或功能的简要描述或请求。 +2. **提出澄清性问题:** 在编写 PRD *之前*,AI *必须*提出澄清性问题以收集足够的细节。目标是理解功能的“是什么”和“为什么”,而不是“如何做”(开发人员将负责解决)。务必以字母/数字列表的形式提供选项,以便我可以用选择轻松回复。 +3. **生成 PRD:** 基于初始提示和用户对澄清性问题的回答,使用下面概述的结构生成 PRD。 +4. **保存 PRD:** 将生成的文档保存为 `/tasks` 目录中的 `prd-[功能名称].md`。 + +## 澄清性问题 (示例) (Clarifying Questions (Examples)) + +AI 应根据提示调整其问题,但以下是一些常见的探索领域: + +* **问题/目标:** “这个功能为用户解决了什么问题?” 或 “我们希望通过这个功能实现的主要目标是什么?” +* **目标用户:** “这个功能的主要用户是谁?” +* **核心功能:** “您能描述用户应该能够使用此功能执行的关键操作吗?” +* **用户故事:** “您能提供一些用户故事吗?(例如:作为 [用户类型],我希望 [执行操作],以便 [获得收益]。)” +* **验收标准:** “我们如何知道此功能已成功实施?关键的成功标准是什么?” +* **范围/边界:** “有没有此功能*不应该*做的特定事情(非目标)?” +* **数据需求:** “此功能需要显示或操作什么类型的数据?” +* **设计/UI:** “是否有现有的设计稿或 UI 指南需要遵循?” 或 “您能描述期望的外观和感觉吗?” +* **边界情况:** “是否有任何潜在的边界情况或错误条件我们需要考虑?” + +## PRD 结构 (PRD Structure) + +生成的 PRD 应包含以下部分: + +1. **引言/概述:** 简要描述该功能及其解决的问题。陈述目标。 +2. **目标:** 列出此功能的具体、可衡量的目标。 +3. **用户故事:** 详细描述描述功能使用和收益的用户叙事。 +4. **功能需求:** 列出该功能必须具备的具体功能。使用清晰、简洁的语言(例如:“系统必须允许用户上传个人资料图片。”)。对这些需求进行编号。 +5. **非目标(范围之外):** 明确说明此功能*不会*包含的内容,以管理范围。 +6. **设计考虑(可选):** 如果适用,链接到设计稿,描述 UI/UX 要求,或提及相关组件/样式。 +7. **技术考虑(可选):** 提及任何已知的技术限制、依赖项或建议(例如:“应与现有的 Auth 模块集成”)。 +8. **成功指标:** 如何衡量此功能的成功?(例如:“用户参与度提高 10%”、“减少与 X 相关的支持工单”)。 +9. **待解决问题:** 列出任何剩余问题或需要进一步澄清的领域。 + +## 目标受众 (Target Audience) + +假设 PRD 的主要读者是**初级开发人员**。因此,需求应明确、无歧义,并尽可能避免行话。提供足够的细节供他们理解功能的目的和核心逻辑。 + +## 输出 (Output) + +* **格式:** Markdown (`.md`) +* **位置:** `/tasks/` +* **文件名:** `prd-[功能名称].md` + +## 最终指令 (Final instructions) + +1. 不要开始实施 PRD。 +2. 务必向用户提出澄清性问题。 +3. 获取用户对澄清性问题的答案,并据此改进 PRD。 \ No newline at end of file diff --git a/.cursor/rules/global-rules/global.mdc b/.cursor/rules/global-rules/global.mdc new file mode 100644 index 0000000..ff824c9 --- /dev/null +++ b/.cursor/rules/global-rules/global.mdc @@ -0,0 +1,10 @@ +--- +description: +globs: +alwaysApply: false +--- +# 特殊要求 + +1. 删除文件时一定要请求确认 + +2. 不允许执行脚本,告诉我命令即可 \ No newline at end of file diff --git a/.cursor/rules/global-rules/process-list-task.mdc b/.cursor/rules/global-rules/process-list-task.mdc new file mode 100644 index 0000000..80713f0 --- /dev/null +++ b/.cursor/rules/global-rules/process-list-task.mdc @@ -0,0 +1,66 @@ +--- +description: +globs: +alwaysApply: true +--- + +--- + +**文档2:任务清单管理 (Task List Management)** + +```markdown +--- +描述 (description): +全局模式 (globs): +始终应用 (alwaysApply): false +--- +# 任务清单管理 + +管理Markdown文件中的任务清单以跟踪完成PRD进度的指南。 + +## 任务实施 (Task Implementation) +* **一次处理一个子任务:** 在询问用户并获得“yes”或“y”的许可之前,**不要**开始下一个子任务。 +* **完成协议 (Completion protocol):** + 1. 当你完成一个**子任务**时,立即通过将 `[ ]` 改为 `[x]` 将其标记为已完成。 + 2. 如果一个父任务下的**所有**子任务现在都是 `[x]`,则按以下顺序执行: + * **首先:** 运行完整的测试套件(`pytest`, `npm test`, `bin/rails test` 等)。 + * **仅当所有测试通过时:** 暂存更改 (`git add .`)。 + * **清理:** 提交前删除所有临时文件和临时代码。 + * **提交 (Commit):** 使用描述性的提交消息: + * 采用常规提交格式 (`feat:`, `fix:`, `refactor:`, 等)。 + * 总结父任务中完成的内容。 + * 列出关键的更改和新增。 + * 引用任务编号和PRD上下文。 + * **使用 `-m` 标志将消息格式化为单行命令**,例如: + ``` + git commit -m "feat: 添加支付验证逻辑" -m "- 验证卡类型和有效期" -m "- 为边界情况添加单元测试" -m "关联PRD中的T123" + ``` + 3. 一旦所有子任务标记为完成且更改已提交,将**父任务**标记为完成 (`[x]`)。 +* 完成每个子任务后停止并等待用户的批准 (`go-ahead`)。 + +## 任务清单维护 (Task List Maintenance) + +1. **边工作边更新任务清单:** + * 按照上述协议将任务和子任务标记为完成 (`[x]`)。 + * 添加新出现的任务。 + * **注意,每完成一个子任务时,都要更新任务清单状态** +2. **维护“相关文件”部分:** + * 列出每个创建或修改的文件。 + * 为每个文件提供一行其用途描述。 + +## AI 指令 (AI Instructions) + +当使用任务清单时,AI 必须: + +1. 完成任何重要工作后定期更新任务清单文件。 +2. 遵守完成协议: + * 将每个完成的**子任务**标记为 `[x]`。 + * 一旦其**所有**子任务都是 `[x]`,将**父任务**标记为 `[x]`。 +3. 添加新发现的任务。 +4. 保持“相关文件”准确且最新。 +5. 开始工作前,检查下一个子任务是什么。 +6. 实现一个子任务后,更新文件,然后暂停等待用户批准。 +7. 在执行任务前请扫描项目目录 +8. 完成一个子任务后,提交git +9. 将已完成的任务归档到tasks的归档目录archive并修改文件名,文件名前添加时间 +10. 将需求prd也归档 diff --git a/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 100644 index 0000000..b8100b7 --- /dev/null +++ b/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a40c12a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +bun.lockb \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7db0a9a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +bun.lockb \ No newline at end of file diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..35f04e3 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,25 @@ +/** + * @file Prettier 配置文件 + * @author hotok + * @date 2024-06-10 + * @lastEditor hotok + * @lastEditTime 2024-06-10 + * @description 统一代码格式,配合 ESLint 使用 + */ +module.exports = { + printWidth: 120, // 每行最大长度 + tabWidth: 4, // 缩进空格数 + useTabs: false, // 使用空格缩进 + semi: true, // 语句末尾加分号 + singleQuote: true, // 使用单引号 + quoteProps: 'as-needed', // 仅在需要时为对象的key加引号 + jsxSingleQuote: true, // JSX中使用单引号 + trailingComma: 'all', // 多行对象/数组最后一个元素后加逗号 + bracketSpacing: true, // 对象大括号内两边加空格 + jsxBracketSameLine: false, // JSX > 是否另起一行 + arrowParens: 'always', // 箭头函数参数总是加括号 + endOfLine: 'lf', // 换行符使用lf + proseWrap: 'preserve', // markdown等文本自动换行 + htmlWhitespaceSensitivity: 'css', // html空白敏感度 + embeddedLanguageFormatting: 'auto', // 格式化嵌入代码 +}; diff --git a/.test-rules.md b/.test-rules.md new file mode 100644 index 0000000..fef1106 --- /dev/null +++ b/.test-rules.md @@ -0,0 +1,99 @@ +# Elysia + Vitest 测试用例编写规范 + +## 1. 文件结构与命名 + +- 每个被测模块应有对应的 `xxx.test.ts` 文件,放在同目录或 `tests/` 下。 +- 测试文件需有文件头注释,说明作者、日期、描述等。 + +## 2. 测试用例基本格式 + +- 使用 `describe` 分组,`it`(或 `test`)描述单个场景。 +- 每个 `it` 需有明确的行为描述。 + +## 3. 参数传递与类型 + +- 明确传递参数类型,推荐用 TypeBox schema 自动推导类型。 +- 测试用例中参数应覆盖正常、异常、边界值。 + +## 4. 响应结构与 code 校验 + +- 断言 HTTP 状态码(如 200、400、401)。 +- 断言响应体结构(如 code、message、data),类型可用 `as any` 或 schema 类型断言。 + +## 5. 边界与异常测试 + +- 必须覆盖参数边界(如最短/最长用户名、最短密码等)。 +- 必须覆盖异常分支(如缺少参数、token 错误、未授权等)。 + +## 6. 类型安全 + +- 推荐用 TypeBox 的 `Static` 推导类型,或在测试用例中用 `as any` 简化断言。 +- 对于复杂响应,可定义类型辅助断言。 + +## 7. 断言范围 + +- 断言应覆盖:HTTP 状态码、响应 code、message、data 字段、关键数据类型。 +- 对于 token、id 等动态值,用 `typeof` 或正则断言。 + +## 8. 其他建议 + +- 用 `it.only`/`describe.only` 聚焦调试,提交前移除。 +- 用 `console.log` 输出实际响应,便于排查失败。 +- 每个接口的正常、异常、边界场景都应有测试覆盖。 + +## 9. 示例 + +```typescript +/** + * @file 用户登录接口测试 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 覆盖登录接口的正常、异常、边界场景 + */ + +describe('POST /api/login', () => { + it('应登录成功并返回token', async () => { + const res = await app.fetch( + new Request('http://localhost/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: '123456' }), + }), + ); + const body = (await res.json()) as any; + expect(res.status).toBe(200); + expect(body.code).toBe(0); + expect(typeof body.data.token).toBe('string'); + }); + + it('用户名过短应返回400', async () => { + const res = await app.fetch( + new Request('http://localhost/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'a', password: '123456' }), + }), + ); + const body = (await res.json()) as any; + expect(res.status).toBe(400); + expect(body.code).toBe(400); + expect(body.message).toMatch(/用户名长度/); + }); + + it('密码错误应返回400', async () => { + const res = await app.fetch( + new Request('http://localhost/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'wrong' }), + }), + ); + const body = (await res.json()) as any; + expect(res.status).toBe(400); + expect(body.code).toBe(400); + expect(body.message).toBe('用户名或密码错误'); + }); +}); +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9db309 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# cursor-init + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.17. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. + +# 项目初始化 diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..b84ce1b --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[paths] +"@/*" = "./src/*" \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..332d647 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,70 @@ +// @ts-check +// ESLint 9.x Flat Config for Elysia + TypeScript 项目 +// 详细注释,含风格规范(四空格缩进、分号、单引号等) + +import js from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; + +/** + * @type {import("eslint").Linter.FlatConfig[]} + * FlatConfig 方式配置,兼容ESLint 9.x及TypeScript + */ +export default [ + // 基础JS推荐规则 + js.configs.recommended, + { + // 仅检查src和tests目录下的ts文件 + files: ['src/**/*.ts', 'tests/**/*.ts', '*.ts'], + languageOptions: { + // TypeScript解析器 + parser: tsparser, + parserOptions: { + sourceType: 'module', // ES模块 + ecmaVersion: 'latest', // 最新ECMAScript + }, + // 全局变量声明,避免no-undef + globals: { + Request: 'readonly', + console: 'readonly', + process: 'readonly', + Bun: 'readonly', + }, + }, + plugins: { + // TypeScript插件 + '@typescript-eslint': tseslint, + }, + rules: { + // 关闭原生no-unused-vars,使用TS版本 + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'warn', + // 关闭强制函数返回类型 + '@typescript-eslint/explicit-function-return-type': 'off', + // 允许any + '@typescript-eslint/no-explicit-any': 'off', + // 允许ts-ignore等注释 + '@typescript-eslint/ban-ts-comment': 'off', + // 强制四空格缩进 + indent: ['error', 4], + // 强制分号 + semi: ['error', 'always'], + // 强制单引号 + quotes: ['error', 'single'], + // 末尾逗号(多行对象/数组) + 'comma-dangle': ['error', 'always-multiline'], + // 对象key统一加引号 + 'quote-props': ['error', 'as-needed'], + // 关键字前后空格 + 'keyword-spacing': ['error', { before: true, after: true }], + // 大括号风格 + 'brace-style': ['error', '1tbs'], + // 禁止多余空行 + 'no-multiple-empty-lines': ['error', { max: 1 }], + }, + }, + { + // 忽略目录和文件 + ignores: ['node_modules/', 'dist/', 'bun.lockb', '*.config.js', '*.config.ts'], + }, +]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..b2ea8a2 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "cursor-init", + "version": "0.1.0", + "description": "Bun + Elysia 后端API项目,支持MySQL、JWT、Swagger、Vitest等", + "author": "hotok ", + "license": "MIT", + "repository": { + "type": "gitea", + "url": "git+https://github.com/yourname/yourrepo.git" + }, + "type": "module", + "private": true, + "engines": { + "bun": ">=1.0.25" + }, + "devDependencies": { + "@types/bun": "^1.0.25", + "@typescript-eslint/eslint-plugin": "^8.35.0", + "@typescript-eslint/parser": "^8.35.0", + "eslint": "^9.29.0", + "eslint-config-prettier": "^10.1.5", + "prettier": "^3.6.2", + "typescript": "^5.8.3", + "vitest": "^3.2.4" + }, + "dependencies": { + "@elysiajs/jwt": "^1.3.1", + "@elysiajs/swagger": "^1.3.0", + "mysql2": "^3.14.1", + "undici": "^7.11.0" + }, + "scripts": { + "dev": "bun --hot src/server.ts", + "start": "bun src/server.ts", + "test": "bun test", + "test:watch": "bun test --watch", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "format": "prettier --write ." + } +} diff --git a/src/app.test.ts b/src/app.test.ts new file mode 100644 index 0000000..a8fdcfe --- /dev/null +++ b/src/app.test.ts @@ -0,0 +1,96 @@ +// @ts-nocheck +/** + * @file 主应用接口自动化测试(Elysia fetch版) + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 覆盖健康检查、登录、受保护接口的集成测试,直接调用app.fetch + */ + +import { describe, it, expect } from 'vitest'; +import { app } from './app'; + +let token = ''; + +/** + * 健康检查接口测试 + */ +describe('GET /api/health', () => { + it('应返回服务运行正常', async () => { + const res = await app.fetch(new Request('http://localhost/api/health')); + const body = await res.json(); + expect(res.status).toBe(200); + expect(body.code).toBe(0); + expect(body.message).toBe('服务运行正常'); + }); +}); + +/** + * 登录接口测试 + */ +describe('POST /api/login', () => { + it('应登录成功并返回token', async () => { + const res = await app.fetch( + new Request('http://localhost/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: '123456' }), + }), + ); + const body = await res.json(); + expect(res.status).toBe(200); + expect(body.code).toBe(0); + expect(typeof body.data.token).toBe('string'); + token = body.data.token; + }); + + it('用户名或密码错误应返回400', async () => { + const res = await app.fetch( + new Request('http://localhost/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'wrong6' }), + }), + ); + const body = await res.json(); + expect(res.status).toBe(400); + expect(body.code).toBe(400); + expect(body.message).toBe('用户名或密码错误'); + }); +}); + +/** + * 受保护接口测试 + */ +describe('GET /api/protected', () => { + it('无Token应返回401', async () => { + const res = await app.fetch(new Request('http://localhost/api/protected')); + const body = await res.json(); + expect(res.status).toBe(401); + expect(body.code).toBe(401); + }); + + it('Token错误应返回401', async () => { + const res = await app.fetch( + new Request('http://localhost/api/protected', { + headers: { Authorization: 'Bearer wrongtoken' }, + }), + ); + const body = await res.json(); + expect(res.status).toBe(401); + expect(body.code).toBe(401); + }); + + it('Token正确应返回用户信息', async () => { + const res = await app.fetch( + new Request('http://localhost/api/protected', { + headers: { Authorization: `Bearer ${token}` }, + }), + ); + const body = await res.json(); + expect(res.status).toBe(200); + expect(body.code).toBe(0); + expect(body.data.username).toBe('admin'); + }); +}); diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..23b0ed2 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,40 @@ +/** + * @file 应用主入口,初始化Elysia实例 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description Elysia API服务应用入口文件 + */ + +import { Elysia } from 'elysia'; +import { swaggerPlugin } from '@/plugins/swagger'; +import { authController } from '@/controllers/try/auth.controller'; +import { protectedController } from '@/controllers/try/protected.controller'; + +/** + * Elysia应用实例 + * @type {Elysia} + */ +export const app = new Elysia() + .use(swaggerPlugin) + .use(authController) + .use(protectedController) + .state('counter', 0) // 定义名为 counter 的初始值 + + // 批量定义 不会覆盖前面单独定义的 + .state({ + version: '1.0', + server: 'Bun', + }) + .state('db', '一个方法') + .decorate('closeDB', () => console.log('关闭方法')); // 添加关闭方法 + +// 健康检查接口 +app.get('/api/health', () => ({ + code: 0, + message: '服务运行正常', + data: null, +})); + +// app.closeDB() 可以以在路由中调用 diff --git a/src/config/db.config.ts b/src/config/db.config.ts new file mode 100644 index 0000000..4f29b1c --- /dev/null +++ b/src/config/db.config.ts @@ -0,0 +1,29 @@ +/** + * @file MySQL数据库配置 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 读取环境变量并导出MySQL连接配置 + */ + +/** + * MySQL数据库连接配置 + * @property {string} host - 数据库主机地址 + * @property {number} port - 数据库端口号 + * @property {string} user - 数据库用户名 + * @property {string} password - 数据库密码 + * @property {string} database - 数据库名称 + */ +export const dbConfig = { + /** 数据库主机地址 */ + host: process.env.DB_HOST || '172.16.1.3', + /** 数据库端口号 */ + port: Number(process.env.DB_PORT) || 3306, + /** 数据库用户名 */ + user: process.env.DB_USER || 'docker', + /** 数据库密码 */ + password: process.env.DB_PASSWORD || 'docker', + /** 数据库名称 */ + database: process.env.DB_NAME || 'docker', +}; diff --git a/src/config/jwt.config.ts b/src/config/jwt.config.ts new file mode 100644 index 0000000..fb3b868 --- /dev/null +++ b/src/config/jwt.config.ts @@ -0,0 +1,20 @@ +/** + * @file JWT配置 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 统一导出JWT密钥和过期时间 + */ + +/** + * JWT配置 + * @property {string} secret - JWT签名密钥 + * @property {string} exp - Token有效期 + */ +export const jwtConfig = { + /** JWT签名密钥 */ + secret: process.env.JWT_SECRET || 'your_jwt_secret', + /** Token有效期 */ + exp: '7d', // token有效期 +}; diff --git a/src/controllers/try/auth.controller.ts b/src/controllers/try/auth.controller.ts new file mode 100644 index 0000000..dece3f3 --- /dev/null +++ b/src/controllers/try/auth.controller.ts @@ -0,0 +1,34 @@ +/** + * @file 用户认证控制器(试运行) + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 提供基础登录接口,集成JWT签发与参数校验,业务逻辑分离到service + */ + +import { Elysia } from 'elysia'; +import { jwtPlugin } from '@/plugins/jwt'; +import { loginBodySchema, type LoginBody } from '@/validators/try/auth.validator'; +import { loginResponse200Schema, loginResponse400Schema } from '@/validators/try/auth.response'; +import { loginService } from '@/services/try/auth.service'; + +/** + * 登录接口 + * @param app Elysia实例 + * @returns Elysia实例(带登录路由) + */ +export const authController = new Elysia() + .use(jwtPlugin) + .post('/api/login', ({ body, jwt, set }: { body: LoginBody; jwt: any; set: any }) => loginService(body, jwt, set), { + body: loginBodySchema, + detail: { + tags: ['认证'], + summary: '用户登录', + description: '基础登录接口,用户名/密码校验通过后返回JWT', + }, + response: { + 200: loginResponse200Schema, + 400: loginResponse400Schema, + }, + }); diff --git a/src/controllers/try/protected.controller.ts b/src/controllers/try/protected.controller.ts new file mode 100644 index 0000000..0f2226b --- /dev/null +++ b/src/controllers/try/protected.controller.ts @@ -0,0 +1,33 @@ +/** + * @file 受保护接口控制器(试运行) + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 提供需要JWT认证的受保护接口,业务逻辑分离到service + */ + +import { Elysia } from 'elysia'; +import { jwtAuthPlugin } from '@/plugins/jwt-auth'; +import { protectedResponse200Schema, protectedResponse401Schema } from '@/validators/try/protected.response'; +import { protectedService } from '@/services/try/protected.service'; + +/** + * 受保护接口 + * @param app Elysia实例 + * @returns Elysia实例(带受保护路由) + */ +export const protectedController = new Elysia() + .use(jwtAuthPlugin) + .get('/api/protected', ({ user }: any) => protectedService(user), { + detail: { + summary: '受保护接口', + tags: ['认证'], + description: '需要JWT认证的受保护接口,返回当前用户信息', + security: [{ bearerAuth: [] }], + }, + response: { + 200: protectedResponse200Schema, + 401: protectedResponse401Schema, + }, + }); diff --git a/src/plugins/jwt-auth.ts b/src/plugins/jwt-auth.ts new file mode 100644 index 0000000..97ac023 --- /dev/null +++ b/src/plugins/jwt-auth.ts @@ -0,0 +1,49 @@ +/** + * @file JWT认证插件(Elysia官方推荐链式插件) + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 以Elysia官方链式插件写法实现JWT校验,未通过则返回401 + */ + +import { Elysia } from 'elysia'; +import { jwtPlugin } from './jwt'; + +export const jwtAuthPlugin = (app: Elysia) => + app + .use(jwtPlugin) + .derive(async ({ jwt, headers, status }) => { + const authHeader = headers['authorization']; + if (!authHeader?.startsWith('Bearer ')) { + return status(401, { + code: 401, + message: '未携带Token', + data: null, + }); + } + const token = authHeader.replace('Bearer ', ''); + try { + const user = await jwt.verify(token); + if (!user) + return status(401, { + code: 401, + message: 'Token无效', + data: null, + }); + console.log('USER', user); + return { user }; + } catch { + return {}; + } + }) + .onBeforeHandle(({ user, set }) => { + if (!user) { + set.status = 401; + return { + code: 401 as const, + message: '未授权', + data: null, + }; + } + }); diff --git a/src/plugins/jwt.ts b/src/plugins/jwt.ts new file mode 100644 index 0000000..bc80a2f --- /dev/null +++ b/src/plugins/jwt.ts @@ -0,0 +1,17 @@ +/** + * @file JWT插件封装 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 封装Elysia JWT插件,统一配置密钥 + */ + +import { jwt } from '@elysiajs/jwt'; +import { jwtConfig } from '@/config/jwt.config'; + +export const jwtPlugin = jwt({ + name: 'jwt', + secret: jwtConfig.secret, + exp: jwtConfig.exp, +}); diff --git a/src/plugins/swagger.ts b/src/plugins/swagger.ts new file mode 100644 index 0000000..2729247 --- /dev/null +++ b/src/plugins/swagger.ts @@ -0,0 +1,35 @@ +/** + * @file Swagger插件封装 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 封装Elysia Swagger插件,统一管理API文档配置 + */ + +import { swagger } from '@elysiajs/swagger'; + +/** + * Swagger插件实例 + * @description 统一API文档配置,便于主应用和测试复用 + */ +export const swaggerPlugin = swagger({ + documentation: { + info: { + title: 'API服务', + version: '1.0.0', + description: '基于Elysia的API服务,集成JWT、MySQL等功能', + }, + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: 'JWT认证,需在header中携带Authorization: Bearer ', + }, + }, + }, + security: [{ bearerAuth: [] }], + }, +}); diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..5984528 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,13 @@ +/** + * @file 服务启动入口 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 启动Elysia服务 + */ + +import { app } from './app'; + +app.listen(3000); +console.log('🚀 服务已启动:http://localhost:3000'); diff --git a/src/services/try/auth.service.ts b/src/services/try/auth.service.ts new file mode 100644 index 0000000..00f1b4c --- /dev/null +++ b/src/services/try/auth.service.ts @@ -0,0 +1,33 @@ +/** + * @file 用户认证业务逻辑 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 登录业务逻辑,供controller调用 + */ + +/** + * 登录业务逻辑 + * @param body 登录请求体 + * @param jwt JWT插件实例 + * @param set Elysia set对象 + * @returns 登录响应对象 + */ +export const loginService = async (body: { username: string; password: string }, jwt: any, set: any) => { + const { username, password } = body; + if (username !== 'admin' || password !== '123456') { + set.status = 400; + return { + code: 400, + message: '用户名或密码错误', + data: null, + }; + } + const token = await jwt.sign({ username }); + return { + code: 0, + message: '登录成功', + data: { token }, + }; +}; diff --git a/src/services/try/protected.service.ts b/src/services/try/protected.service.ts new file mode 100644 index 0000000..e28cc66 --- /dev/null +++ b/src/services/try/protected.service.ts @@ -0,0 +1,25 @@ +/** + * @file 受保护接口业务逻辑 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 受保护接口业务逻辑,供controller调用 + */ + +/** + * 受保护接口业务逻辑 + * @param store Elysia store对象 + * @returns 受保护接口响应对象 + */ +export const protectedService = (user: any) => { + /** + * @type {any} user - JWT解码后的用户信息 + * @description 由jwtAuthPlugin中间件注入 + */ + return { + code: 0, + message: '受保护资源访问成功', + data: { username: user?.username ?? '' }, + }; +}; diff --git a/src/utils/mysql.test.ts b/src/utils/mysql.test.ts new file mode 100644 index 0000000..e1f9456 --- /dev/null +++ b/src/utils/mysql.test.ts @@ -0,0 +1,28 @@ +/** + * @file MySQL连接池测试 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 测试MySQL连接池是否可用 + */ + +import { describe, it, expect } from 'vitest'; +import { pool } from './mysql'; + +// 基础连接测试 + +describe('MySQL连接池', () => { + it('应能成功建立连接', async () => { + const conn = await pool.getConnection(); + expect(conn).toBeDefined(); + await conn.release(); + }); + + it('应能执行简单查询', async () => { + const [rows] = await pool.query('SELECT 1 + 1 AS result'); + // mysql2返回的rows类型为 RowDataPacket[] + expect(Array.isArray(rows)).toBe(true); + expect((rows as any)[0]?.result).toBe(2); + }); +}); diff --git a/src/utils/mysql.ts b/src/utils/mysql.ts new file mode 100644 index 0000000..c007ec4 --- /dev/null +++ b/src/utils/mysql.ts @@ -0,0 +1,22 @@ +/** + * @file MySQL数据库连接工具 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 提供MySQL连接池实例,供全局复用 + */ + +import mysql from 'mysql2/promise'; +import { dbConfig } from '@/config/db.config'; + +export const pool = mysql.createPool({ + host: dbConfig.host, + port: dbConfig.port, + user: dbConfig.user, + password: dbConfig.password, + database: dbConfig.database, + waitForConnections: true, + connectionLimit: 10, // 连接池最大连接数 + queueLimit: 0, +}); diff --git a/src/validators/try/auth.response.ts b/src/validators/try/auth.response.ts new file mode 100644 index 0000000..c83965b --- /dev/null +++ b/src/validators/try/auth.response.ts @@ -0,0 +1,24 @@ +/** + * @file 登录接口响应schema + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 登录接口响应结构定义 + */ + +import { t } from 'elysia'; + +/** 登录成功响应schema */ +export const loginResponse200Schema = t.Object({ + code: t.Literal(0), + message: t.String(), + data: t.Object({ token: t.String() }), +}); + +/** 登录失败响应schema */ +export const loginResponse400Schema = t.Object({ + code: t.Literal(400), + message: t.String(), + data: t.Null(), +}); diff --git a/src/validators/try/auth.validator.ts b/src/validators/try/auth.validator.ts new file mode 100644 index 0000000..553cd43 --- /dev/null +++ b/src/validators/try/auth.validator.ts @@ -0,0 +1,23 @@ +/** + * @file 用户认证参数校验规则 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 登录接口参数校验规则,含中文错误提示 + */ + +import { t } from 'elysia'; +import type { Static } from 'elysia'; + +/** + * 登录请求参数校验规则 + * @property {string} username - 用户名,最少3位 + * @property {string} password - 密码,最少6位 + */ +export const loginBodySchema = t.Object({ + username: t.String({ minLength: 2, maxLength: 16 }), + password: t.String({ minLength: 6, maxLength: 32 }), +}); + +export type LoginBody = Static; diff --git a/src/validators/try/protected.response.ts b/src/validators/try/protected.response.ts new file mode 100644 index 0000000..79ca673 --- /dev/null +++ b/src/validators/try/protected.response.ts @@ -0,0 +1,42 @@ +/** + * @file 受保护接口响应校验规则 + * @author hotok + * @date 2025-06-28 + * @lastEditor hotok + * @lastEditTime 2025-06-28 + * @description 受保护接口的响应类型定义,含成功和未授权两种情况 + */ + +import { t } from 'elysia'; +import type { Static } from 'elysia'; + +/** + * 受保护接口 200 响应 schema + */ +export const protectedResponse200Schema = t.Object({ + code: t.Literal(0), + message: t.String(), + data: t.Object({ + username: t.String(), + // 可根据实际业务扩展字段 + }), +}); + +/** + * 受保护接口 401 响应 schema + */ +export const protectedResponse401Schema = t.Object({ + code: t.Literal(401), + message: t.String(), + data: t.Null(), +}); + +/** + * 受保护接口 200 响应类型 + */ +export type ProtectedResponse200 = Static; + +/** + * 受保护接口 401 响应类型 + */ +export type ProtectedResponse401 = Static; diff --git a/tasks/archive/20240610-prd-项目初始化.md b/tasks/archive/20240610-prd-项目初始化.md new file mode 100644 index 0000000..6f3324b --- /dev/null +++ b/tasks/archive/20240610-prd-项目初始化.md @@ -0,0 +1,107 @@ +# 项目初始化 PRD(Bun.js + Elysia API 服务) + +## 1. 引言/概述 + +本项目旨在基于 Bun.js 下的 Elysia 框架,搭建一个高效、可维护的 API 服务后端。项目将集成 MySQL 数据库、JWT 用户认证、自动生成 Swagger API 文档,采用 TypeScript 进行开发,配备 Vitest 测试框架,并严格遵循 ESLint + Prettier 代码规范和团队注释规范。项目结构分层清晰,便于后续扩展和团队协作。 + +## 2. 目标 + +- 快速搭建可运行的 Elysia API 服务基础工程。 +- 支持 MySQL 数据库连接与基础操作。 +- 集成 JWT 认证中间件,支持用户登录鉴权。 +- 自动生成并暴露 Swagger API 文档。 +- 采用 TypeScript,保证类型安全。 +- 集成 Vitest 测试框架,支持单元与接口测试。 +- 配置 ESLint + Prettier,统一代码风格。 +- 遵循团队目录结构与注释规范。 + +## 3. 用户故事 + +- 作为开发者,我希望有一个结构清晰、规范统一的 API 服务项目模板,便于快速开发和维护。 +- 作为开发者,我希望能方便地连接和操作 MySQL 数据库。 +- 作为开发者,我希望项目自带 JWT 认证能力,便于后续扩展用户系统。 +- 作为开发者,我希望能通过 Swagger UI 查看和调试所有 API。 +- 作为开发者,我希望有完善的测试和代码规范工具,保证代码质量。 + +## 4. 功能需求 + +1. 项目初始化: + - 必须基于 Bun.js 和 Elysia 框架初始化项目。 + - 必须采用 TypeScript。 +2. 目录结构与规范: + - 必须采用 rules 文件中最新推荐的分层目录结构: + ``` + ├── src/ + │ ├── controllers/ # 路由与业务入口 + │ ├── services/ # 业务逻辑 + │ ├── models/ # 数据模型与类型定义 + │ ├── middlewares/ # 中间件 + │ ├── plugins/ # Elysia 插件 + │ ├── utils/ # 工具函数 + │ ├── validators/ # 参数校验 + │ ├── config/ # 配置文件 + │ ├── type/ # 类型定义文件 + │ └── app.ts # 应用入口 + ├── tests/ # 测试用例 + ├── public/ # 静态资源 + ├── .env # 环境变量 + ├── bun.lockb # Bun 依赖锁 + ├── package.json + └── README.md + ``` + - 每个文件、类、方法、复杂逻辑必须有规范注释(参考注释规范 rules)。 +3. 数据库集成: + - 必须集成 MySQL 数据库,支持基础连接与操作。 + - 数据库配置需通过环境变量管理。 +4. 认证与鉴权: + - 必须集成 JWT 认证中间件,支持生成和校验 Token。 + - 提供基础的登录接口示例。 +5. API 文档: + - 必须集成 @elysiajs/swagger 插件,自动生成并暴露 Swagger UI。 + - 所有接口需完整描述参数、返回值、错误码等。 +6. 代码规范与工具: + - 必须集成 ESLint(含 TypeScript 规则)和 Prettier。 + - 必须配置 Vitest 测试框架,支持单元和接口测试。 + - 必须提供基础的 CI/CD 配置(如 GitHub Actions 示例)。 +7. 示例接口: + - 提供一个基础的健康检查接口(/api/health)。 + - 提供一个带 JWT 认证的示例接口。 + +## 5. 非目标(范围之外) + +- 不实现具体的业务功能(如用户注册、复杂业务逻辑等)。 +- 不集成除 MySQL 外的其他数据库。 +- 不实现前端页面,仅提供 API 服务。 +- 不实现国际化、定时任务、第三方服务集成等高级功能。 + +## 6. 设计考虑 + +- 目录结构、注释风格、接口风格等均严格参考 rules 文件规范。 +- 所有配置(如数据库、JWT 密钥等)均通过 .env 文件和 config 目录集中管理。 +- API 路由统一前缀为 /api。 + +## 7. 技术考虑 + +- 使用 Bun.js 作为运行时,Elysia 作为主框架。 +- MySQL 连接推荐使用官方或社区兼容库(如 bun-mysql、mysql2 等)。 +- JWT 推荐使用 @elysiajs/jwt 插件。 +- Swagger 推荐使用 @elysiajs/swagger 插件。 +- 测试推荐使用 Vitest,接口测试可用 supertest/undici。 +- 代码规范工具需支持 TypeScript。 + +## 8. 成功指标 + +- 项目可一键启动并通过健康检查接口返回正常。 +- 能成功连接 MySQL 并进行基础操作。 +- JWT 认证流程可用,接口能正确校验 Token。 +- Swagger UI 能自动展示所有接口文档。 +- 通过 Vitest 测试,覆盖率不低于 80%。 +- 代码通过 ESLint 检查并格式化。 + +## 9. 待解决问题 + +- MySQL 连接库的最终选择(bun-mysql、mysql2 等)。 +- JWT 密钥和安全配置的管理细节。 +- CI/CD 工具链的具体实现细节(如是否接入特定平台)。 + + diff --git a/tasks/archive/20240610-tasks-prd-项目初始化.md b/tasks/archive/20240610-tasks-prd-项目初始化.md new file mode 100644 index 0000000..068ca9b --- /dev/null +++ b/tasks/archive/20240610-tasks-prd-项目初始化.md @@ -0,0 +1,64 @@ +## 相关文件 (Relevant Files) + +- `src/app.ts` - 应用主入口,负责Elysia实例初始化和全局插件注册。 +- `src/controllers/` - 路由与业务入口目录。 +- `src/services/` - 业务逻辑实现目录。 +- `src/models/` - 数据模型与类型定义目录。 +- `src/middlewares/` - 中间件实现目录。 +- `src/plugins/` - Elysia插件扩展目录。 +- `src/utils/` - 工具函数目录。 +- `src/validators/` - 参数校验规则目录。 +- `src/config/` - 配置文件目录。 +- `src/type/` - 类型定义文件目录。 +- `tests/` - 测试用例目录。 +- `public/` - 静态资源目录。 +- `.env` - 环境变量配置文件。 +- `bun.lockb` - Bun依赖锁定文件。 +- `package.json` - 项目依赖与脚本配置。 +- `README.md` - 项目说明文档。 +- `.git/` - Git版本控制目录,跟踪项目历史和协作。 +- `.gitignore` - Git忽略文件,排除不需纳入版本控制的内容。 +- `tsconfig.json` - TypeScript配置文件。 + +### 备注 (Notes) + +- 单元测试通常应放置在与它们测试的代码文件相同的目录中(例如:在同一目录下的 `MyService.ts` 和 `MyService.test.ts`)。 + +## 任务 (Tasks) + +- [ ] 1.0 初始化Bun.js + Elysia + TypeScript项目结构 + - [x] 1.1 初始化git仓库,配置.gitignore + - [x] 1.2 使用Bun初始化项目,创建`package.json`和`bun.lockb` + - [x] 1.3 配置TypeScript支持,生成`tsconfig.json` + - [x] 1.4 按照规范创建`src/`及各子目录(controllers、services、models等) + - [x] 1.5 创建`src/app.ts`作为应用入口,初始化Elysia实例 + - [x] 1.6 创建`.env`和`README.md`基础文件 + - [x] 1.7 在所有源文件添加文件头注释模板 + +- [ ] 2.0 集成MySQL数据库支持 + - [x] 2.1 选择并安装MySQL驱动(如`bun-mysql`或`mysql2`) + - [x] 2.2 在`src/config/`中编写数据库配置文件,支持环境变量 + - [x] 2.3 在`src/models/`中定义基础数据模型 + - [x] 2.4 编写数据库连接工具或服务,支持连接池 + - [x] 2.5 实现数据库连接测试脚本 + +- [ ] 3.0 集成JWT认证与基础登录接口 + - [x] 3.1 安装`@elysiajs/jwt`插件 + - [x] 3.2 在`src/plugins/`中封装JWT插件配置 + - [x] 3.3 在`src/controllers/`中实现基础登录接口(如`/api/login`) + - [x] 3.4 在`src/middlewares/`中实现JWT校验中间件 + - [x] 3.5 提供一个带JWT保护的示例接口 + - [x] 3.6 集成控制器并测试主应用 + +- [ ] 4.0 集成Swagger API文档自动生成 + - [x] 4.1 安装`@elysiajs/swagger`插件 + - [x] 4.2 在`src/plugins/`中封装Swagger插件配置 + - [x] 4.3 为所有接口补充Swagger注解,完善参数/返回/错误码描述 + - [x] 4.4 验证Swagger UI可正常访问并展示所有接口 + +- [ ] 5.0 配置Vitest测试、ESLint、Prettier及CI/CD工具链 + - [x] 5.1 安装并配置Vitest,编写基础单元测试和接口测试 + - [x] 5.2 安装并配置ESLint(含TypeScript规则) + - [x] 5.3 安装并配置Prettier,统一代码风格 + - [x] 5.4 配置Git钩子或CI自动检查代码规范 + - [x] 5.5 编写GitHub Actions等CI/CD示例配置 diff --git a/tasks/prd-架构优化.md b/tasks/prd-架构优化.md new file mode 100644 index 0000000..a1f8e6e --- /dev/null +++ b/tasks/prd-架构优化.md @@ -0,0 +1,105 @@ +# 架构优化 PRD + +## 1. 引言/概述 + +本次架构优化旨在提升后端服务的可维护性、可观测性和健壮性。通过引入统一日志、全局错误处理和响应封装,规范接口行为,便于团队协作和后续扩展。新增 Redis 支持,为缓存、限流等功能打下基础。 + +## 2. 目标 + +- 实现统一、可扩展的日志记录方案,支持分环境美化/存储/清理 +- 实现全局错误捕获与标准化响应,支持分环境详细度 +- 实现接口响应结构统一封装 +- 集成 Redis,支持缓存、限流等能力 +- 提升系统可观测性和异常可追溯性 + +## 3. 用户故事 + +- 作为开发者,我希望所有接口出错时能收到统一格式的错误响应,便于前端处理和排查。 +- 作为运维,我希望能通过日志快速定位线上问题。 +- 作为开发者,我希望所有接口响应结构一致,便于前后端协作。 +- 作为开发者,我希望在开发环境下看到彩色美观的日志和详细错误堆栈。 +- 作为运维,我希望生产环境日志能自动分文件存储并定期清理。 +- 作为开发者,我希望能方便地使用 Redis 进行缓存、限流等操作。 + +## 4. 功能需求 + +1. **日志记录器** + - 支持 info、warn、error、debug 等日志等级 + - 日志内容包含时间、等级、消息、上下文信息 + - 日志默认输出到控制台,后续可扩展到文件/远程 + - 关键操作、异常、接口请求需有日志 + - 每条请求日志包含唯一请求 id、method、url、状态码、耗时、IP、时间戳 + - dev 环境日志美化输出(带颜色)、控制台打印 + - prod 环境日志按天/小时分文件存储,支持定时清理历史日志 + - 日志格式美观,dev 环境带颜色区分 info/warn/error + +2. **全局错误处理器** + - 捕获所有未处理异常,返回统一 JSON 结构 + - 支持自定义业务异常类型 + - 错误日志自动记录 + - 错误响应结构需包含 code、message、data 字段 + - dev 环境异常响应包含详细堆栈,prod 环境仅输出友好错误信息 + +3. **响应封装** + - 所有接口返回统一结构:`{ code, message, data }` + - 支持自定义响应码和 message + - 可扩展 traceId、耗时等字段 + +4. **请求日志中间件** + - 记录 method、url、耗时、状态码、IP、请求 id + - 日志链路追踪 + +5. **健康检查接口** + - 提供 `/health` 路由,返回服务健康状态 + +6. **配置中心优化** + - 所有配置集中到 config,支持多环境 + +7. **Redis 支持** + - 集成 Redis,配置集中管理 + - Redis 连接池、健康检查 + - 预留缓存、限流、会话等场景的基础能力 + +8. **接口文档自动化完善** + - Swagger UI 增加全局响应示例、错误码说明 + +## 5. 非目标(范围之外) + +- 日志持久化到远程(本期仅本地文件) +- 速率限制、权限控制等安全功能 + +## 6. 设计考虑 + +- 日志、错误、响应封装均以中间件/插件方式实现,便于复用和扩展 +- 代码与注释规范保持一致 +- 目录结构建议: + - `src/middlewares/logger.ts` + - `src/middlewares/error-handler.ts` + - `src/utils/response.ts` + - `src/config/redis.config.ts` +- 日志库建议用 pino/winston/simple 自研 +- 日志文件存储建议按天分目录,定时清理可用定时任务 +- 请求 id 可用 nanoid/uuid 生成,并通过中间件注入上下文 +- Redis 推荐用 bun:redis 原生或社区库 + +## 7. 技术考虑 + +- 日志库选型、日志文件清理策略 +- 错误类型建议自定义 Error 类 +- 响应封装为工具函数或 Elysia 插件 +- Redis 连接池与健康检查实现 + +## 8. 成功指标 + +- 100% 接口响应结构统一 +- 关键操作/异常均有日志 +- 错误响应无堆栈泄漏,信息友好 +- 日志分环境输出,生产环境日志可定期清理 +- Redis 连接稳定,健康检查通过 + +## 9. 待解决问题 + +- 日志等级与格式细节 +- 错误码与 message 规范 +- 响应结构是否需 traceId、耗时等扩展字段 +- Redis 具体使用场景细化 \ No newline at end of file diff --git a/tasks/tasks-prd-架构优化.md b/tasks/tasks-prd-架构优化.md new file mode 100644 index 0000000..d104746 --- /dev/null +++ b/tasks/tasks-prd-架构优化.md @@ -0,0 +1,44 @@ +## 相关文件 (Relevant Files) + +- `src/middlewares/logger.ts` - 日志记录中间件,支持分环境、彩色/文件输出、请求id链路追踪。 +- `src/middlewares/error-handler.ts` - 全局错误处理中间件,支持分环境详细度。 +- `src/utils/response.ts` - 统一响应封装工具函数。 +- `src/config/redis.config.ts` - Redis 配置与连接池。 +- `src/middlewares/request-id.ts` - 请求id生成与注入中间件。 +- `src/routes/health.controller.ts` - 健康检查接口。 +- `src/tests/logger.test.ts` - 日志中间件单元测试。 +- `src/tests/error-handler.test.ts` - 错误处理中间件单元测试。 +- `src/tests/response.test.ts` - 响应封装工具测试。 +- `src/tests/redis.test.ts` - Redis 连接与健康检查测试。 + +### 备注 (Notes) + +- 单元测试建议与业务代码分离,统一放在 `src/tests/` 目录。 +- 日志文件建议存放在 `logs/` 目录,按天分文件。 + +## 任务 (Tasks) + +- [ ] 1.0 设计与实现日志记录器 + - [ ] 1.1 选型并集成日志库(如 pino/winston/自研) + - [ ] 1.2 实现分环境日志输出(dev 彩色控制台,prod 文件存储) + - [ ] 1.3 日志内容包含请求id、method、url、状态码、耗时、IP + - [ ] 1.4 日志文件按天分割,支持定时清理 + - [ ] 1.5 日志中间件单元测试 +- [ ] 2.0 设计与实现全局错误处理器 + - [ ] 2.1 支持自定义业务异常类型 + - [ ] 2.2 dev 环境输出详细堆栈,prod 环境输出友好信息 + - [ ] 2.3 错误日志自动记录 + - [ ] 2.4 错误处理中间件单元测试 +- [ ] 3.0 设计与实现统一响应封装 + - [ ] 3.1 封装统一响应结构(code/message/data/traceId/耗时) + - [ ] 3.2 响应封装工具单元测试 +- [ ] 4.0 集成 Redis + - [ ] 4.1 编写 Redis 配置与连接池 + - [ ] 4.2 实现 Redis 健康检查 + - [ ] 4.3 Redis 相关单元测试 +- [ ] 5.0 健康检查接口 + - [ ] 5.1 实现 /health 路由,返回服务与依赖健康状态 +- [ ] 6.0 配置中心优化 + - [ ] 6.1 所有配置集中到 config,支持多环境 +- [ ] 7.0 Swagger 文档完善 + - [ ] 7.1 增加全局响应示例、错误码说明 \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f518ca7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "Node", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + + // Paths + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "outDir": "dist", + "rootDir": "src", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..ec338fe --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.test.ts", "src/**/*.spec.ts", "tests/**/*.ts"], + "compilerOptions": { + "noImplicitAny": false, + "strict": false, + "skipLibCheck": true + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..e0fba81 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsconfigPaths()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + test: { + globals: true, + environment: 'node', + }, +});