feat: validate consumer behavior questionnaire

main
chenyuan 3 weeks ago
parent 1e409dfa69
commit fc5b3e7b96

@ -32,6 +32,9 @@ import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepOneValidationReque
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepOneValidationResult;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepTwoValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepTwoValidationResult;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationResult;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorWordPreviewResult;
import com.sztzjy.linkCommerce.service.NewProductSurveyStepOneService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepOneService;
import com.sztzjy.linkCommerce.service.MarketOpportunitySelectionStepTwoService;
@ -46,6 +49,7 @@ import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepThreeService
import com.sztzjy.linkCommerce.service.ProductDevelopmentFactorsStepFourService;
import com.sztzjy.linkCommerce.service.ConsumerBehaviorStepOneService;
import com.sztzjy.linkCommerce.service.ConsumerBehaviorStepTwoService;
import com.sztzjy.linkCommerce.service.ConsumerBehaviorStepThreeService;
import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService;
import com.sztzjy.linkCommerce.util.ResultEntity;
import io.swagger.annotations.Api;
@ -53,6 +57,7 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
@ -103,6 +108,9 @@ public class StudentTrainingAnswerController {
@Autowired
private ConsumerBehaviorStepTwoService consumerBehaviorStepTwoService;
@Autowired
private ConsumerBehaviorStepThreeService consumerBehaviorStepThreeService;
@GetMapping("/{taskKey}")
@ApiOperation("查询当前学生指定实训任务答案")
public ResultEntity<StudentTrainingAnswer> detail(@PathVariable String taskKey,
@ -285,6 +293,28 @@ public class StudentTrainingAnswerController {
}
}
@PostMapping("/consumer-behavior/step-3/validate")
@ApiOperation("消费者需求与行为第三步校验")
public ResultEntity<ConsumerBehaviorStepThreeValidationResult> validateConsumerBehaviorStepThree(
@RequestBody ConsumerBehaviorStepThreeValidationRequest answer, HttpServletRequest request) {
try {
return new ResultEntity<>(HttpStatus.OK, "校验通过", consumerBehaviorStepThreeService.validate(answer, TokenProvider.getJWTUser(request)));
} catch (ServiceException e) {
return new ResultEntity<>(e.getCode(), e.getMessage());
}
}
@PostMapping(value = "/consumer-behavior/step-3/word-preview", consumes = "multipart/form-data")
@ApiOperation("消费者需求与行为第三步Word预览")
public ResultEntity<ConsumerBehaviorWordPreviewResult> previewConsumerBehaviorStepThreeWord(
@RequestPart("file") MultipartFile file, HttpServletRequest request) {
try {
return new ResultEntity<>(HttpStatus.OK, "预览成功", consumerBehaviorStepThreeService.previewWord(file, TokenProvider.getJWTUser(request)));
} catch (ServiceException e) {
return new ResultEntity<>(e.getCode(), e.getMessage());
}
}
@DeleteMapping("/{taskKey}")
@ApiOperation("删除当前学生指定实训任务答案")
public ResultEntity delete(@PathVariable String taskKey,

@ -0,0 +1,29 @@
package com.sztzjy.linkCommerce.entity.dto;
import java.util.List;
public class ConsumerBehaviorStepThreeValidationRequest {
private List<Question> questions;
private String importedDocumentUrl;
public List<Question> getQuestions() { return questions; }
public void setQuestions(List<Question> questions) { this.questions = questions; }
public String getImportedDocumentUrl() { return importedDocumentUrl; }
public void setImportedDocumentUrl(String importedDocumentUrl) { this.importedDocumentUrl = importedDocumentUrl; }
public static class Question {
private String section;
private String type;
private String title;
private String options;
public String getSection() { return section; }
public void setSection(String section) { this.section = section; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getOptions() { return options; }
public void setOptions(String options) { this.options = options; }
}
}

@ -0,0 +1,11 @@
package com.sztzjy.linkCommerce.entity.dto;
public class ConsumerBehaviorStepThreeValidationResult {
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 ConsumerBehaviorWordPreviewResult {
private String content;
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}

@ -0,0 +1,12 @@
package com.sztzjy.linkCommerce.service;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationResult;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorWordPreviewResult;
import org.springframework.web.multipart.MultipartFile;
public interface ConsumerBehaviorStepThreeService {
ConsumerBehaviorStepThreeValidationResult validate(ConsumerBehaviorStepThreeValidationRequest request, JwtUser user);
ConsumerBehaviorWordPreviewResult previewWord(MultipartFile file, JwtUser user);
}

@ -0,0 +1,120 @@
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.dto.ConsumerBehaviorStepThreeValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationResult;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorWordPreviewResult;
import com.sztzjy.linkCommerce.service.ConsumerBehaviorStepThreeService;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@Service
public class ConsumerBehaviorStepThreeServiceImpl implements ConsumerBehaviorStepThreeService {
private static final Set<String> REQUIRED_SECTIONS = new LinkedHashSet<>(Arrays.asList("基本信息", "购买行为", "需求偏好", "开放建议"));
private static final Set<String> QUESTION_TYPES = new LinkedHashSet<>(Arrays.asList("SINGLE", "MULTIPLE", "LIKERT", "TEXT"));
private static final long MAX_WORD_FILE_SIZE = 10L * 1024 * 1024;
private static final int MAX_PREVIEW_LENGTH = 30000;
@Override
public ConsumerBehaviorStepThreeValidationResult validate(ConsumerBehaviorStepThreeValidationRequest request, JwtUser user) {
requireStudent(user);
if (request != null && StringUtils.isNotBlank(request.getImportedDocumentUrl())) {
return validResult();
}
List<ConsumerBehaviorStepThreeValidationRequest.Question> questions = request == null ? null : request.getQuestions();
if (questions == null || questions.isEmpty()) {
throw badRequest("请完整设计问卷,或导入 Word 问卷");
}
Set<String> completedSections = new LinkedHashSet<>();
boolean hasLikert = false;
for (ConsumerBehaviorStepThreeValidationRequest.Question question : questions) {
if (question == null || StringUtils.isBlank(question.getTitle())
|| !REQUIRED_SECTIONS.contains(StringUtils.trimToEmpty(question.getSection()))
|| !QUESTION_TYPES.contains(StringUtils.trimToEmpty(question.getType()))) {
throw badRequest("请完整填写每道问卷题目的分区、题型和内容");
}
if (("SINGLE".equals(question.getType()) || "MULTIPLE".equals(question.getType())) && StringUtils.isBlank(question.getOptions())) {
throw badRequest("单选题和多选题请填写选项");
}
completedSections.add(question.getSection());
hasLikert = hasLikert || ("需求偏好".equals(question.getSection()) && "LIKERT".equals(question.getType()));
}
if (!completedSections.containsAll(REQUIRED_SECTIONS) || !hasLikert) {
throw badRequest("问卷需包含基本信息、购买行为、需求偏好、开放建议四部分,并在需求偏好中包含 Likert 五级量表");
}
return validResult();
}
@Override
public ConsumerBehaviorWordPreviewResult previewWord(MultipartFile file, JwtUser user) {
requireStudent(user);
validateWordFile(file);
try (XWPFDocument document = new XWPFDocument(file.getInputStream())) {
StringBuilder content = new StringBuilder();
document.getParagraphs().forEach(paragraph -> appendLine(content, paragraph.getText()));
for (XWPFTable table : document.getTables()) {
for (XWPFTableRow row : table.getRows()) {
StringBuilder rowContent = new StringBuilder();
for (XWPFTableCell cell : row.getTableCells()) {
if (rowContent.length() > 0) rowContent.append(" | ");
rowContent.append(StringUtils.trimToEmpty(cell.getText()).replaceAll("\\s+", " "));
}
appendLine(content, rowContent.toString());
}
}
if (content.length() > MAX_PREVIEW_LENGTH) content.setLength(MAX_PREVIEW_LENGTH);
ConsumerBehaviorWordPreviewResult result = new ConsumerBehaviorWordPreviewResult();
result.setContent(content.toString().trim());
return result;
} catch (IOException e) {
throw badRequest("Word 文档解析失败,请确认文件未损坏");
}
}
private ConsumerBehaviorStepThreeValidationResult validResult() {
ConsumerBehaviorStepThreeValidationResult result = new ConsumerBehaviorStepThreeValidationResult();
result.setValid(true);
result.setMessage("校验通过");
return result;
}
private void validateWordFile(MultipartFile file) {
String fileName = file == null ? "" : StringUtils.defaultString(file.getOriginalFilename());
if (file == null || file.isEmpty()) throw badRequest("请上传 Word 问卷");
if (!StringUtils.endsWithIgnoreCase(fileName, ".docx")) throw badRequest("仅支持 .docx 格式的 Word 问卷预览");
if (file.getSize() > MAX_WORD_FILE_SIZE) throw badRequest("Word 问卷大小不能超过 10MB");
}
private void appendLine(StringBuilder content, String value) {
String line = StringUtils.trimToEmpty(value);
if (line.isEmpty() || content.length() >= MAX_PREVIEW_LENGTH) return;
if (content.length() > 0) content.append('\n');
content.append(line);
}
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);
}
}

@ -0,0 +1,76 @@
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.dto.ConsumerBehaviorStepThreeValidationRequest;
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationResult;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ConsumerBehaviorStepThreeServiceImplTest {
@Test
void acceptsCompleteQuestionnaireForStudent() {
ConsumerBehaviorStepThreeServiceImpl service = new ConsumerBehaviorStepThreeServiceImpl();
ConsumerBehaviorStepThreeValidationResult result = service.validate(validRequest(), student());
assertTrue(result.isValid());
}
@Test
void rejectsQuestionnaireWithoutLikertOrOptions() {
ConsumerBehaviorStepThreeServiceImpl service = new ConsumerBehaviorStepThreeServiceImpl();
ConsumerBehaviorStepThreeValidationRequest noLikert = validRequest();
noLikert.getQuestions().get(2).setType("TEXT");
assertThrows(ServiceException.class, () -> service.validate(noLikert, student()));
ConsumerBehaviorStepThreeValidationRequest noOptions = validRequest();
noOptions.getQuestions().get(0).setOptions(" ");
assertThrows(ServiceException.class, () -> service.validate(noOptions, student()));
}
@Test
void rejectsNonStudent() {
ConsumerBehaviorStepThreeServiceImpl service = new ConsumerBehaviorStepThreeServiceImpl();
assertThrows(ServiceException.class, () -> service.validate(validRequest(), teacher()));
}
private ConsumerBehaviorStepThreeValidationRequest validRequest() {
ConsumerBehaviorStepThreeValidationRequest request = new ConsumerBehaviorStepThreeValidationRequest();
request.setQuestions(Arrays.asList(
question("基本信息", "SINGLE", "您的年龄处于以下哪个区间?", "18岁以下18-25岁26-35岁"),
question("购买行为", "MULTIPLE", "您通常通过什么渠道购买智能产品?", "电商平台,线下门店,社交平台"),
question("需求偏好", "LIKERT", "白噪声可定时关闭对您有多重要?", ""),
question("开放建议", "TEXT", "您希望智能助眠产品增加哪些功能?", "")));
return request;
}
private ConsumerBehaviorStepThreeValidationRequest.Question question(String section, String type, String title, String options) {
ConsumerBehaviorStepThreeValidationRequest.Question question = new ConsumerBehaviorStepThreeValidationRequest.Question();
question.setSection(section);
question.setType(type);
question.setTitle(title);
question.setOptions(options);
return question;
}
private JwtUser student() {
JwtUser user = new JwtUser();
user.setUserId("stu-1");
user.setRoleId(4);
return user;
}
private JwtUser teacher() {
JwtUser user = new JwtUser();
user.setUserId("teacher-1");
user.setRoleId(2);
return user;
}
}
Loading…
Cancel
Save