Compare commits
6
Commits
55f0c81f2c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b806312c | ||
|
|
f6add00677 | ||
|
|
0ac92d5cee | ||
|
|
6e5b8756aa | ||
|
|
f7e5bf0de0 | ||
|
|
76493683c3 |
@@ -25,7 +25,10 @@ jobs:
|
||||
options: --network infra-infra-net
|
||||
steps:
|
||||
- name: 安装 git / curl(alpine)
|
||||
run: apk add --no-cache git curl
|
||||
run: |
|
||||
# 统一走 aliyun 源:dl-cdn 时段性被拦会卡满 60s×2(2026-10-20 事故)
|
||||
sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories
|
||||
apk add --no-cache git curl
|
||||
|
||||
# 内网 Gitea 无法访问 GitHub 拉 actions/checkout,手动 clone 本仓库
|
||||
- name: 拉取源码(内网无 GitHub,手动 clone 本仓库)
|
||||
@@ -55,18 +58,24 @@ jobs:
|
||||
- name: 构建 jar(maven 容器,复用宿主 m2 缓存)
|
||||
run: |
|
||||
set -e
|
||||
docker rm -f mavenbuild 2>/dev/null || true
|
||||
docker run -d --name mavenbuild \
|
||||
# 构建容器名必须全局唯一:runner 挂的是宿主 docker.sock,容器名在整个 daemon 上共享
|
||||
# 命名空间。固定名会在多仓/多条 workflow 并发时互相 docker rm -f 掉对方正在构建的
|
||||
# 容器,表现为无编译报错的 exit 137(SIGKILL,2026-10-21 实测复现)。
|
||||
# GITHUB_RUN_ID 实例级唯一,再拼时间戳兜底。
|
||||
BUILD_CONTAINER="mavenbuild-${GITHUB_RUN_ID:-0}-$(date +%s)"
|
||||
echo "BUILD_CONTAINER=${BUILD_CONTAINER}"
|
||||
docker rm -f "$BUILD_CONTAINER" 2>/dev/null || true
|
||||
docker run -d --name "$BUILD_CONTAINER" \
|
||||
--network infra-infra-net \
|
||||
-v /opt/docker/projman-m2:/root/.m2 \
|
||||
-w /build maven:3.6.3-jdk-8 sleep 3600
|
||||
docker cp "${PWD}/." mavenbuild:/build/
|
||||
docker exec mavenbuild sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
||||
docker exec mavenbuild sh -c 'cd /build && mvn -B -DskipTests clean package'
|
||||
docker exec mavenbuild sh -c 'cd /build/target && cp *.jar app.jar'
|
||||
docker cp "${PWD}/." "$BUILD_CONTAINER":/build/
|
||||
docker exec "$BUILD_CONTAINER" sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
||||
docker exec "$BUILD_CONTAINER" sh -c 'cd /build && mvn -B -DskipTests clean package'
|
||||
docker exec "$BUILD_CONTAINER" sh -c 'cd /build/target && cp *.jar app.jar'
|
||||
mkdir -p "${PWD}/target"
|
||||
docker cp mavenbuild:/build/target/app.jar "${PWD}/target/app.jar"
|
||||
docker rm -f mavenbuild
|
||||
docker cp "$BUILD_CONTAINER":/build/target/app.jar "${PWD}/target/app.jar"
|
||||
docker rm -f "$BUILD_CONTAINER"
|
||||
|
||||
- name: 构建并推送镜像
|
||||
run: |
|
||||
|
||||
@@ -20,6 +20,16 @@ public class HomeworkQuery extends BasePageQuery {
|
||||
/** 状态过滤:null 不过滤;1 已发布 */
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 状态过滤(前端 Tab,旧四态派生值):all 全部 / collecting 收集中 / closed 已截止 /
|
||||
* done 已完成 / draft 草稿。口径与列表返回的 hwStatus 派生规则一致,
|
||||
* null/空/未知值不过滤。
|
||||
*/
|
||||
private String hwStatus;
|
||||
|
||||
/** 学科过滤(字典编码,与 homework.subject 精确匹配) */
|
||||
private String subject;
|
||||
|
||||
/** 类型过滤:1 普通 / 2 重要 */
|
||||
private Integer type;
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ public class HomeworkStatisticsVo {
|
||||
private Integer id;
|
||||
private String title;
|
||||
private String subject;
|
||||
/** 学科名称(本校学科字典解析,未命中回退编码) */
|
||||
private String subjectName;
|
||||
/** 旧契约四态派生值:draft / collecting / closed / done */
|
||||
private String hwStatus;
|
||||
private String hwStatusDesc;
|
||||
|
||||
@@ -48,6 +48,9 @@ public class HomeworkVo {
|
||||
/** 接收人列表(JSON) */
|
||||
private String receiveList;
|
||||
|
||||
/** 班级展示文本(年级名前缀,如「高一1班、高一2班」;前端「班级」列直接展示) */
|
||||
private String receiveText;
|
||||
|
||||
/** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */
|
||||
private String UpFile;
|
||||
|
||||
@@ -56,6 +59,9 @@ public class HomeworkVo {
|
||||
/** 学科(字典值) */
|
||||
private String subject;
|
||||
|
||||
/** 学科名称(本校学科字典解析,未命中回退编码) */
|
||||
private String subjectName;
|
||||
|
||||
/** 截止时间(前端契约字段名 deadline) */
|
||||
private LocalDateTime deadline;
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@ public class HomeworkV2Vo {
|
||||
|
||||
private String title;
|
||||
|
||||
/** 学科 */
|
||||
/** 学科编码 */
|
||||
private String subject;
|
||||
|
||||
/** 学科名称(本校学科字典解析,未命中回退编码) */
|
||||
private String subjectName;
|
||||
|
||||
/** 作业说明(富文本;列表场景可置空省流) */
|
||||
private String description;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ public class StudentHomeworkVo {
|
||||
/** 学科 */
|
||||
private String subject;
|
||||
|
||||
/** 学科名称(本校学科字典解析,未命中回退编码) */
|
||||
private String subjectName;
|
||||
|
||||
/** 任课教师(发布人姓名) */
|
||||
private String teacherName;
|
||||
|
||||
|
||||
@@ -92,6 +92,12 @@ public class HomeworkService implements IHomeworkService {
|
||||
@Autowired
|
||||
private IDictService dictService;
|
||||
|
||||
@Autowired
|
||||
private SubjectDictService subjectDictService;
|
||||
|
||||
@Autowired
|
||||
private RosterService rosterService;
|
||||
|
||||
private static final int PUBLISHED = HomeworkStatusEnum.PUBLISHED.getCode();
|
||||
|
||||
// ============================ 发作业 ============================
|
||||
@@ -269,8 +275,9 @@ public class HomeworkService implements IHomeworkService {
|
||||
|
||||
Page<Homework> page = homeworkRepository.findAll(userSpec(query, readIds), pageable);
|
||||
|
||||
Map<String, String> subjectNames = subjectDictService.nameMap();
|
||||
List<HomeworkVo> content = page.getContent().stream()
|
||||
.map(n -> toVo(n, readIds.contains(n.getId())))
|
||||
.map(n -> toVo(n, readIds.contains(n.getId()), subjectNames))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long unreadCount = 0;
|
||||
@@ -322,8 +329,9 @@ public class HomeworkService implements IHomeworkService {
|
||||
@Override
|
||||
public BasePageResult<HomeworkVo> adminList(HomeworkQuery query, Pageable pageable) {
|
||||
Page<Homework> page = homeworkRepository.findAll(adminSpec(query), pageable);
|
||||
Map<String, String> subjectNames = subjectDictService.nameMap();
|
||||
List<HomeworkVo> content = page.getContent().stream()
|
||||
.map(n -> toVo(n, false))
|
||||
.map(n -> toVo(n, false, subjectNames))
|
||||
.collect(Collectors.toList());
|
||||
// 批量填充附件ID串("1,2,3"):列表页"附件下载"入口依赖 UpFile 字段
|
||||
if (!content.isEmpty()) {
|
||||
@@ -333,11 +341,43 @@ public class HomeworkService implements IHomeworkService {
|
||||
List<Integer> fids = fileMap.getOrDefault(vo.getId(), Collections.emptyList());
|
||||
vo.setUpFile(fids.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
}
|
||||
fillReceiveText(content);
|
||||
fillProgress(content);
|
||||
}
|
||||
return PageUtils.toResult(new PageImpl<>(content, pageable, page.getTotalElements()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量回填列表页「班级」列展示文本(年级名前缀,如「高一1班、高一2班」):
|
||||
* receiveList 为 ALL/空 → 「全部班级」;其余按 homework_class 关联实时解析
|
||||
* (RosterService 映射含年级前缀),班级名快照兜底、再退班级ID;一次批量查询 + 一次 bschclass 外呼。
|
||||
* 无班级关联的行保持 null(前端回退「-」)。
|
||||
*/
|
||||
private void fillReceiveText(List<HomeworkVo> content) {
|
||||
List<Integer> homeworkIds = content.stream().map(HomeworkVo::getId).collect(Collectors.toList());
|
||||
Map<Integer, List<HomeworkClass>> byHw = homeworkClassRepository.findByHomeworkIds(homeworkIds).stream()
|
||||
.collect(Collectors.groupingBy(HomeworkClass::getHomeworkId));
|
||||
Map<Integer, String> classNames = rosterService.classNames(byHw.values().stream()
|
||||
.flatMap(List::stream)
|
||||
.map(HomeworkClass::getClassId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList()));
|
||||
for (HomeworkVo vo : content) {
|
||||
String rl = vo.getReceiveList();
|
||||
if (rl == null || rl.trim().isEmpty() || "ALL".equalsIgnoreCase(rl.trim())) {
|
||||
vo.setReceiveText("全部班级");
|
||||
continue;
|
||||
}
|
||||
List<HomeworkClass> classes = byHw.getOrDefault(vo.getId(), Collections.emptyList());
|
||||
if (classes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
vo.setReceiveText(classes.stream()
|
||||
.map(c -> RosterService.displayOr(classNames.get(c.getClassId()), c.getClassName(), c.getClassId()))
|
||||
.collect(Collectors.joining("、")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量回填列表页提交/批改进度(「全部作业」页的 提交进度 / 未读未交 / 批改 列)。
|
||||
* <p>口径与 V2 一致:草稿两态与仅查阅型作业不计(progress 保持 null,前端显示「—」);
|
||||
@@ -408,12 +448,16 @@ public class HomeworkService implements IHomeworkService {
|
||||
if (StringUtils.hasText(query.getKeyword())) {
|
||||
predicates.add(cb.like(root.get("title"), "%" + query.getKeyword() + "%"));
|
||||
}
|
||||
if (StringUtils.hasText(query.getSubject())) {
|
||||
predicates.add(cb.equal(root.get("subject"), query.getSubject()));
|
||||
}
|
||||
if (query.getType() != null) {
|
||||
predicates.add(cb.equal(root.get("type"), query.getType()));
|
||||
}
|
||||
if (StringUtils.hasText(query.getDeptId())) {
|
||||
predicates.add(cb.equal(root.get("deptId"), query.getDeptId()));
|
||||
}
|
||||
appendLegacyHwStatus(query.getHwStatus(), root, cb, predicates);
|
||||
// 前端传 yyyy-MM-dd 短日期:startTime 当天 00:00:00 起,endTime 补全到当天 23:59:59
|
||||
if (query.getStartTime() != null) {
|
||||
predicates.add(cb.greaterThanOrEqualTo(root.get("createTime"), query.getStartTime().atStartOfDay()));
|
||||
@@ -423,6 +467,46 @@ public class HomeworkService implements IHomeworkService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态过滤(前端 Tab 的旧四态值 all/collecting/closed/done/draft),口径与
|
||||
* {@link #legacyHwStatus(Homework)} 派生规则一致,保证「展示什么状态就筛出什么状态」:
|
||||
* draft/collecting 兼容存量 hw_status 为空的行(按定时发送时间是否到点判定),
|
||||
* closed/done 直接等值;null/空/all/未知值不过滤。
|
||||
*/
|
||||
private static void appendLegacyHwStatus(String hwStatus, Root<Homework> root, CriteriaBuilder cb,
|
||||
List<Predicate> predicates) {
|
||||
if (!StringUtils.hasText(hwStatus) || "all".equalsIgnoreCase(hwStatus.trim())) {
|
||||
return;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
switch (hwStatus.trim()) {
|
||||
case "draft":
|
||||
predicates.add(cb.or(
|
||||
root.get("hwStatus").in(Arrays.asList(
|
||||
HwStatusEnum.DRAFT_UNPUBLISHED.getCode(), HwStatusEnum.DRAFT_SCHEDULED.getCode())),
|
||||
cb.and(cb.isNull(root.get("hwStatus")),
|
||||
cb.isNotNull(root.get("timingSendTime")),
|
||||
cb.greaterThan(root.get("timingSendTime"), now))));
|
||||
break;
|
||||
case "collecting":
|
||||
predicates.add(cb.or(
|
||||
cb.equal(root.get("hwStatus"), HwStatusEnum.PUBLISHED.getCode()),
|
||||
cb.and(cb.isNull(root.get("hwStatus")),
|
||||
cb.or(cb.isNull(root.get("timingSendTime")),
|
||||
cb.lessThanOrEqualTo(root.get("timingSendTime"), now)))));
|
||||
break;
|
||||
case "closed":
|
||||
predicates.add(cb.equal(root.get("hwStatus"), HwStatusEnum.CLOSED.getCode()));
|
||||
break;
|
||||
case "done":
|
||||
predicates.add(cb.equal(root.get("hwStatus"), HwStatusEnum.ARCHIVED.getCode()));
|
||||
break;
|
||||
default:
|
||||
// 未知值按不过滤处理(同 all),避免前后端值不一致时列表误清空
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ 详情(顺带标记已读) ============================
|
||||
|
||||
@Override
|
||||
@@ -431,7 +515,7 @@ public class HomeworkService implements IHomeworkService {
|
||||
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "作业不存在: " + id));
|
||||
Integer userId = UserContext.getUserId();
|
||||
boolean read = homeworkReadRepository.existsByHomeworkIdAndReaderId(id, userId);
|
||||
HomeworkVo vo = toVo(entity, read);
|
||||
HomeworkVo vo = toVo(entity, read, subjectDictService.nameMap());
|
||||
if (!read) {
|
||||
markRead(id);
|
||||
vo.setRead(true);
|
||||
@@ -445,7 +529,7 @@ public class HomeworkService implements IHomeworkService {
|
||||
public HomeworkVo getDetail(Integer id) {
|
||||
Homework entity = homeworkRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "作业不存在: " + id));
|
||||
HomeworkVo vo = toVo(entity, false);
|
||||
HomeworkVo vo = toVo(entity, false, subjectDictService.nameMap());
|
||||
vo.setReadCount(homeworkReadRepository.countByHomeworkId(entity.getId()));
|
||||
return vo;
|
||||
}
|
||||
@@ -779,6 +863,7 @@ public class HomeworkService implements IHomeworkService {
|
||||
Map<Integer, List<Integer>> classMap = homeworkClassRepository.findByHomeworkIds(hwIds).stream()
|
||||
.collect(Collectors.groupingBy(HomeworkClass::getHomeworkId,
|
||||
Collectors.mapping(HomeworkClass::getClassId, Collectors.toList())));
|
||||
Map<String, String> subjectNames = subjectDictService.nameMap();
|
||||
|
||||
long pendingTotal = 0;
|
||||
double submitRateSum = 0;
|
||||
@@ -794,6 +879,7 @@ public class HomeworkService implements IHomeworkService {
|
||||
d.setId(h.getId());
|
||||
d.setTitle(h.getTitle());
|
||||
d.setSubject(h.getSubject());
|
||||
d.setSubjectName(subjectDictService.resolve(h.getSubject(), subjectNames));
|
||||
String legacy = legacyHwStatus(h);
|
||||
d.setHwStatus(legacy);
|
||||
d.setHwStatusDesc(legacyHwStatusDesc(legacy));
|
||||
@@ -882,23 +968,19 @@ public class HomeworkService implements IHomeworkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收对象可读文本:优先用 homework_class 的班级名快照("高一(1)班、高一(2)班")。
|
||||
* 快照缺失时退回 id 串,保证「接收班级」标签不显示空白。
|
||||
* 接收对象可读文本(年级名前缀,如「高一(1)班、高一(2)班」):实时解析
|
||||
* (RosterService 映射含年级前缀),班级名快照兜底、再退 id 串,保证「接收班级」标签不显示空白。
|
||||
*/
|
||||
private String receiveText(Integer homeworkId) {
|
||||
List<HomeworkClass> classes = homeworkClassRepository.findByHomeworkId(homeworkId);
|
||||
if (classes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String names = classes.stream()
|
||||
.map(HomeworkClass::getClassName)
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.joining("、"));
|
||||
if (StringUtils.hasText(names)) {
|
||||
return names;
|
||||
}
|
||||
Map<Integer, String> classNames = rosterService.classNames(classes.stream()
|
||||
.map(HomeworkClass::getClassId)
|
||||
.collect(Collectors.toList()));
|
||||
return classes.stream()
|
||||
.map(c -> String.valueOf(c.getClassId()))
|
||||
.map(c -> RosterService.displayOr(classNames.get(c.getClassId()), c.getClassName(), c.getClassId()))
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
@@ -1009,7 +1091,7 @@ public class HomeworkService implements IHomeworkService {
|
||||
}
|
||||
}
|
||||
|
||||
private HomeworkVo toVo(Homework entity, boolean read) {
|
||||
private HomeworkVo toVo(Homework entity, boolean read, Map<String, String> subjectNames) {
|
||||
HomeworkVo vo = new HomeworkVo();
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
vo.setTypeDesc(HomeworkTypeEnum.of(entity.getType()).getDesc());
|
||||
@@ -1017,6 +1099,7 @@ public class HomeworkService implements IHomeworkService {
|
||||
// V2 扩展字段回填(hwStatus 输出旧四态派生值,submitType/deadline 手工映射)
|
||||
vo.setDeadline(entity.getDeadlineAt());
|
||||
vo.setSubmitType(SubmitTypes.parse(entity.getSubmitType()));
|
||||
vo.setSubjectName(subjectDictService.resolve(entity.getSubject(), subjectNames));
|
||||
String legacy = legacyHwStatus(entity);
|
||||
vo.setHwStatus(legacy);
|
||||
vo.setHwStatusDesc(legacyHwStatusDesc(legacy));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.yunhai.homework.service.impl;
|
||||
|
||||
import com.yunhai.homework.model.bsch.SchClassDto;
|
||||
import com.yunhai.homework.model.bsch.SchGradeDto;
|
||||
import com.yunhai.homework.model.bsch.SchInfoDto;
|
||||
import com.yunhai.homework.model.bsch.SchStudentDto;
|
||||
import com.yunhai.homework.service.IClassService;
|
||||
@@ -88,7 +89,8 @@ public class RosterService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 班级ID → 班级名(bschclass GetVClassList 映射)。
|
||||
* 班级ID → 班级展示名(bschclass GetVClassList 映射,前缀年级名,如「高一1班」;
|
||||
* 班级名已含年级前缀时不重复拼接)。年级列表拉取失败时退化为原班级名,
|
||||
* 远端不可用或无学校身份时降级为空映射,由调用方回退班级ID展示,不阻断名单本身。
|
||||
*/
|
||||
public Map<Integer, String> classNames(Collection<Integer> classIds) {
|
||||
@@ -102,19 +104,25 @@ public class RosterService {
|
||||
return names;
|
||||
}
|
||||
Set<Integer> pending = new LinkedHashSet<>(classIds);
|
||||
Map<Integer, SchClassDto> matched = new LinkedHashMap<>();
|
||||
for (SchClassDto c : classService.getSchClassList(schSerId)) {
|
||||
if (c != null && c.getClassId() != null && pending.remove(c.getClassId())
|
||||
&& StringUtils.hasText(c.getClassName())) {
|
||||
names.put(c.getClassId(), c.getClassName());
|
||||
matched.put(c.getClassId(), c);
|
||||
}
|
||||
}
|
||||
Map<Integer, String> grades = matched.isEmpty() ? Collections.emptyMap() : gradeNames(schSerId);
|
||||
for (Map.Entry<Integer, SchClassDto> e : matched.entrySet()) {
|
||||
SchClassDto c = e.getValue();
|
||||
names.put(e.getKey(), displayName(c.getClassName(), c.getGradeId(), grades));
|
||||
}
|
||||
} catch (BizException e) {
|
||||
log.warn("[roster] 班级名称映射失败, 回退班级ID展示: {}", e.getMessage());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/** 班级ID → 班级名(单班便捷方法,缺失回退班级ID) */
|
||||
/** 班级ID → 班级展示名(单班便捷方法,缺失回退班级ID) */
|
||||
public String className(Integer classId) {
|
||||
if (classId == null) {
|
||||
return null;
|
||||
@@ -123,6 +131,45 @@ public class RosterService {
|
||||
return StringUtils.hasText(name) ? name : String.valueOf(classId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 班级展示名合成:年级名 + 班级名(如「高一」+「1班」=「高一1班」),
|
||||
* 班级名已以年级名开头时不重复拼接;缺年级或无班级名时原样返回。
|
||||
*/
|
||||
public static String displayName(String className, Integer gradeId, Map<Integer, String> gradeNames) {
|
||||
if (!StringUtils.hasText(className) || gradeId == null
|
||||
|| gradeNames == null || gradeNames.isEmpty()) {
|
||||
return className;
|
||||
}
|
||||
String gradeName = gradeNames.get(gradeId);
|
||||
if (!StringUtils.hasText(gradeName) || className.startsWith(gradeName)) {
|
||||
return className;
|
||||
}
|
||||
return gradeName + className;
|
||||
}
|
||||
|
||||
/** 列表展示名兜底:实时名(含年级前缀)优先,其次班级名快照,最后班级ID */
|
||||
public static String displayOr(String liveName, String snapshotName, Integer classId) {
|
||||
if (StringUtils.hasText(liveName)) {
|
||||
return liveName;
|
||||
}
|
||||
return StringUtils.hasText(snapshotName) ? snapshotName : String.valueOf(classId);
|
||||
}
|
||||
|
||||
/** 年级ID → 年级名(bschclass GetSchLenLevellist;失败降级空映射,仅退化为不带年级前缀) */
|
||||
private Map<Integer, String> gradeNames(String schSerId) {
|
||||
Map<Integer, String> names = new LinkedHashMap<>();
|
||||
try {
|
||||
for (SchGradeDto g : classService.getSchGradeList(schSerId)) {
|
||||
if (g != null && g.getId() != null && StringUtils.hasText(g.getName())) {
|
||||
names.put(g.getId(), g.getName());
|
||||
}
|
||||
}
|
||||
} catch (BizException e) {
|
||||
log.warn("[roster] 年级列表拉取失败, 班级名退化为不带年级前缀: {}", e.getMessage());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录学生所在班级条目(实时 bschclass 全校学生按登录人用户ID过滤)。
|
||||
* 学生ID = 登录人用户中心数字ID;一人多班各一条。
|
||||
|
||||
@@ -71,6 +71,9 @@ public class StudentHomeworkService {
|
||||
@Autowired
|
||||
private RosterService rosterService;
|
||||
|
||||
@Autowired
|
||||
private SubjectDictService subjectDictService;
|
||||
|
||||
// ============================ 页 1:作业列表 ============================
|
||||
|
||||
/**
|
||||
@@ -96,8 +99,9 @@ public class StudentHomeworkService {
|
||||
.collect(Collectors.toMap(HomeworkSubmission::getHomeworkId, s -> s, (a, b) -> a));
|
||||
|
||||
List<StudentHomeworkVo> rows = new ArrayList<>();
|
||||
Map<String, String> subjectNames = subjectDictService.nameMap();
|
||||
for (Homework h : homeworks) {
|
||||
StudentHomeworkVo vo = toVo(h, myLatest.get(h.getId()));
|
||||
StudentHomeworkVo vo = toVo(h, myLatest.get(h.getId()), subjectNames);
|
||||
rows.add(vo);
|
||||
}
|
||||
rows = filterByTab(rows, tab);
|
||||
@@ -150,13 +154,11 @@ public class StudentHomeworkService {
|
||||
HomeworkSubmission latest = myLatest(homework.getId());
|
||||
|
||||
StudentHomeworkDetailVo vo = new StudentHomeworkDetailVo();
|
||||
copyBase(vo, homework, latest);
|
||||
copyBase(vo, homework, latest, subjectDictService.nameMap());
|
||||
vo.setDescription(homework.getContent());
|
||||
vo.setAttachments(homeworkAttachmentRepository.findByHomeworkIdOrderBySortAscIdAsc(id).stream()
|
||||
.map(this::toAttachmentItem).collect(Collectors.toList()));
|
||||
vo.setClasses(homeworkClassRepository.findByHomeworkId(id).stream()
|
||||
.map(c -> new ClassItem(c.getClassId(), c.getClassName()))
|
||||
.collect(Collectors.toList()));
|
||||
vo.setClasses(classDisplayItems(homeworkClassRepository.findByHomeworkId(id)));
|
||||
if (latest != null) {
|
||||
vo.setMyText(latest.getText());
|
||||
vo.setMyAttachments(submissionAttachmentRepository
|
||||
@@ -398,16 +400,31 @@ public class StudentHomeworkService {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private StudentHomeworkVo toVo(Homework h, HomeworkSubmission latest) {
|
||||
private StudentHomeworkVo toVo(Homework h, HomeworkSubmission latest, Map<String, String> subjectNames) {
|
||||
StudentHomeworkVo vo = new StudentHomeworkVo();
|
||||
copyBase(vo, h, latest);
|
||||
copyBase(vo, h, latest, subjectNames);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private void copyBase(StudentHomeworkVo vo, Homework h, HomeworkSubmission latest) {
|
||||
/**
|
||||
* 参与班级条目:展示名实时解析(bschclass 映射,含年级前缀),
|
||||
* 实时名缺失回退作业上的班级名快照,再退班级ID。
|
||||
*/
|
||||
private List<ClassItem> classDisplayItems(List<HomeworkClass> classes) {
|
||||
Map<Integer, String> classNames = rosterService.classNames(
|
||||
classes.stream().map(HomeworkClass::getClassId).collect(Collectors.toList()));
|
||||
return classes.stream()
|
||||
.map(c -> new ClassItem(c.getClassId(),
|
||||
RosterService.displayOr(classNames.get(c.getClassId()), c.getClassName(), c.getClassId())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void copyBase(StudentHomeworkVo vo, Homework h, HomeworkSubmission latest,
|
||||
Map<String, String> subjectNames) {
|
||||
vo.setId(h.getId());
|
||||
vo.setTitle(h.getTitle());
|
||||
vo.setSubject(h.getSubject());
|
||||
vo.setSubjectName(subjectDictService.resolve(h.getSubject(), subjectNames));
|
||||
vo.setTeacherName(h.getPublisherName());
|
||||
vo.setDeadlineAt(h.getDeadlineAt());
|
||||
vo.setSubmitType(SubmitTypes.parse(h.getSubmitType()));
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.yunhai.homework.service.impl;
|
||||
|
||||
import com.yunhai.homework.model.bsch.SchInfoDto;
|
||||
import com.yunhai.homework.model.bsch.SchSubjectDto;
|
||||
import com.yunhai.homework.service.IClassService;
|
||||
import com.yunhai.homework.utils.UserContext;
|
||||
import com.yunhai.platform.apistd.BizException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 本校学科字典门面(bschclass SubInfo/GetSchSublist):
|
||||
* 学科编码 → 名称解析,供各作业列表回填 subjectName 与学科清单输出共用。
|
||||
* 无学校身份或远端不可用时降级为空字典,仅影响名称展示(回退编码),不阻断业务本身。
|
||||
*/
|
||||
@Service
|
||||
public class SubjectDictService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SubjectDictService.class);
|
||||
|
||||
private final IClassService classService;
|
||||
|
||||
public SubjectDictService(IClassService classService) {
|
||||
this.classService = classService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录人学校的学科字典(保留 SubInfo 排序号,学科清单场景按其排序输出)。
|
||||
*/
|
||||
public List<SchSubjectDto> dictionary() {
|
||||
try {
|
||||
Integer schId = UserContext.getSchId();
|
||||
if (schId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
SchInfoDto sch = classService.getSchFirst(schId);
|
||||
if (sch == null || !StringUtils.hasText(sch.getSchSerId())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return classService.getSchSubList(sch.getSchSerId());
|
||||
} catch (BizException e) {
|
||||
log.warn("[subjectDict] 本校学科字典拉取失败, 降级为编码展示: {}", e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 学科编码 → 名称映射(列表回填 subjectName 用,一次批量外呼)。
|
||||
*/
|
||||
public Map<String, String> nameMap() {
|
||||
Map<String, String> names = new HashMap<>();
|
||||
for (SchSubjectDto subject : dictionary()) {
|
||||
if (subject != null && StringUtils.hasText(subject.getSubId())
|
||||
&& StringUtils.hasText(subject.getSubName())) {
|
||||
names.put(subject.getSubId(), subject.getSubName());
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学科编码 → 名称:映射命中取名称,未命中或映射缺失时回退编码本身。
|
||||
*/
|
||||
public String resolve(String code, Map<String, String> names) {
|
||||
if (!StringUtils.hasText(code)) {
|
||||
return null;
|
||||
}
|
||||
String name = names == null ? null : names.get(code);
|
||||
return StringUtils.hasText(name) ? name : code;
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,9 @@ public class TeacherHomeworkV2Service {
|
||||
@Autowired
|
||||
private IClassService classService;
|
||||
|
||||
@Autowired
|
||||
private SubjectDictService subjectDictService;
|
||||
|
||||
// ============================ 页 1:创建 / 编辑 ============================
|
||||
|
||||
/**
|
||||
@@ -254,7 +257,11 @@ public class TeacherHomeworkV2Service {
|
||||
Arrays.asList(HwStatusEnum.DRAFT_UNPUBLISHED.getCode(), HwStatusEnum.DRAFT_SCHEDULED.getCode()),
|
||||
query),
|
||||
PageUtils.toPageable(query, sort == null ? Sort.by(Sort.Direction.DESC, "updateTime") : sort));
|
||||
return PageUtils.toResult(page.map(h -> toVo(h, false, false)));
|
||||
Map<String, String> subjectNames = subjectDictService.nameMap();
|
||||
Map<Integer, List<HomeworkClass>> classesByHw = classesByHomework(page.getContent());
|
||||
Map<Integer, String> classNames = rosterService.classNames(allClassIds(classesByHw));
|
||||
return PageUtils.toResult(page.map(h -> toVo(h, false, false, subjectNames,
|
||||
classesByHw.getOrDefault(h.getId(), Collections.emptyList()), classNames)));
|
||||
}
|
||||
|
||||
// ============================ 页 3:已发布列表 ============================
|
||||
@@ -271,7 +278,11 @@ public class TeacherHomeworkV2Service {
|
||||
Page<Homework> page = homeworkRepository.findAll(
|
||||
ownedStatusSpec(UserContext.getUserId(), statuses, query),
|
||||
PageUtils.toPageable(query, sort == null ? Sort.by(Sort.Direction.DESC, "publishedAt") : sort));
|
||||
return PageUtils.toResult(page.map(h -> toVo(h, false, true)));
|
||||
Map<String, String> subjectNames = subjectDictService.nameMap();
|
||||
Map<Integer, List<HomeworkClass>> classesByHw = classesByHomework(page.getContent());
|
||||
Map<Integer, String> classNames = rosterService.classNames(allClassIds(classesByHw));
|
||||
return PageUtils.toResult(page.map(h -> toVo(h, false, true, subjectNames,
|
||||
classesByHw.getOrDefault(h.getId(), Collections.emptyList()), classNames)));
|
||||
}
|
||||
|
||||
private Specification<Homework> ownedStatusSpec(Integer publisherId, List<String> statuses, TeacherHomeworkQuery query) {
|
||||
@@ -391,7 +402,10 @@ public class TeacherHomeworkV2Service {
|
||||
@Transactional(readOnly = true)
|
||||
public HomeworkV2Vo detail(Integer id) {
|
||||
Homework homework = stateService.requireOwned(id, UserContext.getUserId());
|
||||
return toVo(homework, true, !HwStatusEnum.of(homework.getHwStatus()).isDraft());
|
||||
List<HomeworkClass> classes = homeworkClassRepository.findByHomeworkId(id);
|
||||
Map<Integer, String> classNames = rosterService.classNames(classIds(classes));
|
||||
return toVo(homework, true, !HwStatusEnum.of(homework.getHwStatus()).isDraft(),
|
||||
subjectDictService.nameMap(), classes, classNames);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -929,7 +943,7 @@ public class TeacherHomeworkV2Service {
|
||||
return ids;
|
||||
}
|
||||
|
||||
/** 学校年级ID → 年级名(bschclass GetSchLenLevellist;远端失败降级空映射,班级归入「未分组」) */
|
||||
/** 学校年级ID → 年级名(bschclass GetSchLenLevellist;远端失败降级空映射,班级归入「未分组」/不带前缀) */
|
||||
private Map<Integer, String> gradeNames(String schSerId) {
|
||||
Map<Integer, String> names = new java.util.LinkedHashMap<>();
|
||||
try {
|
||||
@@ -939,7 +953,7 @@ public class TeacherHomeworkV2Service {
|
||||
}
|
||||
}
|
||||
} catch (BizException e) {
|
||||
log.warn("[classTree] 年级列表拉取失败, 降级「未分组」展示: {}", e.getMessage());
|
||||
log.warn("[gradeNames] 年级列表拉取失败, 降级不带年级前缀展示: {}", e.getMessage());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
@@ -1006,12 +1020,14 @@ public class TeacherHomeworkV2Service {
|
||||
if (classIds.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Map<Integer, String> gradeNames = gradeNames(schSerId);
|
||||
List<TeachingClassVo> result = new ArrayList<>();
|
||||
try {
|
||||
for (SchClassDto schClass : classService.getSchClassList(schSerId)) {
|
||||
Integer classId = schClass.getClassId();
|
||||
if (classId != null && classIds.remove(classId)) {
|
||||
result.add(teachingClassVo(classId, schClass.getClassName()));
|
||||
result.add(teachingClassVo(classId, RosterService.displayName(
|
||||
schClass.getClassName(), schClass.getGradeId(), gradeNames)));
|
||||
}
|
||||
}
|
||||
} catch (BizException e) {
|
||||
@@ -1068,20 +1084,7 @@ public class TeacherHomeworkV2Service {
|
||||
* 仅影响名称解析(学科名称回退为编码),不阻断学科清单本身。
|
||||
*/
|
||||
private List<SchSubjectDto> subjectDictionary() {
|
||||
try {
|
||||
Integer schId = UserContext.getSchId();
|
||||
if (schId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
SchInfoDto sch = classService.getSchFirst(schId);
|
||||
if (sch == null || !StringUtils.hasText(sch.getSchSerId())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return classService.getSchSubList(sch.getSchSerId());
|
||||
} catch (BizException e) {
|
||||
log.warn("[subjectDictionary] 本校学科字典拉取失败, 降级为编码展示: {}", e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return subjectDictService.dictionary();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1120,11 +1123,14 @@ public class TeacherHomeworkV2Service {
|
||||
|
||||
// ============================ VO 组装 ============================
|
||||
|
||||
private HomeworkV2Vo toVo(Homework h, boolean withAttachments, boolean withProgress) {
|
||||
private HomeworkV2Vo toVo(Homework h, boolean withAttachments, boolean withProgress,
|
||||
Map<String, String> subjectNames, List<HomeworkClass> classes,
|
||||
Map<Integer, String> classNames) {
|
||||
HomeworkV2Vo vo = new HomeworkV2Vo();
|
||||
vo.setId(h.getId());
|
||||
vo.setTitle(h.getTitle());
|
||||
vo.setSubject(h.getSubject());
|
||||
vo.setSubjectName(subjectDictService.resolve(h.getSubject(), subjectNames));
|
||||
vo.setDescription(withAttachments ? h.getContent() : null);
|
||||
vo.setHwStatus(h.getHwStatus());
|
||||
vo.setHwStatusDesc(HwStatusEnum.of(h.getHwStatus()).getDesc());
|
||||
@@ -1136,8 +1142,10 @@ public class TeacherHomeworkV2Service {
|
||||
vo.setAllowLate(h.getAllowLate());
|
||||
vo.setRemind1d(h.getRemind1d());
|
||||
vo.setRemind1h(h.getRemind1h());
|
||||
vo.setClasses(homeworkClassRepository.findByHomeworkId(h.getId()).stream()
|
||||
.map(c -> new ClassItem(c.getClassId(), c.getClassName()))
|
||||
// 班级展示名实时解析(含年级前缀),存量快照仅作兜底
|
||||
vo.setClasses(classes.stream()
|
||||
.map(c -> new ClassItem(c.getClassId(),
|
||||
RosterService.displayOr(classNames.get(c.getClassId()), c.getClassName(), c.getClassId())))
|
||||
.collect(Collectors.toList()));
|
||||
if (withAttachments) {
|
||||
vo.setAttachments(homeworkAttachmentRepository.findByHomeworkIdOrderBySortAscIdAsc(h.getId()).stream()
|
||||
@@ -1156,11 +1164,31 @@ public class TeacherHomeworkV2Service {
|
||||
vo.setCreateTime(h.getCreateTime());
|
||||
vo.setUpdateTime(h.getUpdateTime());
|
||||
if (withProgress && !HwStatusEnum.of(h.getHwStatus()).isDraft()) {
|
||||
vo.setProgress(progressCalculator.compute(h.getId(), classIds(homeworkClassRepository.findByHomeworkId(h.getId()))).total);
|
||||
vo.setProgress(progressCalculator.compute(h.getId(), classIds(classes)).total);
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页内作业的班级关联分组(findByHomeworkIds 批量一次查询,避免逐行 N+1)。
|
||||
*/
|
||||
private Map<Integer, List<HomeworkClass>> classesByHomework(List<Homework> homeworks) {
|
||||
List<Integer> ids = homeworks.stream().map(Homework::getId).collect(Collectors.toList());
|
||||
if (ids.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return homeworkClassRepository.findByHomeworkIds(ids).stream()
|
||||
.collect(Collectors.groupingBy(HomeworkClass::getHomeworkId));
|
||||
}
|
||||
|
||||
private static List<Integer> allClassIds(Map<Integer, List<HomeworkClass>> classesByHw) {
|
||||
return classesByHw.values().stream()
|
||||
.flatMap(List::stream)
|
||||
.map(HomeworkClass::getClassId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ============================ 名册辅助 ============================
|
||||
|
||||
/** 参与班级在班学生 id 集合(实时 bschclass 名册) */
|
||||
|
||||
Reference in New Issue
Block a user