refactor: 学校端课程响应重构

- 将 toSchoolCourseResponse 转换逻辑从 Controller 移到 Response 类
- 使用静态方法引用简化 Controller 代码
- 删除已弃用的文档文件
This commit is contained in:
En 2026-03-20 09:42:04 +08:00
parent c3b1056c29
commit a054c410c2
3 changed files with 83 additions and 114 deletions

View File

@ -1,35 +0,0 @@
# 提示词记录
## 2026-03-15
| 时间 | 提示词 | 备注 |
|------|--------|------|
| 11:31 | 全面测试,有头模式,超管端的所有页面的新增,修改,查看 | 创建了 27 个 E2E 测试用例,覆盖超管端所有功能模块,通过率 100% |
---
## 详细信息
### 测试任务执行
**用户请求**: "全面测试,有头模式,超管端的所有页面的新增,修改,查看"
**执行内容**:
1. 创建 comprehensive 测试文件 `admin-comprehensive.spec.ts`
2. 修复测试中的问题:
- 登录流程超时
- 表格选择器严格模式冲突
- 公告管理页面未实现 (404)
3. 运行有头模式测试
4. 创建测试报告文档
**测试结果**: 27 个测试全部通过 ✅
**修改的文件**:
- `tests/e2e/admin/helpers.ts` - 修复登录函数和表格等待函数
- `tests/e2e/admin/admin-comprehensive.spec.ts` - 新建测试文件
- `docs/test-logs/admin/2026-03-15-comprehensive-test.md` - 测试报告
- `docs/dev-logs/2026-03-15.md` - 更新开发日志
- `docs/CHANGELOG.md` - 更新变更日志
---

View File

@ -53,7 +53,7 @@ public class SchoolCourseController {
tenantId, pageNum, pageSize, keyword, grade, domain, lessonType, null);
List<SchoolCourseResponse> list = page.getRecords().stream()
.map(pkg -> toSchoolCourseResponse(pkg))
.map(SchoolCourseResponse::toSchoolCourseResponse)
.collect(Collectors.toList());
// 填充 lessonTags
@ -76,85 +76,8 @@ public class SchoolCourseController {
log.info("获取课程详情id={}", id);
Long tenantId = SecurityUtils.getCurrentTenantId();
CoursePackage course = courseService.getCourseByIdWithTenantCheck(id, tenantId);
return Result.success(toSchoolCourseResponse(course));
return Result.success(SchoolCourseResponse.toSchoolCourseResponse(course));
}
/**
* 转换为学校端课程响应gradeTags/domainTags 规范为 String[]
*/
private SchoolCourseResponse toSchoolCourseResponse(CoursePackage pkg) {
return SchoolCourseResponse.builder()
.id(pkg.getId())
.tenantId(pkg.getTenantId())
.name(pkg.getName())
.code(pkg.getCode())
.description(pkg.getDescription())
.pictureBookName(pkg.getPictureBookName())
.coverImagePath(pkg.getCoverImagePath())
.coverUrl(pkg.getCoverUrl())
.gradeTags(parseJsonArray(pkg.getGradeTags()))
.domainTags(parseJsonArray(pkg.getDomainTags()))
.duration(pkg.getDurationMinutes())
.usageCount(pkg.getUsageCount())
.teacherCount(pkg.getTeacherCount())
.avgRating(pkg.getAvgRating())
.status(pkg.getStatus())
.createdAt(pkg.getCreatedAt())
.updatedAt(pkg.getUpdatedAt())
.build();
}
/**
* 解析 JSON 数组为 String[]兼容多种格式
* - 标准 JSON: ["小班","中班"]
* - 逗号分隔: 小班,中班
* - 错误格式split 导致: ["[\"小班\"", " \"中班\""] -> 提取有效值
*/
private String[] parseJsonArray(String json) {
if (!StringUtils.hasText(json)) {
return new String[0];
}
String s = json.trim();
try {
if (s.startsWith("[")) {
var list = JSON.parseArray(s, String.class);
if (list != null && !list.isEmpty()) {
// 检查是否为被错误 split 的格式 ["[\"小班\"", " \"中班\""]
String first = list.get(0);
if (first != null && first.startsWith("[\"") && !first.contains(",")) {
return list.stream()
.map(String::trim)
.map(this::extractQuotedValue)
.filter(v -> v != null && !v.isEmpty())
.toArray(String[]::new);
}
return list.stream()
.map(v -> v != null ? v.trim() : "")
.filter(v -> !v.isEmpty())
.toArray(String[]::new);
}
return new String[0];
}
return java.util.Arrays.stream(s.split(","))
.map(String::trim)
.filter(v -> !v.isEmpty())
.toArray(String[]::new);
} catch (Exception e) {
log.warn("解析 JSON 数组失败: {}", json, e);
return new String[0];
}
}
private String extractQuotedValue(String s) {
if (s == null) return null;
s = s.trim();
int start = s.indexOf('"');
if (start >= 0) {
int end = s.indexOf('"', start + 1);
if (end > start) {
return s.substring(start + 1, end).trim();
}
}
return s.replaceAll("^\\[\"|\"\\]?$", "").trim();
}
}

View File

@ -1,8 +1,11 @@
package com.reading.platform.dto.response;
import com.alibaba.fastjson2.JSON;
import com.reading.platform.entity.CoursePackage;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@ -69,4 +72,82 @@ public class SchoolCourseResponse {
@Schema(description = "课程环节标签(列表展示用,仅 name 和 lessonType")
private List<LessonTagResponse> lessonTags;
/**
* 转换为学校端课程响应gradeTags/domainTags 规范为 String[]
*/
public static SchoolCourseResponse toSchoolCourseResponse(CoursePackage pkg) {
return SchoolCourseResponse.builder()
.id(pkg.getId())
.tenantId(pkg.getTenantId())
.name(pkg.getName())
.code(pkg.getCode())
.description(pkg.getDescription())
.pictureBookName(pkg.getPictureBookName())
.coverImagePath(pkg.getCoverImagePath())
.coverUrl(pkg.getCoverUrl())
.gradeTags(parseJsonArray(pkg.getGradeTags()))
.domainTags(parseJsonArray(pkg.getDomainTags()))
.duration(pkg.getDurationMinutes())
.usageCount(pkg.getUsageCount())
.teacherCount(pkg.getTeacherCount())
.avgRating(pkg.getAvgRating())
.status(pkg.getStatus())
.createdAt(pkg.getCreatedAt())
.updatedAt(pkg.getUpdatedAt())
.build();
}
/**
* 解析 JSON 数组为 String[]兼容多种格式
* - 标准 JSON: ["小班","中班"]
* - 逗号分隔: 小班,中班
* - 错误格式split 导致: ["[\"小班\"", " \"中班\""] -> 提取有效值
*/
private static String[] parseJsonArray(String json) {
if (!StringUtils.hasText(json)) {
return new String[0];
}
String s = json.trim();
try {
if (s.startsWith("[")) {
var list = JSON.parseArray(s, String.class);
if (list != null && !list.isEmpty()) {
// 检查是否为被错误 split 的格式 ["[\"小班\"", " \"中班\""]
String first = list.get(0);
if (first != null && first.startsWith("[\"") && !first.contains(",")) {
return list.stream()
.map(String::trim)
.map(SchoolCourseResponse::extractQuotedValue)
.filter(v -> v != null && !v.isEmpty())
.toArray(String[]::new);
}
return list.stream()
.map(v -> v != null ? v.trim() : "")
.filter(v -> !v.isEmpty())
.toArray(String[]::new);
}
return new String[0];
}
return java.util.Arrays.stream(s.split(","))
.map(String::trim)
.filter(v -> !v.isEmpty())
.toArray(String[]::new);
} catch (Exception e) {
return new String[0];
}
}
private static String extractQuotedValue(String s) {
if (s == null) return null;
s = s.trim();
int start = s.indexOf('"');
if (start >= 0) {
int end = s.indexOf('"', start + 1);
if (end > start) {
return s.substring(start + 1, end).trim();
}
}
return s.replaceAll("^\\[\"|\"\\]?$", "").trim();
}
}