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..a67505a 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -6,6 +6,8 @@ import com.sztzjy.linkCommerce.config.security.TokenProvider; import com.sztzjy.linkCommerce.entity.StudentTrainingAnswer; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepOneCheckRequest; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepOneCheckResult; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckResult; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepTwoValidationRequest; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepTwoValidationResult; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepThreeValidationRequest; @@ -21,6 +23,7 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeVali import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationRequest; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; +import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepFourService; @@ -48,6 +51,9 @@ public class StudentTrainingAnswerController { @Autowired private NewProductSurveyStepOneService newProductSurveyStepOneService; + @Autowired + private MarketOpportunitySelectionStepOneService marketOpportunitySelectionStepOneService; + @Autowired private NewProductSurveyStepTwoService newProductSurveyStepTwoService; @@ -105,6 +111,17 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/market-opportunity-selection/step-1/check") + @ApiOperation("市场机会识别与选品第一步判题") + public ResultEntity checkMarketOpportunitySelectionStepOne( + @RequestBody MarketOpportunitySelectionStepOneCheckRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "判题完成", marketOpportunitySelectionStepOneService.check(answer, TokenProvider.getJWTUser(request))); + } catch (ServiceException e) { + return new ResultEntity<>(e.getCode(), e.getMessage()); + } + } + @PostMapping("/new-product-survey/step-2/validate") @ApiOperation("新产品调查与分析第二步校验") public ResultEntity validateNewProductSurveyStepTwo( diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/MarketOpportunitySelectionStepOneCheckRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/MarketOpportunitySelectionStepOneCheckRequest.java new file mode 100644 index 0000000..575c70f --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/MarketOpportunitySelectionStepOneCheckRequest.java @@ -0,0 +1,17 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class MarketOpportunitySelectionStepOneCheckRequest { + private List items; + public List getItems() { return items; } + public void setItems(List items) { this.items = items; } + public static class Item { + private String id; + private String category; + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getCategory() { return category; } + public void setCategory(String category) { this.category = category; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/MarketOpportunitySelectionStepOneCheckResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/MarketOpportunitySelectionStepOneCheckResult.java new file mode 100644 index 0000000..af4e808 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/MarketOpportunitySelectionStepOneCheckResult.java @@ -0,0 +1,30 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.ArrayList; +import java.util.List; + +public class MarketOpportunitySelectionStepOneCheckResult { + 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 id; + private boolean correct; + private String message; + public String getId() { return id; } + public void setId(String id) { this.id = id; } + 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/MarketOpportunitySelectionStepOneService.java b/src/main/java/com/sztzjy/linkCommerce/service/MarketOpportunitySelectionStepOneService.java new file mode 100644 index 0000000..f934c19 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/MarketOpportunitySelectionStepOneService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckResult; + +public interface MarketOpportunitySelectionStepOneService { + MarketOpportunitySelectionStepOneCheckResult check(MarketOpportunitySelectionStepOneCheckRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java new file mode 100644 index 0000000..acb61d8 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImpl.java @@ -0,0 +1,78 @@ +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.MarketOpportunitySelectionStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckResult; +import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; +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 MarketOpportunitySelectionStepOneServiceImpl implements MarketOpportunitySelectionStepOneService { + private static final String TASK_KEY = "market-opportunity-selection"; + private static final int STEP_NO = 1; + private static final Set ALLOWED_CATEGORIES = new LinkedHashSet<>(Arrays.asList("市场规模", "市场潜力", "竞争强度", "运营难度")); + private static final Map DEFAULT_ANSWERS = createDefaultAnswers(); + private final JdbcTemplate jdbcTemplate; + + @Autowired public MarketOpportunitySelectionStepOneServiceImpl(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,update_time datetime NULL,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())", "market-opportunity-selection-step-1", TASK_KEY, STEP_NO, JSON.toJSONString(DEFAULT_ANSWERS)); + } + + @Override + public MarketOpportunitySelectionStepOneCheckResult check(MarketOpportunitySelectionStepOneCheckRequest request, JwtUser user) { + requireStudent(user); + Map submitted = validateSubmittedItems(request == null ? null : request.getItems()); + Map standardAnswers = loadStandardAnswers(); + MarketOpportunitySelectionStepOneCheckResult result = new MarketOpportunitySelectionStepOneCheckResult(); + int correctCount = 0; + for (String id : standardAnswers.keySet()) { + boolean correct = standardAnswers.get(id).equals(submitted.get(id)); + MarketOpportunitySelectionStepOneCheckResult.ItemResult item = new MarketOpportunitySelectionStepOneCheckResult.ItemResult(); item.setId(id); 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 (MarketOpportunitySelectionStepOneCheckRequest.Item item : items) { + String id = item == null ? "" : StringUtils.trimToEmpty(item.getId()); String category = item == null ? "" : StringUtils.trimToEmpty(item.getCategory()); + if (!DEFAULT_ANSWERS.containsKey(id) || !ALLOWED_CATEGORIES.contains(category) || submitted.put(id, category) != 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 id : DEFAULT_ANSWERS.keySet()) { Object category = raw == null ? null : raw.get(id); if (category == null || !ALLOWED_CATEGORIES.contains(String.valueOf(category))) throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR, "第 1 步标准答案未正确配置"); answers.put(id, String.valueOf(category)); } + if (raw == null || raw.size() != answers.size()) throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR, "第 1 步标准答案未正确配置"); + return answers; + } + private static Map createDefaultAnswers() { + Map answers = new LinkedHashMap<>(); + answers.put("search-count", "市场规模"); answers.put("demand-supply-ratio", "竞争强度"); answers.put("transaction-amount", "市场规模"); answers.put("transaction-growth", "市场潜力"); answers.put("category-transaction-growth", "市场潜力"); answers.put("online-products", "竞争强度"); answers.put("online-merchants", "竞争强度"); answers.put("organic-traffic-ratio", "运营难度"); + 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..8aea745 100644 --- a/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java @@ -5,6 +5,8 @@ import com.sztzjy.linkCommerce.config.security.JwtUser; import com.sztzjy.linkCommerce.config.security.TokenProvider; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepOneCheckRequest; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepOneCheckResult; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckResult; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepTwoValidationRequest; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepTwoValidationResult; import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepThreeValidationRequest; @@ -18,6 +20,7 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepTwoValida import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeValidationRequest; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepThreeValidationResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; +import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService; import com.sztzjy.linkCommerce.service.NewProductSurveyStepFourService; @@ -57,6 +60,18 @@ class StudentTrainingAnswerControllerTest { verify(service).check(any(NewProductSurveyStepOneCheckRequest.class), any(JwtUser.class)); } + @Test + void checkMarketOpportunitySelectionStepOneUsesTheAuthenticatedStudentAndReturnsTheGrade() { + StudentTrainingAnswerController controller = new StudentTrainingAnswerController(); + MarketOpportunitySelectionStepOneService service = mock(MarketOpportunitySelectionStepOneService.class); + ReflectionTestUtils.setField(controller, "marketOpportunitySelectionStepOneService", service); + MarketOpportunitySelectionStepOneCheckResult grade = new MarketOpportunitySelectionStepOneCheckResult(); grade.setAllCorrect(true); + when(service.check(any(MarketOpportunitySelectionStepOneCheckRequest.class), any(JwtUser.class))).thenReturn(grade); + ResultEntity result = controller.checkMarketOpportunitySelectionStepOne(new MarketOpportunitySelectionStepOneCheckRequest(), authenticatedStudentRequest()); + assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(grade, result.getBody().getData()); + verify(service).check(any(MarketOpportunitySelectionStepOneCheckRequest.class), any(JwtUser.class)); + } + @Test void validateStepTwoUsesTheAuthenticatedStudentAndReturnsTheValidationResult() { StudentTrainingAnswerController controller = new StudentTrainingAnswerController(); diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java new file mode 100644 index 0000000..52c97c8 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/MarketOpportunitySelectionStepOneServiceImplTest.java @@ -0,0 +1,57 @@ +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.MarketOpportunitySelectionStepOneCheckRequest; +import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepOneCheckResult; +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 MarketOpportunitySelectionStepOneServiceImplTest { + @Test + void checkReturnsPerIndicatorFeedbackWithoutExposingTheAnswer() { + MarketOpportunitySelectionStepOneServiceImpl service = new MarketOpportunitySelectionStepOneServiceImpl(standardAnswerJdbcTemplate()); + MarketOpportunitySelectionStepOneCheckResult result = service.check(requestWithChangedSecondCategory("市场规模"), student()); + assertEquals(7, result.getCorrectCount()); assertEquals(8, result.getTotalCount()); assertFalse(result.isAllCorrect()); assertTrue(result.getItems().get(0).isCorrect()); assertFalse(result.getItems().get(1).isCorrect()); assertFalse(JSON.toJSONString(result).contains("correctCategory")); + } + + @Test + void checkRejectsIncompleteAnswersInvalidCategoriesAndNonStudents() { + MarketOpportunitySelectionStepOneServiceImpl service = new MarketOpportunitySelectionStepOneServiceImpl(standardAnswerJdbcTemplate()); + assertThrows(ServiceException.class, () -> service.check(request(Arrays.asList(item("search-count", "市场规模"))), student())); + assertThrows(ServiceException.class, () -> service.check(requestWithChangedSecondCategory("错误分类"), student())); + assertThrows(ServiceException.class, () -> service.check(requestWithChangedSecondCategory("市场规模"), teacher())); + } + + @Test + void initializationCreatesTheStandardAnswerTableWithoutOverwritingAnswers() { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); MarketOpportunitySelectionStepOneServiceImpl service = new MarketOpportunitySelectionStepOneServiceImpl(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 (MarketOpportunitySelectionStepOneCheckRequest.Item item : defaultAnswers()) answers.put(item.getId(), item.getCategory()); when(jdbcTemplate.queryForObject(any(String.class), any(Class.class), any(), any())).thenReturn(JSON.toJSONString(answers)); return jdbcTemplate; } + private List defaultAnswers() { return Arrays.asList(item("search-count", "市场规模"), item("demand-supply-ratio", "竞争强度"), item("transaction-amount", "市场规模"), item("transaction-growth", "市场潜力"), item("category-transaction-growth", "市场潜力"), item("online-products", "竞争强度"), item("online-merchants", "竞争强度"), item("organic-traffic-ratio", "运营难度")); } + private MarketOpportunitySelectionStepOneCheckRequest requestWithChangedSecondCategory(String category) { List items = defaultAnswers(); items.get(1).setCategory(category); return request(items); } + private MarketOpportunitySelectionStepOneCheckRequest request(List items) { MarketOpportunitySelectionStepOneCheckRequest request = new MarketOpportunitySelectionStepOneCheckRequest(); request.setItems(items); return request; } + private MarketOpportunitySelectionStepOneCheckRequest.Item item(String id, String category) { MarketOpportunitySelectionStepOneCheckRequest.Item item = new MarketOpportunitySelectionStepOneCheckRequest.Item(); item.setId(id); item.setCategory(category); 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; } +}