新增内容: - pnpm generate 代码生成命令 - 前端权限控制(PermissionGuard 组件、usePermissionStore) - 后端权限控制(@RequirePermission 装饰器、PermissionModule) - 代码生成器生成内容说明 - DataTable 通用组件说明 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
344 lines
11 KiB
Markdown
344 lines
11 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Project Overview
|
||
|
||
Seclusion 是一个基于 Next.js + NestJS 的 Monorepo 项目模板,使用 pnpm workspace + Turborepo 管理。
|
||
|
||
### 环境要求
|
||
|
||
- Node.js >= 20.0.0
|
||
- pnpm >= 9.0.0
|
||
|
||
## Commands
|
||
|
||
```bash
|
||
# 启动数据库 (PostgreSQL + Redis)
|
||
docker compose -f deploy/docker-compose.yml up -d
|
||
|
||
# 开发(同时启动前后端)
|
||
pnpm dev
|
||
|
||
# 构建
|
||
pnpm build
|
||
|
||
# 代码检查
|
||
pnpm lint
|
||
pnpm format # 格式化
|
||
pnpm format:check # 检查格式
|
||
|
||
# 测试
|
||
pnpm test
|
||
cd apps/api && pnpm test:watch # 单个测试文件监听
|
||
cd apps/api && pnpm test:cov # 测试覆盖率报告
|
||
|
||
# 数据库
|
||
pnpm db:generate # 生成 Prisma Client
|
||
pnpm db:push # 推送 schema 到数据库
|
||
pnpm db:migrate # 运行迁移
|
||
cd apps/api && pnpm db:studio # 打开 Prisma Studio
|
||
|
||
# 代码生成
|
||
pnpm generate # CRUD 模块生成器(交互式,生成后端+前端+类型+种子脚本)
|
||
```
|
||
|
||
## Architecture
|
||
|
||
### Monorepo 结构
|
||
|
||
- **apps/web** - Next.js 16 前端 (端口 3000),使用 React 19 + Tailwind CSS v4
|
||
- **apps/api** - NestJS 10 后端 (端口 4000,API 文档: /api/docs)
|
||
- **packages/shared** - 共享类型定义、工具函数和加密模块
|
||
- **packages/eslint-config** - 共享 ESLint 9 flat config 配置
|
||
- **packages/typescript-config** - 共享 TypeScript 配置
|
||
|
||
### 前端架构 (apps/web)
|
||
|
||
**状态管理分类**
|
||
|
||
| 状态类型 | 管理方式 | 示例 |
|
||
|----------|----------|------|
|
||
| 服务端状态 | TanStack Query | 用户列表、详情数据 |
|
||
| 客户端全局状态 | Zustand | 认证信息、主题设置 |
|
||
| 组件本地状态 | useState | 表单输入、弹窗开关 |
|
||
| URL 状态 | Next.js Router | 分页参数、筛选条件 |
|
||
|
||
**前端权限控制**:使用 `<PermissionGuard>` 组件包裹需要权限的 UI 元素:
|
||
```tsx
|
||
<PermissionGuard permission="user:create">
|
||
<CreateButton />
|
||
</PermissionGuard>
|
||
|
||
// 多个权限(OR 关系)
|
||
<PermissionGuard permission={['user:update', 'user:delete']}>
|
||
<ActionMenu />
|
||
</PermissionGuard>
|
||
|
||
// 多个权限(AND 关系)
|
||
<PermissionGuard permission={['user:update', 'user:delete']} mode="all">
|
||
<AdminPanel />
|
||
</PermissionGuard>
|
||
```
|
||
|
||
权限数据通过 `usePermissionStore` 管理,登录后自动缓存,退出时清除。
|
||
|
||
**数据请求分层**
|
||
|
||
```
|
||
Component → Hook (TanStack Query) → Service → http
|
||
```
|
||
|
||
- **Service 层** (`services/*.service.ts`): 封装 API 调用
|
||
- **Hook 层** (`hooks/use*.ts`): 封装 TanStack Query,提供 queryKey 管理
|
||
- **http** (`lib/http.ts`): Axios HTTP 客户端封装,含加密处理、Token 自动刷新
|
||
|
||
### 后端模块 (apps/api)
|
||
|
||
NestJS 采用模块化架构:
|
||
|
||
- **PrismaModule** - 全局数据库服务,内置软删除扩展
|
||
- **CryptoModule** - 全局加密服务,AES-256-GCM 请求/响应加密
|
||
- **AuthModule** - JWT 认证(注册、登录、token 验证)
|
||
- **UserModule** - 用户 CRUD,继承 CrudService 基类
|
||
- **CaptchaModule** - 验证码服务,支持多场景验证
|
||
- **PermissionModule** - 权限管理(角色、权限、菜单)
|
||
|
||
认证流程:使用 `@Public()` 装饰器标记公开接口,其他接口需要 JWT Bearer Token。使用 `@CurrentUser()` 装饰器获取当前登录用户信息(类型为 `AuthUser`)。
|
||
|
||
**权限控制**:使用 `@RequirePermission()` 装饰器控制接口权限,配合 `PermissionGuard` 使用:
|
||
```typescript
|
||
@UseGuards(JwtAuthGuard, PermissionGuard)
|
||
@RequirePermission('user:create')
|
||
@Post()
|
||
create(@Body() dto: CreateUserDto) { ... }
|
||
```
|
||
|
||
权限码格式:`{resource}:{action}`(如 `user:create`、`user:read`、`user:update`、`user:delete`)
|
||
|
||
**后端导入规范:禁止使用 Barrel Imports**
|
||
|
||
后端代码禁止使用 `index.ts` 统一导出(barrel exports),必须直接从具体文件导入:
|
||
|
||
```typescript
|
||
// ❌ 错误 - 使用 barrel import
|
||
import { RequirePermission } from '@/permission';
|
||
import { PrismaService } from '@/prisma';
|
||
|
||
// ✅ 正确 - 直接导入具体文件
|
||
import { RequirePermission } from '@/permission/decorators/require-permission.decorator';
|
||
import { PrismaService } from '@/prisma/prisma.service';
|
||
|
||
// ✅ 正确 - 模块内部使用相对路径
|
||
import { RequirePermission } from '../decorators/require-permission.decorator';
|
||
```
|
||
|
||
原因:
|
||
- 避免循环依赖
|
||
- 更清晰的依赖关系
|
||
- 更好的 IDE 跳转体验
|
||
|
||
### 软删除机制
|
||
|
||
PrismaService 使用 `$extends` 实现底层自动软删除,自动检测 schema 中有 `deletedAt` 字段的模型:
|
||
|
||
- **查询自动过滤**: `findMany`、`findFirst`、`findUnique`、`findFirstOrThrow`、`findUniqueOrThrow`、`count`、`aggregate`、`groupBy` 自动添加 `deletedAt: null` 条件
|
||
- **更新保护**: `update`、`updateMany` 自动过滤已删除记录,防止误更新
|
||
- **删除自动转换**: `delete`、`deleteMany` 自动转换为设置 `deletedAt` 时间戳
|
||
- **绕过过滤**: 显式指定 `deletedAt` 条件可查询已删除数据,如 `where: { deletedAt: { not: null } }`
|
||
|
||
启用软删除只需在 `schema.prisma` 中为模型添加 `deletedAt DateTime?` 字段,运行 `pnpm db:generate` 后自动生效。
|
||
|
||
### 通信加密
|
||
|
||
基于 AES-256-GCM 的请求/响应 Body 加密,通过 `ENABLE_ENCRYPTION` 环境变量控制:
|
||
|
||
- **后端**: `CryptoModule` 提供 `CryptoService` 和 `EncryptionInterceptor`
|
||
- **前端**: `apps/web/src/lib/crypto.ts` 封装加密客户端
|
||
- **共享**: `packages/shared/src/crypto/` 提供跨平台加密实现
|
||
- **跳过加密**: 使用 `@SkipEncryption()` 装饰器标记不需要加密的接口
|
||
|
||
### CRUD 基类
|
||
|
||
`apps/api/src/common/crud/` 提供通用 CRUD 服务和 DTO:
|
||
|
||
- `CrudService<T>` - 泛型基类,提供分页查询、软删除、恢复等方法
|
||
- `PaginationQueryDto` - 分页查询参数 DTO
|
||
- `createPaginatedResponseDto(ItemDto)` - 分页响应 DTO 工厂函数
|
||
|
||
分页响应 DTO 使用示例:
|
||
```typescript
|
||
import { createPaginatedResponseDto } from '@/common/crud';
|
||
|
||
export class UserResponseDto { ... }
|
||
export class PaginatedUserResponseDto extends createPaginatedResponseDto(UserResponseDto) {}
|
||
```
|
||
|
||
### Swagger 文档规范
|
||
|
||
所有 API 接口必须完整定义 Swagger 文档:
|
||
|
||
**Controller 装饰器要求:**
|
||
- `@ApiTags()` - 接口分组标签
|
||
- `@ApiOperation()` - 接口描述
|
||
- `@ApiOkResponse()` / `@ApiCreatedResponse()` - 成功响应类型
|
||
- `@ApiBearerAuth()` - 需要认证的接口(非 `@Public()` 接口)
|
||
|
||
**DTO 定义要求:**
|
||
- 所有请求和响应都必须定义对应的 DTO class
|
||
- 使用 `@ApiProperty()` / `@ApiPropertyOptional()` 装饰每个字段
|
||
- 必须包含 `example` 和 `description` 属性
|
||
|
||
**示例:**
|
||
```typescript
|
||
// dto/example.dto.ts
|
||
export class ExampleResponseDto implements ExampleResponse {
|
||
@ApiProperty({ example: 'clxxx', description: '记录 ID' })
|
||
id: string;
|
||
}
|
||
|
||
// example.controller.ts
|
||
@ApiTags('示例')
|
||
@ApiBearerAuth()
|
||
@Controller('example')
|
||
export class ExampleController {
|
||
@Get(':id')
|
||
@ApiOperation({ summary: '获取示例详情' })
|
||
@ApiOkResponse({ type: ExampleResponseDto, description: '示例详情' })
|
||
findOne(@Param('id') id: string): Promise<ExampleResponseDto> {
|
||
return this.service.findOne(id);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 共享包使用
|
||
|
||
```typescript
|
||
// 类型导入
|
||
import type { User, AuthUser, UserResponse, TokenPayload } from '@seclusion/shared';
|
||
import type { ApiResponse, PaginatedResponse } from '@seclusion/shared';
|
||
|
||
// 工具函数
|
||
import { formatDate, generateId } from '@seclusion/shared';
|
||
|
||
// 加密模块
|
||
import { createBrowserCrypto, createNodeCrypto } from '@seclusion/shared/crypto';
|
||
|
||
// 常量(使用 const + type 模式)
|
||
import { CaptchaScene } from '@seclusion/shared';
|
||
```
|
||
|
||
**注意**: `packages/shared` 中的工具函数应优先使用 lodash-es 实现。
|
||
|
||
### 前后端共享类型设计规范
|
||
|
||
为避免类型重复定义和前后端不一致,遵循以下设计原则:
|
||
|
||
**类型定义位置**
|
||
|
||
| 类型 | 定义位置 | 说明 |
|
||
|------|----------|------|
|
||
| 枚举/常量 | `packages/shared` | 使用 `const + type` 模式 |
|
||
| 接口类型 | `packages/shared` | 纯类型定义,无装饰器 |
|
||
| 后端 DTO | `apps/api` | 实现共享接口,添加 Swagger/验证装饰器 |
|
||
| 前端特有类型 | `apps/web/src/types/` | 组件 Props、表单状态等 |
|
||
|
||
**枚举定义方式(使用 const + type 替代 enum)**
|
||
```typescript
|
||
// packages/shared/src/types/index.ts
|
||
export const CaptchaScene = {
|
||
LOGIN: 'login',
|
||
REGISTER: 'register',
|
||
} as const;
|
||
export type CaptchaScene = (typeof CaptchaScene)[keyof typeof CaptchaScene];
|
||
```
|
||
|
||
**后端 DTO 实现共享接口**
|
||
```typescript
|
||
// apps/api/src/xxx/dto/example.dto.ts
|
||
import type { ExampleResponse } from '@seclusion/shared';
|
||
|
||
export class ExampleResponseDto implements ExampleResponse {
|
||
@ApiProperty({ description: '示例字段', example: 'value' })
|
||
field: string;
|
||
}
|
||
```
|
||
|
||
**新增共享类型检查清单**
|
||
- [ ] 类型定义在 `packages/shared/src/types/` 中
|
||
- [ ] 枚举使用 `const + type` 模式
|
||
- [ ] 后端 DTO 使用 `implements` 实现共享接口
|
||
- [ ] 前端直接从 `@seclusion/shared` 导入使用
|
||
|
||
## Environment Variables
|
||
|
||
环境变量在各应用目录下独立配置:
|
||
|
||
| 位置 | 文件 | 用途 | 是否提交 |
|
||
|------|------|------|---------|
|
||
| apps/api | `.env.example` | 后端配置模板 | ✅ |
|
||
| apps/api | `.env` | 后端默认配置 | ✅ |
|
||
| apps/api | `.env.local` | 后端本地覆盖(敏感信息) | ❌ |
|
||
| apps/web | `.env` | 前端默认配置 | ✅ |
|
||
| apps/web | `.env.local` | 前端本地覆盖 | ❌ |
|
||
|
||
### 加密相关配置
|
||
|
||
```bash
|
||
# apps/api/.env
|
||
ENABLE_ENCRYPTION=false
|
||
ENCRYPTION_KEY=<32字节Base64密钥>
|
||
|
||
# apps/web/.env
|
||
NEXT_PUBLIC_ENABLE_ENCRYPTION=false
|
||
NEXT_PUBLIC_ENCRYPTION_KEY=<与后端相同的密钥>
|
||
```
|
||
|
||
生成密钥: `openssl rand -base64 32`
|
||
|
||
## Documentation
|
||
|
||
项目文档统一存放在 `docs/` 目录下:
|
||
|
||
```
|
||
docs/
|
||
├── web/ # 前端相关文档
|
||
│ └── DESIGN.md # Web 前端设计文档
|
||
├── api/ # 后端相关文档
|
||
└── shared/ # 共享模块文档
|
||
```
|
||
|
||
| 文档 | 说明 |
|
||
|------|------|
|
||
| `docs/web/DESIGN.md` | Web 前端整体设计(技术栈、目录结构、状态管理、实施计划) |
|
||
|
||
## Key Files
|
||
|
||
- `deploy/docker-compose.yml` - PostgreSQL + Redis 容器配置
|
||
- `apps/api/prisma/schema.prisma` - 数据库模型定义(PostgreSQL,ID 使用 cuid2)
|
||
- `apps/api/src/prisma/prisma.service.ts` - Prisma 服务,含软删除扩展
|
||
- `apps/api/src/common/crud/` - CRUD 基类和分页 DTO
|
||
- `apps/api/src/common/crypto/` - 后端加密模块
|
||
- `packages/shared/src/types/` - 共享类型定义
|
||
- `packages/shared/src/crypto/` - 跨平台加密实现
|
||
- `apps/web/src/types/` - 前端特有类型定义
|
||
|
||
### 代码生成器
|
||
|
||
`pnpm generate` 启动交互式代码生成器,可一键生成完整 CRUD 模块:
|
||
|
||
- **后端**: Controller、Service、DTO、Module
|
||
- **前端**: Service、Hooks、Table、CreateDialog、EditDialog、Page
|
||
- **共享类型**: TypeScript 接口定义
|
||
- **Prisma Model**: 数据库模型
|
||
- **种子脚本**: 菜单和权限初始化数据
|
||
|
||
生成的代码自动集成权限控制(`@RequirePermission` + `PermissionGuard`)。详见 `plop/README.md`。
|
||
|
||
### 通用组件
|
||
|
||
**DataTable** (`components/shared/DataTable/`): 封装 TanStack Table 的通用数据表格组件
|
||
- 支持服务端分页、排序、搜索
|
||
- 内置加载状态、错误状态、空状态渲染
|
||
- 使用示例见 `components/*/XxxTable.tsx`
|