chore: 项目初始化相关配置与文档归档优化
- Prettier 配置迁移为 .prettierrc.cjs,解决 ESM/CJS 兼容问题 - 优化 package.json,补全元信息、整理依赖、完善 Bun 热更新脚本 - 归档项目初始化 PRD 与任务清单到 tasks/archive,并加日期前缀 - 同步代码风格与格式化配置,提升团队协作一致性 归档文件:tasks/archive/20240610-prd-项目初始化.md, tasks/archive/20240610-tasks-prd-项目初始化.md
This commit is contained in:
commit
5b0b37ef78
112
.cursor/rules/cursor-comment-rules.mdc
Normal file
112
.cursor/rules/cursor-comment-rules.mdc
Normal file
@ -0,0 +1,112 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
## Cursor 代码注释规范(Code Comment Rules)
|
||||
|
||||
### 1. 文件头部注释
|
||||
每个源文件开头应包含如下信息:
|
||||
```javascript
|
||||
/**
|
||||
* @file 文件简要说明
|
||||
* @author 创建者姓名(如:张三 <zhangsan@example.com>)
|
||||
* @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`),便于新成员快速上手。
|
||||
- 注释中严禁出现密码、密钥、用户隐私等敏感信息。
|
||||
- 重要模块、核心业务建议将注释内容同步到项目文档,便于知识传承和查阅。
|
||||
|
||||
---
|
||||
|
||||
**请所有开发者严格遵守以上注释规范,提升代码可读性与可维护性。**
|
110
.cursor/rules/elysia-api-rules.mdc
Normal file
110
.cursor/rules/elysia-api-rules.mdc
Normal file
@ -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": "张三" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**请所有开发者严格遵守以上规范,确保接口文档完整、参数校验严格、错误提示友好(中文),提升用户体验与协作效率。**
|
141
.cursor/rules/elysia-backend-rules.mdc
Normal file
141
.cursor/rules/elysia-backend-rules.mdc
Normal file
@ -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 后端服务的健壮性、安全性与可维护性。**
|
108
.cursor/rules/elysia-test-rules.mdc
Normal file
108
.cursor/rules/elysia-test-rules.mdc
Normal file
@ -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 项目的高质量交付。**
|
55
.cursor/rules/git-commit-rules.mdc
Normal file
55
.cursor/rules/git-commit-rules.mdc
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
## Git 提交规范(Commit Message Rules)
|
||||
|
||||
1. **提交格式**
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
```
|
||||
- `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: 升级依赖包
|
||||
```
|
54
.cursor/rules/global-rules/create-prd.mdc
Normal file
54
.cursor/rules/global-rules/create-prd.mdc
Normal file
@ -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 父任务标题 (如果纯粹是结构性的或配置性的,则可能不需要子任务)
|
61
.cursor/rules/global-rules/generate-task.mdc
Normal file
61
.cursor/rules/global-rules/generate-task.mdc
Normal file
@ -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。
|
10
.cursor/rules/global-rules/global.mdc
Normal file
10
.cursor/rules/global-rules/global.mdc
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# 特殊要求
|
||||
|
||||
1. 删除文件时一定要请求确认
|
||||
|
||||
2. 不允许执行脚本,告诉我命令即可
|
66
.cursor/rules/global-rules/process-list-task.mdc
Normal file
66
.cursor/rules/global-rules/process-list-task.mdc
Normal file
@ -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也归档
|
111
.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc
Normal file
111
.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc
Normal file
@ -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 <file>` instead of `node <file>` or `ts-node <file>`
|
||||
- Use `bun test` instead of `jest` or `vitest`
|
||||
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
||||
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
||||
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
||||
- Bun automatically loads .env, so don't use dotenv.
|
||||
|
||||
## APIs
|
||||
|
||||
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
||||
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
||||
- `Bun.redis` for Redis. Don't use `ioredis`.
|
||||
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
||||
- `WebSocket` is built-in. Don't use `ws`.
|
||||
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
||||
- Bun.$`ls` instead of execa.
|
||||
|
||||
## Testing
|
||||
|
||||
Use `bun test` to run tests.
|
||||
|
||||
```ts#index.test.ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("hello world", () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
||||
|
||||
Server:
|
||||
|
||||
```ts#index.ts
|
||||
import index from "./index.html"
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/": index,
|
||||
"/api/users/:id": {
|
||||
GET: (req) => {
|
||||
return new Response(JSON.stringify({ id: req.params.id }));
|
||||
},
|
||||
},
|
||||
},
|
||||
// optional websocket support
|
||||
websocket: {
|
||||
open: (ws) => {
|
||||
ws.send("Hello, world!");
|
||||
},
|
||||
message: (ws, message) => {
|
||||
ws.send(message);
|
||||
},
|
||||
close: (ws) => {
|
||||
// handle close
|
||||
}
|
||||
},
|
||||
development: {
|
||||
hmr: true,
|
||||
console: true,
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
||||
|
||||
```html#index.html
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
<script type="module" src="./frontend.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
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 <h1>Hello, world!</h1>;
|
||||
}
|
||||
|
||||
root.render(<Frontend />);
|
||||
```
|
||||
|
||||
Then, run index.ts
|
||||
|
||||
```sh
|
||||
bun --hot ./index.ts
|
||||
```
|
||||
|
||||
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
bun.lockb
|
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
bun.lockb
|
25
.prettierrc.cjs
Normal file
25
.prettierrc.cjs
Normal file
@ -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', // 格式化嵌入代码
|
||||
};
|
99
.test-rules.md
Normal file
99
.test-rules.md
Normal file
@ -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<typeof schema>` 推导类型,或在测试用例中用 `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('用户名或密码错误');
|
||||
});
|
||||
});
|
||||
```
|
17
README.md
Normal file
17
README.md
Normal file
@ -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.
|
||||
|
||||
# 项目初始化
|
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[paths]
|
||||
"@/*" = "./src/*"
|
70
eslint.config.js
Normal file
70
eslint.config.js
Normal file
@ -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'],
|
||||
},
|
||||
];
|
41
package.json
Normal file
41
package.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "cursor-init",
|
||||
"version": "0.1.0",
|
||||
"description": "Bun + Elysia 后端API项目,支持MySQL、JWT、Swagger、Vitest等",
|
||||
"author": "hotok <x71291@outlook.com>",
|
||||
"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 ."
|
||||
}
|
||||
}
|
96
src/app.test.ts
Normal file
96
src/app.test.ts
Normal file
@ -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');
|
||||
});
|
||||
});
|
40
src/app.ts
Normal file
40
src/app.ts
Normal file
@ -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() 可以以在路由中调用
|
29
src/config/db.config.ts
Normal file
29
src/config/db.config.ts
Normal file
@ -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',
|
||||
};
|
20
src/config/jwt.config.ts
Normal file
20
src/config/jwt.config.ts
Normal file
@ -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有效期
|
||||
};
|
34
src/controllers/try/auth.controller.ts
Normal file
34
src/controllers/try/auth.controller.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
33
src/controllers/try/protected.controller.ts
Normal file
33
src/controllers/try/protected.controller.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
49
src/plugins/jwt-auth.ts
Normal file
49
src/plugins/jwt-auth.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
||||
});
|
17
src/plugins/jwt.ts
Normal file
17
src/plugins/jwt.ts
Normal file
@ -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,
|
||||
});
|
35
src/plugins/swagger.ts
Normal file
35
src/plugins/swagger.ts
Normal file
@ -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 <token>',
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ bearerAuth: [] }],
|
||||
},
|
||||
});
|
13
src/server.ts
Normal file
13
src/server.ts
Normal file
@ -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');
|
33
src/services/try/auth.service.ts
Normal file
33
src/services/try/auth.service.ts
Normal file
@ -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 },
|
||||
};
|
||||
};
|
25
src/services/try/protected.service.ts
Normal file
25
src/services/try/protected.service.ts
Normal file
@ -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 ?? '' },
|
||||
};
|
||||
};
|
28
src/utils/mysql.test.ts
Normal file
28
src/utils/mysql.test.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
22
src/utils/mysql.ts
Normal file
22
src/utils/mysql.ts
Normal file
@ -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,
|
||||
});
|
24
src/validators/try/auth.response.ts
Normal file
24
src/validators/try/auth.response.ts
Normal file
@ -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(),
|
||||
});
|
23
src/validators/try/auth.validator.ts
Normal file
23
src/validators/try/auth.validator.ts
Normal file
@ -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<typeof loginBodySchema>;
|
42
src/validators/try/protected.response.ts
Normal file
42
src/validators/try/protected.response.ts
Normal file
@ -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<typeof protectedResponse200Schema>;
|
||||
|
||||
/**
|
||||
* 受保护接口 401 响应类型
|
||||
*/
|
||||
export type ProtectedResponse401 = Static<typeof protectedResponse401Schema>;
|
107
tasks/archive/20240610-prd-项目初始化.md
Normal file
107
tasks/archive/20240610-prd-项目初始化.md
Normal file
@ -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 工具链的具体实现细节(如是否接入特定平台)。
|
||||
|
||||
</rewritten_file>
|
64
tasks/archive/20240610-tasks-prd-项目初始化.md
Normal file
64
tasks/archive/20240610-tasks-prd-项目初始化.md
Normal file
@ -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示例配置
|
105
tasks/prd-架构优化.md
Normal file
105
tasks/prd-架构优化.md
Normal file
@ -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 具体使用场景细化
|
44
tasks/tasks-prd-架构优化.md
Normal file
44
tasks/tasks-prd-架构优化.md
Normal file
@ -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 增加全局响应示例、错误码说明
|
41
tsconfig.json
Normal file
41
tsconfig.json
Normal file
@ -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"]
|
||||
}
|
9
tsconfig.test.json
Normal file
9
tsconfig.test.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/**/*.test.ts", "src/**/*.spec.ts", "tests/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": false,
|
||||
"strict": false,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
16
vitest.config.ts
Normal file
16
vitest.config.ts
Normal file
@ -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',
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user