获取任教班级
build / build-push (push) Failing after 15s

This commit is contained in:
wanghu
2026-09-20 20:55:59 +08:00
parent c31ff87479
commit 2d549eb065
23 changed files with 189 additions and 460 deletions
@@ -25,7 +25,7 @@ import java.util.Map;
/** /**
* 课堂作业 V2 学生端接口(PRD §18 路径一字对齐:/student/homeworks…)。 * 课堂作业 V2 学生端接口(PRD §18 路径一字对齐:/student/homeworks…)。
* <p> * <p>
* 身份:当前登录用户ID ↔ class_student 名册;仅可见本人班级作业,禁 IDOR。 * 身份:当前登录用户ID ↔ bschclass 学生名册(实时匹配);仅可见本人班级作业,禁 IDOR。
*/ */
@RestController @RestController
@RequestMapping(value = Config.API_PATH + "/student/homeworks") @RequestMapping(value = Config.API_PATH + "/student/homeworks")
@@ -17,7 +17,6 @@ import com.yunhai.homework.model.v2.SubmissionVersionVo;
import com.yunhai.homework.model.v2.SubjectDto; import com.yunhai.homework.model.v2.SubjectDto;
import com.yunhai.homework.model.v2.TeacherHomeworkQuery; import com.yunhai.homework.model.v2.TeacherHomeworkQuery;
import com.yunhai.homework.model.v2.TeachingClassVo; import com.yunhai.homework.model.v2.TeachingClassVo;
import com.yunhai.homework.service.impl.ClassStudentService;
import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; import com.yunhai.homework.service.impl.TeacherHomeworkV2Service;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
@@ -51,9 +50,6 @@ public class TeacherHomeworkV2Controller {
@Autowired @Autowired
private TeacherHomeworkV2Service teacherHomeworkV2Service; private TeacherHomeworkV2Service teacherHomeworkV2Service;
@Autowired
private ClassStudentService classStudentService;
// ============================ 页 1:发布作业 ============================ // ============================ 页 1:发布作业 ============================
/** 创建作业(落草稿) */ /** 创建作业(落草稿) */
@@ -198,22 +194,15 @@ public class TeacherHomeworkV2Controller {
/** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */ /** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */
@GetMapping("/subjects/classes") @GetMapping("/subjects/classes")
public List<TeachingClassVo> teachingClasses(@RequestParam("subId") String subId) { public List<TeachingClassVo> teachingClasses(@RequestParam("subject") String subject) {
return teacherHomeworkV2Service.teachingClasses(subId); return teacherHomeworkV2Service.teachingClasses(subject);
} }
// ============================ 辅助:班级树 / 名册同步 ============================ // ============================ 辅助:班级树 / 名册同步 ============================
/** 年级→班级树(发布选班;名册快照聚合) */ /** 年级→班级树(发布选班;subId 传学科编码时仅返回该学科任教班级) */
@GetMapping("/classes") @GetMapping("/classes")
public List<ClassTreeNode> classTree() { public List<ClassTreeNode> classTree(@RequestParam(value = "subject", required = false) String subject) {
return teacherHomeworkV2Service.classTree(); return teacherHomeworkV2Service.classTree(subject);
}
/** 手动触发名册同步(bschclass 接口就绪前为空操作) */
@PostMapping("/classes/sync")
public Map<String, Integer> syncClasses() {
int synced = classStudentService.syncAll();
return java.util.Collections.singletonMap("synced", synced);
} }
} }
@@ -1,59 +0,0 @@
package com.yunhai.homework.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.time.LocalDateTime;
/**
* 班级学生名册快照(PRD class_student)。
* <p>
* 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发;
* 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId())
* ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。
*/
@NoArgsConstructor
@Data
@Entity
@Table(name = "class_student",
uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"}))
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class ClassStudent extends BaseEntity {
/** 学校ID */
@Column(name = "school_id", nullable = false)
private Integer schoolId;
/** 班级ID */
@Column(name = "class_id", nullable = false, length = 64)
private String classId;
/** 班级名快照 */
@Column(name = "class_name", length = 50)
private String className;
/** 年级名快照 */
@Column(name = "grade_name", length = 50)
private String gradeName;
/** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */
@Column(name = "student_id", nullable = false)
private Integer studentId;
/** 学生姓名 */
@Column(name = "student_name", nullable = false, length = 50)
private String studentName;
/** 在班状态:1 在班 / 0 已转出退课 */
@Column(name = "status", nullable = false)
private Integer status = 1;
/** 离班时间 */
@Column(name = "leave_at")
private LocalDateTime leaveAt;
}
@@ -26,8 +26,8 @@ public class HomeworkClass extends BaseEntity {
private Integer homeworkId; private Integer homeworkId;
/** 班级ID */ /** 班级ID */
@Column(name = "class_id", nullable = false, length = 64) @Column(name = "class_id", nullable = false)
private String classId; private Integer classId;
/** 班级名快照 */ /** 班级名快照 */
@Column(name = "class_name", length = 50) @Column(name = "class_name", length = 50)
@@ -37,8 +37,8 @@ public class HomeworkReadRecord extends BaseEntity {
private String studentName; private String studentName;
/** 班级ID */ /** 班级ID */
@Column(name = "class_id", length = 64) @Column(name = "class_id")
private String classId; private Integer classId;
/** 阅读状态:0 UNREAD / 1 READ */ /** 阅读状态:0 UNREAD / 1 READ */
@Column(name = "is_read", nullable = false) @Column(name = "is_read", nullable = false)
@@ -41,8 +41,8 @@ public class HomeworkSubmission extends BaseEntity {
private String studentName; private String studentName;
/** 班级ID */ /** 班级ID */
@Column(name = "class_id", nullable = false, length = 64) @Column(name = "class_id", nullable = false)
private String classId; private Integer classId;
/** 班级名快照 */ /** 班级名快照 */
@Column(name = "class_name", nullable = false, length = 50) @Column(name = "class_name", nullable = false, length = 50)
@@ -1,46 +0,0 @@
package com.yunhai.homework.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。
* <p>
* 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口
* 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId());
* class_id 与 class_student.class_id 同一取值域。
*/
@NoArgsConstructor
@Data
@Entity
@Table(name = "sch_class_teach",
uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"}))
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class SchClassTeach extends BaseEntity {
/** 学校ID */
@Column(name = "school_id", nullable = false)
private Integer schoolId;
/** 班级ID(与 class_student.class_id 同一取值域) */
@Column(name = "class_id", nullable = false, length = 64)
private String classId;
/** 教师ID(用户中心数字ID) */
@Column(name = "tea_id", nullable = false)
private Integer teaId;
/** 学科编码(bschclass SubID;本地无字典时编码即展示名) */
@Column(name = "sub_id", nullable = false, length = 64)
private String subId;
/** 有效状态:1 有效 / 0 失效 */
@Column(name = "status", nullable = false)
private Integer status = 1;
}
@@ -3,9 +3,7 @@ package com.yunhai.homework.job;
import com.yunhai.homework.entity.Homework; import com.yunhai.homework.entity.Homework;
import com.yunhai.homework.enums.HwStatusEnum; import com.yunhai.homework.enums.HwStatusEnum;
import com.yunhai.homework.enums.NotificationTypeEnum; import com.yunhai.homework.enums.NotificationTypeEnum;
import com.yunhai.homework.repository.HomeworkClassRepository;
import com.yunhai.homework.repository.HomeworkRepository; import com.yunhai.homework.repository.HomeworkRepository;
import com.yunhai.homework.service.impl.ClassStudentService;
import com.yunhai.homework.service.impl.HomeworkStateService; import com.yunhai.homework.service.impl.HomeworkStateService;
import com.yunhai.homework.service.impl.NotificationService; import com.yunhai.homework.service.impl.NotificationService;
import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; import com.yunhai.homework.service.impl.TeacherHomeworkV2Service;
@@ -33,9 +31,6 @@ public class HomeworkScheduledJobs {
@Autowired @Autowired
private HomeworkRepository homeworkRepository; private HomeworkRepository homeworkRepository;
@Autowired
private HomeworkClassRepository homeworkClassRepository;
@Autowired @Autowired
private HomeworkStateService stateService; private HomeworkStateService stateService;
@@ -45,9 +40,6 @@ public class HomeworkScheduledJobs {
@Autowired @Autowired
private TeacherHomeworkV2Service teacherHomeworkV2Service; private TeacherHomeworkV2Service teacherHomeworkV2Service;
@Autowired
private ClassStudentService classStudentService;
/** /**
* 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知;
* 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。
@@ -146,16 +138,4 @@ public class HomeworkScheduledJobs {
NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")");
log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent);
} }
/**
* 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。
*/
@Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}")
public void classDataSync() {
try {
classStudentService.syncAll();
} catch (Exception e) {
log.error("[classDataSync] 名册同步失败", e);
}
}
} }
@@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class ClassItem { public class ClassItem {
private String classId; private Integer classId;
private String className; private String className;
} }
@@ -7,12 +7,12 @@ import java.util.List;
/** /**
* 年级→班级树(发布作业选班 + 应交名单基数来源)。 * 年级→班级树(发布作业选班 + 应交名单基数来源)。
* 叶子节点 id = 班级 id(class_student 快照聚合)。 * 叶子节点 id = 班级 id(bschclass 班级/年级实时组装)。
*/ */
@Data @Data
public class ClassTreeNode { public class ClassTreeNode {
/** 年级节点为 gradeName,班级叶子为 classId */ /** 年级节点为年级ID(无年级归属时为「未分组」文案),班级叶子为 classId */
private String id; private String id;
/** 展示名 */ /** 展示名 */
@@ -27,7 +27,7 @@ public class HomeworkStatsVo {
@Data @Data
public static class ByClass { public static class ByClass {
private String classId; private Integer classId;
private String className; private String className;
@@ -26,7 +26,7 @@ public class HomeworkV2Request {
private String description; private String description;
/** 参与班级 id 列表 */ /** 参与班级 id 列表 */
private List<String> classIds; private List<Integer> classIds;
/** 附件列表 */ /** 附件列表 */
private List<AttachmentItem> attachments; private List<AttachmentItem> attachments;
@@ -12,7 +12,7 @@ import lombok.EqualsAndHashCode;
public class StudentMatrixQuery extends BasePageQuery { public class StudentMatrixQuery extends BasePageQuery {
/** 班级筛选(空 = 全部参与班级) */ /** 班级筛选(空 = 全部参与班级) */
private String classId; private Integer classId;
/** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */
private String submitStatus; private String submitStatus;
@@ -21,7 +21,7 @@ public class StudentMatrixVo {
/** 姓名 */ /** 姓名 */
private String studentName; private String studentName;
private String classId; private Integer classId;
private String className; private String className;
@@ -9,7 +9,7 @@ import lombok.Data;
public class TeachingClassVo { public class TeachingClassVo {
/** 班级业务ID(bschclass sch_class.class_id) */ /** 班级业务ID(bschclass sch_class.class_id) */
private String classId; private Integer classId;
/** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */
private String className; private String className;
@@ -1,31 +0,0 @@
package com.yunhai.homework.repository;
import com.yunhai.homework.entity.ClassStudent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* 班级学生名册仓储。
*/
public interface ClassStudentRepository extends JpaRepository<ClassStudent, Integer> {
/** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */
List<ClassStudent> findByStudentIdAndStatus(Integer studentId, Integer status);
/** 班级在班学生 */
List<ClassStudent> findByClassIdAndStatus(String classId, Integer status);
/** 指定班级集合的全部在班学生(应交名单) */
List<ClassStudent> findByClassIdInAndStatus(List<String> classIds, Integer status);
/** 指定学校的全部名册(同步任务全量刷新用) */
List<ClassStudent> findBySchoolId(Integer schoolId);
/** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */
@Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " +
"where c.schoolId = :schoolId and c.status = 1")
List<Object[]> findDistinctClasses(@Param("schoolId") Integer schoolId);
}
@@ -18,7 +18,7 @@ public interface HomeworkClassRepository extends JpaRepository<HomeworkClass, In
/** 指定班级的全部作业ID(学生端列表倒查用) */ /** 指定班级的全部作业ID(学生端列表倒查用) */
@Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds")
List<Integer> findHomeworkIdsByClassIds(@Param("classIds") List<String> classIds); List<Integer> findHomeworkIdsByClassIds(@Param("classIds") List<Integer> classIds);
/** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */
@Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds")
@@ -1,22 +0,0 @@
package com.yunhai.homework.repository;
import com.yunhai.homework.entity.SchClassTeach;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* 班级任教关系快照仓储。
*/
public interface SchClassTeachRepository extends JpaRepository<SchClassTeach, Integer> {
/** 指定学校指定教师的全部有效任教记录 */
List<SchClassTeach> findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status);
/** 指定学校指定教师去重学科编码(字典序由调用方保证) */
@Query("select distinct t.subId from SchClassTeach t " +
"where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1")
List<String> findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId);
}
@@ -1,81 +0,0 @@
package com.yunhai.homework.service.impl;
import com.yunhai.homework.entity.ClassStudent;
import com.yunhai.homework.repository.ClassStudentRepository;
import com.yunhai.homework.utils.UserContext;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* 班级学生名册服务:学生端身份映射 + 名册同步任务入口。
* <p>
* 身份口径:当前登录人用户中心数字ID(UserContext.getUserId(),即 SchStudent.UserStuSerID)
* ↔ class_student.student_id(status=1 在班)。名册不再存登录账号。
* bschclass 名册开放接口未就绪期间(计划 §13-1),联调用种子数据
* (sql/v3_seed.sql)填充本表;syncAll() 预留同步实现位。
*/
@Service
public class ClassStudentService {
private static final Logger log = LoggerFactory.getLogger(ClassStudentService.class);
@Autowired
private ClassStudentRepository classStudentRepository;
/**
* 当前登录用户ID对应的学生名册行(在班)。
*
* @throws BizException 未登录或用户ID未匹配名册
*/
public List<ClassStudent> resolveMe() {
Integer userId = UserContext.getUserId();
if (userId == null) {
throw new BizException(ErrorCode.FORBIDDEN, "未获取到登录身份");
}
List<ClassStudent> me = classStudentRepository.findByStudentIdAndStatus(userId, 1);
if (me.isEmpty()) {
throw new BizException(ErrorCode.FORBIDDEN, "当前用户未匹配到学生名册,请联系管理员确认班级名册");
}
return me;
}
/**
* 学生 id(多班级重复时取第一个;学籍号一人一号,正常唯一)。
*/
public Integer resolveMyStudentId() {
return resolveMe().get(0).getStudentId();
}
/**
* 我所在的班级 id 集合。
*/
public List<String> resolveMyClassIds() {
return resolveMe().stream().map(ClassStudent::getClassId).distinct().collect(Collectors.toList());
}
/**
* 名册全量同步(每日 Job / 手动触发)。
* TODO bschclass「按学校查年级班级树+学生名册」开放接口就绪后接入
* (参照 ClassService.getSchTeaList 的直连模式);未就绪时仅记录日志跳过。
*/
@Transactional
public int syncAll() {
log.warn("[syncAll] bschclass 名册开放接口未就绪,本轮跳过(联调请执行 sql/v3_seed.sql 种子数据)");
return 0;
}
/**
* 班级在班学生(运维排查/种子校验用)。
*/
public List<ClassStudent> findByClassId(String classId) {
return classStudentRepository.findByClassIdAndStatus(classId, 1);
}
}
@@ -1,19 +1,15 @@
package com.yunhai.homework.service.impl; package com.yunhai.homework.service.impl;
import com.yunhai.homework.entity.ClassStudent;
import com.yunhai.homework.entity.HomeworkReadRecord; import com.yunhai.homework.entity.HomeworkReadRecord;
import com.yunhai.homework.entity.HomeworkSubmission; import com.yunhai.homework.entity.HomeworkSubmission;
import com.yunhai.homework.enums.SubmitStatusEnum; import com.yunhai.homework.enums.SubmitStatusEnum;
import com.yunhai.homework.model.v2.ProgressVo; import com.yunhai.homework.model.v2.ProgressVo;
import com.yunhai.homework.repository.ClassStudentRepository;
import com.yunhai.homework.repository.HomeworkReadRecordRepository; import com.yunhai.homework.repository.HomeworkReadRecordRepository;
import com.yunhai.homework.repository.HomeworkSubmissionRepository; import com.yunhai.homework.repository.HomeworkSubmissionRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -27,40 +23,31 @@ import java.util.stream.Collectors;
* 单作业量级 ≤ 数百人,全部实时聚合不落冗余字段: * 单作业量级 ≤ 数百人,全部实时聚合不落冗余字段:
* should = 参与班级在班学生去重数;submitted = 当前版本 status ∈ 已交集合; * should = 参与班级在班学生去重数;submitted = 当前版本 status ∈ 已交集合;
* graded = REVIEWED;unreadUndone = should −(已读 ∪ 已交)。 * graded = REVIEWED;unreadUndone = should −(已读 ∪ 已交)。
* 名册实时取 bschclass({@link RosterService},GetClaArrStulist),无本地快照。
*/ */
@Service @Service
public class HomeworkProgressCalculator { public class HomeworkProgressCalculator {
/**
* 名册条目(去重前):矩阵与提醒名单需要 班级×学生 维度。
*/
public static class RosterEntry {
public Integer studentId;
public String studentName;
public String classId;
public String className;
}
/** /**
* 聚合结果:总进度 + 分班进度 + 全量名册(班级×学生)。 * 聚合结果:总进度 + 分班进度 + 全量名册(班级×学生)。
*/ */
public static class Result { public static class Result {
public ProgressVo total; public ProgressVo total;
public List<ClassProgress> byClass = new ArrayList<>(); public List<ClassProgress> byClass = new ArrayList<>();
public List<RosterEntry> roster = new ArrayList<>(); public List<RosterService.RosterEntry> roster = new ArrayList<>();
} }
/** /**
* 分班进度条目。 * 分班进度条目。
*/ */
public static class ClassProgress { public static class ClassProgress {
public String classId; public Integer classId;
public String className; public String className;
public ProgressVo progress; public ProgressVo progress;
} }
@Autowired @Autowired
private ClassStudentRepository classStudentRepository; private RosterService rosterService;
@Autowired @Autowired
private HomeworkReadRecordRepository readRecordRepository; private HomeworkReadRecordRepository readRecordRepository;
@@ -73,25 +60,18 @@ public class HomeworkProgressCalculator {
* *
* @param classIds 参与班级 id(homework_class 解析结果) * @param classIds 参与班级 id(homework_class 解析结果)
*/ */
public Result compute(Integer homeworkId, List<String> classIds) { public Result compute(Integer homeworkId, List<Integer> classIds) {
Result result = new Result(); Result result = new Result();
// 1. 名册:班级×学生(应交基数) // 1. 名册:班级×学生(应交基数,实时 bschclass)
List<ClassStudent> roster = classIds == null || classIds.isEmpty() List<RosterService.RosterEntry> roster = rosterService.roster(classIds);
? new ArrayList<>()
: classStudentRepository.findByClassIdInAndStatus(classIds, 1);
Set<Integer> distinctStudents = new HashSet<>(); Set<Integer> distinctStudents = new HashSet<>();
for (ClassStudent cs : roster) { for (RosterService.RosterEntry cs : roster) {
RosterEntry entry = new RosterEntry(); result.roster.add(cs);
entry.studentId = cs.getStudentId(); distinctStudents.add(cs.studentId);
entry.studentName = cs.getStudentName();
entry.classId = cs.getClassId();
entry.className = cs.getClassName();
result.roster.add(entry);
distinctStudents.add(cs.getStudentId());
} }
result.roster.sort(Comparator result.roster.sort(java.util.Comparator
.comparing((RosterEntry e) -> nullSafe(e.className)) .comparing((RosterService.RosterEntry e) -> nullSafe(e.className))
.thenComparing(e -> nullSafe(e.studentName))); .thenComparing(e -> nullSafe(e.studentName)));
// 2. 已读集合 // 2. 已读集合
@@ -109,9 +89,9 @@ public class HomeworkProgressCalculator {
result.total = aggregate(distinctStudents, readSet, latest); result.total = aggregate(distinctStudents, readSet, latest);
// 5. 分班进度(班级维度不去重,矩阵口径) // 5. 分班进度(班级维度不去重,矩阵口径)
Map<String, List<RosterEntry>> byClassEntries = result.roster.stream() Map<Integer, List<RosterService.RosterEntry>> byClassEntries = result.roster.stream()
.collect(Collectors.groupingBy(e -> nullSafe(e.classId), LinkedHashMap::new, Collectors.toList())); .collect(Collectors.groupingBy(e -> e.classId, LinkedHashMap::new, Collectors.toList()));
for (Map.Entry<String, List<RosterEntry>> e : byClassEntries.entrySet()) { for (Map.Entry<Integer, List<RosterService.RosterEntry>> e : byClassEntries.entrySet()) {
Set<Integer> classStudentIds = e.getValue().stream().map(r -> r.studentId).collect(Collectors.toSet()); Set<Integer> classStudentIds = e.getValue().stream().map(r -> r.studentId).collect(Collectors.toSet());
ClassProgress cp = new ClassProgress(); ClassProgress cp = new ClassProgress();
cp.classId = e.getKey(); cp.classId = e.getKey();
@@ -354,14 +354,14 @@ public class HomeworkService implements IHomeworkService {
if (collectableIds.isEmpty()) { if (collectableIds.isEmpty()) {
return; return;
} }
Map<Integer, List<String>> classMap = homeworkClassRepository.findByHomeworkIds(collectableIds).stream() Map<Integer, List<Integer>> classMap = homeworkClassRepository.findByHomeworkIds(collectableIds).stream()
.collect(Collectors.groupingBy(c -> c.getHomeworkId(), .collect(Collectors.groupingBy(c -> c.getHomeworkId(),
Collectors.mapping(c -> c.getClassId(), Collectors.toList()))); Collectors.mapping(c -> c.getClassId(), Collectors.toList())));
for (HomeworkVo vo : content) { for (HomeworkVo vo : content) {
if (!collectableIds.contains(vo.getId())) { if (!collectableIds.contains(vo.getId())) {
continue; continue;
} }
List<String> classIds = classMap.getOrDefault(vo.getId(), Collections.emptyList()); List<Integer> classIds = classMap.getOrDefault(vo.getId(), Collections.emptyList());
ProgressVo progress = homeworkProgressCalculator.compute(vo.getId(), classIds).total; ProgressVo progress = homeworkProgressCalculator.compute(vo.getId(), classIds).total;
vo.setProgress(progress); vo.setProgress(progress);
} }
@@ -776,7 +776,7 @@ public class HomeworkService implements IHomeworkService {
} }
List<Integer> hwIds = collected.stream().map(Homework::getId).collect(Collectors.toList()); List<Integer> hwIds = collected.stream().map(Homework::getId).collect(Collectors.toList());
Map<Integer, List<String>> classMap = homeworkClassRepository.findByHomeworkIds(hwIds).stream() Map<Integer, List<Integer>> classMap = homeworkClassRepository.findByHomeworkIds(hwIds).stream()
.collect(Collectors.groupingBy(HomeworkClass::getHomeworkId, .collect(Collectors.groupingBy(HomeworkClass::getHomeworkId,
Collectors.mapping(HomeworkClass::getClassId, Collectors.toList()))); Collectors.mapping(HomeworkClass::getClassId, Collectors.toList())));
@@ -787,7 +787,7 @@ public class HomeworkService implements IHomeworkService {
long excellent = 0, good = 0, pass = 0, fail = 0; long excellent = 0, good = 0, pass = 0, fail = 0;
for (Homework h : collected) { for (Homework h : collected) {
List<String> classIds = classMap.getOrDefault(h.getId(), Collections.emptyList()); List<Integer> classIds = classMap.getOrDefault(h.getId(), Collections.emptyList());
ProgressVo p = homeworkProgressCalculator.compute(h.getId(), classIds).total; ProgressVo p = homeworkProgressCalculator.compute(h.getId(), classIds).total;
HomeworkStatisticsVo.HomeworkStat d = new HomeworkStatisticsVo.HomeworkStat(); HomeworkStatisticsVo.HomeworkStat d = new HomeworkStatisticsVo.HomeworkStat();
@@ -898,8 +898,7 @@ public class HomeworkService implements IHomeworkService {
return names; return names;
} }
return classes.stream() return classes.stream()
.map(HomeworkClass::getClassId) .map(c -> String.valueOf(c.getClassId()))
.filter(StringUtils::hasText)
.collect(Collectors.joining("、")); .collect(Collectors.joining("、"));
} }
@@ -1,6 +1,5 @@
package com.yunhai.homework.service.impl; package com.yunhai.homework.service.impl;
import com.yunhai.homework.entity.ClassStudent;
import com.yunhai.homework.entity.Homework; import com.yunhai.homework.entity.Homework;
import com.yunhai.homework.entity.HomeworkAttachment; import com.yunhai.homework.entity.HomeworkAttachment;
import com.yunhai.homework.entity.HomeworkClass; import com.yunhai.homework.entity.HomeworkClass;
@@ -19,7 +18,6 @@ import com.yunhai.homework.model.v2.StudentResultVo;
import com.yunhai.homework.model.v2.StudentSubmitRequest; import com.yunhai.homework.model.v2.StudentSubmitRequest;
import com.yunhai.homework.model.v2.SubmissionActionRequest; import com.yunhai.homework.model.v2.SubmissionActionRequest;
import com.yunhai.homework.model.v2.SubmissionVersionVo; import com.yunhai.homework.model.v2.SubmissionVersionVo;
import com.yunhai.homework.repository.ClassStudentRepository;
import com.yunhai.homework.repository.HomeworkAttachmentRepository; import com.yunhai.homework.repository.HomeworkAttachmentRepository;
import com.yunhai.homework.repository.HomeworkClassRepository; import com.yunhai.homework.repository.HomeworkClassRepository;
import com.yunhai.homework.repository.HomeworkReadRecordRepository; import com.yunhai.homework.repository.HomeworkReadRecordRepository;
@@ -46,8 +44,8 @@ import java.util.stream.Collectors;
/** /**
* 课堂作业 V2 学生端业务(PRD §18 学生页 1~3)。 * 课堂作业 V2 学生端业务(PRD §18 学生页 1~3)。
* <p> * <p>
* 身份:登录平台用户ID ↔ class_student.student_id 名册(ClassStudentService);仅可见本人班级作业。 * 身份:登录平台用户ID ↔ bschclass 学生名册({@link RosterService} 实时匹配,无本地快照);
* 提交多版本:提交/打回重交/撤回均新增行(version+1、is_latest 翻转),历史保留。 * 仅可见本人班级作业。提交多版本:提交/打回重交/撤回均新增行(version+1、is_latest 翻转),历史保留。
*/ */
@Service @Service
public class StudentHomeworkService { public class StudentHomeworkService {
@@ -71,10 +69,7 @@ public class StudentHomeworkService {
private SubmissionAttachmentRepository submissionAttachmentRepository; private SubmissionAttachmentRepository submissionAttachmentRepository;
@Autowired @Autowired
private ClassStudentRepository classStudentRepository; private RosterService rosterService;
@Autowired
private ClassStudentService classStudentService;
// ============================ 页 1:作业列表 ============================ // ============================ 页 1:作业列表 ============================
@@ -83,9 +78,9 @@ public class StudentHomeworkService {
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
public BasePageResult<StudentHomeworkVo> list(String tab, com.yunhai.homework.model.BasePageQuery query) { public BasePageResult<StudentHomeworkVo> list(String tab, com.yunhai.homework.model.BasePageQuery query) {
List<ClassStudent> me = classStudentService.resolveMe(); List<RosterService.RosterEntry> me = rosterService.mine();
List<String> myClassIds = me.stream().map(ClassStudent::getClassId).distinct().collect(Collectors.toList()); List<Integer> myClassIds = me.stream().map(e -> e.classId).distinct().collect(Collectors.toList());
Integer myStudentId = me.get(0).getStudentId(); Integer myStudentId = me.get(0).studentId;
List<Integer> homeworkIds = homeworkClassRepository.findHomeworkIdsByClassIds(myClassIds); List<Integer> homeworkIds = homeworkClassRepository.findHomeworkIdsByClassIds(myClassIds);
if (homeworkIds.isEmpty()) { if (homeworkIds.isEmpty()) {
@@ -182,16 +177,16 @@ public class StudentHomeworkService {
if (status != HwStatusEnum.PUBLISHED && status != HwStatusEnum.CLOSED) { if (status != HwStatusEnum.PUBLISHED && status != HwStatusEnum.CLOSED) {
return; return;
} }
ClassStudent mine = classStudentService.resolveMe().get(0); RosterService.RosterEntry mine = rosterService.mine().get(0);
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
HomeworkReadRecord record = readRecordRepository.findByHomeworkIdAndStudentId(id, mine.getStudentId()) HomeworkReadRecord record = readRecordRepository.findByHomeworkIdAndStudentId(id, mine.studentId)
.orElse(null); .orElse(null);
if (record == null) { if (record == null) {
record = new HomeworkReadRecord(); record = new HomeworkReadRecord();
record.setHomeworkId(id); record.setHomeworkId(id);
record.setStudentId(mine.getStudentId()); record.setStudentId(mine.studentId);
record.setStudentName(mine.getStudentName()); record.setStudentName(mine.studentName);
record.setClassId(mine.getClassId()); record.setClassId(mine.classId);
record.setIsRead(1); record.setIsRead(1);
record.setFirstReadAt(now); record.setFirstReadAt(now);
record.setLastReadAt(now); record.setLastReadAt(now);
@@ -210,9 +205,9 @@ public class StudentHomeworkService {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public StudentResultVo result(Integer id) { public StudentResultVo result(Integer id) {
Homework homework = requireVisible(id); Homework homework = requireVisible(id);
ClassStudent mine = classStudentService.resolveMe().get(0); RosterService.RosterEntry mine = rosterService.mine().get(0);
List<HomeworkSubmission> versions = List<HomeworkSubmission> versions =
submissionRepository.findByHomeworkIdAndStudentIdOrderByVersionDesc(id, mine.getStudentId()); submissionRepository.findByHomeworkIdAndStudentIdOrderByVersionDesc(id, mine.studentId);
StudentResultVo vo = new StudentResultVo(); StudentResultVo vo = new StudentResultVo();
vo.setHomeworkId(id); vo.setHomeworkId(id);
vo.setHomeworkTitle(homework.getTitle()); vo.setHomeworkTitle(homework.getTitle());
@@ -281,7 +276,7 @@ public class StudentHomeworkService {
// ============================ 提交内核 ============================ // ============================ 提交内核 ============================
private Integer doSubmit(Homework homework, String text, List<AttachmentItem> attachments) { private Integer doSubmit(Homework homework, String text, List<AttachmentItem> attachments) {
ClassStudent mine = classStudentService.resolveMe().get(0); RosterService.RosterEntry mine = rosterService.mine().get(0);
HwStatusEnum hwStatus = HwStatusEnum.of(homework.getHwStatus()); HwStatusEnum hwStatus = HwStatusEnum.of(homework.getHwStatus());
if (hwStatus == HwStatusEnum.ARCHIVED) { if (hwStatus == HwStatusEnum.ARCHIVED) {
throw new BizException(ErrorCode.FORBIDDEN, "作业已归档,不可提交"); throw new BizException(ErrorCode.FORBIDDEN, "作业已归档,不可提交");
@@ -320,9 +315,9 @@ public class StudentHomeworkService {
} }
HomeworkSubmission submission = new HomeworkSubmission(); HomeworkSubmission submission = new HomeworkSubmission();
fillSnapshot(submission, homework); fillSnapshot(submission, homework);
submission.setStudentId(mine.getStudentId()); submission.setStudentId(mine.studentId);
submission.setStudentName(mine.getStudentName()); submission.setStudentName(mine.studentName);
submission.setVersion(nextVersion(homework.getId(), mine.getStudentId())); submission.setVersion(nextVersion(homework.getId(), mine.studentId));
submission.setIsLatest(1); submission.setIsLatest(1);
submission.setStatus(late ? SubmitStatusEnum.LATE_SUBMITTED.getCode() : SubmitStatusEnum.SUBMITTED.getCode()); submission.setStatus(late ? SubmitStatusEnum.LATE_SUBMITTED.getCode() : SubmitStatusEnum.SUBMITTED.getCode());
submission.setIsLate(late ? 1 : 0); submission.setIsLate(late ? 1 : 0);
@@ -333,10 +328,10 @@ public class StudentHomeworkService {
int sort = 0; int sort = 0;
for (AttachmentItem item : items) { for (AttachmentItem item : items) {
SubmissionAttachment sa = new SubmissionAttachment(); SubmissionAttachment sa = new SubmissionAttachment();
sa.setSubmissionId(submission.getId()); sa.setSubmissionId(submission.getId());
sa.setHomeworkId(homework.getId()); sa.setHomeworkId(homework.getId());
sa.setStudentId(mine.getStudentId()); sa.setStudentId(mine.studentId);
sa.setFileId(item.getFileId()); sa.setFileId(item.getFileId());
sa.setFileUrl(item.getFileUrl()); sa.setFileUrl(item.getFileUrl());
sa.setFileType(item.getFileType()); sa.setFileType(item.getFileType());
@@ -355,15 +350,15 @@ public class StudentHomeworkService {
private void fillSnapshot(HomeworkSubmission submission, Homework homework) { private void fillSnapshot(HomeworkSubmission submission, Homework homework) {
submission.setHomeworkId(homework.getId()); submission.setHomeworkId(homework.getId());
ClassStudent mine = classStudentService.resolveMe().get(0); RosterService.RosterEntry mine = rosterService.mine().get(0);
submission.setClassId(mine.getClassId()); submission.setClassId(mine.classId);
submission.setClassName(mine.getClassName() == null ? mine.getClassId() : mine.getClassName()); submission.setClassName(mine.className == null ? String.valueOf(mine.classId) : mine.className);
// studentId/studentName 由调用方覆盖(fillSnapshot 先行填充默认) // studentId/studentName 由调用方覆盖(fillSnapshot 先行填充默认)
if (submission.getStudentId() == null) { if (submission.getStudentId() == null) {
submission.setStudentId(mine.getStudentId()); submission.setStudentId(mine.studentId);
} }
if (submission.getStudentName() == null) { if (submission.getStudentName() == null) {
submission.setStudentName(mine.getStudentName()); submission.setStudentName(mine.studentName);
} }
} }
@@ -378,7 +373,8 @@ public class StudentHomeworkService {
if (!visibleStatus(HwStatusEnum.of(homework.getHwStatus()))) { if (!visibleStatus(HwStatusEnum.of(homework.getHwStatus()))) {
throw new BizException(ErrorCode.FORBIDDEN, "作业不在可见范围"); throw new BizException(ErrorCode.FORBIDDEN, "作业不在可见范围");
} }
List<String> myClassIds = classStudentService.resolveMyClassIds(); List<Integer> myClassIds = rosterService.mine().stream()
.map(e -> e.classId).distinct().collect(Collectors.toList());
boolean hit = homeworkClassRepository.findByHomeworkId(homeworkId).stream() boolean hit = homeworkClassRepository.findByHomeworkId(homeworkId).stream()
.map(HomeworkClass::getClassId) .map(HomeworkClass::getClassId)
.anyMatch(myClassIds::contains); .anyMatch(myClassIds::contains);
@@ -397,7 +393,7 @@ public class StudentHomeworkService {
} }
private HomeworkSubmission myLatest(Integer homeworkId) { private HomeworkSubmission myLatest(Integer homeworkId) {
Integer studentId = classStudentService.resolveMyStudentId(); Integer studentId = rosterService.mine().get(0).studentId;
return submissionRepository.findByHomeworkIdAndStudentIdAndIsLatest(homeworkId, studentId, 1) return submissionRepository.findByHomeworkIdAndStudentIdAndIsLatest(homeworkId, studentId, 1)
.orElse(null); .orElse(null);
} }
@@ -2,7 +2,6 @@ package com.yunhai.homework.service.impl;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import com.yunhai.homework.entity.ClassStudent;
import com.yunhai.homework.entity.Homework; import com.yunhai.homework.entity.Homework;
import com.yunhai.homework.entity.HomeworkAttachment; import com.yunhai.homework.entity.HomeworkAttachment;
import com.yunhai.homework.entity.HomeworkClass; import com.yunhai.homework.entity.HomeworkClass;
@@ -15,6 +14,7 @@ import com.yunhai.homework.enums.ReviewStatusEnum;
import com.yunhai.homework.enums.SubmitStatusEnum; import com.yunhai.homework.enums.SubmitStatusEnum;
import com.yunhai.homework.model.BasePageResult; import com.yunhai.homework.model.BasePageResult;
import com.yunhai.homework.model.bsch.SchClassDto; 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.SchInfoDto;
import com.yunhai.homework.model.bsch.SchSubjectDto; import com.yunhai.homework.model.bsch.SchSubjectDto;
import com.yunhai.homework.model.bsch.SchTeachDto; import com.yunhai.homework.model.bsch.SchTeachDto;
@@ -35,14 +35,12 @@ import com.yunhai.homework.model.v2.SubmissionVersionVo;
import com.yunhai.homework.model.v2.SubjectDto; import com.yunhai.homework.model.v2.SubjectDto;
import com.yunhai.homework.model.v2.TeacherHomeworkQuery; import com.yunhai.homework.model.v2.TeacherHomeworkQuery;
import com.yunhai.homework.model.v2.TeachingClassVo; import com.yunhai.homework.model.v2.TeachingClassVo;
import com.yunhai.homework.repository.ClassStudentRepository;
import com.yunhai.homework.repository.HomeworkAttachmentRepository; import com.yunhai.homework.repository.HomeworkAttachmentRepository;
import com.yunhai.homework.repository.HomeworkClassRepository; import com.yunhai.homework.repository.HomeworkClassRepository;
import com.yunhai.homework.repository.HomeworkReadRecordRepository; import com.yunhai.homework.repository.HomeworkReadRecordRepository;
import com.yunhai.homework.repository.HomeworkRepository; import com.yunhai.homework.repository.HomeworkRepository;
import com.yunhai.homework.repository.HomeworkSubmissionRepository; import com.yunhai.homework.repository.HomeworkSubmissionRepository;
import com.yunhai.homework.repository.NotificationRepository; import com.yunhai.homework.repository.NotificationRepository;
import com.yunhai.homework.repository.SchClassTeachRepository;
import com.yunhai.homework.repository.SubmissionAttachmentRepository; import com.yunhai.homework.repository.SubmissionAttachmentRepository;
import com.yunhai.homework.service.IClassService; import com.yunhai.homework.service.IClassService;
import com.yunhai.homework.utils.PageUtils; import com.yunhai.homework.utils.PageUtils;
@@ -110,10 +108,7 @@ public class TeacherHomeworkV2Service {
private SubmissionAttachmentRepository submissionAttachmentRepository; private SubmissionAttachmentRepository submissionAttachmentRepository;
@Autowired @Autowired
private ClassStudentRepository classStudentRepository; private RosterService rosterService;
@Autowired
private SchClassTeachRepository schClassTeachRepository;
@Autowired @Autowired
private NotificationRepository notificationRepository; private NotificationRepository notificationRepository;
@@ -197,21 +192,22 @@ public class TeacherHomeworkV2Service {
homework.setRemind1d(request.getRemind1d() == null ? 1 : request.getRemind1d()); homework.setRemind1d(request.getRemind1d() == null ? 1 : request.getRemind1d());
homework.setRemind1h(request.getRemind1h() == null ? 1 : request.getRemind1h()); homework.setRemind1h(request.getRemind1h() == null ? 1 : request.getRemind1h());
if (request.getClassIds() != null && !request.getClassIds().isEmpty()) { if (request.getClassIds() != null && !request.getClassIds().isEmpty()) {
homework.setReceiveList(String.join(",", request.getClassIds())); homework.setReceiveList(request.getClassIds().stream()
.map(String::valueOf).collect(Collectors.joining(",")));
} }
} }
private void saveChildren(Homework homework, List<String> classIds, List<AttachmentItem> attachments) { private void saveChildren(Homework homework, List<Integer> classIds, List<AttachmentItem> attachments) {
saveClasses(homework, classIds); saveClasses(homework, classIds);
saveAttachments(homework, attachments); saveAttachments(homework, attachments);
} }
private void saveClasses(Homework homework, List<String> classIds) { private void saveClasses(Homework homework, List<Integer> classIds) {
if (classIds == null || classIds.isEmpty()) { if (classIds == null || classIds.isEmpty()) {
throw new BizException(ErrorCode.FORBIDDEN, "请至少选择一个参与班级"); throw new BizException(ErrorCode.FORBIDDEN, "请至少选择一个参与班级");
} }
List<String> distinct = classIds.stream().filter(StringUtils::hasText).distinct().collect(Collectors.toList()); List<Integer> distinct = classIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
for (String classId : distinct) { for (Integer classId : distinct) {
HomeworkClass hc = new HomeworkClass(); HomeworkClass hc = new HomeworkClass();
hc.setHomeworkId(homework.getId()); hc.setHomeworkId(homework.getId());
hc.setClassId(classId); hc.setClassId(classId);
@@ -241,13 +237,9 @@ public class TeacherHomeworkV2Service {
} }
} }
/** 班级名快照:优先取名册,无名册时回退班级 id */ /** 班级名快照:实时取 bschclass 班级列表映射,缺失时回退班级 id */
private String resolveClassName(String classId) { private String resolveClassName(Integer classId) {
return classStudentRepository.findByClassIdAndStatus(classId, 1).stream() return rosterService.className(classId);
.map(ClassStudent::getClassName)
.filter(StringUtils::hasText)
.findFirst()
.orElse(classId);
} }
// ============================ 页 2:草稿箱 ============================ // ============================ 页 2:草稿箱 ============================
@@ -437,15 +429,13 @@ public class TeacherHomeworkV2Service {
public BasePageResult<StudentMatrixVo> students(Integer id, StudentMatrixQuery query) { public BasePageResult<StudentMatrixVo> students(Integer id, StudentMatrixQuery query) {
Homework homework = stateService.requireOwned(id, UserContext.getUserId()); Homework homework = stateService.requireOwned(id, UserContext.getUserId());
List<HomeworkClass> classes = homeworkClassRepository.findByHomeworkId(id); List<HomeworkClass> classes = homeworkClassRepository.findByHomeworkId(id);
List<String> classIds = classIds(classes); List<Integer> classIds = classIds(classes);
if (StringUtils.hasText(query.getClassId())) { if (query.getClassId() != null) {
classIds = classIds.stream().filter(c -> c.equals(query.getClassId())).collect(Collectors.toList()); classIds = classIds.stream().filter(c -> c.equals(query.getClassId())).collect(Collectors.toList());
} }
// 名册(班级×学生) // 名册(班级×学生,实时 bschclass)
List<ClassStudent> roster = classIds.isEmpty() List<RosterService.RosterEntry> roster = rosterService.roster(classIds);
? new ArrayList<>()
: classStudentRepository.findByClassIdInAndStatus(classIds, 1);
Map<Integer, HomeworkReadRecord> readMap = readRecordRepository.findByHomeworkId(id).stream() Map<Integer, HomeworkReadRecord> readMap = readRecordRepository.findByHomeworkId(id).stream()
.collect(Collectors.toMap(HomeworkReadRecord::getStudentId, r -> r, (a, b) -> a)); .collect(Collectors.toMap(HomeworkReadRecord::getStudentId, r -> r, (a, b) -> a));
Map<Integer, HomeworkSubmission> latestMap = submissionRepository.findByHomeworkIdAndIsLatest(id, 1).stream() Map<Integer, HomeworkSubmission> latestMap = submissionRepository.findByHomeworkIdAndIsLatest(id, 1).stream()
@@ -457,17 +447,17 @@ public class TeacherHomeworkV2Service {
.collect(Collectors.groupingBy(SubmissionAttachment::getSubmissionId)); .collect(Collectors.groupingBy(SubmissionAttachment::getSubmissionId));
List<StudentMatrixVo> rows = new ArrayList<>(); List<StudentMatrixVo> rows = new ArrayList<>();
for (ClassStudent cs : roster) { for (RosterService.RosterEntry cs : roster) {
StudentMatrixVo row = new StudentMatrixVo(); StudentMatrixVo row = new StudentMatrixVo();
row.setStudentId(cs.getStudentId()); row.setStudentId(cs.studentId);
row.setStudentName(cs.getStudentName()); row.setStudentName(cs.studentName);
row.setClassId(cs.getClassId()); row.setClassId(cs.classId);
row.setClassName(cs.getClassName()); row.setClassName(cs.className);
HomeworkReadRecord read = readMap.get(cs.getStudentId()); HomeworkReadRecord read = readMap.get(cs.studentId);
row.setIsRead(read != null && read.getIsRead() != null && read.getIsRead() == 1 ? 1 : 0); row.setIsRead(read != null && read.getIsRead() != null && read.getIsRead() == 1 ? 1 : 0);
row.setFirstReadAt(read == null ? null : read.getFirstReadAt()); row.setFirstReadAt(read == null ? null : read.getFirstReadAt());
row.setLastReadAt(read == null ? null : read.getLastReadAt()); row.setLastReadAt(read == null ? null : read.getLastReadAt());
HomeworkSubmission sub = latestMap.get(cs.getStudentId()); HomeworkSubmission sub = latestMap.get(cs.studentId);
if (sub == null) { if (sub == null) {
row.setSubmissionId(0); row.setSubmissionId(0);
row.setSubmitStatus(SubmitStatusEnum.NOT_SUBMITTED.getCode()); row.setSubmitStatus(SubmitStatusEnum.NOT_SUBMITTED.getCode());
@@ -854,31 +844,66 @@ public class TeacherHomeworkV2Service {
// ============================ 班级树 / 名册 ============================ // ============================ 班级树 / 名册 ============================
/** /**
* 年级→班级树(发布选班;来自名册快照聚合)。 * 年级→班级树(发布选班;全校班级,不按学科过滤)。
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<ClassTreeNode> classTree() { public List<ClassTreeNode> classTree() {
Integer schId = UserContext.getSchId(); return classTree(null);
if (schId == null) { }
/**
* 年级→班级树(发布选班)。数据源 bschclass 实时接口:班级 GetVClassList、
* 年级 GetSchLenLevellist、在班人数 GetClaArrStulist(一次批量外呼);
* subId 非空时仅保留当前教师该学科的任教班级(GetSchTeachlist),空年级组不保留。
*/
@Transactional(readOnly = true)
public List<ClassTreeNode> classTree(String subId) {
// subId=null 为无参全校树;显式传入空白串视为参数错误
if (subId != null && !StringUtils.hasText(subId.trim())) {
throw new BizException(ErrorCode.PARAM_ERROR, "学科编码(subId)不得为空");
}
String schSerId = currentSchSerId();
if (schSerId == null) {
return new ArrayList<>(); return new ArrayList<>();
} }
List<Object[]> rows = classStudentRepository.findDistinctClasses(schId); Set<Integer> scope = null;
Map<String, List<Object[]>> byGrade = rows.stream() if (StringUtils.hasText(subId)) {
.collect(Collectors.groupingBy(r -> r[2] == null || !StringUtils.hasText(r[2].toString()) scope = teachingClassIds(schSerId, subId.trim());
? "未分组" : r[2].toString(), java.util.LinkedHashMap::new, Collectors.toList())); if (scope.isEmpty()) {
return new ArrayList<>();
}
}
List<SchClassDto> classes = classService.getSchClassList(schSerId);
Map<Integer, String> gradeNames = gradeNames(schSerId);
Map<Integer, List<SchClassDto>> byGrade = new java.util.LinkedHashMap<>();
List<Integer> scopeIds = new ArrayList<>();
for (SchClassDto c : classes) {
if (c == null || c.getClassId() == null || (scope != null && !scope.contains(c.getClassId()))) {
continue;
}
byGrade.computeIfAbsent(c.getGradeId(), k -> new ArrayList<>()).add(c);
scopeIds.add(c.getClassId());
}
if (scopeIds.isEmpty()) {
return new ArrayList<>();
}
Map<Integer, Integer> counts = rosterService.studentCounts(scopeIds);
List<ClassTreeNode> tree = new ArrayList<>(); List<ClassTreeNode> tree = new ArrayList<>();
for (Map.Entry<String, List<Object[]>> entry : byGrade.entrySet()) { for (Map.Entry<Integer, List<SchClassDto>> entry : byGrade.entrySet()) {
Integer gradeId = entry.getKey();
String gradeName = gradeId != null && gradeNames.containsKey(gradeId)
? gradeNames.get(gradeId) : "未分组";
ClassTreeNode grade = new ClassTreeNode(); ClassTreeNode grade = new ClassTreeNode();
grade.setId(entry.getKey()); grade.setId(gradeId == null ? gradeName : String.valueOf(gradeId));
grade.setName(entry.getKey()); grade.setName(gradeName);
grade.setIsParent(true); grade.setIsParent(true);
for (Object[] r : entry.getValue()) { for (SchClassDto c : entry.getValue()) {
String classId = String.valueOf(r[0]);
ClassTreeNode leaf = new ClassTreeNode(); ClassTreeNode leaf = new ClassTreeNode();
leaf.setId(classId); leaf.setId(String.valueOf(c.getClassId()));
leaf.setName(r[1] == null || !StringUtils.hasText(r[1].toString()) ? classId : r[1].toString()); leaf.setName(StringUtils.hasText(c.getClassName())
? c.getClassName() : String.valueOf(c.getClassId()));
leaf.setIsParent(false); leaf.setIsParent(false);
leaf.setStudentCount(classStudentRepository.findByClassIdAndStatus(classId, 1).size()); leaf.setStudentCount(counts.getOrDefault(c.getClassId(), 0));
grade.getChildren().add(leaf); grade.getChildren().add(leaf);
} }
tree.add(grade); tree.add(grade);
@@ -886,6 +911,39 @@ public class TeacherHomeworkV2Service {
return tree; return tree;
} }
/**
* 当前教师在 bschclass 的 subId 学科任教班级ID集合(实时 GetSchTeachlist,
* sch_class.class_id 取值域);远端不可用时异常向上抛,登录身份缺失时返回空集合。
*/
private Set<Integer> teachingClassIds(String schSerId, String subId) {
Set<Integer> ids = new LinkedHashSet<>();
Integer userId = UserContext.getUserId();
if (schSerId == null || userId == null) {
return ids;
}
for (SchTeachDto teach : classService.getSchTeachList(schSerId, userId)) {
if (subId.equals(teach.getSubId()) && teach.getClassId() != null) {
ids.add(teach.getClassId());
}
}
return ids;
}
/** 学校年级ID → 年级名(bschclass GetSchLenLevellist;远端失败降级空映射,班级归入「未分组」) */
private Map<Integer, String> gradeNames(String schSerId) {
Map<Integer, String> names = new java.util.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("[classTree] 年级列表拉取失败, 降级「未分组」展示: {}", e.getMessage());
}
return names;
}
// ============================ 学科(我布置的 / 我任教的) ============================ // ============================ 学科(我布置的 / 我任教的) ============================
/** /**
@@ -909,49 +967,27 @@ public class TeacherHomeworkV2Service {
} }
/** /**
* 我的任教学科:bschclass 任教关系(sch_class_teach)当前教师去重学科, * 我的任教学科:bschclass 任教关系当前教师去重学科,名称经本校学科字典解析。
* 名称经本校学科字典解析。
*/ */
public List<SubjectDto> teachingSubjects() { public List<SubjectDto> teachingSubjects() {
return toSubjectDtos(teachingSubjectCodes(), subjectDictionary()); return toSubjectDtos(teachingSubjectCodes(), subjectDictionary());
} }
/** /**
* 我的任教去重学科编码:bschclass 任教关系(sch_class_teach)当前教师。 * 我的任教去重学科编码:bschclass 任教关系(GetSchTeachlist)当前教师,实时获取。
* 登录身份缺学校/教师信息时返回空集合;远端不可用时降级读本地任教快照 * 登录身份缺学校/教师信息时返回空集合;远端不可用时异常向上抛
* (sch_class_teach,同 class_student 口径),快照也无记录则保持原语义向上抛出。 * (assignedSubjects 捕获后仅返回布置学科)。
*/ */
private Set<String> teachingSubjectCodes() { private Set<String> teachingSubjectCodes() {
Set<String> codes = new LinkedHashSet<>(); Set<String> codes = new LinkedHashSet<>();
try { for (SchTeachDto teach : currentTeachList()) {
for (SchTeachDto teach : currentTeachList()) { if (StringUtils.hasText(teach.getSubId())) {
if (StringUtils.hasText(teach.getSubId())) { codes.add(teach.getSubId());
codes.add(teach.getSubId());
}
} }
} catch (BizException e) {
codes.addAll(localTeachingSubIds());
if (codes.isEmpty()) {
throw e;
}
log.warn("[teachingSubjectCodes] bschclass 任教拉取失败, 降级本地任教快照: {}", e.getMessage());
} }
return codes; return codes;
} }
/**
* 当前教师在本地任教快照(sch_class_teach)中的去重学科编码;
* 缺学校/教师身份时返回空集合。
*/
private Set<String> localTeachingSubIds() {
Integer schId = UserContext.getSchId();
Integer userId = UserContext.getUserId();
if (schId == null || userId == null) {
return Collections.emptySet();
}
return new LinkedHashSet<>(schClassTeachRepository.findDistinctSubIds(schId, userId));
}
/** /**
* 按学科筛选我的任教班级:任教关系当前教师该学科的班级去重, * 按学科筛选我的任教班级:任教关系当前教师该学科的班级去重,
* 班级名称取学校班级列表(bschclass GetVClassList)映射并按其排序输出, * 班级名称取学校班级列表(bschclass GetVClassList)映射并按其排序输出,
@@ -966,12 +1002,7 @@ public class TeacherHomeworkV2Service {
if (schSerId == null || userId == null) { if (schSerId == null || userId == null) {
return new ArrayList<>(); return new ArrayList<>();
} }
Set<Integer> classIds = new LinkedHashSet<>(); Set<Integer> classIds = teachingClassIds(schSerId, subId);
for (SchTeachDto teach : classService.getSchTeachList(schSerId, userId)) {
if (subId.equals(teach.getSubId()) && teach.getClassId() != null) {
classIds.add(teach.getClassId());
}
}
if (classIds.isEmpty()) { if (classIds.isEmpty()) {
return new ArrayList<>(); return new ArrayList<>();
} }
@@ -994,7 +1025,7 @@ public class TeacherHomeworkV2Service {
private static TeachingClassVo teachingClassVo(Integer classId, String className) { private static TeachingClassVo teachingClassVo(Integer classId, String className) {
TeachingClassVo vo = new TeachingClassVo(); TeachingClassVo vo = new TeachingClassVo();
vo.setClassId(String.valueOf(classId)); vo.setClassId(classId);
vo.setClassName(StringUtils.hasText(className) ? className : String.valueOf(classId)); vo.setClassName(StringUtils.hasText(className) ? className : String.valueOf(classId));
return vo; return vo;
} }
@@ -1012,7 +1043,7 @@ public class TeacherHomeworkV2Service {
} }
/** /**
* 当前教师在 bschclass 的任教关系明细(sch_class_teach)。 * 当前教师在 bschclass 的任教关系明细(GetSchTeachlist 实时)。
* 登录身份缺学校/教师信息时返回空列表;远端不可用时抛 BizException,由调用方决定是否降级。 * 登录身份缺学校/教师信息时返回空列表;远端不可用时抛 BizException,由调用方决定是否降级。
*/ */
private List<SchTeachDto> currentTeachList() { private List<SchTeachDto> currentTeachList() {
@@ -1132,20 +1163,13 @@ public class TeacherHomeworkV2Service {
// ============================ 名册辅助 ============================ // ============================ 名册辅助 ============================
/** 参与班级在班学生 id 集合 */ /** 参与班级在班学生 id 集合(实时 bschclass 名册) */
private List<Integer> rosterStudentIds(Integer homeworkId) { private List<Integer> rosterStudentIds(Integer homeworkId) {
return roster(homeworkId).stream().map(ClassStudent::getStudentId).collect(Collectors.toList()); return rosterService.roster(classIds(homeworkClassRepository.findByHomeworkId(homeworkId)))
.stream().map(e -> e.studentId).collect(Collectors.toList());
} }
private List<ClassStudent> roster(Integer homeworkId) { private static List<Integer> classIds(List<HomeworkClass> classes) {
List<String> classIds = classIds(homeworkClassRepository.findByHomeworkId(homeworkId));
if (classIds.isEmpty()) {
return new ArrayList<>();
}
return classStudentRepository.findByClassIdInAndStatus(classIds, 1);
}
private static List<String> classIds(List<HomeworkClass> classes) {
return classes.stream().map(HomeworkClass::getClassId).collect(Collectors.toList()); return classes.stream().map(HomeworkClass::getClassId).collect(Collectors.toList());
} }