# CLAUDE.md - 开发规范

> **重要**: 每次开始开发任务前，请阅读本文档并严格遵守。

---

## 常用命令

### 服务端口配置

| 服务 | 默认端口 | 测试验证端口 | 说明 |
|------|---------|------------|------|
| 后端 API | **8480** | **8481** | Spring Boot 服务 |
| 前端 Dev Server | 5173 | 5174 | Vite 开发服务器 |
| 数据库 MySQL | 3306 | 3306 | 开发环境数据库 |
| Redis | 6379 | 6379 | 缓存服务 |

**重要**:
- 日常开发使用默认端口 **8480** 和 **5173**
- **每次测试验证时启动通过变量启动后端 8481 和前端 5174 (重要)，不要占用 8480 和 5173 端口**

### 测试验证环境启动方式

```bash
# 启动测试验证环境（后端 8481 + 前端 5174）
# 方式一：使用环境变量
export SERVER_PORT=8481
export VITE_APP_PORT=5174
./start-all.sh

# 方式二：分别启动
# 后端（端口 8481）
export SERVER_PORT=8481
cd lesingle-edu-reading-platform-backend && mvn spring-boot:run

# 前端（端口 5174，新终端）
export PORT=5174
cd lesingle-edu-reading-platform-frontend && npm run dev
```

### 启动服务

```bash
# 启动所有服务（推荐）
./start-all.sh

# 仅启动 Java 后端
./start-java-backend.sh

# 停止所有服务
./stop-all.sh
```

### 前端命令 (lesingle-edu-reading-platform-frontend/)

```bash
npm run dev              # 开发服务器
npm run build            # 生产构建
npm run lint             # 代码检查
npm run test:e2e         # 端到端测试 (Playwright)
npm run api:update       # 从 OpenAPI 生成 TypeScript 类型
```

### 后端命令 (lesingle-edu-reading-platform-backend/)

```bash
# 运行后端（使用 JDK 17）
mvn spring-boot:run

# 构建 JAR（使用 JDK 17）
mvn clean package -DskipTests

# 运行测试
mvn test
```

### JDK 版本要求

**重要**: 本项目必须使用 **JDK 17** 进行编译和运行。

如果系统环境变量配置的是 JDK 1.8，请在编译前设置 `JAVA_HOME`：

```bash
# Windows (Git Bash) - 根据实际安装路径选择
export JAVA_HOME="/f/Java/jdk-17"
mvn clean compile -DskipTests

# 或者在启动时指定
mvn spring-boot:run -Djava.home="/f/Java/jdk-17"
```

**常见 JDK 17 安装路径**：
- `F:\Java\jdk-17`
- `C:\Program Files\Java\jdk-17`
- `C:\Program Files\Eclipse Adoptium\jdk-17`

**检查当前 Java 版本**：
```bash
java -version
javac -version
```

---

## 多环境配置规范

### 配置文件目录结构

```
lesingle-edu-reading-platform-backend/src/main/resources/
├── application.yml              # 主配置文件（共用配置）
├── application-dev.yml          # 开发环境配置
├── application-test.yml         # 测试环境配置
├── application-prod.yml         # 生产环境配置
├── db/migration/                # Flyway 迁移脚本
├── logback-spring.xml           # 日志配置
└── mapper/                      # MyBatis XML
```

### 环境配置说明

| 配置项       | 开发环境 (dev) | 测试环境 (test) | 生产环境 (prod) |
| ------------ | -------------- | --------------- | --------------- |
| 数据库       | 本地 MySQL     | 测试服务器      | 生产服务器      |
| SQL 日志     | 开启           | 开启            | 关闭            |
| Swagger      | 开启           | 开启            | 关闭            |
| Flyway Clean | 允许           | 禁止            | 禁止            |
| JWT 密钥     | 默认值         | 默认值          | 必须环境变量    |
| Redis 连接池 | 默认           | 默认            | 优化配置        |
| 日志级别     | DEBUG          | INFO            | WARN            |

### 环境切换方式

#### 方式一：环境变量（推荐）

```bash
# Linux/Mac
export SPRING_PROFILES_ACTIVE=prod
java -jar lesingle-edu-reading-platform-backend.jar

# Windows (Git Bash)
export SPRING_PROFILES_ACTIVE=prod
java -jar lesingle-edu-reading-platform-backend.jar
```

#### 方式二：命令行参数

```bash
java -jar lesingle-edu-reading-platform-backend.jar --spring.profiles.active=prod
```

#### 方式三：Maven 启动

```bash
# 开发环境
mvn spring-boot:run

# 测试环境
mvn spring-boot:run -Dspring-boot.run.profiles=test

# 生产环境
mvn spring-boot:run -Dspring-boot.run.profiles=prod
```

### 环境变量列表

| 变量名 | 说明 | 开发环境默认值 | 生产环境要求 |
|--------|------|---------------|-------------|
| `SPRING_PROFILES_ACTIVE` | 激活的环境 | `dev` | 必须设置 |
| `SERVER_PORT` | 服务器端口 | `8080` | 可选 |
| `DB_HOST` | 数据库主机 | `localhost` | 必须设置 |
| `DB_PORT` | 数据库端口 | `3306` | 可选 |
| `DB_USERNAME` | 数据库用户名 | `root` | 必须设置 |
| `DB_PASSWORD` | 数据库密码 | `root` | 必须设置 |
| `REDIS_HOST` | Redis 主机 | `localhost` | 必须设置 |
| `REDIS_PORT` | Redis 端口 | `6379` | 可选 |
| `REDIS_PASSWORD` | Redis 密码 | 空 | 建议设置 |
| `JWT_SECRET` | JWT 密钥 | 默认值 | 必须设置 |
| `JWT_EXPIRATION` | Token 过期时间 | `86400000` | 可选 |

---

## 技术栈

### 后端技术栈（必须遵守）

⚠️ **严禁使用 Node.js/NestJS 进行后端开发**

| 组件 | 技术选型 | 版本 | 说明 |
|------|---------|------|------|
| 框架 | **Spring Boot** | 3.2+ | 基于 Java 17 |
| 持久层 | **MyBatis-Plus** | 3.5+ | 简化 CRUD |
| 数据库连接池 | **Alibaba Druid** | 1.2+ | 数据库连接池 + 监控 |
| 安全 | **Spring Security + JWT** | - | 无状态认证 + RBAC |
| API 文档 | **Knife4j (SpringDoc)** | 4.x | OpenAPI 3.0 |
| 数据库 | **MySQL** | 8.0+ | 关系型数据库 |
| 迁移 | **Flyway** | - | 版本化数据库变更 |
| 校验 | **Hibernate Validator** | - | JSR-303 参数校验 |
| 缓存 | **Redis + Spring Data Redis** | - | 缓存、会话存储 |
| 日志 | **Logback** | - | 结构化日志 |
| JSON | **FastJSON** | 2.x | JSON 序列化 |
| 工具类 | **Hutool** | 5.x | 常用工具集合 |
| 文件存储 | 阿里云 OSS | - | 对象存储 |

### 前端技术栈

| 组件 | 技术选型 | 版本 | 说明 |
|------|---------|------|------|
| 框架 | **Vue 3** | 3.4+ | Composition API |
| 语言 | **TypeScript** | 5.x | 严格模式 |
| UI 库 | **Ant Design Vue** | 4.x | 企业级组件库 |
| 构建 | **Vite** | 5.x | 快速开发服务器 |
| 状态 | **Pinia** | 2.x | 轻量状态管理 |
| 请求 | **Axios** | 1.x | HTTP 客户端 |
| API 生成 | **Orval** | 7.x | OpenAPI → TypeScript |
| 路由 | **Vue Router** | 4.x | SPA 路由 |

---

## 核心原则

1. **后端只写 Java** - ⚠️ **所有后端开发必须基于 `lesingle-edu-reading-platform-backend/` (Spring Boot)，严禁使用 Node.js/NestJS**
2. **OpenAPI 规范驱动** - 前后端通过接口规范对齐，零沟通成本
3. **类型安全优先** - TypeScript 强制类型校验，早发现早修复
4. **约定大于配置** - 统一代码风格和目录结构，降低认知负担
5. **自动化优先** - 能自动化的绝不手动（代码生成、部署、测试）
6. **三层架构分离** - Controller、Service、Mapper 职责清晰

---

## 项目结构

```
kindergarten_java/
├── docs/                          # 📁 项目文档
│   ├── README.md                  # 项目说明
│   ├── CHANGELOG.md               # 变更日志
│   ├── dev-logs/                  # 开发日志
│   ├── test-logs/                 # 测试记录
│   │   ├── admin/                 # 超管端测试
│   │   ├── school/                # 学校端测试
│   │   ├── teacher/               # 教师端测试
│   │   └── parent/                # 家长端测试
│   └── design/                    # 设计文档
├── lesingle-edu-reading-platform-frontend/     # 前端项目 (Vue 3)
├── lesingle-edu-reading-platform-backend/      # 后端项目 (Spring Boot) ← 唯一后端
├── reading-platform-backend/                   # ⚠️ 已弃用 (NestJS，不再维护)
├── start-all.sh                   # 统一启动
└── stop-all.sh                    # 统一停止
```

### 后端目录结构（Spring Boot）

```
lesingle-edu-reading-platform-backend/
├── src/main/java/com/lesingle/edu/
│   ├── ReadingPlatformApplication.java    # 启动类
│   ├── common/                            # 公共模块
│   │   ├── config/                        # 配置类
│   │   │   ├── MybatisPlusConfig.java     # MP 配置
│   │   │   ├── RedisConfig.java           # Redis 配置
│   │   │   ├── SecurityConfig.java        # 安全配置
│   │   │   └── OpenApiConfig.java         # API 文档配置
│   │   ├── security/                      # 安全相关
│   │   │   ├── JwtAuthenticationFilter.java
│   │   │   ├── JwtTokenProvider.java
│   │   │   └── SecurityUtils.java
│   │   ├── response/                      # 统一响应
│   │   │   ├── Result.java
│   │   │   └── PageResult.java
│   │   ├── exception/                     # 异常处理
│   │   │   ├── BusinessException.java
│   │   │   └── GlobalExceptionHandler.java
│   │   ├── annotation/                    # 自定义注解
│   │   │   └── RequireRole.java
│   │   ├── aspect/                        # AOP 切面
│   │   │   └── RoleAspect.java
│   │   ├── enums/                         # 枚举类
│   │   └── util/                          # 工具类
│   ├── controller/                        # 控制器层
│   │   ├── AuthController.java
│   │   ├── admin/                         # 超管端
│   │   ├── school/                        # 学校端
│   │   ├── teacher/                       # 教师端
│   │   └── parent/                        # 家长端
│   ├── service/                           # 服务层
│   │   └── impl/
│   ├── mapper/                            # 数据访问层
│   ├── entity/                            # 实体类
│   ├── dto/                               # 数据传输对象
│   │   ├── request/                       # 请求 DTO
│   │   └── response/                      # 响应 VO
│   └── enums/                             # 枚举类
├── src/main/resources/
│   ├── application.yml                    # 主配置文件
│   ├── application-dev.yml                # 开发环境
│   ├── application-prod.yml               # 生产环境
├── pom.xml
└── Dockerfile
```

### 前端目录结构（Vue 3）

```
lesingle-edu-reading-platform-frontend/
├── src/
│   ├── main.ts                            # 入口文件
│   ├── App.vue                            # 根组件
│   ├── api/                               # API 接口
│   │   ├── generated/                     # Orval 自动生成（禁止手改）
│   │   ├── index.ts                       # 统一入口，导出 http 方法
│   │   └── *.ts                           # 业务适配层（admin.ts, school.ts, teacher.ts 等）
│   ├── assets/                            # 静态资源
│   ├── components/                        # 公共组件
│   ├── composables/                       # 组合式函数
│   ├── layouts/                           # 布局组件
│   ├── router/                            # 路由配置
│   ├── stores/                            # Pinia 状态管理
│   ├── types/                             # 类型定义
│   ├── utils/                             # 工具函数
│   ├── views/                             # 页面组件
│   │   ├── login/                         # 登录页
│   │   ├── admin/                         # 超管端
│   │   ├── school/                        # 学校端
│   │   ├── teacher/                       # 教师端
│   │   └── parent/                        # 家长端
│   └── constants/                         # 常量定义
├── orval.config.ts                       # API 生成配置
├── index.html
├── package.json
└── vite.config.ts
```

---

## 三层架构规范

### 核心原则

**Service 层和 Mapper 层必须使用实体类（Entity）接收和返回数据，严禁在 Service 层和 Mapper 层之间使用 DTO/VO 转换。**

| 层级 | 职责 | 数据类型 |
|------|------|----------|
| **Controller** | 接收请求、参数校验、返回响应 | DTO ↔ Entity/VO |
| **Service** | 业务逻辑、事务控制 | Entity |
| **Mapper** | 数据库操作 | Entity |

### Controller 层规范

**API 路径约定**：
所有 API 路径统一使用 `/api/v1/` 前缀，实现 API 版本控制。

- 超管端：`/api/v1/admin/*`
- 学校端：`/api/v1/school/*`
- 教师端：`/api/v1/teacher/*`
- 家长端：`/api/v1/parent/*`
- 认证：`/api/v1/auth/*`
- 文件上传：`/api/v1/files/*`

**分页响应结构约定**：
所有分页接口统一使用 `PageResult<T>` 返回结构，字段如下：

```typescript
// 前端期望的分页响应结构
{
  list: T[];       // 数据列表
  total: number;   // 总记录数
  pageNum: number; // 当前页码
  pageSize: number; // 每页大小
  pages: number;   // 总页数
}
```

```java
@RestController
@RequestMapping("/api/v1/admin/xxx")  // 超管端使用 /api/v1/admin/
@Tag(name = "XXX 管理", description = "XXX 相关接口")
@RequiredArgsConstructor
public class XxxController {

    private final XxxService xxxService;

    @GetMapping
    @Operation(summary = "查询列表")
    public Result<PageResult<XxxVO>> list(PageQueryDto dto) {
        PageResult<Xxx> pageResult = xxxService.page(dto);
        return Result.success(convertToVO(pageResult));
    }
}
```

### Service 层规范

```java
public interface XxxService extends IService<Xxx> {
    PageResult<Xxx> page(PageQueryDto dto);
}

@Service
@RequiredArgsConstructor
public class XxxServiceImpl extends ServiceImpl<XxxMapper, Xxx>
    implements XxxService {

    @Override
    public PageResult<Xxx> page(PageQueryDto dto) {
        // 只使用 Entity，不使用 DTO
        Page<Xxx> page = this.lambdaQuery()
            .eq(Xxx::getStatus, 1)
            .page(new Page<>(dto.getPage(), dto.getPageSize()));
        return PageUtils.of(page);
    }
}
```

### Mapper 层规范

```java
@Mapper
public interface XxxMapper extends BaseMapper<Xxx> {
    // 继承 BaseMapper，使用 MyBatis-Plus 内置方法
}
```

---

## 前端开发规范

### API 开发规范

1. **生成代码只读** - 不得在 `src/api/generated/` 内做任何手工修改
2. **以生成类型为准** - 参数/返回类型优先使用生成的类型
3. **统一调用入口** - 通过 `src/api/index.ts` 导出的 `http` 方法

### 推荐调用方式

**方式一：使用 http 方法（推荐）**

```typescript
import { http } from '@/api';

async function loadTenant(id: number) {
  return http.get<TenantDetail>(`/v1/admin/tenants/${id}`);
}

async function createTenant(data: CreateTenantDto) {
  return http.post<Tenant>('/v1/admin/tenants', data);
}
```

**方式二：使用业务适配层（推荐）**

```typescript
import { getTenant, createTenant } from '@/api/admin';

async function loadTenant(id: number) {
  return getTenant(id);
}

async function createNewTenant(data: CreateTenantDto) {
  return createTenant(data);
}
```

### API 路径规范

| 端 | 后端路径 | 前端路径 |
|----|----------|----------|
| 超管 | `/api/v1/admin/*` | `/v1/admin/*` |
| 学校 | `/api/v1/school/*` | `/v1/school/*` |
| 教师 | `/api/v1/teacher/*` | `/v1/teacher/*` |
| 家长 | `/api/v1/parent/*` | `/v1/parent/*` |
| 认证 | `/api/v1/auth/*` | `/v1/auth/*` |
| 文件 | `/api/v1/files/*` | `/v1/files/*` |

### Vue SFC 约定

- 优先使用 `<script lang="ts" setup>`
- 页面样式使用 `scoped`
- 允许使用 UnoCSS 原子类

### 路由规范

```typescript
import { definePage } from 'vue-router/auto';

definePage({
  alias: ['/xxx', '/yyy'],
});
```

---

## 文档规范

### 开发日志
- **位置**: `/docs/dev-logs/`
- **命名**: `YYYY-MM-DD.md`
- **创建时机**: 每天开始开发时检查并创建

---

## 文件写入最佳实践（JetBrains 环境）

> **重要**: 在 JetBrains IDE 插件中使用 Claude Code 时，按以下优先级选择写入方式：

### 写入方式优先级

| 优先级 | 方式 | 适用场景 | 示例 |
|--------|------|----------|------|
| **1** | Write 工具 | 简单文件、单文件写入 | `Write(file_path="...", content="...")` |
| **2** | Bash heredoc | 复杂文件、批量写入、Write 失败时 | `cat > /path/to/file << 'EOF'` |
| **3** | Python 写入 | 需要复杂逻辑处理时 | `python3 -c "..."` |
| **❌ 避免** | sed/awk | Windows Git Bash 中兼容性差 | — |

### Write 工具使用规范

```bash
# ✅ 正确：使用 Unix 风格路径
Write(file_path="/f/LesingleProject/.../file.txt", content="...")

# ❌ 错误：不要使用 Windows 路径
Write(file_path="F:\\LesingleProject\\...\\file.txt", content="...")
```

### Bash heredoc 方式（推荐备选）

```bash
# 多行文件写入
cat > /f/LesingleProject/.../file.txt << 'EOF'
第一行内容
第二行内容
EOF

# 单行追加
echo "追加内容" >> /f/LesingleProject/.../file.txt
```

### 写入前检查清单

- [ ] 确保目录存在：`mkdir -p /f/.../父目录`
- [ ] 使用 Unix 路径格式：`/f/...` 不是 `F:\...`
- [ ] 检查文件是否被占用（IDE 索引、格式化中）

### 写入失败诊断

```bash
# 检查目录是否存在
ls -la /f/路径/到的/父目录/

# 检查文件属性
ls -la /f/路径/到/文件.txt

# 测试写入权限
echo 'test' > /f/路径/到/文件.txt && echo '成功' || echo '失败'
```

### 测试记录
- **位置**: `/docs/test-logs/{端}/`
- **命名**: `YYYY-MM-DD.md`
- **创建时机**: 每次功能测试时创建

### 变更日志
- **位置**: `/docs/CHANGELOG.md`
- **更新时机**: 完成重要功能或修复时更新

---

## 每日开发流程

1. 读取 `/docs/dev-logs/` 下最新的日志，了解进度
2. 检查当天日志是否存在，不存在则创建
3. 开始开发任务（后端用 Java，前端用 TypeScript）
4. 结束时更新日志和 CHANGELOG

---

## 功能测试流程

1. 启动服务：`./start-all.sh`
2. 在 `/docs/test-logs/{端}/` 下创建测试记录
3. 按功能模块逐一测试
4. 发现问题立即记录并修复

---

## 代码修改验证流程

**重要**: 修改代码后必须执行以下验证步骤，确保没有影响其他模块。

### 修改后验证清单（必须执行）

**1. 检查是否影响了其他模块**
- 确认修改的文件不会被其他模块引用
- 检查是否有共享组件、工具类或配置被修改
- 验证 API 路径是否与预期一致（超管端用 `/api/v1/admin/*`，学校端用 `/api/v1/school/*`）

**2. 确认 API 调用的正确性**
- 前端调用的 API 路径与后端 Controller 的 `@RequestMapping` 一致
- 返回的数据结构与前端期望的类型匹配
- 分页接口使用 `PageResult<T>` 统一结构

**3. 验证数据流向**
- 数据库表名、字段名与实体类映射正确
- 三层架构（Controller → Service → Mapper）数据传递使用正确的类型
- DTO/VO 仅在 Controller 层使用，Service 和 Mapper 层使用 Entity

### 问题定位步骤

当发现功能异常时，按以下步骤定位：

1. **打开浏览器开发者工具（F12）**
   - 查看 Console 标签页：是否有 JavaScript 错误
   - 查看 Network 标签页：API 请求返回了什么数据
   - 检查请求路径是否正确（应该 `/api/v1/...` 不是 `/api/api/v1/...`）

2. **检查后端日志**
   - 确认后端服务是否启动（端口 8480）
   - 查看 SQL 查询日志，确认查询的表和字段正确
   - 检查是否有异常堆栈信息

3. **验证数据库数据**
   - 确认数据库表中的数据是否符合预期
   - 检查关联关系（外键、中间表）是否正确
   - 验证三层架构的数据层级关系是否混淆

### 常见问题排查

| 问题症状 | 可能原因 | 排查方法 |
|---------|---------|---------|
| API 404 错误 | 前端调用路径与后端不匹配 | 对比前端 API 路径和后端 `@RequestMapping` |
| 数据格式错误 | 返回类型与前端期望不符 | 检查后端返回的 VO 和前端类型定义 |
| 空数据或错误数据 | 查询了错误的表或字段 | 查看后端 SQL 日志和数据库实际数据 |
| 编译错误 | 类型不匹配或导入错误 | 运行 `npm run build` 或 `mvn compile` 检查 |

---

## 测试完成后清理

**重要**: 测试完成后请关闭前后端服务，避免占用端口和资源。

### 关闭服务方法

```bash
# 方法一：使用统一停止脚本（推荐）
./stop-all.sh

# 方法二：手动停止
# 停止前端：在运行前端的终端按 Ctrl+C
# 停止后端：在运行后端的终端按 Ctrl+C

# 方法三：强制终止 Java 进程（Windows）
# 查看占用端口的进程
netstat -ano | findstr :8080
# 终止指定 PID 的进程
taskkill //F //PID <PID>
```

### 清理检查清单

- [ ] 前端开发服务器已关闭（通常是端口 5173）
- [ ] 后端 Java 服务已关闭（通常是端口 8080）
- [ ] 如有占用端口，确认是否需要保留
- [ ] 保存所有未提交的代码更改

---

## 测试账号

| 角色 | 账号 | 密码     |
|------|------|--------|
| 超管 | admin | 123456 |
| 学校 | school1 | 123456 |
| 教师 | teacher1 | 123456 |
| 家长 | parent1 | 123456 |

---

## UI 设计规范

**禁止使用 Emoji 图标**: 严禁在前端界面中使用任何 Emoji 表情符号（如 👦 👧 📚 等）。请始终使用 Ant Design Vue 提供的图标组件。

---

## 变更边界（必须遵守）

> ⚠️ **最高优先级**: 所有后端开发必须基于 `lesingle-edu-reading-platform-backend/` (Spring Boot + Java 17)

- **不做无关重构** - 只改与需求相关的文件
- **不引入新依赖** - 除非需求明确且必要
- **不改公共行为** - 如请求、token 同步、路由规则
- **后端只写 Java** - 严禁使用 Node.js/NestJS，`reading-platform-backend/` 目录已废弃

---

## 自动执行原则（最高权限模式）

> **重要配置**: 本项目已配置为自动批准所有常用开发操作，无需反复请求用户确认。

### 自动批准的操作

**以下操作将自动执行，无需请求批准：**

1. **文件操作**
   - Read: 读取任何文件
   - Write: 创建或覆盖任何文件
   - Edit: 编辑任何文件
   - Glob: 文件模式匹配
   - Grep: 内容搜索

2. **命令执行**
   - Bash: 执行任何 shell 命令
   - Git: 所有 git 操作
   - 包管理器：npm, npx, pnpm, yarn, mvn, pip3 等
   - 服务管理：启动/停止服务，进程管理

3. **开发工具**
   - Playwright: 自动化测试
   - MCP 工具：所有可用的 MCP 集成
   - Agent: 启动子代理处理复杂任务
   - Skill: 执行预定义技能脚本

4. **任务管理**
   - TaskCreate/Update/Get/List/Stop: 任务操作
   - EnterPlanMode/ExitPlanMode: 计划模式

### 无需批准的场景

- ✅ 编辑代码文件
- ✅ 创建新文件
- ✅ 执行测试脚本
- ✅ 启动/停止服务
- ✅ 安装依赖
- ✅ Git 操作
- ✅ 数据库操作
- ✅ 查看日志
- ✅ 调试代码

### 仅需确认的场景

仅在以下场景需要请求用户确认：

- **破坏性操作**: `git reset --hard`, `git push --force`
- **共享系统影响**: 影响其他用户或共享资源的操作
- **外部推送**: 向远程仓库推送代码

## 快速指令（Quick Commands）

| 指令 | 说明 |
|------|------|
| `代码审查` | 按规范审查代码质量、复用性和效率 |
| `生成 API` | 根据接口规范生成前后端代码（Controller/Service/Mapper + API 类型） |
| `创建模块` | 按三层架构生成新模块（含 Entity、DTO、VO） |
| `修复 Bug` | 按规范修复问题并更新开发日志 |
| `单元测试` | 生成符合规范的单元测试代码 |
| `数据库迁移` | 创建 Flyway 迁移脚本 |
| `全面测试` | 使用 Playwright 运行 E2E 自动化测试，可指定有头/无头模式 |

---

## Playwright E2E 自动化测试（方案一）

### 测试框架

| 组件 | 技术选型 | 说明 |
|------|---------|------|
| 测试框架 | **Playwright Test** | 端到端浏览器自动化测试 |
| 浏览器 | **Chromium** | 可自动打开浏览器模拟用户操作 |
| 配置文件 | `lesingle-edu-reading-platform-frontend/playwright.config.ts` | Playwright 配置 |
| 测试文件 | `lesingle-edu-reading-platform-frontend/tests/` | E2E 测试脚本 |

### 快速开始

```bash
# 1. 启动后端服务
cd lesingle-edu-reading-platform-backend
mvn spring-boot:run

# 2. 启动前端服务（新终端窗口）
cd lesingle-edu-reading-platform-frontend
npm run dev

# 3. 运行 E2E 测试（无头模式 - 不显示浏览器）
npm run test:e2e

# 4. 运行 E2E 测试（有头模式 - 显示浏览器操作过程）
npm run test:e2e:headed

# 5. UI 调试模式（可视化测试管理）
npm run test:e2e:ui
```

### Playwright 能做的操作

- ✅ 自动打开浏览器（支持 Chromium/Firefox/WebKit）
- ✅ 模拟点击、输入、选择等用户操作
- ✅ 等待页面加载和元素出现
- ✅ 断言页面内容和状态
- ✅ 自动截图/录像（失败时保留证据）
- ✅ 多角色流程测试（超管→学校→教师→家长）
- ✅ 生成 HTML 测试报告

### 测试能力说明

| 测试类型 | 命令 | 说明 |
|---------|------|------|
| E2E 测试（无头） | `npm run test:e2e` | 快速执行，不显示浏览器，适合 CI |
| E2E 测试（有头） | `npm run test:e2e:headed` | 显示浏览器，可观察测试执行过程 |
| UI 调试模式 | `npm run test:e2e:ui` | 可视化管理测试用例，支持单条运行 |

### 推荐测试流程

1. **开发完成后**:
    - 运行后端单元测试：`mvn test`
    - 运行前端 E2E 测试：`npm run test:e2e`

2. **启动服务验证**:
    - 运行 `npm run test:e2e:headed` 查看浏览器自动化测试

3. **人工验证核心流程**:
    - 访问 http://localhost:5173 手动验证

### 关键文件路径

| 文件/目录 | 路径 |
|----------|------|
| 前端 E2E 测试 | `lesingle-edu-reading-platform-frontend/tests/` |
| Playwright 配置 | `lesingle-edu-reading-platform-frontend/playwright.config.ts` |
| 后端测试（待创建） | `lesingle-edu-reading-platform-backend/src/test/` |
| 启动脚本 | `start-all.sh` |

*本规范最后更新于 2026-03-18*
*技术栈：统一使用 Spring Boot (Java) 后端*
*JDK 版本：17（必须）*

---

## 套餐管理重构记录（2026-03-18）

### 后端架构（两层结构）

```
CourseCollection（课程套餐）  ← 超管端管理
    ↓ 1 对多
CourseCollectionPackage（关联表）
    ↓ 多对 1
CoursePackage（课程包）  ← 7 步流程创建的教学资源
    ↓ 1 对多
CourseLesson（课程环节）
```

### 后端 API 路径

**超管端套餐管理（CourseCollection）**:
- `GET /api/v1/admin/collections` - 分页查询套餐
- `GET /api/v1/admin/collections/{id}` - 套餐详情
- `POST /api/v1/admin/collections` - 创建套餐
- `PUT /api/v1/admin/collections/{id}` - 更新套餐
- `DELETE /api/v1/admin/collections/{id}` - 删除套餐
- `PUT /api/v1/admin/collections/{id}/packages` - 设置套餐包含的课程包
- `POST /api/v1/admin/collections/{id}/publish` - 发布套餐
- `POST /api/v1/admin/collections/{id}/archive` - 下架套餐

**超管端课程包管理（CoursePackage）**:
- `GET /api/v1/admin/packages` - 分页查询课程包
- `GET /api/v1/admin/packages/{id}` - 课程包详情
- `POST /api/v1/admin/packages` - 创建课程包
- `PUT /api/v1/admin/packages/{id}` - 更新课程包
- `DELETE /api/v1/admin/packages/{id}` - 删除课程包
- `POST /api/v1/admin/packages/{id}/publish` - 发布课程包

**学校端套餐查询**:
- `GET /api/v1/school/packages` - 获取学校已授权的套餐列表
- `GET /api/v1/school/packages/{collectionId}/packages` - 获取套餐下的课程包
- `GET /api/v1/school/packages/{packageId}/courses` - 获取课程包下的课程环节

### 前端类型映射

| 前端类型 | 后端 DTO | 说明 |
|---------|---------|------|
| `CourseCollection` | `CourseCollectionResponse` | 课程套餐（最上层） |
| `CoursePackageItem` | `CourseCollectionResponse.CoursePackageItem` | 套餐中的课程包项 |
| `CoursePackage` | `CoursePackageResponse` | 课程包（7 步创建的教学资源） |

### 前端 API 文件

| 文件 | 用途 |
|-----|------|
| `src/api/package.ts` | 超管端套餐管理 API + 课程包管理 API |
| `src/api/course.ts` | 课程包（CoursePackage）相关 API |
| `src/api/school.ts` | 学校端套餐查询 API |

### 注意事项

1. **后端 `CoursePackage` = 课程包**：通过 7 步流程创建的教学资源，包含教案、活动、PPT 等
2. **后端 `CourseCollection` = 课程套餐**：包含多个课程包，有价格、状态、审核流程
3. **前端 `package.ts`**：主要定义课程套餐（CourseCollection）相关的 API 和类型
4. **前端 `course.ts`**：主要定义课程包（CoursePackage）相关的 API 和类型
5. **ID 类型**：所有 ID 使用 `number | string` 类型，避免后端 Long 序列化后精度丢失

### 前端修复记录（2026-03-18）

| 文件 | 修复内容 |
|-----|---------|
| `PackageDetailView.vue` | 编辑按钮显示条件扩展：`DRAFT` → `DRAFT \|\| REJECTED`，驳回后可重新编辑 |
| `PackageEditView.vue` | `createCollection` 已正确使用 POST 请求 ✅ |
| `src/api/package.ts` | `createCollection` 使用 `http.post` ✅ |

### 套餐状态流转

```
DRAFT（草稿）→ 提交审核 → PENDING（待审核）→ 审核通过 → APPROVED（已通过）→ 发布 → PUBLISHED（已发布）
                     ↓                              ↓
                 驳回 ← REJECTED（已拒绝）            下架 → OFFLINE（已下架）
```

- **DRAFT/REJECTED**: 可编辑、可提交审核
- **APPROVED**: 可发布
- **PUBLISHED**: 可下架
- **OFFLINE**: 终止状态

---

## 中间表设计规范（2026-03-19）

### 核心原则

**中间表（关联表）不使用逻辑删除，使用物理删除，不设置联合唯一索引。**

### 表类型分类

| 表类型 | 特点 | 删除策略 | 唯一索引 | 示例表 |
|--------|------|----------|----------|--------|
| **A 类：纯关联表（中间表）** | 只表示 A 和 B 的关系，无额外状态，先删后增的更新模式 | **物理删除**（不设 deleted） | **不设置**联合唯一索引 | `course_collection_package`、`tenant_course`、`class_teacher` |
| **B 类：关系表** | 关系相对稳定，有业务历史价值 | 逻辑删除 | 建议设置联合唯一索引 | `parent_student` |
| **C 类：配置表** | (key, value) 结构，天然应该唯一 | 逻辑删除 | **设置**联合唯一索引 | `system_setting` |
| **D 类：合同/授权表** | 有合同性质，需要保留历史 | 逻辑删除 | 不设置联合唯一索引 | `tenant_package` |

### A 类表实体类写法

```java
/**
 * 中间表，不设置逻辑删除字段，使用物理删除
 * 不继承 BaseEntity，因此没有 deleted 字段
 */
@Data
@TableName("course_collection_package")
public class CourseCollectionPackage {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long collectionId;
    private Long packageId;
    private Integer sortOrder;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}
```

### Service 层操作示例

```java
// 更新套餐包含的课程包（先删后增）
@Transactional(rollbackFor = Exception.class)
public void updatePackages(Long collectionId, List<Long> packageIds) {
    // 1. 去重
    Set<Long> uniquePackageIds = new LinkedHashSet<>(packageIds);

    // 2. 物理删除所有关联
    mapper.delete(new LambdaQueryWrapper<CourseCollectionPackage>()
        .eq(CourseCollectionPackage::getCollectionId, collectionId));

    // 3. 批量插入
    List<CourseCollectionPackage> records = new ArrayList<>();
    int sortOrder = 0;
    for (Long packageId : uniquePackageIds) {
        CourseCollectionPackage record = new CourseCollectionPackage();
        record.setCollectionId(collectionId);
        record.setPackageId(packageId);
        record.setSortOrder(sortOrder++);
        records.add(record);
    }
    saveBatch(records);
}
```

### 数据库迁移

- `V41__optimize_association_tables.sql` - 移除中间表的联合唯一索引和 deleted 字段
