feat: grade market opportunity selection step one

main
chenyuan 3 weeks ago
parent 50bf9265d5
commit 38aa2bb68d

@ -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<MarketOpportunitySelectionStepOneCheckResult> 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<NewProductSurveyStepTwoValidationResult> validateNewProductSurveyStepTwo(

@ -0,0 +1,17 @@
package com.sztzjy.linkCommerce.entity.dto;
import java.util.List;
public class MarketOpportunitySelectionStepOneCheckRequest {
private List<Item> items;
public List<Item> getItems() { return items; }
public void setItems(List<Item> 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; }
}
}

@ -0,0 +1,30 @@
package com.sztzjy.linkCommerce.entity.dto;
import java.util.ArrayList;
import java.util.List;
public class MarketOpportunitySelectionStepOneCheckResult {
private List<ItemResult> items = new ArrayList<>();
private int correctCount;
private int totalCount;
private boolean allCorrect;
public List<ItemResult> getItems() { return items; }
public void setItems(List<ItemResult> 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; }
}
}

@ -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);
}

@ -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<String> ALLOWED_CATEGORIES = new LinkedHashSet<>(Arrays.asList("市场规模", "市场潜力", "竞争强度", "运营难度"));
private static final Map<String, String> 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<String, String> submitted = validateSubmittedItems(request == null ? null : request.getItems());
Map<String, String> 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<String, String> validateSubmittedItems(List<MarketOpportunitySelectionStepOneCheckRequest.Item> items) {
if (items == null || items.size() != DEFAULT_ANSWERS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请完成全部指标分类后再判题");
Map<String, String> 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<String, String> 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<String, Object> raw = JSON.parseObject(answerJson, LinkedHashMap.class); Map<String, String> 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<String, String> createDefaultAnswers() {
Map<String, String> 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;
}
}

@ -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<MarketOpportunitySelectionStepOneCheckResult> 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();

@ -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<String, String> 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<MarketOpportunitySelectionStepOneCheckRequest.Item> 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<MarketOpportunitySelectionStepOneCheckRequest.Item> items = defaultAnswers(); items.get(1).setCategory(category); return request(items); }
private MarketOpportunitySelectionStepOneCheckRequest request(List<MarketOpportunitySelectionStepOneCheckRequest.Item> 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; }
}
Loading…
Cancel
Save