feat: complete market opportunity selection training workflow

main
chenyuan 5 days ago
parent 3fa742c5bc
commit 4424196fc9

@ -22,6 +22,10 @@ import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepThreeVal
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepThreeValidationResult;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFourValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFourValidationResult;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFiveValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFiveValidationResult;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepSixValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepSixValidationResult;
import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepTwoValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepTwoValidationResult;
import com.sztzjy.linkCommerce.entity.dto.NewProductSurveyStepThreeValidationRequest;
@ -132,6 +136,8 @@ import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepTwoService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepThreeService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepFourService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepFiveService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepSixService;
import com.sztzjy.linkCommerce.service.NewProductSurveyStepTwoService;
import com.sztzjy.linkCommerce.service.NewProductSurveyStepThreeService;
import com.sztzjy.linkCommerce.service.NewProductSurveyStepFourService;
@ -233,6 +239,12 @@ public class StudentTrainingAnswerController {
@Autowired
private MarketOpportunitySelectionStepFourService marketOpportunitySelectionStepFourService;
@Autowired
private MarketOpportunitySelectionStepFiveService marketOpportunitySelectionStepFiveService;
@Autowired
private MarketOpportunitySelectionStepSixService marketOpportunitySelectionStepSixService;
@Autowired
private NewProductSurveyStepTwoService newProductSurveyStepTwoService;
@ -545,6 +557,28 @@ public class StudentTrainingAnswerController {
}
}
@PostMapping("/market-opportunity-selection/step-5/validate")
@ApiOperation("市场机会识别与选品第五步校验")
public ResultEntity<MarketOpportunitySelectionStepFiveValidationResult> validateMarketOpportunitySelectionStepFive(
@RequestBody MarketOpportunitySelectionStepFiveValidationRequest answer, HttpServletRequest request) {
try {
return new ResultEntity<>(HttpStatus.OK, "校验通过", marketOpportunitySelectionStepFiveService.validate(answer, TokenProvider.getJWTUser(request)));
} catch (ServiceException e) {
return new ResultEntity<>(e.getCode(), e.getMessage());
}
}
@PostMapping("/market-opportunity-selection/step-6/validate")
@ApiOperation("市场机会识别与选品第六步校验")
public ResultEntity<MarketOpportunitySelectionStepSixValidationResult> validateMarketOpportunitySelectionStepSix(
@RequestBody MarketOpportunitySelectionStepSixValidationRequest answer, HttpServletRequest request) {
try {
return new ResultEntity<>(HttpStatus.OK, "校验通过", marketOpportunitySelectionStepSixService.validate(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,27 @@
package com.sztzjy.linkCommerce.entity.dto;
import java.math.BigDecimal;
import java.util.List;
public class MarketOpportunitySelectionStepFiveValidationRequest {
private List<Insight> insights;
public List<Insight> getInsights() { return insights; }
public void setInsights(List<Insight> insights) { this.insights = insights; }
public static class Insight {
private String productId;
private BigDecimal favorableRate;
private String painPoint;
private String unmetNeed;
public String getProductId() { return productId; }
public void setProductId(String productId) { this.productId = productId; }
public BigDecimal getFavorableRate() { return favorableRate; }
public void setFavorableRate(BigDecimal favorableRate) { this.favorableRate = favorableRate; }
public String getPainPoint() { return painPoint; }
public void setPainPoint(String painPoint) { this.painPoint = painPoint; }
public String getUnmetNeed() { return unmetNeed; }
public void setUnmetNeed(String unmetNeed) { this.unmetNeed = unmetNeed; }
}
}

@ -0,0 +1,11 @@
package com.sztzjy.linkCommerce.entity.dto;
public class MarketOpportunitySelectionStepFiveValidationResult {
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; }
}

@ -0,0 +1,8 @@
package com.sztzjy.linkCommerce.entity.dto;
public class MarketOpportunitySelectionStepSixValidationRequest {
private String developmentConclusion;
public String getDevelopmentConclusion() { return developmentConclusion; }
public void setDevelopmentConclusion(String developmentConclusion) { this.developmentConclusion = developmentConclusion; }
}

@ -0,0 +1,11 @@
package com.sztzjy.linkCommerce.entity.dto;
public class MarketOpportunitySelectionStepSixValidationResult {
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; }
}

@ -3,9 +3,20 @@ package com.sztzjy.linkCommerce.entity.dto;
import java.util.List;
public class MarketOpportunitySelectionStepTwoValidationRequest {
private List<Weight> weights;
public List<Weight> getWeights() { return weights; }
public void setWeights(List<Weight> weights) { this.weights = weights; }
private List<PrimaryWeight> primaryWeights;
private List<Weight> dimensionWeights;
public List<PrimaryWeight> getPrimaryWeights() { return primaryWeights; }
public void setPrimaryWeights(List<PrimaryWeight> primaryWeights) { this.primaryWeights = primaryWeights; }
public List<Weight> getDimensionWeights() { return dimensionWeights; }
public void setDimensionWeights(List<Weight> dimensionWeights) { this.dimensionWeights = dimensionWeights; }
public static class PrimaryWeight {
private String group;
private Integer weight;
public String getGroup() { return group; }
public void setGroup(String group) { this.group = group; }
public Integer getWeight() { return weight; }
public void setWeight(Integer weight) { this.weight = weight; }
}
public static class Weight {
private String id;
private Integer weight;

@ -0,0 +1,9 @@
package com.sztzjy.linkCommerce.service;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFiveValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFiveValidationResult;
public interface MarketOpportunitySelectionStepFiveService {
MarketOpportunitySelectionStepFiveValidationResult validate(MarketOpportunitySelectionStepFiveValidationRequest request, JwtUser user);
}

@ -0,0 +1,9 @@
package com.sztzjy.linkCommerce.service;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepSixValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepSixValidationResult;
public interface MarketOpportunitySelectionStepSixService {
MarketOpportunitySelectionStepSixValidationResult validate(MarketOpportunitySelectionStepSixValidationRequest request, JwtUser user);
}

@ -0,0 +1,69 @@
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.StudentTrainingAnswer;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFiveValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepFiveValidationResult;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepFiveService;
import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
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 MarketOpportunitySelectionStepFiveServiceImpl implements MarketOpportunitySelectionStepFiveService {
private static final String TASK_KEY = "market-opportunity-selection";
private static final Set<String> PRODUCT_IDS = new LinkedHashSet<>(Arrays.asList("smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror"));
@Autowired private StudentTrainingAnswerService studentTrainingAnswerService;
@Override
public MarketOpportunitySelectionStepFiveValidationResult validate(MarketOpportunitySelectionStepFiveValidationRequest request, JwtUser user) {
requireStudent(user);
StudentTrainingAnswer savedAnswer = studentTrainingAnswerService.get(TASK_KEY, null, user);
if (savedAnswer == null || StringUtils.isBlank(savedAnswer.getStep4Answer())) throw badRequest("请先完成并保存第 4 步数据可视化");
requireCompletedStepFour(savedAnswer.getStep4Answer());
List<MarketOpportunitySelectionStepFiveValidationRequest.Insight> insights = request == null ? null : request.getInsights();
if (insights == null || insights.size() != PRODUCT_IDS.size()) throw badRequest("请完整填写三类产品的用户需求洞察");
Set<String> filledProducts = new LinkedHashSet<>();
for (MarketOpportunitySelectionStepFiveValidationRequest.Insight insight : insights) {
if (insight == null || !PRODUCT_IDS.contains(insight.getProductId()) || !filledProducts.add(insight.getProductId())) throw badRequest("用户需求洞察必须包含且仅包含三类产品");
BigDecimal favorableRate = insight.getFavorableRate();
if (favorableRate == null || favorableRate.compareTo(BigDecimal.ZERO) < 0 || favorableRate.compareTo(new BigDecimal("100")) > 0 || favorableRate.scale() > 2) throw badRequest("好评率须为 0 至 100最多保留两位小数");
if (StringUtils.isBlank(insight.getPainPoint()) || StringUtils.isBlank(insight.getUnmetNeed())) throw badRequest("请填写每类产品的用户痛点和未被满足的需求");
}
if (!filledProducts.equals(PRODUCT_IDS)) throw badRequest("用户需求洞察必须包含且仅包含三类产品");
MarketOpportunitySelectionStepFiveValidationResult result = new MarketOpportunitySelectionStepFiveValidationResult();
result.setValid(true);
result.setMessage("用户需求洞察校验通过");
return result;
}
@SuppressWarnings("unchecked")
private void requireCompletedStepFour(String answerJson) {
Map<String, Object> outcome = JSON.parseObject(answerJson, LinkedHashMap.class);
Object rawVisualization = outcome == null ? null : outcome.get("visualization");
Object url = rawVisualization instanceof Map ? ((Map<String, Object>) rawVisualization).get("url") : null;
if (!(rawVisualization instanceof Map) || url == null || StringUtils.isBlank(String.valueOf(url))) throw badRequest("请先完成并保存第 4 步数据可视化");
}
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 ServiceException badRequest(String message) { return new ServiceException(HttpStatus.BAD_REQUEST, message); }
}

@ -1,5 +1,6 @@
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.StudentTrainingAnswer;
@ -12,9 +13,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@Service
public class MarketOpportunitySelectionStepFourServiceImpl implements MarketOpportunitySelectionStepFourService {
private static final String TASK_KEY = "market-opportunity-selection";
private static final Set<String> PRODUCT_IDS = new LinkedHashSet<>(Arrays.asList("smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror"));
@Autowired private StudentTrainingAnswerService studentTrainingAnswerService;
@ -24,9 +33,27 @@ public class MarketOpportunitySelectionStepFourServiceImpl implements MarketOppo
if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验");
StudentTrainingAnswer savedAnswer = studentTrainingAnswerService.get(TASK_KEY, null, user);
if (savedAnswer == null || StringUtils.isBlank(savedAnswer.getStep3Answer())) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存第 3 步综合得分计算");
requireCompletedStepThree(savedAnswer.getStep3Answer());
if (request == null || StringUtils.isBlank(request.getVisualizationUrl())) throw new ServiceException(HttpStatus.BAD_REQUEST, "请上传数据可视化结果图");
MarketOpportunitySelectionStepFourValidationResult result = new MarketOpportunitySelectionStepFourValidationResult();
result.setValid(true); result.setMessage("校验通过");
return result;
}
@SuppressWarnings("unchecked")
private void requireCompletedStepThree(String answerJson) {
Map<String, Object> outcome = JSON.parseObject(answerJson, LinkedHashMap.class);
Object rawScores = outcome == null ? null : outcome.get("scores");
if (!(rawScores instanceof Map) || ((Map<String, Object>) rawScores).size() != PRODUCT_IDS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 3 步综合得分计算");
Map<String, Object> scores = (Map<String, Object>) rawScores;
if (!scores.keySet().equals(PRODUCT_IDS)) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 3 步综合得分计算");
for (Object value : scores.values()) {
try {
BigDecimal score = new BigDecimal(String.valueOf(value));
if (score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(new BigDecimal("100")) > 0 || score.scale() > 2) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 3 步综合得分计算");
} catch (NumberFormatException error) {
throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 3 步综合得分计算");
}
}
}
}

@ -33,7 +33,7 @@ public class MarketOpportunitySelectionStepOneServiceImpl implements MarketOppor
@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));
jdbcTemplate.update("INSERT INTO training_step_standard_answer (id, task_key, step_no, answer_json, create_time, update_time) VALUES (?, ?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE answer_json = VALUES(answer_json), update_time = NOW()", "market-opportunity-selection-step-1", TASK_KEY, STEP_NO, JSON.toJSONString(DEFAULT_ANSWERS));
}
@Override
@ -72,7 +72,7 @@ public class MarketOpportunitySelectionStepOneServiceImpl implements MarketOppor
}
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", "运营难度");
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", "运营难度"); answers.put("return-rate", "运营难度");
return answers;
}
}

@ -0,0 +1,74 @@
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.StudentTrainingAnswer;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepSixValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.MarketOpportunitySelectionStepSixValidationResult;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepSixService;
import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@Service
public class MarketOpportunitySelectionStepSixServiceImpl implements MarketOpportunitySelectionStepSixService {
private static final String TASK_KEY = "market-opportunity-selection";
private static final Set<String> PRODUCT_IDS = new LinkedHashSet<>(Arrays.asList("smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror"));
@Autowired private StudentTrainingAnswerService studentTrainingAnswerService;
@Override
public MarketOpportunitySelectionStepSixValidationResult validate(MarketOpportunitySelectionStepSixValidationRequest request, JwtUser user) {
requireStudent(user);
StudentTrainingAnswer savedAnswer = studentTrainingAnswerService.get(TASK_KEY, null, user);
if (savedAnswer == null || StringUtils.isBlank(savedAnswer.getDynamicStepAnswers())) throw badRequest("请先完成并保存第 5 步用户需求洞察");
requireCompletedStepFive(savedAnswer.getDynamicStepAnswers());
if (request == null || StringUtils.isBlank(request.getDevelopmentConclusion())) throw badRequest("请填写建议开发的产品及具体原因");
MarketOpportunitySelectionStepSixValidationResult result = new MarketOpportunitySelectionStepSixValidationResult();
result.setValid(true);
result.setMessage("选品结论校验通过");
return result;
}
@SuppressWarnings("unchecked")
private void requireCompletedStepFive(String answerJson) {
Map<String, Object> dynamicAnswers = JSON.parseObject(answerJson, LinkedHashMap.class);
Object rawStepFive = dynamicAnswers == null ? null : dynamicAnswers.get("step5");
Object rawInsights = rawStepFive instanceof Map ? ((Map<String, Object>) rawStepFive).get("reviewInsights") : null;
if (!(rawInsights instanceof Map)) throw badRequest("请先完成并保存第 5 步用户需求洞察");
Map<String, Object> insights = (Map<String, Object>) rawInsights;
if (!insights.keySet().equals(PRODUCT_IDS)) throw badRequest("请先完成并保存第 5 步用户需求洞察");
for (Object rawInsight : insights.values()) {
if (!(rawInsight instanceof Map)) throw badRequest("请先完成并保存第 5 步用户需求洞察");
Map<String, Object> insight = (Map<String, Object>) rawInsight;
Object rawRate = insight.get("favorableRate");
try {
BigDecimal rate = new BigDecimal(String.valueOf(rawRate));
if (rawRate == null || rate.compareTo(BigDecimal.ZERO) < 0 || rate.compareTo(new BigDecimal("100")) > 0 || rate.scale() > 2) throw badRequest("请先完成并保存第 5 步用户需求洞察");
} catch (NumberFormatException error) {
throw badRequest("请先完成并保存第 5 步用户需求洞察");
}
Object painPoint = insight.get("painPoint");
Object unmetNeed = insight.get("unmetNeed");
if (painPoint == null || unmetNeed == null || StringUtils.isBlank(String.valueOf(painPoint)) || StringUtils.isBlank(String.valueOf(unmetNeed))) throw badRequest("请先完成并保存第 5 步用户需求洞察");
}
}
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 ServiceException badRequest(String message) { return new ServiceException(HttpStatus.BAD_REQUEST, message); }
}

@ -1,5 +1,6 @@
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.StudentTrainingAnswer;
@ -14,7 +15,9 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@Service
@ -29,13 +32,23 @@ public class MarketOpportunitySelectionStepThreeServiceImpl implements MarketOpp
if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验");
StudentTrainingAnswer savedAnswer = studentTrainingAnswerService.get(TASK_KEY, null, user);
if (savedAnswer == null || StringUtils.isBlank(savedAnswer.getStep2Answer())) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存第 2 步指标权重设定");
requireCompletedStepTwo(savedAnswer.getStep2Answer());
if (request == null || request.getScores() == null || request.getScores().size() != PRODUCT_IDS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请为三类产品填写综合得分");
Set<String> submittedIds = new LinkedHashSet<>();
for (MarketOpportunitySelectionStepThreeValidationRequest.Score item : request.getScores()) {
String id = item == null ? "" : StringUtils.trimToEmpty(item.getId()); BigDecimal score = item == null ? null : item.getScore();
if (!PRODUCT_IDS.contains(id) || !submittedIds.add(id) || score == null || score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(new BigDecimal("100")) > 0) throw new ServiceException(HttpStatus.BAD_REQUEST, "综合得分需为 0 至 100 的数值");
if (!PRODUCT_IDS.contains(id) || !submittedIds.add(id) || score == null || score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(new BigDecimal("100")) > 0 || score.scale() > 2) throw new ServiceException(HttpStatus.BAD_REQUEST, "综合得分需为 0 至 100 的数值,且最多保留两位小数");
}
if (!submittedIds.equals(PRODUCT_IDS)) throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的产品品类不完整");
MarketOpportunitySelectionStepThreeValidationResult result = new MarketOpportunitySelectionStepThreeValidationResult(); result.setValid(true); result.setMessage("校验通过"); return result;
}
@SuppressWarnings("unchecked")
private void requireCompletedStepTwo(String answerJson) {
Map<String, Object> outcome = JSON.parseObject(answerJson, LinkedHashMap.class);
if (outcome == null || !(outcome.get("primaryWeights") instanceof Map) || !(outcome.get("dimensionWeights") instanceof Map)) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 2 步指标权重设定");
Map<String, Object> primaryWeights = (Map<String, Object>) outcome.get("primaryWeights");
Map<String, Object> dimensionWeights = (Map<String, Object>) outcome.get("dimensionWeights");
if (primaryWeights.size() != 4 || dimensionWeights.size() != 9) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 2 步指标权重设定");
}
}

@ -1,5 +1,6 @@
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.StudentTrainingAnswer;
@ -13,13 +14,18 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
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 MarketOpportunitySelectionStepTwoServiceImpl implements MarketOpportunitySelectionStepTwoService {
private static final String TASK_KEY = "market-opportunity-selection";
private static final Set<String> INDICATOR_IDS = new LinkedHashSet<>(Arrays.asList("search-count", "transaction-amount", "transaction-growth", "category-transaction-growth", "demand-supply-ratio", "online-products", "online-merchants", "organic-traffic-ratio"));
private static final Set<String> INDICATOR_IDS = new LinkedHashSet<>(Arrays.asList("search-count", "demand-supply-ratio", "transaction-amount", "transaction-growth", "category-transaction-growth", "online-products", "online-merchants", "organic-traffic-ratio", "return-rate"));
private static final Set<String> PRIMARY_GROUPS = new LinkedHashSet<>(Arrays.asList("市场规模", "市场潜力", "竞争强度", "运营难度"));
private static final Map<String, String> INDICATOR_GROUPS = createIndicatorGroups();
@Autowired private StudentTrainingAnswerService studentTrainingAnswerService;
@Override
@ -28,15 +34,60 @@ public class MarketOpportunitySelectionStepTwoServiceImpl implements MarketOppor
if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验");
StudentTrainingAnswer savedAnswer = studentTrainingAnswerService.get(TASK_KEY, null, user);
if (savedAnswer == null || StringUtils.isBlank(savedAnswer.getStep1Answer())) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存第 1 步指标分类");
if (request == null || request.getWeights() == null || request.getWeights().size() != INDICATOR_IDS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请为全部二级指标填写权重");
Set<String> submittedIds = new LinkedHashSet<>(); int totalWeight = 0;
for (MarketOpportunitySelectionStepTwoValidationRequest.Weight item : request.getWeights()) {
requireCompletedStepOne(savedAnswer.getStep1Answer());
if (request == null || request.getPrimaryWeights() == null || request.getDimensionWeights() == null) throw new ServiceException(HttpStatus.BAD_REQUEST, "请填写一级指标与维度内权重");
Map<String, Integer> primaryWeights = validatePrimaryWeights(request.getPrimaryWeights());
Map<String, Integer> dimensionWeights = validateDimensionWeights(request.getDimensionWeights());
validateCorrespondingWeightTotal(primaryWeights, dimensionWeights);
MarketOpportunitySelectionStepTwoValidationResult result = new MarketOpportunitySelectionStepTwoValidationResult(); result.setValid(true); result.setMessage("校验通过"); return result;
}
private Map<String, Integer> validatePrimaryWeights(List<MarketOpportunitySelectionStepTwoValidationRequest.PrimaryWeight> items) {
if (items.size() != PRIMARY_GROUPS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请为四类一级指标填写权重");
Map<String, Integer> weights = new LinkedHashMap<>(); int total = 0;
for (MarketOpportunitySelectionStepTwoValidationRequest.PrimaryWeight item : items) {
String group = item == null ? "" : StringUtils.trimToEmpty(item.getGroup()); Integer weight = item == null ? null : item.getWeight();
if (!PRIMARY_GROUPS.contains(group) || weights.put(group, weight) != null || !isValidWeight(weight)) throw new ServiceException(HttpStatus.BAD_REQUEST, "一级指标权重需为 1 至 100 的整数");
total += weight;
}
if (!weights.keySet().equals(PRIMARY_GROUPS) || total != 100) throw new ServiceException(HttpStatus.BAD_REQUEST, "一级指标权重合计必须为 100%");
return weights;
}
private Map<String, Integer> validateDimensionWeights(List<MarketOpportunitySelectionStepTwoValidationRequest.Weight> items) {
if (items.size() != INDICATOR_IDS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请为全部二级指标填写维度内权重");
Map<String, Integer> weights = new LinkedHashMap<>();
for (MarketOpportunitySelectionStepTwoValidationRequest.Weight item : items) {
String id = item == null ? "" : StringUtils.trimToEmpty(item.getId()); Integer weight = item == null ? null : item.getWeight();
if (!INDICATOR_IDS.contains(id) || !submittedIds.add(id) || weight == null || weight < 1 || weight > 100) throw new ServiceException(HttpStatus.BAD_REQUEST, "二级指标权重需为 1 至 100 的整数");
totalWeight += weight;
if (!INDICATOR_IDS.contains(id) || weights.put(id, weight) != null || !isValidWeight(weight)) throw new ServiceException(HttpStatus.BAD_REQUEST, "维度内权重需为 1 至 100 的整数");
}
if (!submittedIds.equals(INDICATOR_IDS)) throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的二级指标不完整");
if (totalWeight != 100) throw new ServiceException(HttpStatus.BAD_REQUEST, "二级指标权重合计必须为 100%");
MarketOpportunitySelectionStepTwoValidationResult result = new MarketOpportunitySelectionStepTwoValidationResult(); result.setValid(true); result.setMessage("校验通过"); return result;
if (!weights.keySet().equals(INDICATOR_IDS)) throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的二级指标不完整");
for (String group : PRIMARY_GROUPS) {
int total = INDICATOR_IDS.stream().filter((id) -> group.equals(INDICATOR_GROUPS.get(id))).mapToInt(weights::get).sum();
if (total != 100) throw new ServiceException(HttpStatus.BAD_REQUEST, group + "的维度内权重合计必须为 100%");
}
return weights;
}
private void validateCorrespondingWeightTotal(Map<String, Integer> primaryWeights, Map<String, Integer> dimensionWeights) {
int totalHundredths = INDICATOR_IDS.stream().mapToInt((id) -> primaryWeights.get(INDICATOR_GROUPS.get(id)) * dimensionWeights.get(id)).sum();
if (totalHundredths != 10000) throw new ServiceException(HttpStatus.BAD_REQUEST, "对应总权重合计必须为 100%");
}
@SuppressWarnings("unchecked")
private void requireCompletedStepOne(String answerJson) {
Map<String, Object> outcome = JSON.parseObject(answerJson, LinkedHashMap.class);
Object rawAnswers = outcome == null ? null : outcome.get("answers");
if (!(rawAnswers instanceof Map)) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 1 步指标分类");
Map<String, Object> answers = (Map<String, Object>) rawAnswers;
if (answers.size() != INDICATOR_IDS.size()) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 1 步指标分类");
for (String id : INDICATOR_IDS) if (!INDICATOR_GROUPS.get(id).equals(String.valueOf(answers.get(id)))) throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并通过第 1 步指标分类");
}
private boolean isValidWeight(Integer weight) { return weight != null && weight >= 1 && weight <= 100; }
private static Map<String, String> createIndicatorGroups() {
Map<String, String> groups = new LinkedHashMap<>();
groups.put("search-count", "市场规模"); groups.put("demand-supply-ratio", "竞争强度"); groups.put("transaction-amount", "市场规模"); groups.put("transaction-growth", "市场潜力"); groups.put("category-transaction-growth", "市场潜力"); groups.put("online-products", "竞争强度"); groups.put("online-merchants", "竞争强度"); groups.put("organic-traffic-ratio", "运营难度"); groups.put("return-rate", "运营难度");
return groups;
}
}

@ -206,7 +206,7 @@ public class StudentTrainingAnswerServiceImpl implements StudentTrainingAnswerSe
boolean submitted = ACTION_SUBMIT.equals(StringUtils.upperCase(StringUtils.trimToEmpty(request.getSaveAction())));
record.setSubmitted(submitted);
record.setCurrentStep(submitted ? taskStepCount(task) : normalizeCurrentStepValue(request.getCurrentStep()));
record.setCurrentStep(submitted ? Math.max(taskStepCount(task), normalizeCurrentStepValue(request.getCurrentStep())) : normalizeCurrentStepValue(request.getCurrentStep()));
record.setProgressStatus(submitted ? STATUS_COMPLETED : resolveProgressStatus(record));
if (submitted) {
record.setSubmitTime(new Date());

Loading…
Cancel
Save