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 093b733..d67c30f 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -34,6 +34,8 @@ import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationRequ import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationResult; import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepThreeValidationRequest; 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.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepTwoService; @@ -49,6 +51,7 @@ import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepFourService; 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.StudentTrainingAnswerService; import com.sztzjy.linkCommerce.util.ResultEntity; import io.swagger.annotations.Api; @@ -109,6 +112,9 @@ public class StudentTrainingAnswerController { @Autowired private TargetUserProfileStepThreeService targetUserProfileStepThreeService; + @Autowired + private TargetUserProfileStepFourService targetUserProfileStepFourService; + @GetMapping("/{taskKey}") @ApiOperation("查询当前学生指定实训任务答案") public ResultEntity detail(@PathVariable String taskKey, @@ -302,6 +308,17 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/target-user-profile/step-4/validate") + @ApiOperation("目标用户画像第四步校验") + public ResultEntity validateTargetUserProfileStepFour( + @RequestBody TargetUserProfileStepFourValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", targetUserProfileStepFourService.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/TargetUserProfileStepFourValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFourValidationRequest.java new file mode 100644 index 0000000..0775d0d --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFourValidationRequest.java @@ -0,0 +1,35 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class TargetUserProfileStepFourValidationRequest { + private List records; + + public List getRecords() { return records; } + public void setRecords(List records) { this.records = records; } + + public static class UserPortrait { + private String code; + private String user; + private String basicAttributes; + private String tags; + private String behavior; + private String painPoints; + private String psychology; + + public String getCode() { return code; } + public void setCode(String code) { this.code = code; } + public String getUser() { return user; } + public void setUser(String user) { this.user = user; } + public String getBasicAttributes() { return basicAttributes; } + public void setBasicAttributes(String basicAttributes) { this.basicAttributes = basicAttributes; } + public String getTags() { return tags; } + public void setTags(String tags) { this.tags = tags; } + public String getBehavior() { return behavior; } + public void setBehavior(String behavior) { this.behavior = behavior; } + public String getPainPoints() { return painPoints; } + public void setPainPoints(String painPoints) { this.painPoints = painPoints; } + public String getPsychology() { return psychology; } + public void setPsychology(String psychology) { this.psychology = psychology; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFourValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFourValidationResult.java new file mode 100644 index 0000000..a7daf69 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepFourValidationResult.java @@ -0,0 +1,11 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class TargetUserProfileStepFourValidationResult { + 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/TargetUserProfileStepFourService.java b/src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepFourService.java new file mode 100644 index 0000000..9cb1847 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepFourService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFourValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFourValidationResult; + +public interface TargetUserProfileStepFourService { + TargetUserProfileStepFourValidationResult validate(TargetUserProfileStepFourValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFourServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFourServiceImpl.java new file mode 100644 index 0000000..f1268aa --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFourServiceImpl.java @@ -0,0 +1,65 @@ +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.TargetUserProfileStepFourValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepFourValidationResult; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import com.sztzjy.linkCommerce.service.TargetUserProfileStepFourService; +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 TargetUserProfileStepFourServiceImpl implements TargetUserProfileStepFourService { + private static final String TASK_KEY = "target-user-profile"; + private static final Set REQUIRED_CODES = new LinkedHashSet<>(Arrays.asList("U01", "U02", "U03")); + private static final Set REQUIRED_INSIGHT_DIMENSIONS = new LinkedHashSet<>(Arrays.asList("高价值用户占比", "用户活跃度", "消费能力", "用户忠诚度", "重点客户群特征")); + + @Autowired + private StudentTrainingAnswerService studentTrainingAnswerService; + + @Override + public TargetUserProfileStepFourValidationResult validate(TargetUserProfileStepFourValidationRequest 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); + if (savedAnswer == null || StringUtils.isBlank(savedAnswer.getStep1Answer())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完成并保存第 3 步 RFM 用户群体特点分析"); + } + String savedContent = savedAnswer.getStep1Answer(); + if (REQUIRED_INSIGHT_DIMENSIONS.stream().anyMatch(dimension -> !StringUtils.contains(savedContent, dimension))) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请先完整保存第 3 步 RFM 用户群体特点分析"); + } + if (request == null || request.getRecords() == null || request.getRecords().size() != REQUIRED_CODES.size()) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完成三位重点用户的完整画像"); + } + Set codes = new LinkedHashSet<>(); + for (TargetUserProfileStepFourValidationRequest.UserPortrait record : request.getRecords()) { + String code = record == null ? "" : StringUtils.trimToEmpty(record.getCode()); + if (!REQUIRED_CODES.contains(code) || !codes.add(code) || record == null + || StringUtils.isBlank(record.getUser()) || StringUtils.isBlank(record.getBasicAttributes()) + || StringUtils.isBlank(record.getTags()) || StringUtils.isBlank(record.getBehavior()) + || StringUtils.isBlank(record.getPainPoints()) || StringUtils.isBlank(record.getPsychology())) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完成三位重点用户的完整画像"); + } + } + if (!codes.equals(REQUIRED_CODES)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "重点用户访谈记录不完整"); + } + TargetUserProfileStepFourValidationResult result = new TargetUserProfileStepFourValidationResult(); + result.setValid(true); + result.setMessage("校验通过"); + return result; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFourServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFourServiceImplTest.java new file mode 100644 index 0000000..339d278 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepFourServiceImplTest.java @@ -0,0 +1,77 @@ +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.TargetUserProfileStepFourValidationRequest; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +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 TargetUserProfileStepFourServiceImplTest { + @Test + void validatesThreeCompletePortraitsAfterInsightsAreSaved() { + assertTrue(serviceWithInsights().validate(request(), student()).isValid()); + } + + @Test + void rejectsIncompletePortraitAndMissingInsightHistory() { + TargetUserProfileStepFourValidationRequest incomplete = request(); + incomplete.setRecords(new ArrayList<>(incomplete.getRecords())); + incomplete.getRecords().get(1).setPsychology(""); + assertThrows(ServiceException.class, () -> serviceWithInsights().validate(incomplete, student())); + + TargetUserProfileStepFourServiceImpl service = new TargetUserProfileStepFourServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); + StudentTrainingAnswer answer = new StudentTrainingAnswer(); + answer.setStep1Answer("{\"rfmInsights\":[{\"dimension\":\"用户活跃度\"}]} "); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); + ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + assertThrows(ServiceException.class, () -> service.validate(request(), student())); + } + + private TargetUserProfileStepFourServiceImpl serviceWithInsights() { + TargetUserProfileStepFourServiceImpl service = new TargetUserProfileStepFourServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); + StudentTrainingAnswer answer = new StudentTrainingAnswer(); + answer.setStep1Answer("{\"rfmInsights\":[\"高价值用户占比\",\"用户活跃度\",\"消费能力\",\"用户忠诚度\",\"重点客户群特征\"]}"); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); + ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + return service; + } + + private TargetUserProfileStepFourValidationRequest request() { + TargetUserProfileStepFourValidationRequest request = new TargetUserProfileStepFourValidationRequest(); + request.setRecords(Arrays.asList(record("U01"), record("U02"), record("U03"))); + return request; + } + + private TargetUserProfileStepFourValidationRequest.UserPortrait record(String code) { + TargetUserProfileStepFourValidationRequest.UserPortrait record = new TargetUserProfileStepFourValidationRequest.UserPortrait(); + record.setCode(code); + record.setUser(code + " 用户"); + record.setBasicAttributes("基础属性"); + record.setTags("用户标签"); + record.setBehavior("行为特征"); + record.setPainPoints("需求痛点"); + record.setPsychology("消费心理"); + return record; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("stu-1"); + user.setRoleId(4); + return user; + } +}