From c4773ebf5d5d944038322956995f49c5fe0b71ef Mon Sep 17 00:00:00 2001 From: chenyuan Date: Sat, 1 Aug 2026 19:16:39 +0800 Subject: [PATCH] feat: add six-module student report API --- .../StudentExperimentReportController.java | 3 ++ .../dto/StudentExperimentReportDTO.java | 4 ++ .../dto/StudentExperimentReportModuleDTO.java | 4 ++ .../dto/StudentExperimentReportTaskDTO.java | 3 ++ .../StudentExperimentReportService.java | 2 + .../StudentExperimentReportServiceImpl.java | 10 ++++ ...StudentExperimentReportControllerTest.java | 3 ++ ...tudentExperimentReportServiceImplTest.java | 51 +++++++++++++++++++ 8 files changed, 80 insertions(+) create mode 100644 src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportController.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportDTO.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportModuleDTO.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportTaskDTO.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/StudentExperimentReportService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImpl.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportControllerTest.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImplTest.java diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportController.java b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportController.java new file mode 100644 index 0000000..2fee20b --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportController.java @@ -0,0 +1,3 @@ +package com.sztzjy.linkCommerce.controller.stu; +import com.sztzjy.linkCommerce.config.security.JwtUser; import com.sztzjy.linkCommerce.config.security.TokenProvider; import com.sztzjy.linkCommerce.entity.dto.StudentExperimentReportDTO; import com.sztzjy.linkCommerce.service.StudentExperimentReportService; import com.sztzjy.linkCommerce.util.ResultEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; +@RestController @RequestMapping("api/student/experiment-report") public class StudentExperimentReportController { @Autowired public StudentExperimentReportService studentExperimentReportService; @GetMapping("/current") public ResultEntity current(HttpServletRequest request){return new ResultEntity<>(HttpStatus.OK,"查询成功",studentExperimentReportService.getCurrentReport(currentUser(request)));} protected JwtUser currentUser(HttpServletRequest request){return TokenProvider.getJWTUser(request);} } diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportDTO.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportDTO.java new file mode 100644 index 0000000..ee5b400 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportDTO.java @@ -0,0 +1,4 @@ +package com.sztzjy.linkCommerce.entity.dto; +import java.math.BigDecimal; import java.util.List; +public class StudentExperimentReportDTO { private String teachingClassId; private Integer rank; private BigDecimal totalScore; private List modules; private List tasks; + public String getTeachingClassId(){return teachingClassId;} public void setTeachingClassId(String v){teachingClassId=v;} public Integer getRank(){return rank;} public void setRank(Integer v){rank=v;} public BigDecimal getTotalScore(){return totalScore;} public void setTotalScore(BigDecimal v){totalScore=v;} public List getModules(){return modules;} public void setModules(List v){modules=v;} public List getTasks(){return tasks;} public void setTasks(List v){tasks=v;} } diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportModuleDTO.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportModuleDTO.java new file mode 100644 index 0000000..034216c --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportModuleDTO.java @@ -0,0 +1,4 @@ +package com.sztzjy.linkCommerce.entity.dto; +import java.math.BigDecimal; +public class StudentExperimentReportModuleDTO { private String moduleName; private BigDecimal weight; private BigDecimal score=BigDecimal.ZERO; private Integer completedTaskCount=0; private Integer totalTaskCount=0; + public String getModuleName(){return moduleName;} public void setModuleName(String v){moduleName=v;} public BigDecimal getWeight(){return weight;} public void setWeight(BigDecimal v){weight=v;} public BigDecimal getScore(){return score;} public void setScore(BigDecimal v){score=v;} public Integer getCompletedTaskCount(){return completedTaskCount;} public void setCompletedTaskCount(Integer v){completedTaskCount=v;} public Integer getTotalTaskCount(){return totalTaskCount;} public void setTotalTaskCount(Integer v){totalTaskCount=v;} } diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportTaskDTO.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportTaskDTO.java new file mode 100644 index 0000000..3d906a1 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/StudentExperimentReportTaskDTO.java @@ -0,0 +1,3 @@ +package com.sztzjy.linkCommerce.entity.dto; +public class StudentExperimentReportTaskDTO { private String taskName; private String moduleName; private String progressStatus; private Boolean submitted; private Integer aiAssessmentScore; + public String getTaskName(){return taskName;} public void setTaskName(String v){taskName=v;} public String getModuleName(){return moduleName;} public void setModuleName(String v){moduleName=v;} public String getProgressStatus(){return progressStatus;} public void setProgressStatus(String v){progressStatus=v;} public Boolean getSubmitted(){return submitted;} public void setSubmitted(Boolean v){submitted=v;} public Integer getAiAssessmentScore(){return aiAssessmentScore;} public void setAiAssessmentScore(Integer v){aiAssessmentScore=v;} } diff --git a/src/main/java/com/sztzjy/linkCommerce/service/StudentExperimentReportService.java b/src/main/java/com/sztzjy/linkCommerce/service/StudentExperimentReportService.java new file mode 100644 index 0000000..4e0a990 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/StudentExperimentReportService.java @@ -0,0 +1,2 @@ +package com.sztzjy.linkCommerce.service; import com.sztzjy.linkCommerce.config.security.JwtUser; import com.sztzjy.linkCommerce.entity.dto.StudentExperimentReportDTO; +public interface StudentExperimentReportService { StudentExperimentReportDTO getCurrentReport(JwtUser user); } diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImpl.java new file mode 100644 index 0000000..eed583e --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImpl.java @@ -0,0 +1,10 @@ +package com.sztzjy.linkCommerce.service.impl; +import com.sztzjy.linkCommerce.config.security.JwtUser; import com.sztzjy.linkCommerce.entity.*; import com.sztzjy.linkCommerce.entity.dto.*; import com.sztzjy.linkCommerce.mapper.*; import com.sztzjy.linkCommerce.service.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.math.*; import java.util.*; +@Service public class StudentExperimentReportServiceImpl implements StudentExperimentReportService { + @Autowired public StudentTeachingClassResolver studentTeachingClassResolver; @Autowired public SchoolDefaultTaskService schoolDefaultTaskService; @Autowired public TrainingTaskMapper trainingTaskMapper; @Autowired public StudentTrainingAnswerMapper studentTrainingAnswerMapper; @Autowired(required=false) public TeachingClassScoreWeightMapper teachingClassScoreWeightMapper; + private static final String[] NAMES={"互联网产品开发基础认知","市场洞察与需求分析","产品规划与设计","产品开发与测试验证","产品上线与运营推广","综合实训"}; + private static final BigDecimal[] DEFAULTS={new BigDecimal("0.10"),new BigDecimal("0.30"),new BigDecimal("0.10"),new BigDecimal("0.10"),new BigDecimal("0.20"),new BigDecimal("0.20")}; + public StudentExperimentReportDTO getCurrentReport(JwtUser user){ String classId=studentTeachingClassResolver.resolveRequired(user); StudentExperimentReportDTO report=new StudentExperimentReportDTO(); report.setTeachingClassId(classId); List modules=new ArrayList<>(); Map index=new HashMap<>(); BigDecimal[] weights=weights(classId); for(int i=0;i tasks=new ArrayList<>(); for(TaskAllocation a: schoolDefaultTaskService.resolveForTeachingClass(classId)){if(a==null||a.getDisabledStatus()!=null&&a.getDisabledStatus()==1||TaskAllocation.PUBLICATION_MARKER.equals(a.getModule()))continue; TrainingTask t=trainingTaskMapper.selectByTaskKey(a.getModule()); if(t==null||!index.containsKey(t.getProjectName()))continue; StudentTrainingAnswer ans=studentTrainingAnswerMapper.selectByStudentClassAndTask(user.getUserId(),classId,t.getId()); StudentExperimentReportModuleDTO m=index.get(t.getProjectName());m.setTotalTaskCount(m.getTotalTaskCount()+1);int score=ans==null||ans.getAiAssessmentScore()==null?0:ans.getAiAssessmentScore();m.setScore(m.getScore().add(BigDecimal.valueOf(score)));if(ans!=null&&(Boolean.TRUE.equals(ans.getSubmitted())||"COMPLETED".equals(ans.getProgressStatus())))m.setCompletedTaskCount(m.getCompletedTaskCount()+1);StudentExperimentReportTaskDTO row=new StudentExperimentReportTaskDTO();row.setTaskName(t.getTaskName());row.setModuleName(t.getProjectName());if(ans!=null){row.setProgressStatus(ans.getProgressStatus());row.setSubmitted(ans.getSubmitted());row.setAiAssessmentScore(ans.getAiAssessmentScore());}tasks.add(row);} BigDecimal total=BigDecimal.ZERO;for(StudentExperimentReportModuleDTO m:modules){BigDecimal score=m.getTotalTaskCount()==0?BigDecimal.ZERO:m.getScore().divide(BigDecimal.valueOf(m.getTotalTaskCount()),2,BigDecimal.ROUND_HALF_UP);m.setScore(score);total=total.add(score.multiply(m.getWeight()));}report.setTotalScore(total.setScale(2,BigDecimal.ROUND_HALF_UP));report.setModules(modules);report.setTasks(tasks);return report; } + private BigDecimal[] weights(String id){TeachingClassScoreWeight w=teachingClassScoreWeightMapper==null?null:teachingClassScoreWeightMapper.selectByTeachingClassId(id);if(w==null)return DEFAULTS;return new BigDecimal[]{w.getFoundationWeight(),w.getMarketInsightWeight(),w.getPlanningDesignWeight(),w.getDevelopmentValidationWeight(),w.getLaunchOperationWeight(),w.getComprehensiveTrainingWeight()};} +} diff --git a/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportControllerTest.java b/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportControllerTest.java new file mode 100644 index 0000000..e6f2f87 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentExperimentReportControllerTest.java @@ -0,0 +1,3 @@ +package com.sztzjy.linkCommerce.controller.stu; +import com.sztzjy.linkCommerce.config.security.JwtUser; import com.sztzjy.linkCommerce.entity.dto.StudentExperimentReportDTO; import com.sztzjy.linkCommerce.service.StudentExperimentReportService; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +class StudentExperimentReportControllerTest { @Test void currentReportUsesAuthenticatedStudent(){ JwtUser user=new JwtUser();user.setUserId("student-1");user.setRoleId(4); StudentExperimentReportController c=new StudentExperimentReportController(){protected JwtUser currentUser(javax.servlet.http.HttpServletRequest r){return user;}}; StudentExperimentReportService s=mock(StudentExperimentReportService.class); ReflectionTestUtils.setField(c,"studentExperimentReportService",s); assertEquals(HttpStatus.OK,c.current(new MockHttpServletRequest()).getStatusCode()); verify(s).getCurrentReport(user); } } diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImplTest.java new file mode 100644 index 0000000..0d73e8f --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/StudentExperimentReportServiceImplTest.java @@ -0,0 +1,51 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.StudentTrainingAnswer; +import com.sztzjy.linkCommerce.entity.TaskAllocation; +import com.sztzjy.linkCommerce.entity.TrainingTask; +import com.sztzjy.linkCommerce.entity.dto.StudentExperimentReportDTO; +import com.sztzjy.linkCommerce.mapper.StudentTrainingAnswerMapper; +import com.sztzjy.linkCommerce.mapper.TrainingTaskMapper; +import com.sztzjy.linkCommerce.service.SchoolDefaultTaskService; +import com.sztzjy.linkCommerce.service.StudentTeachingClassResolver; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +class StudentExperimentReportServiceImplTest { + @Test + void aggregatesCurrentClassAiScoresIntoSixModules() { + StudentExperimentReportServiceImpl service = new StudentExperimentReportServiceImpl(); + StudentTeachingClassResolver resolver = mock(StudentTeachingClassResolver.class); + SchoolDefaultTaskService taskService = mock(SchoolDefaultTaskService.class); + TrainingTaskMapper taskMapper = mock(TrainingTaskMapper.class); + StudentTrainingAnswerMapper answerMapper = mock(StudentTrainingAnswerMapper.class); + ReflectionTestUtils.setField(service, "studentTeachingClassResolver", resolver); + ReflectionTestUtils.setField(service, "schoolDefaultTaskService", taskService); + ReflectionTestUtils.setField(service, "trainingTaskMapper", taskMapper); + ReflectionTestUtils.setField(service, "studentTrainingAnswerMapper", answerMapper); + when(resolver.resolveRequired(any())).thenReturn("class-1"); + when(taskService.resolveForTeachingClass("class-1")).thenReturn(Arrays.asList(allocation("market-task"), allocation("disabled-task", (byte) 1))); + when(taskMapper.selectByTaskKey("market-task")).thenReturn(task("task-1", "市场洞察与需求分析", "需求访谈")); + when(answerMapper.selectByStudentClassAndTask("student-1", "class-1", "task-1")).thenReturn(answer(80, true)); + + StudentExperimentReportDTO report = service.getCurrentReport(student()); + + assertEquals(6, report.getModules().size()); + assertEquals(new BigDecimal("80.00"), report.getModules().get(1).getScore()); + assertEquals(new BigDecimal("0.30"), report.getModules().get(1).getWeight()); + assertEquals(new BigDecimal("24.00"), report.getTotalScore()); + assertEquals(1, report.getTasks().size()); + } + private JwtUser student() { JwtUser user = new JwtUser(); user.setUserId("student-1"); user.setRoleId(4); return user; } + private TaskAllocation allocation(String key) { return allocation(key, (byte) 0); } + private TaskAllocation allocation(String key, byte disabled) { TaskAllocation value = new TaskAllocation(); value.setModule(key); value.setDisabledStatus(disabled); return value; } + private TrainingTask task(String id, String project, String name) { TrainingTask value = new TrainingTask(); value.setId(id); value.setProjectName(project); value.setTaskName(name); return value; } + private StudentTrainingAnswer answer(int score, boolean submitted) { StudentTrainingAnswer value = new StudentTrainingAnswer(); value.setAiAssessmentScore(score); value.setSubmitted(submitted); value.setProgressStatus("COMPLETED"); return value; } +}