From 5dfb1a269a60c321d89e72feed70528c92a60a10 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Fri, 31 Jul 2026 09:43:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=95=99=E5=AD=A6?= =?UTF-8?q?=E7=8F=AD=E5=AD=A6=E7=94=9F=E4=B8=8E=E6=BC=94=E7=A4=BA=E6=8E=92?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/stu/ScoreController.java | 5 +- .../controller/stu/UserController.java | 111 +++++++++++++++++- .../sztzjy/linkCommerce/entity/Userinfo.java | 11 ++ ...ScoreControllerTeachingClassTrendTest.java | 35 ++++++ .../stu/UserControllerTeacherAdminTest.java | 95 +++++++++++++++ 5 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/sztzjy/linkCommerce/controller/stu/ScoreControllerTeachingClassTrendTest.java diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/stu/ScoreController.java b/src/main/java/com/sztzjy/linkCommerce/controller/stu/ScoreController.java index c91770d..763e120 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/ScoreController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/ScoreController.java @@ -217,7 +217,10 @@ public class ScoreController { scoreRankExample.setOrderByClause("score desc"); List scoreRankList = stuRankMapper.selectByExample(scoreRankExample); if (scoreRankList.isEmpty()) { //代表成绩还没更新 - scoreRankService.doRankOne(schoolId); + // 教学班(包含演示模式)必须按教学班成员计算排名;旧行政班才回退到全校排名。 + if (!Boolean.TRUE.equals(scoreRankService.doRankTeachingClass(schoolId, classId))) { + scoreRankService.doRankOne(schoolId); + } scoreRankList = stuRankMapper.selectByExample(scoreRankExample); } if(scoreRankList.isEmpty() || scoreRankList==null){ diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/stu/UserController.java b/src/main/java/com/sztzjy/linkCommerce/controller/stu/UserController.java index e05cf48..4906e50 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/UserController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/UserController.java @@ -859,6 +859,86 @@ public class UserController { } } + @PostMapping("/teacher-roster/students") + @ApiOperation("教师维护名单模式-新增学生档案") + @AnonymousAccess + public ResultEntity addTeacherRosterStudent(@RequestParam String name, + @RequestParam String userName, + @RequestParam String teachingClassId, + @RequestParam String operatorId) { + Userinfo operator = userinfoMapper.selectByPrimaryKey(operatorId); + if (!isSchoolTeacher(operator) || !isTeacherRosterMode(operator.getSchoolId())) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "无权维护学生名单"); + } + SchoolClass teachingClass = schoolClassMapper.selectByPrimaryKey(teachingClassId); + if (!isOwnedTeachingClass(teachingClass, operatorId)) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "只能选择自己创建的教学班"); + } + if (StringUtils.isAnyBlank(StringUtils.trimToNull(name), StringUtils.trimToNull(userName))) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "学生姓名和学号不能为空"); + } + String normalizedUserName = StringUtils.trim(userName); + if (userinfoMapper.selectBySchoolIdAndUsername(operator.getSchoolId(), normalizedUserName) != null + || userInfoService.existsByUserName(normalizedUserName)) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "账号已存在"); + } + Userinfo student = new Userinfo(); + student.setUserId(UUID.randomUUID().toString()); + student.setName(StringUtils.trim(name)); + student.setUsername(normalizedUserName); + student.setPassword("123qwe"); + student.setRole(4); + student.setTeacherAdmin(false); + student.setSchoolId(operator.getSchoolId()); + student.setCreateTime(new Date()); + student.setCodeFrom("教师名单添加"); + if (userinfoMapper.insertSelective(student) <= 0) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "新增学生失败"); + } + teachingClassStudentMapper.exitActiveByStudentUserIdExceptClass(student.getUserId(), teachingClassId); + upsertActiveTeachingClassMember(teachingClassId, student, null, "TEACHER_ROSTER_MANUAL"); + return new ResultEntity<>(HttpStatus.OK, "新增学生成功"); + } + + @PostMapping("/teacher-roster/students/{studentUserId}") + @ApiOperation("教师维护名单模式-编辑学生档案") + @AnonymousAccess + public ResultEntity updateTeacherRosterStudent(@PathVariable String studentUserId, + @RequestParam String name, + @RequestParam String userName, + @RequestParam String teachingClassId, + @RequestParam String operatorId) { + Userinfo operator = userinfoMapper.selectByPrimaryKey(operatorId); + Userinfo existingStudent = userinfoMapper.selectByPrimaryKey(studentUserId); + if (!isSchoolTeacher(operator) || !isTeacherRosterMode(operator.getSchoolId()) + || existingStudent == null || !Integer.valueOf(4).equals(existingStudent.getRole()) + || !StringUtils.equals(operator.getSchoolId(), existingStudent.getSchoolId())) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "无权维护该学生名单"); + } + SchoolClass teachingClass = schoolClassMapper.selectByPrimaryKey(teachingClassId); + if (!isOwnedTeachingClass(teachingClass, operatorId)) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "只能选择自己创建的教学班"); + } + if (StringUtils.isAnyBlank(StringUtils.trimToNull(name), StringUtils.trimToNull(userName))) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "学生姓名和学号不能为空"); + } + String normalizedUserName = StringUtils.trim(userName); + if (!StringUtils.equals(normalizedUserName, existingStudent.getUsername()) + && userInfoService.existsByUserName(normalizedUserName)) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "账号已存在"); + } + Userinfo update = new Userinfo(); + update.setUserId(studentUserId); + update.setName(StringUtils.trim(name)); + update.setUsername(normalizedUserName); + if (userinfoMapper.updateByPrimaryKeySelective(update) <= 0) { + return new ResultEntity<>(HttpStatus.BAD_REQUEST, "编辑学生失败"); + } + teachingClassStudentMapper.exitActiveByStudentUserIdExceptClass(studentUserId, teachingClassId); + upsertActiveTeachingClassMember(teachingClassId, existingStudent, null, "TEACHER_ROSTER_MANUAL"); + return new ResultEntity<>(HttpStatus.OK, "编辑学生成功"); + } + //查询 @PostMapping("/selectStudent") @ApiOperation("学生管理-查询学生账号信息") @@ -905,9 +985,10 @@ public class UserController { if (StringUtils.isNotBlank(schoolClassId)) { userInfoList.get(i).setClassName(schoolClass.getClassName()); } else { - String schoolClassId1 = userInfoList.get(i).getSchoolClassId(); - schoolClass = schoolClassMapper.selectByPrimaryKey(schoolClassId1); - userInfoList.get(i).setClassName(schoolClass.getClassName()); + Userinfo student = userInfoList.get(i); + if (!populateTeacherRosterTeachingClass(student, schoolId)) { + student.setClassName(safeClassName(student.getSchoolClassId())); + } } } else { String schoolId1 = userInfoList.get(i).getSchoolId(); @@ -916,9 +997,10 @@ public class UserController { if (StringUtils.isNotBlank(schoolClassId)) { userInfoList.get(i).setClassName(schoolClass.getClassName()); } else { - String schoolClassId1 = userInfoList.get(i).getSchoolClassId(); - schoolClass = schoolClassMapper.selectByPrimaryKey(schoolClassId1); - userInfoList.get(i).setClassName(schoolClass.getClassName()); + Userinfo student = userInfoList.get(i); + if (!populateTeacherRosterTeachingClass(student, schoolId1)) { + student.setClassName(safeClassName(student.getSchoolClassId())); + } } } } @@ -1499,6 +1581,23 @@ public class UserController { return schoolClass == null ? "" : schoolClass.getClassName(); } + private boolean populateTeacherRosterTeachingClass(Userinfo student, String schoolId) { + if (!isTeacherRosterMode(schoolId) || teachingClassStudentMapper == null || student == null) { + return false; + } + TeachingClassStudent membership = teachingClassStudentMapper.selectActiveByStudentUserId(student.getUserId()); + if (membership == null || StringUtils.isBlank(membership.getTeachingClassId())) { + return false; + } + SchoolClass teachingClass = schoolClassMapper.selectByPrimaryKey(membership.getTeachingClassId()); + if (teachingClass == null || !"TEACHING".equals(teachingClass.getClassType())) { + return false; + } + student.setTeachingClassId(teachingClass.getSchoolClassId()); + student.setClassName(teachingClass.getClassName()); + return true; + } + private boolean hasMajorUnderFaculty(String schoolFacultyId) { SchoolMajorExample example = new SchoolMajorExample(); example.createCriteria().andSchoolFacultyIdEqualTo(schoolFacultyId); diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/Userinfo.java b/src/main/java/com/sztzjy/linkCommerce/entity/Userinfo.java index 5f0e722..05d78ed 100644 --- a/src/main/java/com/sztzjy/linkCommerce/entity/Userinfo.java +++ b/src/main/java/com/sztzjy/linkCommerce/entity/Userinfo.java @@ -72,6 +72,9 @@ public class Userinfo { @ApiModelProperty(notes = "班级ID(关联班级表,教师账号为null)") private String schoolClassId; + @ApiModelProperty("当前教学班ID,列表与编辑回显用") + private String teachingClassId; + @ApiModelProperty("班级名称 回显用") private String className; @ApiModelProperty("学校名称 回显用") @@ -260,4 +263,12 @@ public class Userinfo { public void setSchoolClassId(String schoolClassId) { this.schoolClassId = schoolClassId == null ? null : schoolClassId.trim(); } + + public String getTeachingClassId() { + return teachingClassId; + } + + public void setTeachingClassId(String teachingClassId) { + this.teachingClassId = teachingClassId == null ? null : teachingClassId.trim(); + } } diff --git a/src/test/java/com/sztzjy/linkCommerce/controller/stu/ScoreControllerTeachingClassTrendTest.java b/src/test/java/com/sztzjy/linkCommerce/controller/stu/ScoreControllerTeachingClassTrendTest.java new file mode 100644 index 0000000..98e5245 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/controller/stu/ScoreControllerTeachingClassTrendTest.java @@ -0,0 +1,35 @@ +package com.sztzjy.linkCommerce.controller.stu; + +import com.sztzjy.linkCommerce.mapper.StuRankMapper; +import com.sztzjy.linkCommerce.service.ScoreRankService; +import com.sztzjy.linkCommerce.util.ResultEntity; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ScoreControllerTeachingClassTrendTest { + + @Test + void studentTrendBuildsRanksInsideTeachingClassBeforeFallingBackToLegacySchoolRanking() { + ScoreController controller = new ScoreController(); + controller.stuRankMapper = mock(StuRankMapper.class); + controller.scoreRankService = mock(ScoreRankService.class); + when(controller.scoreRankService.getNowDate()).thenReturn("2026-07-31"); + when(controller.stuRankMapper.selectByExample(any())).thenReturn(Collections.emptyList()); + when(controller.scoreRankService.doRankTeachingClass("school-1", "teaching-1")).thenReturn(true); + + ResultEntity result = controller.getStuScoreTrend("demo:session-1", "teaching-1", "school-1"); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + verify(controller.scoreRankService).doRankTeachingClass("school-1", "teaching-1"); + verify(controller.scoreRankService, never()).doRankOne("school-1"); + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/controller/stu/UserControllerTeacherAdminTest.java b/src/test/java/com/sztzjy/linkCommerce/controller/stu/UserControllerTeacherAdminTest.java index 2238724..be7299a 100644 --- a/src/test/java/com/sztzjy/linkCommerce/controller/stu/UserControllerTeacherAdminTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/controller/stu/UserControllerTeacherAdminTest.java @@ -19,6 +19,7 @@ import com.sztzjy.linkCommerce.entity.importDto.SchoolAdminStudentImportDTO; import com.sztzjy.linkCommerce.entity.importDto.TeachingClassStudentImportDTO; import com.sztzjy.linkCommerce.util.Pinyin4jUtil; import com.sztzjy.linkCommerce.util.ResultEntity; +import com.github.pagehelper.PageInfo; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.http.HttpStatus; @@ -632,6 +633,69 @@ class UserControllerTeacherAdminTest { assertEquals(userCaptor.getValue().getUserId(), memberCaptor.getValue().getStudentUserId()); } + @Test + void teacherCanCreateStudentRosterEntryInSelectedTeachingClassWithoutOrganizationFields() { + UserController controller = controllerWithCommonMocks(); + controller.schoolProductConfigService = mock(SchoolProductConfigService.class); + SchoolClass teachingClass = classCreatedBy("teacher-1"); + teachingClass.setSchoolClassId("teaching-1"); + when(controller.userinfoMapper.selectByPrimaryKey("teacher-1")).thenReturn(teacher("teacher-1")); + when(controller.schoolProductConfigService.isTeacherRosterManaged("school-1")).thenReturn(true); + when(controller.schoolClassMapper.selectByPrimaryKey("teaching-1")).thenReturn(teachingClass); + when(controller.userinfoMapper.selectBySchoolIdAndUsername("school-1", "s001")).thenReturn(null); + when(controller.userInfoService.existsByUserName("s001")).thenReturn(false); + when(controller.userinfoMapper.insertSelective(any(Userinfo.class))).thenReturn(1); + when(controller.teachingClassStudentMapper.insertSelective(any(TeachingClassStudent.class))).thenReturn(1); + + ResultEntity result = controller.addTeacherRosterStudent("学生", "s001", "teaching-1", "teacher-1"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Userinfo.class); + ArgumentCaptor memberCaptor = ArgumentCaptor.forClass(TeachingClassStudent.class); + verify(controller.userinfoMapper).insertSelective(captor.capture()); + verify(controller.teachingClassStudentMapper).insertSelective(memberCaptor.capture()); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("school-1", captor.getValue().getSchoolId()); + assertNull(captor.getValue().getSchoolFacultyId()); + assertNull(captor.getValue().getSchoolMajorId()); + assertNull(captor.getValue().getSchoolClassId()); + assertEquals("teaching-1", memberCaptor.getValue().getTeachingClassId()); + assertEquals(captor.getValue().getUserId(), memberCaptor.getValue().getStudentUserId()); + } + + @Test + void teacherCanMoveStudentRosterEntryToSelectedTeachingClass() { + UserController controller = controllerWithCommonMocks(); + controller.schoolProductConfigService = mock(SchoolProductConfigService.class); + Userinfo student = new Userinfo(); + student.setUserId("student-1"); + student.setSchoolId("school-1"); + student.setRole(4); + student.setName("旧姓名"); + student.setUsername("old001"); + SchoolClass teachingClass = classCreatedBy("teacher-1"); + teachingClass.setSchoolClassId("teaching-2"); + when(controller.userinfoMapper.selectByPrimaryKey("teacher-1")).thenReturn(teacher("teacher-1")); + when(controller.userinfoMapper.selectByPrimaryKey("student-1")).thenReturn(student); + when(controller.schoolProductConfigService.isTeacherRosterManaged("school-1")).thenReturn(true); + when(controller.schoolClassMapper.selectByPrimaryKey("teaching-2")).thenReturn(teachingClass); + when(controller.userInfoService.existsByUserName("new001")).thenReturn(false); + when(controller.userinfoMapper.updateByPrimaryKeySelective(any(Userinfo.class))).thenReturn(1); + when(controller.teachingClassStudentMapper.insertSelective(any(TeachingClassStudent.class))).thenReturn(1); + + ResultEntity result = controller.updateTeacherRosterStudent("student-1", "新姓名", "new001", "teaching-2", "teacher-1"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Userinfo.class); + ArgumentCaptor memberCaptor = ArgumentCaptor.forClass(TeachingClassStudent.class); + verify(controller.userinfoMapper).updateByPrimaryKeySelective(captor.capture()); + verify(controller.teachingClassStudentMapper).exitActiveByStudentUserIdExceptClass("student-1", "teaching-2"); + verify(controller.teachingClassStudentMapper).insertSelective(memberCaptor.capture()); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("新姓名", captor.getValue().getName()); + assertEquals("new001", captor.getValue().getUsername()); + assertNull(captor.getValue().getSchoolClassId()); + assertEquals("teaching-2", memberCaptor.getValue().getTeachingClassId()); + } + @Test void teacherCannotInitializeTeachingClassCreatedByAnotherTeacher() { UserController controller = controllerWithCommonMocks(); @@ -695,6 +759,37 @@ class UserControllerTeacherAdminTest { verify(controller.teachingClassStudentMapper).selectByTeachingClassId("teaching-1"); } + @Test + void selectStudentAllowsStudentsWithoutAnAssignedClass() { + UserController controller = controllerWithCommonMocks(); + Userinfo student = student("student-1", "student", "2024001", null); + when(controller.userinfoMapper.selectByExample(any())).thenReturn(Collections.singletonList(student)); + + ResultEntity> result = controller.selectStudent("school-1", null, null, null, 1, 10); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("", result.getBody().getData().getList().get(0).getClassName()); + } + + @Test + void selectStudentInTeacherRosterModeDisplaysActiveTeachingClass() { + UserController controller = controllerWithCommonMocks(); + when(controller.schoolProductConfigService.isTeacherRosterManaged("school-1")).thenReturn(true); + Userinfo student = student("student-1", "student", "2024001", null); + TeachingClassStudent membership = new TeachingClassStudent(); + membership.setTeachingClassId("teaching-1"); + SchoolClass teachingClass = classCreatedBy("teacher-1"); + teachingClass.setSchoolClassId("teaching-1"); + teachingClass.setClassName("Teaching 1"); + when(controller.userinfoMapper.selectByExample(any())).thenReturn(Collections.singletonList(student)); + when(controller.teachingClassStudentMapper.selectActiveByStudentUserId("student-1")).thenReturn(membership); + when(controller.schoolClassMapper.selectByPrimaryKey("teaching-1")).thenReturn(teachingClass); + + ResultEntity> result = controller.selectStudent("school-1", null, null, null, 1, 10); + + assertEquals("Teaching 1", result.getBody().getData().getList().get(0).getClassName()); + } + @Test void importTeachingClassStudentRowsCreatesMembershipForMatchedAdminStudents() { UserController controller = controllerWithCommonMocks();