- 修改后端目录从 reading-platform-backend 改为 reading-platform-java - 修改后端端口从 3000 改为 8080 - 修改启动命令从 npm run start:dev 改为 mvn spring-boot:run - 添加 JAVA_HOME 自动检测和设置(默认使用 /f/Java/jdk-17) - 修改日志文件从 reading-platform-backend.log 改为 reading-platform-java.log - 修改健康检查接口为 /actuator/health - 增加启动等待超时时间到 60 秒(Java 启动较慢) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
273 lines
7.2 KiB
Markdown
273 lines
7.2 KiB
Markdown
# 套餐数据显示问题修复测试
|
||
|
||
## 问题描述
|
||
|
||
数据库中有很多套餐和课程包数据,但超管端、学校端页面上没有显示数据。接口有返回数据,但前端无法正确解析显示。
|
||
|
||
## 问题原因
|
||
|
||
1. **字段格式不匹配**:后端 `gradeLevels` 存储的是 JSON 字符串或逗号分隔字符串,前端期望数组格式
|
||
2. **数据结构不匹配**:学校端期望的数据结构与后端返回不一致
|
||
3. **缺少字段**:前端需要 `courses` 字段(包含的课程列表),但后端未返回
|
||
|
||
## 修复方案
|
||
|
||
### 后端修改
|
||
|
||
#### 1. CoursePackageResponse.java
|
||
|
||
修改字段类型和添加新字段:
|
||
|
||
```java
|
||
// gradeLevels: String → String[]
|
||
@Schema(description = "年级水平(数组)")
|
||
private String[] gradeLevels;
|
||
|
||
// 新增字段
|
||
@Schema(description = "包含的课程")
|
||
private List<CoursePackageCourseItem> courses;
|
||
|
||
@Schema(description = "开始日期(租户套餐)")
|
||
private LocalDate startDate;
|
||
|
||
@Schema(description = "结束日期(租户套餐)")
|
||
private LocalDate endDate;
|
||
```
|
||
|
||
#### 2. CoursePackageService.java
|
||
|
||
添加 `gradeLevels` 转换逻辑和 `courses` 填充逻辑:
|
||
|
||
```java
|
||
// 解析 gradeLevels,数据库中存储的是 JSON 数组字符串
|
||
String[] gradeLevelsArray = null;
|
||
if (pkg.getGradeLevels() != null && !pkg.getGradeLevels().isEmpty()) {
|
||
try {
|
||
// 尝试解析 JSON 数组格式:["小班","中班"]
|
||
gradeLevelsArray = JSON.parseArray(pkg.getGradeLevels(), String.class).toArray(new String[0]);
|
||
} catch (Exception e) {
|
||
// 如果不是 JSON 格式,尝试按逗号分隔处理
|
||
gradeLevelsArray = pkg.getGradeLevels().split(",");
|
||
}
|
||
}
|
||
|
||
// 查询套餐包含的课程
|
||
List<CoursePackageCourse> packageCourses = packageCourseMapper.selectList(
|
||
new LambdaQueryWrapper<CoursePackageCourse>()
|
||
.eq(CoursePackageCourse::getPackageId, pkg.getId())
|
||
.orderByAsc(CoursePackageCourse::getSortOrder)
|
||
);
|
||
```
|
||
|
||
#### 3. CoursePackageMapper.java
|
||
|
||
添加 MapStruct 类型转换方法:
|
||
|
||
```java
|
||
@Named("stringToArray")
|
||
default String[] stringToArray(String value) {
|
||
if (value == null || value.isEmpty()) {
|
||
return new String[0];
|
||
}
|
||
return value.split(",");
|
||
}
|
||
|
||
@Named("arrayToString")
|
||
default String arrayToString(String[] value) {
|
||
if (value == null || value.length == 0) {
|
||
return null;
|
||
}
|
||
return String.join(",", value);
|
||
}
|
||
```
|
||
|
||
#### 4. SchoolPackageController.java
|
||
|
||
修改返回类型:
|
||
|
||
```java
|
||
// 导入
|
||
import com.reading.platform.dto.response.CoursePackageResponse;
|
||
|
||
// 修改返回类型
|
||
public Result<List<CoursePackageResponse>> findTenantPackages() {
|
||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||
return Result.success(packageService.findTenantPackages(tenantId));
|
||
}
|
||
```
|
||
|
||
### 前端修改
|
||
|
||
#### 1. PackageView.vue
|
||
|
||
修改数据访问路径:
|
||
|
||
```vue
|
||
<!-- 修改前 -->
|
||
<div class="package-name">{{ item.package.name }}</div>
|
||
<span class="info-value">{{ item.package.courseCount }} 个</span>
|
||
|
||
<!-- 修改后 -->
|
||
<div class="package-name">{{ item.name }}</div>
|
||
<span class="info-value">{{ item.courseCount }} 个</span>
|
||
```
|
||
|
||
#### 2. school.ts
|
||
|
||
更新 `CoursePackage` 接口定义:
|
||
|
||
```typescript
|
||
export interface CoursePackage {
|
||
id: number;
|
||
name: string;
|
||
description?: string;
|
||
price: number;
|
||
discountPrice?: number;
|
||
discountType?: string;
|
||
gradeLevels: string[];
|
||
status: string;
|
||
courseCount: number;
|
||
tenantCount: number;
|
||
createdAt: string;
|
||
startDate?: string;
|
||
endDate?: string;
|
||
courses?: Array<{
|
||
id: number;
|
||
name: string;
|
||
gradeLevel: string;
|
||
sortOrder: number;
|
||
}>;
|
||
}
|
||
```
|
||
|
||
## 测试验证
|
||
|
||
### 超管端套餐列表接口测试
|
||
|
||
```bash
|
||
curl -X GET "http://localhost:8080/api/v1/admin/packages?page=1&pageSize=10" \
|
||
-H "Authorization: Bearer <admin_token>"
|
||
```
|
||
|
||
**返回结果**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"list": [
|
||
{
|
||
"id": 3,
|
||
"name": "幼儿园阅读启蒙套餐",
|
||
"gradeLevels": ["小班", "中班"],
|
||
"courseCount": 5,
|
||
"tenantCount": 1,
|
||
"courses": [
|
||
{"id": 6, "name": "小猪佩奇绘本阅读", "gradeLevel": "小班", "sortOrder": 1},
|
||
{"id": 7, "name": "彩虹色的花", "gradeLevel": "小班", "sortOrder": 2},
|
||
{"id": 8, "name": "牙齿大街的新鲜事", "gradeLevel": "中班", "sortOrder": 3},
|
||
{"id": 9, "name": "猜猜我有多爱你", "gradeLevel": "小班", "sortOrder": 4},
|
||
{"id": 10, "name": "逃家小兔", "gradeLevel": "小班", "sortOrder": 5}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 学校端套餐列表接口测试
|
||
|
||
```bash
|
||
curl -X GET "http://localhost:8080/api/v1/school/packages" \
|
||
-H "Authorization: Bearer <school_token>"
|
||
```
|
||
|
||
**返回结果**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"id": 3,
|
||
"name": "幼儿园阅读启蒙套餐",
|
||
"gradeLevels": ["小班", "中班"],
|
||
"courseCount": 5,
|
||
"startDate": "2026-01-01",
|
||
"endDate": "2026-12-31",
|
||
"courses": [
|
||
{"id": 6, "name": "小猪佩奇绘本阅读", "gradeLevel": "小班", "sortOrder": 1},
|
||
{"id": 7, "name": "彩虹色的花", "gradeLevel": "小班", "sortOrder": 2},
|
||
...
|
||
]
|
||
},
|
||
{
|
||
"id": 4,
|
||
"name": "亲子共读成长套餐",
|
||
"gradeLevels": ["小班", "中班", "大班"],
|
||
"courseCount": 5,
|
||
"startDate": "2026-02-01",
|
||
"endDate": "2027-01-31",
|
||
"courses": [...]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 测试结果
|
||
|
||
| 接口 | 测试项 | 结果 |
|
||
|------|--------|------|
|
||
| 超管端套餐列表 | 返回数据格式 | ✅ 通过 |
|
||
| 超管端套餐列表 | gradeLevels 数组格式 | ✅ 通过 |
|
||
| 超管端套餐列表 | courses 字段填充 | ✅ 通过 |
|
||
| 超管端套餐列表 | tenantCount 统计 | ✅ 通过 |
|
||
| 学校端套餐列表 | 返回数据格式 | ✅ 通过 |
|
||
| 学校端套餐列表 | startDate/endDate 字段 | ✅ 通过 |
|
||
| 学校端套餐列表 | gradeLevels 数组格式 | ✅ 通过 |
|
||
| 学校端套餐列表 | courses 字段填充 | ✅ 通过 |
|
||
|
||
## 修改的文件
|
||
|
||
### 后端文件
|
||
|
||
| 文件 | 修改内容 |
|
||
|------|----------|
|
||
| `CoursePackageResponse.java` | gradeLevels 改为 String[],添加 courses、startDate、endDate 字段 |
|
||
| `CoursePackageService.java` | 添加 gradeLevels 转换和 courses 填充逻辑 |
|
||
| `CoursePackageMapper.java` | 添加 MapStruct 类型转换方法 |
|
||
| `SchoolPackageController.java` | 修改返回类型为 CoursePackageResponse |
|
||
| `V8__add_tenant_package_test_data.sql` | 添加租户套餐测试数据 |
|
||
|
||
### 前端文件
|
||
|
||
| 文件 | 修改内容 |
|
||
|------|----------|
|
||
| `PackageView.vue` | 修改数据访问路径 |
|
||
| `school.ts` | 更新 CoursePackage 接口定义 |
|
||
|
||
## 测试数据
|
||
|
||
V8 迁移脚本添加的测试数据:
|
||
|
||
```sql
|
||
INSERT INTO tenant_package (id, tenant_id, package_id, start_date, end_date, price_paid, status) VALUES
|
||
(1, 1, 3, '2026-01-01', '2026-12-31', 7999, 'ACTIVE'),
|
||
(2, 1, 4, '2026-02-01', '2027-01-31', 15999, 'ACTIVE');
|
||
```
|
||
|
||
## 总结
|
||
|
||
本次修复解决了前后端数据结构不匹配的问题:
|
||
|
||
1. 后端正确返回 `gradeLevels` 数组格式
|
||
2. 后端补充了前端需要的 `courses` 字段
|
||
3. 学校端套餐接口返回包含 `startDate` 和 `endDate` 字段
|
||
4. 前端正确解析并显示数据
|
||
|
||
修复后,超管端和学校端的套餐页面都能正确显示数据。
|
||
|
||
## 测试日期
|
||
|
||
2026-03-14
|