feat: validate target user rfm charts

main
chenyuan 3 weeks ago
parent 4929b26861
commit 7909d4799c

@ -30,6 +30,8 @@ import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValid
import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationResult; import com.sztzjy.linkCommerce.entity.dto.ProductDevelopmentFactorsStepFourValidationResult;
import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepOneValidationRequest; import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepOneValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.TargetUserProfileStepOneValidationResult; 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.NewProductSurveyStepOneService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService; import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepTwoService; 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.ProductDevelopmentFactorsStepThreeService;
import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepFourService; import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepFourService;
import com.sztzjy.linkCommerce.service.TargetUserProfileStepOneService; import com.sztzjy.linkCommerce.service.TargetUserProfileStepOneService;
import com.sztzjy.linkCommerce.service.TargetUserProfileStepTwoService;
import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService;
import com.sztzjy.linkCommerce.util.ResultEntity; import com.sztzjy.linkCommerce.util.ResultEntity;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@ -97,6 +100,9 @@ public class StudentTrainingAnswerController {
@Autowired @Autowired
private TargetUserProfileStepOneService targetUserProfileStepOneService; private TargetUserProfileStepOneService targetUserProfileStepOneService;
@Autowired
private TargetUserProfileStepTwoService targetUserProfileStepTwoService;
@GetMapping("/{taskKey}") @GetMapping("/{taskKey}")
@ApiOperation("查询当前学生指定实训任务答案") @ApiOperation("查询当前学生指定实训任务答案")
public ResultEntity<StudentTrainingAnswer> detail(@PathVariable String taskKey, public ResultEntity<StudentTrainingAnswer> detail(@PathVariable String taskKey,
@ -268,6 +274,17 @@ public class StudentTrainingAnswerController {
} }
} }
@PostMapping("/target-user-profile/step-2/validate")
@ApiOperation("目标用户画像第二步校验")
public ResultEntity<TargetUserProfileStepTwoValidationResult> 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}") @DeleteMapping("/{taskKey}")
@ApiOperation("删除当前学生指定实训任务答案") @ApiOperation("删除当前学生指定实训任务答案")
public ResultEntity delete(@PathVariable String taskKey, public ResultEntity delete(@PathVariable String taskKey,

@ -0,0 +1,23 @@
package com.sztzjy.linkCommerce.entity.dto;
import java.util.List;
public class TargetUserProfileStepTwoValidationRequest {
private List<ChartUpload> charts;
public List<ChartUpload> getCharts() { return charts; }
public void setCharts(List<ChartUpload> 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; }
}
}

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

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

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

@ -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;
}
}
Loading…
Cancel
Save