From 9c5ef5bb9dfc246f39bd744d952e7f85f676f9aa Mon Sep 17 00:00:00 2001 From: chenyuan Date: Fri, 7 Aug 2026 15:58:43 +0800 Subject: [PATCH] feat: validate product development process step two --- .../stu/StudentTrainingAnswerController.java | 17 ++++ ...opmentProcessStepTwoValidationRequest.java | 29 ++++++ ...lopmentProcessStepTwoValidationResult.java | 11 +++ ...oductDevelopmentProcessStepTwoService.java | 9 ++ ...tDevelopmentProcessStepTwoServiceImpl.java | 97 +++++++++++++++++++ .../StudentTrainingAnswerControllerTest.java | 20 ++++ ...elopmentProcessStepTwoServiceImplTest.java | 81 ++++++++++++++++ 7 files changed, 264 insertions(+) create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepTwoService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImpl.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImplTest.java 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 2d3fcaa..6a7fad1 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -22,6 +22,8 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValid 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.entity.dto.ProductDevelopmentProcessStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepTwoValidationResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService; @@ -31,6 +33,7 @@ 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.ProductDevelopmentProcessStepTwoService; import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; import com.sztzjy.linkCommerce.util.ResultEntity; import io.swagger.annotations.Api; @@ -73,6 +76,9 @@ public class StudentTrainingAnswerController { @Autowired private ProductDevelopmentProcessStepOneService productDevelopmentProcessStepOneService; + @Autowired + private ProductDevelopmentProcessStepTwoService productDevelopmentProcessStepTwoService; + @GetMapping("/{taskKey}") @ApiOperation("查询当前学生指定实训任务答案") public ResultEntity detail(@PathVariable String taskKey, @@ -200,6 +206,17 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/product-development-process/step-2/validate") + @ApiOperation("产品开发主要流程第二步校验") + public ResultEntity validateProductDevelopmentProcessStepTwo( + @RequestBody ProductDevelopmentProcessStepTwoValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", productDevelopmentProcessStepTwoService.validate(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/ProductDevelopmentProcessStepTwoValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationRequest.java new file mode 100644 index 0000000..82b0f59 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationRequest.java @@ -0,0 +1,29 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class ProductDevelopmentProcessStepTwoValidationRequest { + 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; + private String startDate; + private String endDate; + private String dependency; + + 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; } + public String getStartDate() { return startDate; } + public void setStartDate(String startDate) { this.startDate = startDate; } + public String getEndDate() { return endDate; } + public void setEndDate(String endDate) { this.endDate = endDate; } + public String getDependency() { return dependency; } + public void setDependency(String dependency) { this.dependency = dependency; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationResult.java new file mode 100644 index 0000000..5055230 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentProcessStepTwoValidationResult.java @@ -0,0 +1,11 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class ProductDevelopmentProcessStepTwoValidationResult { + private boolean valid; + private String message; + + public boolean isValid() { return valid; } + public void setValid(boolean valid) { this.valid = valid; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepTwoService.java b/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepTwoService.java new file mode 100644 index 0000000..c6850f5 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentProcessStepTwoService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepTwoValidationResult; + +public interface ProductDevelopmentProcessStepTwoService { + ProductDevelopmentProcessStepTwoValidationResult validate(ProductDevelopmentProcessStepTwoValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImpl.java new file mode 100644 index 0000000..3935d11 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImpl.java @@ -0,0 +1,97 @@ +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.ProductDevelopmentProcessStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepTwoValidationResult; +import com.sztzjy.linkCommerce.service.ProductDevelopmentProcessStepTwoService; +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 java.time.LocalDate; +import java.time.format.DateTimeParseException; +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 ProductDevelopmentProcessStepTwoServiceImpl implements ProductDevelopmentProcessStepTwoService { + 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 Set TASK_NUMBERS = new LinkedHashSet<>(Arrays.asList( + "1.1", "1.2", "2.1", "2.2", "2.3", "3.1", "3.2", "3.3", "3.4", "3.5", "3.6", + "4.1", "4.2", "4.3", "4.4", "5.1", "5.2", "5.3", "5.4")); + private final JdbcTemplate jdbcTemplate; + + @Autowired + public ProductDevelopmentProcessStepTwoServiceImpl(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } + + @Override + public ProductDevelopmentProcessStepTwoValidationResult validate(ProductDevelopmentProcessStepTwoValidationRequest request, JwtUser user) { + requireStudent(user); + Map standardAnswers = loadStandardAnswers(); + validateItems(request == null ? null : request.getItems(), standardAnswers); + ProductDevelopmentProcessStepTwoValidationResult result = new ProductDevelopmentProcessStepTwoValidationResult(); + result.setValid(true); + result.setMessage("校验通过"); + 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 void validateItems(List items, Map standardAnswers) { + if (items == null || items.size() != TASK_NUMBERS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写全部任务的工期与项目依赖关系"); + Set submittedTasks = new LinkedHashSet<>(); + for (ProductDevelopmentProcessStepTwoValidationRequest.Item item : items) { + String taskNo = item == null ? "" : StringUtils.trimToEmpty(item.getTaskNo()); + String classification = item == null ? "" : StringUtils.trimToEmpty(item.getClassification()); + String dependency = item == null ? "" : StringUtils.trimToEmpty(item.getDependency()); + if (!TASK_NUMBERS.contains(taskNo) || !submittedTasks.add(taskNo) || !ALLOWED_CLASSIFICATIONS.contains(classification)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的任务数据不合法"); + } + if (!classification.equals(standardAnswers.get(taskNo))) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第一步关键任务判断"); + if (dependency.isEmpty()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请填写任务 " + taskNo + " 的项目依赖关系"); + LocalDate startDate = parseDate(item.getStartDate(), taskNo, "开始时间"); + LocalDate endDate = parseDate(item.getEndDate(), taskNo, "完成时间"); + if (endDate.isBefore(startDate)) throw new ServiceException(HttpStatus.BAD_REQUEST, "任务 " + taskNo + " 的完成时间不能早于开始时间"); + } + if (!submittedTasks.equals(TASK_NUMBERS)) throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的任务数据不完整"); + } + + private LocalDate parseDate(String value, String taskNo, String label) { + try { + return LocalDate.parse(StringUtils.trimToEmpty(value)); + } catch (DateTimeParseException e) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请填写任务 " + taskNo + " 的" + label); + } + } + + @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 : TASK_NUMBERS) { + String classification = raw == null || raw.get(taskNo) == null ? "" : String.valueOf(raw.get(taskNo)); + if (!ALLOWED_CLASSIFICATIONS.contains(classification)) throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR, "第一步标准答案未正确配置"); + answers.put(taskNo, classification); + } + if (raw == null || raw.size() != answers.size()) throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR, "第一步标准答案未正确配置"); + 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 77290d4..00f3ad8 100644 --- a/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java @@ -19,6 +19,8 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeVali 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.entity.dto.ProductDevelopmentProcessStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepTwoValidationResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService; @@ -27,6 +29,7 @@ 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.service.ProductDevelopmentProcessStepTwoService; import com.sztzjy.linkCommerce.util.ResultEntity; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -175,6 +178,23 @@ class StudentTrainingAnswerControllerTest { verify(service).check(any(ProductDevelopmentProcessStepOneCheckRequest.class), any(JwtUser.class)); } + @Test + void validateProductDevelopmentProcessStepTwoUsesTheAuthenticatedStudentAndReturnsTheValidationResult() { + StudentTrainingAnswerController controller = new StudentTrainingAnswerController(); + ProductDevelopmentProcessStepTwoService service = mock(ProductDevelopmentProcessStepTwoService.class); + ReflectionTestUtils.setField(controller, "productDevelopmentProcessStepTwoService", service); + ProductDevelopmentProcessStepTwoValidationResult validation = new ProductDevelopmentProcessStepTwoValidationResult(); + validation.setValid(true); + when(service.validate(any(ProductDevelopmentProcessStepTwoValidationRequest.class), any(JwtUser.class))).thenReturn(validation); + + ResultEntity result = controller + .validateProductDevelopmentProcessStepTwo(new ProductDevelopmentProcessStepTwoValidationRequest(), authenticatedStudentRequest()); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals(validation, result.getBody().getData()); + verify(service).validate(any(ProductDevelopmentProcessStepTwoValidationRequest.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/ProductDevelopmentProcessStepTwoServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImplTest.java new file mode 100644 index 0000000..d741613 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentProcessStepTwoServiceImplTest.java @@ -0,0 +1,81 @@ +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.ProductDevelopmentProcessStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentProcessStepTwoValidationResult; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ProductDevelopmentProcessStepTwoServiceImplTest { + @Test + void validateAcceptsAllTasksWithAValidSchedule() { + ProductDevelopmentProcessStepTwoServiceImpl service = new ProductDevelopmentProcessStepTwoServiceImpl(standardAnswerJdbcTemplate()); + + ProductDevelopmentProcessStepTwoValidationResult result = service.validate(request(defaultItems()), student()); + + assertTrue(result.isValid()); + } + + @Test + void validateRejectsMissingDependencyInvertedDatesAndWrongFirstStepClassification() { + ProductDevelopmentProcessStepTwoServiceImpl service = new ProductDevelopmentProcessStepTwoServiceImpl(standardAnswerJdbcTemplate()); + List missingDependency = defaultItems(); + missingDependency.get(0).setDependency(""); + assertThrows(ServiceException.class, () -> service.validate(request(missingDependency), student())); + + List invertedDates = defaultItems(); + invertedDates.get(0).setEndDate("2026-01-01"); + assertThrows(ServiceException.class, () -> service.validate(request(invertedDates), student())); + + List wrongClassification = defaultItems(); + wrongClassification.get(3).setClassification("关键任务"); + assertThrows(ServiceException.class, () -> service.validate(request(wrongClassification), student())); + } + + private JdbcTemplate standardAnswerJdbcTemplate() { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + Map answers = new LinkedHashMap<>(); + for (ProductDevelopmentProcessStepTwoValidationRequest.Item item : defaultItems()) answers.put(item.getTaskNo(), item.getClassification()); + when(jdbcTemplate.queryForObject(any(String.class), any(Class.class), any(), any())).thenReturn(JSON.toJSONString(answers)); + return jdbcTemplate; + } + + private ProductDevelopmentProcessStepTwoValidationRequest request(List items) { + ProductDevelopmentProcessStepTwoValidationRequest request = new ProductDevelopmentProcessStepTwoValidationRequest(); + request.setItems(items); + return request; + } + + private List defaultItems() { + 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 ProductDevelopmentProcessStepTwoValidationRequest.Item item(String taskNo, String classification) { + ProductDevelopmentProcessStepTwoValidationRequest.Item item = new ProductDevelopmentProcessStepTwoValidationRequest.Item(); + item.setTaskNo(taskNo); + item.setClassification(classification); + item.setStartDate("2026-02-01"); + item.setEndDate("2026-02-02"); + item.setDependency("无"); + return item; + } + + private JwtUser student() { JwtUser user = new JwtUser(); user.setUserId("stu-1"); user.setRoleId(4); return user; } +}