From 231d98984308a9952f6d0dfa695afccaf59bdf53 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Tue, 11 Aug 2026 18:23:18 +0800 Subject: [PATCH] feat: validate target user market selection --- .../stu/StudentTrainingAnswerController.java | 17 +++++ ...tUserProfileStepFiveValidationRequest.java | 32 +++++++++ ...etUserProfileStepFiveValidationResult.java | 11 +++ .../TargetUserProfileStepFiveService.java | 9 +++ .../TargetUserProfileStepFiveServiceImpl.java | 60 ++++++++++++++++ ...getUserProfileStepFiveServiceImplTest.java | 72 +++++++++++++++++++ 6 files changed, 201 insertions(+) create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationRequest.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationResult.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepFiveService.java create mode 100644 src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImpl.java create mode 100644 src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImplTest.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 d67c30f..0b8609c 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -36,6 +36,8 @@ import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepThreeValidationRe import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepThreeValidationResult; import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFourValidationRequest; import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFourValidationResult; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFiveValidationResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepTwoService; @@ -52,6 +54,7 @@ import com.sztzjy.linkCommerce.service.TargetUserProfileStepOneService; import com.sztzjy.linkCommerce.service.TargetUserProfileStepTwoService; import com.sztzjy.linkCommerce.service.TargetUserProfileStepThreeService; import com.sztzjy.linkCommerce.service.TargetUserProfileStepFourService; +import com.sztzjy.linkCommerce.service.TargetUserProfileStepFiveService; import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; import com.sztzjy.linkCommerce.util.ResultEntity; import io.swagger.annotations.Api; @@ -115,6 +118,9 @@ public class StudentTrainingAnswerController { @Autowired private TargetUserProfileStepFourService targetUserProfileStepFourService; + @Autowired + private TargetUserProfileStepFiveService targetUserProfileStepFiveService; + @GetMapping("/{taskKey}") @ApiOperation("查询当前学生指定实训任务答案") public ResultEntity detail(@PathVariable String taskKey, @@ -319,6 +325,17 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/target-user-profile/step-5/validate") + @ApiOperation("目标用户画像第五步校验") + public ResultEntity validateTargetUserProfileStepFive( + @RequestBody TargetUserProfileStepFiveValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", targetUserProfileStepFiveService.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/TargetUserProfileStepFiveValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationRequest.java new file mode 100644 index 0000000..d67e3bc --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationRequest.java @@ -0,0 +1,32 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class TargetUserProfileStepFiveValidationRequest { + private List segments; + private String marketSelection; + + public List getSegments() { return segments; } + public void setSegments(List segments) { this.segments = segments; } + public String getMarketSelection() { return marketSelection; } + public void setMarketSelection(String marketSelection) { this.marketSelection = marketSelection; } + + public static class MarketSegment { + private String market; + private String attractiveness; + private String painPoints; + private String behaviorCharacteristics; + private String productOpportunity; + + public String getMarket() { return market; } + public void setMarket(String market) { this.market = market; } + public String getAttractiveness() { return attractiveness; } + public void setAttractiveness(String attractiveness) { this.attractiveness = attractiveness; } + public String getPainPoints() { return painPoints; } + public void setPainPoints(String painPoints) { this.painPoints = painPoints; } + public String getBehaviorCharacteristics() { return behaviorCharacteristics; } + public void setBehaviorCharacteristics(String behaviorCharacteristics) { this.behaviorCharacteristics = behaviorCharacteristics; } + public String getProductOpportunity() { return productOpportunity; } + public void setProductOpportunity(String productOpportunity) { this.productOpportunity = productOpportunity; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationResult.java new file mode 100644 index 0000000..f9e43e3 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFiveValidationResult.java @@ -0,0 +1,11 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class TargetUserProfileStepFiveValidationResult { + 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/TargetUserProfileStepFiveService.java b/src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepFiveService.java new file mode 100644 index 0000000..7a40b23 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepFiveService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFiveValidationResult; + +public interface TargetUserProfileStepFiveService { + TargetUserProfileStepFiveValidationResult validate(TargetUserProfileStepFiveValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImpl.java new file mode 100644 index 0000000..ac8cd26 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImpl.java @@ -0,0 +1,60 @@ +package com.sztzjy.linkCommerce.service.impl; + +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.TargetUserProfileStepFiveValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFiveValidationResult; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import com.sztzjy.linkCommerce.service.TargetUserProfileStepFiveService; +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.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +@Service +public class TargetUserProfileStepFiveServiceImpl implements TargetUserProfileStepFiveService { + private static final String TASK_KEY = "target-user-profile"; + private static final int MAX_SEGMENTS = 6; + private static final Set ATTRACTIVENESS_OPTIONS = new LinkedHashSet<>(Arrays.asList("高", "中", "低")); + + @Autowired + private StudentTrainingAnswerService studentTrainingAnswerService; + + @Override + public TargetUserProfileStepFiveValidationResult validate(TargetUserProfileStepFiveValidationRequest request, JwtUser user) { + if (user == null || StringUtils.isBlank(user.getUserId())) { + throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再校验"); + } + if (user.getRoleId() != 4) { + throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可进行实训校验"); + } + StudentTrainingAnswer savedAnswer = studentTrainingAnswerService.get(TASK_KEY, null, user); + String savedContent = savedAnswer == null ? "" : savedAnswer.getStep1Answer(); + if (StringUtils.isBlank(savedContent) || !StringUtils.contains(savedContent, "keyUserRecords") + || !StringUtils.contains(savedContent, "U01") || !StringUtils.contains(savedContent, "U02") || !StringUtils.contains(savedContent, "U03")) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存第 4 步重点用户画像"); + } + if (request == null || request.getSegments() == null || request.getSegments().isEmpty() || request.getSegments().size() > MAX_SEGMENTS) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完成细分市场分析表,最多可填写 6 行"); + } + for (TargetUserProfileStepFiveValidationRequest.MarketSegment segment : request.getSegments()) { + if (segment == null || StringUtils.isBlank(segment.getMarket()) || !ATTRACTIVENESS_OPTIONS.contains(StringUtils.trim(segment.getAttractiveness())) + || StringUtils.isBlank(segment.getPainPoints()) || StringUtils.isBlank(segment.getBehaviorCharacteristics()) + || StringUtils.isBlank(segment.getProductOpportunity())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整填写每个细分市场的吸引力、需求痛点、行为特征和产品机会"); + } + } + if (StringUtils.isBlank(request.getMarketSelection())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完成市场选择结论"); + } + TargetUserProfileStepFiveValidationResult result = new TargetUserProfileStepFiveValidationResult(); + result.setValid(true); + result.setMessage("校验通过"); + return result; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImplTest.java new file mode 100644 index 0000000..b8e4ab5 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFiveServiceImplTest.java @@ -0,0 +1,72 @@ +package com.sztzjy.linkCommerce.service.impl; + +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.TargetUserProfileStepFiveValidationRequest; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Arrays; + +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.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TargetUserProfileStepFiveServiceImplTest { + @Test + void validatesCompleteMarketSegmentsAfterPortraitsAreSaved() { + assertTrue(serviceWithPortraitHistory().validate(request(), student()).isValid()); + } + + @Test + void rejectsIncompleteSegmentAndMissingPortraitHistory() { + TargetUserProfileStepFiveValidationRequest incomplete = request(); + incomplete.getSegments().get(0).setAttractiveness(""); + assertThrows(ServiceException.class, () -> serviceWithPortraitHistory().validate(incomplete, student())); + + TargetUserProfileStepFiveServiceImpl service = new TargetUserProfileStepFiveServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(new StudentTrainingAnswer()); + ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + assertThrows(ServiceException.class, () -> service.validate(request(), student())); + } + + private TargetUserProfileStepFiveServiceImpl serviceWithPortraitHistory() { + TargetUserProfileStepFiveServiceImpl service = new TargetUserProfileStepFiveServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); + StudentTrainingAnswer answer = new StudentTrainingAnswer(); + answer.setStep1Answer("{\"keyUserRecords\":[{\"code\":\"U01\"},{\"code\":\"U02\"},{\"code\":\"U03\"}]}"); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); + ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + return service; + } + + private TargetUserProfileStepFiveValidationRequest request() { + TargetUserProfileStepFiveValidationRequest request = new TargetUserProfileStepFiveValidationRequest(); + request.setSegments(Arrays.asList(segment("职场白领"), segment("精致妈妈"))); + request.setMarketSelection("优先服务有明确助眠需求的职场白领,并开发便捷的智能助眠产品。"); + return request; + } + + private TargetUserProfileStepFiveValidationRequest.MarketSegment segment(String market) { + TargetUserProfileStepFiveValidationRequest.MarketSegment segment = new TargetUserProfileStepFiveValidationRequest.MarketSegment(); + segment.setMarket(market); + segment.setAttractiveness("高"); + segment.setPainPoints("睡眠质量差,需要便捷的改善方案"); + segment.setBehaviorCharacteristics("愿意尝试智能设备,并会关注健康数据"); + segment.setProductOpportunity("提供智能灯光、睡眠内容和数据报告的一体化产品"); + return segment; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("stu-1"); + user.setRoleId(4); + return user; + } +}