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 db23151..3a7010b 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java @@ -30,6 +30,8 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValid import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationResult; import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepOneValidationRequest; import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepOneValidationResult; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationResult; import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepTwoService; @@ -43,6 +45,7 @@ import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepTwoService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepThreeService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepFourService; import com.sztzjy.linkCommerce.service.TargetUserProfileStepOneService; +import com.sztzjy.linkCommerce.service.TargetUserProfileStepTwoService; import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; import com.sztzjy.linkCommerce.util.ResultEntity; import io.swagger.annotations.Api; @@ -97,6 +100,9 @@ public class StudentTrainingAnswerController { @Autowired private TargetUserProfileStepOneService targetUserProfileStepOneService; + @Autowired + private TargetUserProfileStepTwoService targetUserProfileStepTwoService; + @GetMapping("/{taskKey}") @ApiOperation("查询当前学生指定实训任务答案") public ResultEntity detail(@PathVariable String taskKey, @@ -268,6 +274,17 @@ public class StudentTrainingAnswerController { } } + @PostMapping("/target-user-profile/step-2/validate") + @ApiOperation("目标用户画像第二步校验") + public ResultEntity validateTargetUserProfileStepTwo( + @RequestBody TargetUserProfileStepTwoValidationRequest answer, HttpServletRequest request) { + try { + return new ResultEntity<>(HttpStatus.OK, "校验通过", targetUserProfileStepTwoService.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/TargetUserProfileStepTwoValidationRequest.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepTwoValidationRequest.java new file mode 100644 index 0000000..b937b47 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepTwoValidationRequest.java @@ -0,0 +1,23 @@ +package com.sztzjy.linkCommerce.entity.dto; + +import java.util.List; + +public class TargetUserProfileStepTwoValidationRequest { + private List charts; + + public List getCharts() { return charts; } + public void setCharts(List charts) { this.charts = charts; } + + public static class ChartUpload { + private String type; + private String url; + private String name; + + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepTwoValidationResult.java b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepTwoValidationResult.java new file mode 100644 index 0000000..db4f160 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/dto/TargetUserProfileStepTwoValidationResult.java @@ -0,0 +1,11 @@ +package com.sztzjy.linkCommerce.entity.dto; + +public class TargetUserProfileStepTwoValidationResult { + 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/TargetUserProfileStepTwoService.java b/src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepTwoService.java new file mode 100644 index 0000000..3c2b49a --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/TargetUserProfileStepTwoService.java @@ -0,0 +1,9 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.config.security.JwtUser; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationResult; + +public interface TargetUserProfileStepTwoService { + TargetUserProfileStepTwoValidationResult validate(TargetUserProfileStepTwoValidationRequest request, JwtUser user); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepTwoServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepTwoServiceImpl.java new file mode 100644 index 0000000..b6cdda4 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepTwoServiceImpl.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.TargetUserProfileStepTwoValidationRequest; +import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepTwoValidationResult; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import com.sztzjy.linkCommerce.service.TargetUserProfileStepTwoService; +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 TargetUserProfileStepTwoServiceImpl implements TargetUserProfileStepTwoService { + private static final String TASK_KEY = "target-user-profile"; + private static final Set REQUIRED_CHART_TYPES = new LinkedHashSet<>(Arrays.asList("segment-share", "average-spend", "score-heatmap")); + + @Autowired + private StudentTrainingAnswerService studentTrainingAnswerService; + + @Override + public TargetUserProfileStepTwoValidationResult validate(TargetUserProfileStepTwoValidationRequest 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, "请先完成并保存第 1 步 RFM 用户分层分析"); + } + if (request == null || request.getCharts() == null || request.getCharts().size() != REQUIRED_CHART_TYPES.size()) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请上传三类 RFM 可视化图表"); + } + Set chartTypes = new LinkedHashSet<>(); + for (TargetUserProfileStepTwoValidationRequest.ChartUpload chart : request.getCharts()) { + String type = chart == null ? "" : StringUtils.trimToEmpty(chart.getType()); + String url = chart == null ? "" : StringUtils.trimToEmpty(chart.getUrl()); + String name = chart == null ? "" : StringUtils.trimToEmpty(chart.getName()).toLowerCase(); + if (!REQUIRED_CHART_TYPES.contains(type) || !chartTypes.add(type) || StringUtils.isBlank(url) + || !(name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".webp"))) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "请完整上传客户分层占比饼图、平均消费对比柱状图和 RFM 分数分布热力图"); + } + } + if (!chartTypes.equals(REQUIRED_CHART_TYPES)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "提交的 RFM 图表类型不完整"); + } + TargetUserProfileStepTwoValidationResult result = new TargetUserProfileStepTwoValidationResult(); + result.setValid(true); + result.setMessage("校验通过"); + return result; + } +} diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepTwoServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepTwoServiceImplTest.java new file mode 100644 index 0000000..1d2a123 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/TargetUserProfileStepTwoServiceImplTest.java @@ -0,0 +1,75 @@ +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.TargetUserProfileStepTwoValidationRequest; +import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Arrays; +import java.util.ArrayList; + +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 TargetUserProfileStepTwoServiceImplTest { + @Test + void validatesAllThreeRfmChartsAfterStepOneIsSaved() { + assertTrue(serviceWithStepOne().validate(request(), student()).isValid()); + } + + @Test + void rejectsIncompleteChartsAndMissingStepOne() { + TargetUserProfileStepTwoValidationRequest incomplete = request(); + incomplete.setCharts(new ArrayList<>(incomplete.getCharts())); + incomplete.getCharts().remove(2); + assertThrows(ServiceException.class, () -> serviceWithStepOne().validate(incomplete, student())); + + TargetUserProfileStepTwoServiceImpl service = new TargetUserProfileStepTwoServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(null); + ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + assertThrows(ServiceException.class, () -> service.validate(request(), student())); + } + + private TargetUserProfileStepTwoServiceImpl serviceWithStepOne() { + TargetUserProfileStepTwoServiceImpl service = new TargetUserProfileStepTwoServiceImpl(); + StudentTrainingAnswerService answers = mock(StudentTrainingAnswerService.class); + StudentTrainingAnswer answer = new StudentTrainingAnswer(); + answer.setStep1Answer("{\"profileFileUrl\":\"/rfm.xlsx\"}"); + when(answers.get(anyString(), any(), any(JwtUser.class))).thenReturn(answer); + ReflectionTestUtils.setField(service, "studentTrainingAnswerService", answers); + return service; + } + + private TargetUserProfileStepTwoValidationRequest request() { + TargetUserProfileStepTwoValidationRequest request = new TargetUserProfileStepTwoValidationRequest(); + request.setCharts(Arrays.asList( + chart("segment-share", "share.png"), + chart("average-spend", "spend.jpg"), + chart("score-heatmap", "heatmap.webp") + )); + return request; + } + + private TargetUserProfileStepTwoValidationRequest.ChartUpload chart(String type, String name) { + TargetUserProfileStepTwoValidationRequest.ChartUpload chart = new TargetUserProfileStepTwoValidationRequest.ChartUpload(); + chart.setType(type); + chart.setName(name); + chart.setUrl("/profile/" + name); + return chart; + } + + private JwtUser student() { + JwtUser user = new JwtUser(); + user.setUserId("stu-1"); + user.setRoleId(4); + return user; + } +}