- 修改后端目录从 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>
7.2 KiB
7.2 KiB
套餐数据显示问题修复测试
问题描述
数据库中有很多套餐和课程包数据,但超管端、学校端页面上没有显示数据。接口有返回数据,但前端无法正确解析显示。
问题原因
- 字段格式不匹配:后端
gradeLevels存储的是 JSON 字符串或逗号分隔字符串,前端期望数组格式 - 数据结构不匹配:学校端期望的数据结构与后端返回不一致
- 缺少字段:前端需要
courses字段(包含的课程列表),但后端未返回
修复方案
后端修改
1. CoursePackageResponse.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 填充逻辑:
// 解析 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 类型转换方法:
@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
修改返回类型:
// 导入
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
修改数据访问路径:
<!-- 修改前 -->
<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 接口定义:
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;
}>;
}
测试验证
超管端套餐列表接口测试
curl -X GET "http://localhost:8080/api/v1/admin/packages?page=1&pageSize=10" \
-H "Authorization: Bearer <admin_token>"
返回结果:
{
"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}
]
}
]
}
}
学校端套餐列表接口测试
curl -X GET "http://localhost:8080/api/v1/school/packages" \
-H "Authorization: Bearer <school_token>"
返回结果:
{
"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 迁移脚本添加的测试数据:
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');
总结
本次修复解决了前后端数据结构不匹配的问题:
- 后端正确返回
gradeLevels数组格式 - 后端补充了前端需要的
courses字段 - 学校端套餐接口返回包含
startDate和endDate字段 - 前端正确解析并显示数据
修复后,超管端和学校端的套餐页面都能正确显示数据。
测试日期
2026-03-14