diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java index cbf8e22..2d3fcaa 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -20,6 +20,8 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeVali import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeValidationResult; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationRequest; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService; @@ -28,6 +30,7 @@ import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepOneService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepTwoService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepThreeService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepFourService; +import com.sztzjy.linkCommerce.service.ProductDevelopmentProcessStepOneService; import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; import com.sztzjy.linkCommerce.util.ResultEntity; import io.swagger.annotations.Api; @@ -67,6 +70,9 @@ public class StudentTrainingAnswerController { @Autowired private ProductDevelopmentFactorsStepFourService productDevelopmentFactorsStepFourService; + @Autowired + private ProductDevelopmentProcessStepOneService productDevelopmentProcessStepOneService; + @GetMapping("/{taskKey}") @ApiOperation("查询当前学生指定实训任务答案") public ResultEntity detail(@PathVariable String taskKey, @@ -183,6 +189,17 @@ public class StudentTrainingAnswerController { catch (ServiceException e) { return new ResultEntity<>(e.getCode(), e.getMessage()); } } + @PostMapping("/product-development-process/step-1/check") + @ApiOperation("产品开发主要流程第一步判题") + public ResultEntity checkProductDevelopmentProcessStepOne( + @RequestBody ProductDevelopmentProcessStepOneCheckRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "判题完成", productDevelopmentProcessStepOneService.check(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + @DeleteMapping("/{taskKey}") @ApiOperation("删除当前学生指定实训任务答案") public ResultEntity delete(@PathVariable String taskKey, diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepOneCheckRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepOneCheckRequest.java new file mode 100644 index 0000000..5a5f39d --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepOneCheckRequest.java @@ -0,0 +1,20 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductDevelopmentProcessStepOneCheckRequest { + private List items; + + public List getItems() { return items; } + public void setItems(List items) { this.items = items; } + + public static class Item { + private String taskNo; + private String classification; + + public String getTaskNo() { return taskNo; } + public void setTaskNo(String taskNo) { this.taskNo = taskNo; } + public String getClassification() { return classification; } + public void setClassification(String classification) { this.classification = classification; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepOneCheckResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepOneCheckResult.java new file mode 100644 index 0000000..87d3760 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepOneCheckResult.java @@ -0,0 +1,33 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.ArrayList; +import java.util.List; + +public class ProductDevelopmentProcessStepOneCheckResult { + private List items = new ArrayList<>(); + private int correctCount; + private int totalCount; + private boolean allCorrect; + + public List getItems() { return items; } + public void setItems(List items) { this.items = items; } + public int getCorrectCount() { return correctCount; } + public void setCorrectCount(int correctCount) { this.correctCount = correctCount; } + public int getTotalCount() { return totalCount; } + public void setTotalCount(int totalCount) { this.totalCount = totalCount; } + public boolean isAllCorrect() { return allCorrect; } + public void setAllCorrect(boolean allCorrect) { this.allCorrect = allCorrect; } + + public static class ItemResult { + private String taskNo; + private boolean correct; + private String message; + + public String getTaskNo() { return taskNo; } + public void setTaskNo(String taskNo) { this.taskNo = taskNo; } + public boolean isCorrect() { return correct; } + public void setCorrect(boolean correct) { this.correct = correct; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepOneService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepOneService.java new file mode 100644 index 0000000..45b3f8e --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepOneService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckResult; + +public interface ProductDevelopmentProcessStepOneService { + ProductDevelopmentProcessStepOneCheckResult check(ProductDevelopmentProcessStepOneCheckRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepOneServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepOneServiceImpl.java new file mode 100644 index 0000000..cbb5ae5 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepOneServiceImpl.java @@ -0,0 +1,148 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.alibaba.fastjson.JSON; +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckResult; +import com.sztzjy.linkCommerce.service.ProductDevelopmentProcessStepOneService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class ProductDevelopmentProcessStepOneServiceImpl implements ProductDevelopmentProcessStepOneService { + private static final String TASK_KEY = "product-development-process"; + private static final int STEP_NO = 1; + private static final String KEY_TASK = "关键任务"; + private static final String NON_KEY_TASK = "非关键任务"; + private static final Set ALLOWED_CLASSIFICATIONS = new LinkedHashSet<>(Arrays.asList(KEY_TASK, NON_KEY_TASK)); + private static final Map DEFAULT_ANSWERS = createDefaultAnswers(); + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ProductDevelopmentProcessStepOneServiceImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @PostConstruct + public void initializeStandardAnswers() { + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS training_step_standard_answer (" + + "id varchar(64) NOT NULL COMMENT 'primary id'," + + "task_key varchar(128) NOT NULL COMMENT 'training task key'," + + "step_no int NOT NULL COMMENT 'step number'," + + "answer_json longtext NOT NULL COMMENT 'standard answer json'," + + "create_time datetime NULL COMMENT 'create time'," + + "update_time datetime NULL COMMENT 'update time'," + + "PRIMARY KEY (id)," + + "UNIQUE KEY uk_training_step_standard_answer (task_key, step_no)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='training step standard answer'"); + jdbcTemplate.update("INSERT IGNORE INTO training_step_standard_answer " + + "(id, task_key, step_no, answer_json, create_time, update_time) VALUES (?, ?, ?, ?, NOW(), NOW())", + "product-development-process-step-1", TASK_KEY, STEP_NO, JSON.toJSONString(DEFAULT_ANSWERS)); + } + + @Override + public ProductDevelopmentProcessStepOneCheckResult check(ProductDevelopmentProcessStepOneCheckRequest request, JwtUser user) { + requireStudent(user); + Map submitted = validateSubmittedItems(request == null ? null : request.getItems()); + Map standardAnswers = loadStandardAnswers(); + ProductDevelopmentProcessStepOneCheckResult result = new ProductDevelopmentProcessStepOneCheckResult(); + int correctCount = 0; + for (String taskNo : standardAnswers.keySet()) { + boolean correct = standardAnswers.get(taskNo).equals(submitted.get(taskNo)); + ProductDevelopmentProcessStepOneCheckResult.ItemResult item = new ProductDevelopmentProcessStepOneCheckResult.ItemResult(); + item.setTaskNo(taskNo); + item.setCorrect(correct); + item.setMessage(correct ? "分类正确" : "分类有误,请重新选择"); + result.getItems().add(item); + if (correct) correctCount++; + } + result.setCorrectCount(correctCount); + result.setTotalCount(standardAnswers.size()); + result.setAllCorrect(correctCount == standardAnswers.size()); + return result; + } + + private void requireStudent(JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再判题"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训判题"); + } + } + + private Map validateSubmittedItems(List items) { + if (items == null || items.size() != DEFAULT_ANSWERS.size()) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完成全部任务分类后再判题"); + } + Map submitted = new LinkedHashMap<>(); + for (ProductDevelopmentProcessStepOneCheckRequest.Item item : items) { + String taskNo = item == null ? null : StringUtils.trimToEmpty(item.getTaskNo()); + String classification = item == null ? null : StringUtils.trimToEmpty(item.getClassification()); + if (!DEFAULT_ANSWERS.containsKey(taskNo) || !ALLOWED_CLASSIFICATIONS.contains(classification) || submitted.put(taskNo, classification) != null) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的任务分类数据不合法"); + } + } + if (!submitted.keySet().equals(DEFAULT_ANSWERS.keySet())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的任务分类数据不完整"); + } + return submitted; + } + + @SuppressWarnings("unchecked") + private Map loadStandardAnswers() { + String answerJson = jdbcTemplate.queryForObject( + "SELECT answer_json FROM training_step_standard_answer WHERE task_key = ? AND step_no = ?", + String.class, TASK_KEY, STEP_NO); + Map raw = JSON.parseObject(answerJson, LinkedHashMap.class); + Map answers = new LinkedHashMap<>(); + for (String taskNo : DEFAULT_ANSWERS.keySet()) { + Object classification = raw == null ? null : raw.get(taskNo); + if (classification == null || !ALLOWED_CLASSIFICATIONS.contains(String.valueOf(classification))) { + throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR, "第一步标准答案未正确配置"); + } + answers.put(taskNo, String.valueOf(classification)); + } + if (raw == null || raw.size() != answers.size()) { + throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR, "第一步标准答案未正确配置"); + } + return answers; + } + + private static Map createDefaultAnswers() { + Map answers = new LinkedHashMap<>(); + answers.put("1.1", KEY_TASK); + answers.put("1.2", KEY_TASK); + answers.put("2.1", KEY_TASK); + answers.put("2.2", NON_KEY_TASK); + answers.put("2.3", KEY_TASK); + answers.put("3.1", KEY_TASK); + answers.put("3.2", KEY_TASK); + answers.put("3.3", NON_KEY_TASK); + answers.put("3.4", KEY_TASK); + answers.put("3.5", KEY_TASK); + answers.put("3.6", KEY_TASK); + answers.put("4.1", KEY_TASK); + answers.put("4.2", KEY_TASK); + answers.put("4.3", KEY_TASK); + answers.put("4.4", KEY_TASK); + answers.put("5.1", KEY_TASK); + answers.put("5.2", KEY_TASK); + answers.put("5.3", KEY_TASK); + answers.put("5.4", KEY_TASK); + return answers; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java b/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java index 801bc42..77290d4 100644 --- a/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java @@ -17,6 +17,8 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepTwoValida import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepTwoValidationResult; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeValidationRequest; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeValidationResult; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService; @@ -24,6 +26,7 @@ import com.sztzjy.linkCommerce.service.NewProductSurveyStepFourService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepOneService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepTwoService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepThreeService; +import com.sztzjy.linkCommerce.service.ProductDevelopmentProcessStepOneService; import com.sztzjy.linkCommerce.util.ResultEntity; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -155,6 +158,23 @@ class StudentTrainingAnswerControllerTest { verify(service).validate(any(ProductDevelopmentFactorsStepThreeValidationRequest.class), any(JwtUser.class)); } + @Test + void checkProductDevelopmentProcessStepOneUsesTheAuthenticatedStudentAndReturnsTheGrade() { + StudentTrainingAnswerController controller = new StudentTrainingAnswerController(); + ProductDevelopmentProcessStepOneService service = mock(ProductDevelopmentProcessStepOneService.class); + ReflectionTestUtils.setField(controller, "productDevelopmentProcessStepOneService", service); + ProductDevelopmentProcessStepOneCheckResult grade = new ProductDevelopmentProcessStepOneCheckResult(); + grade.setAllCorrect(true); + when(service.check(any(ProductDevelopmentProcessStepOneCheckRequest.class), any(JwtUser.class))).thenReturn(grade); + + ResultEntity result = controller + .checkProductDevelopmentProcessStepOne(new ProductDevelopmentProcessStepOneCheckRequest(), authenticatedStudentRequest()); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals(grade, result.getBody().getData()); + verify(service).check(any(ProductDevelopmentProcessStepOneCheckRequest.class), any(JwtUser.class)); + } + private HttpServletRequest authenticatedStudentRequest() { JwtUser user = new JwtUser(); user.setUserId("stu-1"); diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepOneServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepOneServiceImplTest.java new file mode 100644 index 0000000..fe4dcbd --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepOneServiceImplTest.java @@ -0,0 +1,100 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.alibaba.fastjson.JSON; +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepOneCheckResult; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ProductDevelopmentProcessStepOneServiceImplTest { + @Test + void checkReturnsPerTaskFeedbackWithoutReturningTheAnswers() { + ProductDevelopmentProcessStepOneServiceImpl service = new ProductDevelopmentProcessStepOneServiceImpl(standardAnswerJdbcTemplate()); + + ProductDevelopmentProcessStepOneCheckResult result = service.check(requestWithClassification("2.2", "关键任务"), student()); + + assertEquals(18, result.getCorrectCount()); + assertEquals(19, result.getTotalCount()); + assertFalse(result.isAllCorrect()); + assertTrue(result.getItems().get(0).isCorrect()); + assertFalse(result.getItems().get(3).isCorrect()); + assertEquals("分类有误,请重新选择", result.getItems().get(3).getMessage()); + assertFalse(JSON.toJSONString(result).contains("correctClassification")); + } + + @Test + void checkRejectsIncompleteAnswersInvalidClassificationAndNonStudents() { + ProductDevelopmentProcessStepOneServiceImpl service = new ProductDevelopmentProcessStepOneServiceImpl(standardAnswerJdbcTemplate()); + + assertThrows(ServiceException.class, () -> service.check(request(Arrays.asList(item("1.1", "关键任务"))), student())); + assertThrows(ServiceException.class, () -> service.check(requestWithClassification("2.2", "未知任务"), student())); + assertThrows(ServiceException.class, () -> service.check(requestWithClassification("2.2", "非关键任务"), teacher())); + } + + @Test + void initializationKeepsAnyExistingStandardAnswer() { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + ProductDevelopmentProcessStepOneServiceImpl service = new ProductDevelopmentProcessStepOneServiceImpl(jdbcTemplate); + + service.initializeStandardAnswers(); + + verify(jdbcTemplate).execute(contains("CREATE TABLE IF NOT EXISTS training_step_standard_answer")); + verify(jdbcTemplate).update(startsWith("INSERT IGNORE INTO training_step_standard_answer"), any(), any(), any(), any()); + } + + private JdbcTemplate standardAnswerJdbcTemplate() { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + Map answers = new LinkedHashMap<>(); + for (ProductDevelopmentProcessStepOneCheckRequest.Item item : defaultAnswers()) answers.put(item.getTaskNo(), item.getClassification()); + when(jdbcTemplate.queryForObject(any(String.class), any(Class.class), any(), any())).thenReturn(JSON.toJSONString(answers)); + return jdbcTemplate; + } + + private ProductDevelopmentProcessStepOneCheckRequest requestWithClassification(String taskNo, String classification) { + List items = defaultAnswers(); + items.stream().filter(item -> taskNo.equals(item.getTaskNo())).findFirst().orElseThrow(IllegalStateException::new).setClassification(classification); + return request(items); + } + + private ProductDevelopmentProcessStepOneCheckRequest request(List items) { + ProductDevelopmentProcessStepOneCheckRequest request = new ProductDevelopmentProcessStepOneCheckRequest(); + request.setItems(items); + return request; + } + + private List defaultAnswers() { + return Arrays.asList( + item("1.1", "关键任务"), item("1.2", "关键任务"), item("2.1", "关键任务"), item("2.2", "非关键任务"), item("2.3", "关键任务"), + item("3.1", "关键任务"), item("3.2", "关键任务"), item("3.3", "非关键任务"), item("3.4", "关键任务"), item("3.5", "关键任务"), item("3.6", "关键任务"), + item("4.1", "关键任务"), item("4.2", "关键任务"), item("4.3", "关键任务"), item("4.4", "关键任务"), + item("5.1", "关键任务"), item("5.2", "关键任务"), item("5.3", "关键任务"), item("5.4", "关键任务")); + } + + private ProductDevelopmentProcessStepOneCheckRequest.Item item(String taskNo, String classification) { + ProductDevelopmentProcessStepOneCheckRequest.Item item = new ProductDevelopmentProcessStepOneCheckRequest.Item(); + item.setTaskNo(taskNo); + item.setClassification(classification); + return item; + } + + private JwtUser student() { JwtUser user = new JwtUser(); user.setUserId("stu-1"); user.setRoleId(4); return user; } + private JwtUser teacher() { JwtUser user = student(); user.setRoleId(3); return user; } +}