diff --git a/src/main/java/com/yunhai/bschclassmanage/model/vo/sch/OapiGroupVO.java b/src/main/java/com/yunhai/bschclassmanage/model/vo/sch/OapiGroupVO.java new file mode 100644 index 0000000..e655a5a --- /dev/null +++ b/src/main/java/com/yunhai/bschclassmanage/model/vo/sch/OapiGroupVO.java @@ -0,0 +1,40 @@ +package com.yunhai.bschclassmanage.model.vo.sch; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.yunhai.bschclassmanage.model.vo.SelectIntVO; +import com.yunhai.bschclassmanage.model.vo.SelectStrVO; +import lombok.Data; + +import java.util.List; + +/** + * 教师分组信息(oapi/sch/getTeaCustomGroupList 输出,对应教师分组管理页面的组/组员/组长三栏) + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class OapiGroupVO { + + /** 组ID(sch_teacher_custom_group.id) */ + private Integer groupId; + + /** 组名称 */ + private String groupName; + + /** 组类型(1年级组 2教研组 3备课组) */ + private Integer groupType; + + /** 组类型名称 */ + private String groupTypeName; + + /** 学科集合 [{id:学科编码, name:学科名称}] */ + private List subjects; + + /** 年级集合 [{id:年级编码, name:年级名称}] */ + private List grades; + + /** 组员集合 [{id:教师id, name:教师名称}](不含组长) */ + private List members; + + /** 组长集合 [{id:教师id, name:教师名称}] */ + private List leaders; +} diff --git a/src/test/java/com/yunhai/bschclassmanage/ReproduceHistoryTeachByStuIdTest.java b/src/test/java/com/yunhai/bschclassmanage/ReproduceHistoryTeachByStuIdTest.java new file mode 100644 index 0000000..d509398 --- /dev/null +++ b/src/test/java/com/yunhai/bschclassmanage/ReproduceHistoryTeachByStuIdTest.java @@ -0,0 +1,106 @@ +package com.yunhai.bschclassmanage; + +import com.yunhai.bschclassmanage.service.IStudentClassHistoryService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 本地复现 api/history/getHistoryClassTeachByStuId?stuId=10068 的内部服务错误。 + * 直接调用 service 层(绕过 SSO 过滤器),打印真实异常堆栈。 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +class ReproduceHistoryTeachByStuIdTest { + + @Autowired + private IStudentClassHistoryService studentClassHistoryService; + + @Test + void reproduce() { + try { + // 模拟真实 HTTP 场景:UserContextInterceptor.resolveUser 恒返回 null,schId 为 null + Object result = studentClassHistoryService.getHistoryClassTeachByStuId(null, 10068); + System.out.println("=== SUCCESS (schId=null) ==="); + System.out.println(result); + } catch (Throwable e) { + System.out.println("=== EXCEPTION (schId=null): " + e.getClass().getName() + " ==="); + System.out.println("message: " + e.getMessage()); + e.printStackTrace(); + } + } + + @Autowired + private com.yunhai.bschclassmanage.utils.RedisUtil redisUtil; + + @Autowired + private com.yunhai.bschclassmanage.Repository.SchStudentRepository schStudentRepository; + + /** + * 回归用例:同一 stu_id 在 sch_student 存在多行(学生在多所学校各有一条就学记录)。 + * 修复前 findByStuId 返回 Optional,会抛 NonUniqueResultException(线上表现为 code 6 内部服务错误)。 + * 修复后应取当前学校那一行正常返回。@Transactional 保证测试数据回滚。 + */ + @org.springframework.transaction.annotation.Transactional + @Test + void duplicateStuIdShouldNotCrash() { + // 本地库里 stu_id=10068 已有一行(sch_id=3, class_id=298),再造一行别校的记录 + com.yunhai.bschclassmanage.entity.SchStudent other = new com.yunhai.bschclassmanage.entity.SchStudent(); + other.setStuId(10068); + other.setSchId(999); + other.setClassId(888); + other.setName("重复记录-别校"); + schStudentRepository.save(other); + schStudentRepository.flush(); + + // 当前学校 schId=3 的记录应被正确选中,不再抛 NonUniqueResultException + Object result = studentClassHistoryService.getHistoryClassTeachByStuId(3, 10068); + System.out.println("=== DUPLICATE stu_id RESULT ==="); + System.out.println(result); + if (result == null) { + throw new AssertionError("expected non-null result"); + } + + // 换成重复行所在学校 schId=999:实时表命中但班级不存在 → 应返回业务异常而不是内部错误 + try { + studentClassHistoryService.getHistoryClassTeachByStuId(999, 10068); + System.out.println("=== schId=999 unexpectedly succeeded ==="); + } catch (com.yunhai.platform.utils.exception.DException e) { + System.out.println("=== schId=999 DException (expected): " + e.getMessage()); + } + } + + @Test + void poisonCacheRead() { + for (String key : new String[]{ + "bsch.classmanage.system_yearterm_poison", + "bsch.classmanage.system_yearterm_poison2"}) { + try { + Object raw = redisUtil.get(key, Object.class); + System.out.println("=== RAW GET [" + key + "]: " + (raw == null ? "null" : raw.getClass().getName())); + } catch (Throwable e) { + System.out.println("=== RAW GET EXCEPTION [" + key + "]: " + e.getClass().getName() + " ==="); + System.out.println("message: " + e.getMessage()); + } + try { + Object list = redisUtil.getList(key, com.yunhai.bschclassmanage.entity.BaseYearTerm.class); + System.out.println("=== GET LIST [" + key + "]: " + list); + } catch (Throwable e) { + System.out.println("=== GET LIST EXCEPTION [" + key + "]: " + e.getClass().getName() + " ==="); + System.out.println("message: " + e.getMessage()); + } + } + } + + @Test + void reproduceWithSchId() { + try { + Object result = studentClassHistoryService.getHistoryClassTeachByStuId(3, 10068); + System.out.println("=== SUCCESS (schId=3) ==="); + System.out.println(result); + } catch (Throwable e) { + System.out.println("=== EXCEPTION (schId=3): " + e.getClass().getName() + " ==="); + System.out.println("message: " + e.getMessage()); + e.printStackTrace(); + } + } +}